untung99play.xyz: Unity Tutorial How to Make a Game Like Space Invaders
Untung99 menawarkan beragam permainan yang menarik, termasuk slot online, poker, roulette, blackjack, dan taruhan olahraga langsung. Dengan koleksi permainan yang lengkap dan terus diperbarui, pemain memiliki banyak pilihan untuk menjaga kegembiraan mereka. Selain itu, Untung99 juga menyediakan bonus dan promosi menarik yang meningkatkan peluang kemenangan dan memberikan nilai tambah kepada pemain.
Berikut adalah artikel atau berita tentang Harian untung99play.xyz dengan judul untung99play.xyz: Unity Tutorial How to Make a Game Like Space Invaders yang telah tayang di untung99play.xyz terimakasih telah menyimak. Bila ada masukan atau komplain mengenai artikel berikut silahkan hubungi email kami di koresponden@untung99play.xyz, Terimakasih.
Space Invaders, known in Japan as Supēsuinbēdā (スペースインベーダー), is one of the most well known retro games in the world. Released to arcades in 1978 by the Japanese game company Taito, it quickly became a massive hit.
The laser bullet-shooting cannon represents the player. The player can hide behind any of four torchka (トーチカ), also known as a pillboxes.
There are three types of invaders: crab, squid and octopus. They appear in a swarm of multiple rows and move from the top to the bottom of the screen. The crab invader has become an iconic symbol universally associated with arcades and games in general.
The game’s objective is to eliminate the invaders before they reach the bottom of the screen while dodging and hiding behind torchkas. In addition to the swarm, there’s also a UFO that appears from time to time. However, you’ll focus on the swarm.
In this tutorial, you’ll replicate the core gameplay features of Space Invaders using Unity. Along the way, you’ll learn how to:
- Spawn and move the invader swarm.
- Make the invaders at the swarm’s head shoot laser bullets.
- Move and shoot back as the player.
- Change the music tempo and swarm speed based on kill-count.
Time to get started!
Note: This tutorial assumes you already have basic experience with Unity and an intermediate knowledge of C#. Additionally, this tutorial assumes you’re using C# 7 and Unity 2020.3.
Getting Started
Download the project materials by clicking the Download Materials button at the top or bottom of this tutorial.
Extract the zip file to a convenient location. After extracting, you’ll notice Starter and Final project folders. Open Starter in Unity.
The project contains some folders to help you to get started. Open Assets/RW and you’ll find the following directories:
- Animations: Contains pre-made animations and the animator for the bullet and explosion prefabs.
- Prefabs: Has all the prefabs the project uses.
- Resources: Contains fonts and textures.
- Scenes: Contains the main scene you’ll work with.
- Scripts: Has all the project scripts. Most of them are empty shells which you’ll fill.
- Sounds: Has all the sound effects and the music file.
- Sprites: Contains all the pixel art for the project.
Navigate to RW/Scenes and open Main. Click Play and you’ll see the red player cannon in the bottom center of the screen and hear the music loop. In addition, you’ll see the Score and Lives UI labels.
You can’t interact with the game yet, but you’ll add the interactivity in this tutorial.
Stop the game and select Edit ▸ Project Settings ▸ Physics 2D. Take a look at the Layer Collision Matrix. Notice this project has two custom layers: Hero and Enemy.
The Physics 2D collision detection for GameObjects in the Enemy layer only work with GameObjects on the Hero layer. Hero layer GameObjects work for both Enemy and other Hero layered GameObjects. This information will be important later.
Now, take a look at the GameObjects in the Hierarchy. Some have custom components attached that you’ll work on in this tutorial.
Game Controller has Invader Swarm, Torchka Manager and Game Manager components attached. In addition to these, there’s an Audio Source component you’ll use for the sound effects.
Music has Music Control and an Audio Source. The Audio Source plays Beats located in RW/Sounds. This is the game’s main music. Currently, the music has a fixed tempo, but later you’ll make it dynamic.
CANNON has Cannon Control and a Box Collider 2D for collision detection. Notice its layer is set to Hero. It also has two immediate children: Sprite, which is its associated sprite, and Muzzle, an empty GameObject that represents the position for bullet instantiation while shooting.
Main Camera is the game’s main and only camera and has the Audio Listener. It’s orthographic with a position offset of -10 along the Z-axis.
All the other GameObjects have Z set to 0. In fact, for the others, assume the Z-axis doesn’t exist since this is a 2D game, and you’ll stick to positioning them only on the X and Y axes.
INVADERS Spawn Start and TORCHKA Spawn Center are two empty helper GameObjects parented to Helpers. Their positions will help you spawn the invader swarm and torchkas later on.
Canvas and Event System are for the UI. Canvas has the following children:
- Score Text: This displays the score.
- Lives Text: You’ll use this to display the remaining player lives.
- Game Over Panel: This panel displays the Game Over message when a player runs out of lives.
- All Clear: You’ll use this panel to display the All Clear message when the player eliminates all the invaders.
- Restart Button: This button restarts the game.
Now that you’re done with the tour, it’s time to add the good stuff. :] In the next section, you’ll work on the player controls.
Implementing the Player Controls
Select the CannonControl script attached to CANNON. Open it in your code editor. Paste the following code inside the class:
[SerializeField] private float speed = 500f; private void Update() { if (Input.GetKey(KeyCode.D)) { transform.Translate(speed * Time.deltaTime, 0, 0); } else if (Input.GetKey(KeyCode.A)) { transform.Translate(-speed * Time.deltaTime, 0, 0); } }
Here’s a code breakdown:
- The variable
speed
controls the cannon’s speed. - Inside
Update
, you check if the player is holding down keys D or A. If the player holds down D, the cannon moves right byspeed * Time.deltaTime
every frame. If they hold down A, the cannon moves left by the same amount.
Save the file. Return to Unity and select Play inside the editor. Now you can move the ship using D and A.
Next, you’ll add the shooting mechanics by using the Bullet prefab inside RW/Prefabs.
Select Bullet and take a look at the Inspector.
The prefab has a Kinematic Rigidbody 2D component. It’s Kinematic because you won’t rely on physics to move the bullet, but instead translate it via a script.
The Rigidbody 2D‘s layer is set to Hero and it has the Box Collider 2D component for collision detection. There’s also a Bullet component which doesn’t do anything yet.
Open the Bullet script inside your code editor. Add the following code inside the class:
[SerializeField] private float speed = 200f; [SerializeField] private float lifeTime = 5f; internal void DestroySelf() { gameObject.SetActive(false); Destroy(gameObject); } private void Awake() { Invoke("DestroySelf", lifeTime); } private void Update() { transform.Translate(speed * Time.deltaTime * Vector2.up); } private void OnCollisionEnter2D(Collision2D other) { DestroySelf(); }
In the code above, you:
- Use
Update
to move the bullet byspeed * Time.deltaTime
every frame toward the top. - Use
DestroySelf
to destroy the bullet GameObject. Before callingDestroy
, you disable the bullet because destruction takes some frames to process but disabling is almost instantaneous. -
Awake
invokesDestroySelf
to destroy the bullet automatically afterlifetime
seconds.DestroySelf
is also called when a bullet collides with another GameObject.
You’ll need sound effects during shooting. Inside RW/Scripts, open GameManager and add the following inside the class:
internal static GameManager Instance; [SerializeField] private AudioSource sfx; internal void PlaySfx(AudioClip clip) => sfx.PlayOneShot(clip); private void Awake() { if (Instance == null) { Instance = this; } else if (Instance != this) { Destroy(gameObject); } }
The above code turns GameManager
into a singleton, which ensures it only has a single instance at any given time. It also adds a utility method, PlaySfx
, which accepts an Audio Clip and plays it using the Audio Source sfx
.
To use the bullet, you need to instantiate it. Open CannonControl again and add the following after the declaration for speed
:
[SerializeField] private Transform muzzle; [SerializeField] private AudioClip shooting; [SerializeField] private float coolDownTime = 0.5f; [SerializeField] private Bullet bulletPrefab; private float shootTimer;
Then, after the earlier movement code inside Update
, paste:
shootTimer += Time.deltaTime; if (shootTimer > coolDownTime && Input.GetKey(KeyCode.Space)) { shootTimer = 0f; Instantiate(bulletPrefab, muzzle.position, Quaternion.identity); GameManager.Instance.PlaySfx(shooting); }
This code increments shootTimer
every frame until it reaches coolDownTime
. If the player holds down the Space key, the code resets shootTimer
and instantiates the bulletPrefab
at muzzle.position
. It also plays the shooting
sound effect by using the PlaySfx
inside GameManager
.
Save everything and return to Unity.
Go back to the Bullet prefab. Notice the new fields for the Bullet component.
Now, select Game Controller from the Hierarchy. Set Game Manager‘s Sfx to the Audio Source attached to the Game Controller.
Select CANNON from the Hierarchy. In Cannon Control, first set Muzzle to the Muzzle Transform that’s CANNON‘s child. Then set Bullet Prefab to the Bullet found in RW/Prefabs. Finally, set Shooting to CannonBullet found under RW/Sounds.
Save and Play. Now you can move the cannon and shoot laser bullets by holding down the Space key.
Your hero is ready! Now it’s time to add villains. In the next section, you’ll add the invader swarm.
Creating the Invaders
How do all antagonists start out their lives in games? Spawning of course! You’ll do that next.
Spawning the Invaders
Navigate to RW/Scripts to examine the SimpleAnimator script. You’ll use it to animate the invaders, but you don’t need to worry about its inner workings for this tutorial.
When you add SimpleAnimator to a GameObject you also add a SpriteRenderer because it’s a required component for the script to function.
SimpleAnimator also requires an array of sprites. It’ll keep updating the SpriteRenderer‘s sprite cyclically using the sprites inside the array, essentially animating it.
Now, select Invader Swarm attached to Game Controller. Open the associated script inside your code editor. Add the following code the inside the class:
[System.Serializable] private struct InvaderType { public string name; public Sprite[] sprites; public int points; public int rowCount; }
Here’s a code breakdown:
- This code defines an InvaderType struct.
- The
sprites
array stores all the sprites associated with the invader type, which SimpleAnimator uses to animate the invader. -
rowCount
is the number of rows the invader type will have in the swarm. -
name
stores the type name for the invader andpoints
stores the score contribution if the player eliminates an invader of this type. These will come in handy later.
Now add the following right below InvaderType
:
[Header("Spawning")] [SerializeField] private InvaderType[] invaderTypes; [SerializeField] private int columnCount = 11; [SerializeField] private int ySpacing; [SerializeField] private int xSpacing; [SerializeField] private Transform spawnStartPoint; private float minX;
You’ll use all of these fields for the spawning logic:
-
invaderTypes
: Represents all the invader types in use. -
columnCount
: Total number of columns for the swarm. -
ySpacing
: The spacing between each invader in the swarm along the Y-axis. -
xSpacing
: The spacing between each invader in the swarm along the X-axis. -
spawnStartPoint
: Spawning point for the first invader. -
minX
: Stores the minimum X position value for the swarm.
Next, paste the following code after all the variable declarations:
private void Start() { minX = spawnStartPoint.position.x; GameObject swarm = new GameObject { name = "Swarm" }; Vector2 currentPos = spawnStartPoint.position; int rowIndex = 0; foreach (var invaderType in invaderTypes) { var invaderName = invaderType.name.Trim(); for (int i = 0, len = invaderType.rowCount; i().sprites = invaderType.sprites; invader.transform.position = currentPos; invader.transform.SetParent(swarm.transform); currentPos.x += xSpacing; } currentPos.x = minX; currentPos.y -= ySpacing; rowIndex++; } } }
Here’s a code breakdown:
- This code sets up
minX
insideStart
. Then it creates an empty GameObject named Swarm. -
xSpacing
andySpacing
updatecurrentPos
along the X and Y-axis, respectively. -
currentPos.x
increments after every invader iteration in the row. After a row is complete,currentPos.y
decrements. - Looping over the members of
invaderTypes
, for eachinvaderType
, you iterate row-wise to create individual invader GameObjects at thecurrentPos
position. -
xSpacing
andySpacing
updatecurrentPos
along the X and Y axis, respectively. - Each created invader GameObject has its name set to
invaderName
. You then add aSimpleAnimator
component, and assign its sprite array to thesprites
associated with theinvaderType
. - Finally, the invader becomes a child of
Swarm
and its position is set tocurrentPos
.
Save everything and go back to Unity.
Select Game Controller from the Hierarchy. On Invader Swarm, set:
- Y Spacing to 25
- X Spacing to 25
- Spawn Start Point to INVADERS Spawn Start, which is a child of Helpers.
Then, set the Invader Types size to 3 and set the member fields as follows:
- 0: Set Name to SQUID, Points to 30 and Row Count to 1.
- 1: Set Name to CRAB, Points to 20 and Row Count to 1.
- 2: Set Name to OCTOPUS, Points to 10 and Row Count to 2.
Now, navigate to RW/Sprites and look at the INVADERS spritesheet. These aren’t the original Space Invaders but they’ll work for this tutorial.
Go back to Invader Swarm and set it up as follows, using the spritesheet:
For the SQUID entry, set the Sprites list to contain the following sprites from the spritesheet in this order:
- bugs_invaders_0
- bugs_invaders_5
- bugs_invaders_9
- bugs_invaders_4
Perform the same exercise, but this time for CRAB using the following sprites:
- bugs_invaders_13
- bugs_invaders_18
Lastly, assign the sprites for OCTOPUS using:
- bugs_invaders_7
- bugs_invaders_2
Here is a visual reference of what things should look like now:
Save and Play. Notice the swarm spawns, and the invaders animate in one place.
Wonderful! But they’re not moving. You’ll fix that in the next section.
Moving the Invaders
Go back to the InvaderSwarm.cs script and add the following after the existing variable declarations:
[Space] [Header("Movement")] [SerializeField] private float speedFactor = 10f; private Transform[,] invaders; private int rowCount; private bool isMovingRight = true; private float maxX; private float currentX; private float xIncrement;
All of these variables will help move the swarm:
-
speedFactor
for now represents the speed at which the invaders move along the X-axis. Later, speed will involve the music tempo so the actual speed is the product of the two. -
invaders
stores the Transforms of all the created invader GameObjects. -
rowCount
stores the total row count of the swarm. -
isMovingRight
represents the direction of movement and is set totrue
by default. -
maxX
is the maximum X position for the swarm movement. -
currentX
represents the overall X position of the swarm. -
xIncrement
is the value per frame that moves the invaders along the X-axis.
Now, in Start
, add the following code right above int rowIndex = 0;
:
foreach (var invaderType in invaderTypes) { rowCount += invaderType.rowCount; } maxX = minX + 2f * xSpacing * columnCount; currentX = minX; invaders = new Transform[rowCount, columnCount];
This code calculates the total row count and stores it inside rowCount
. Then, you calculate maxX
based on the total columns and the spacing between each invader. Initially, currentX
is set to the spawnStartPoint
‘s X position.
You declared the invaders
array. To populate it, you’ll need one more line of code.
Paste the following line inside the innermost for
loop, right above currentPos.x += xSpacing;
:
invaders[rowIndex, j] = invader.transform;
This line takes care of populating invaders
.
Finally, right after Start
, paste:
private void Update() { xIncrement = speedFactor * Time.deltaTime; if (isMovingRight) { currentX += xIncrement; if (currentXminX) { MoveInvaders(-xIncrement, 0); } else { ChangeDirection(); } } } private void MoveInvaders(float x, float y) for (int i = 0; i Here's a code breakdown:
MoveInvaders
accepts two float values:x
andy
. It moves each Transform ininvaders
by the same value along the X and Y axis respectively.ChangeDirection
togglesisMovingRight
and moves the swarm down by theySpacing
amount.- Inside
Update
, you calculatexIncrement
and updatecurrentX
based on the direction every frame.- You use
currentX
to check whether the swarm X position is approaching a threshold. If yes, you callChangeDirection
. If not, you move the swarm usingMoveInvaders
.Save everything. Go back to Unity and click Play. You'll see the invader swarm moving. While in Play Mode, try different values for the Invader Swarm's Speed Factor and see how it affects the swarm's speed.
The invaders are moving, but they don't shoot bullets yet. You'll work on that in the next section.
Making the Invaders Shoot Lasers
You'll use a variant of the Bullet prefab for the invaders. Navigate to RW/Prefabs and find the EnemyBullet. It's the same as Bullet, except the sprite points in the opposite Y direction and its layer is set to Enemy.
Select EnemyBullet. Notice the Speed of Bullet is set to -200. This ensures the bullet moves in the opposite direction of the cannon bullets but with the same magnitude.
In the game, only invaders at the front of the swarm shoot laser bullets. To achieve this, you'll use the BulletSpawner prefab located in RW/Prefabs.
You'll instantiate as many of them as the invader column count. You'll also make the bullet spawners follow the invader Transforms at the front of the swarm.
This will ensure that when firing, the bullets will appear to be coming from the front row invaders.
Before you do that, you need a way to get an invader Transform at a specific row and column from the
invaders
array insideInvaderSwarm
.Open the InvaderSwarm.cs script and add the following line after the
InvaderType
struct declaration:internal static InvaderSwarm Instance;This helps turn the
InvaderSwarm
into a Singleton.Then paste the following right above
Start
:internal Transform GetInvader(int row, int column) column >= invaders.GetLength(1)) { return null; } return invaders[row, column]; private void Awake() { if (Instance == null) { Instance = this; } else if (Instance != this) { Destroy(gameObject); } } 0>
GetInvader
returns the invader Transform at therow
andcolumn
index of invaders.Awake
turns theInvaderSwarm
into a Singleton by ensuring that when the game starts, only one instance ofInvaderSwarm
will be alive.Now, select the BulletSpawner prefab and take a look at the Inspector. Notice it has Bullet Spawner attached to it.
There's also a Kinematic Rigidbody 2D, a Box Collider 2D and the layer is set to Enemy. You won't add colliders to the invaders, but rather use this collider to detect hits from the cannon bullets.
In your code editor, open the BulletSpawner.cs script attached to BulletSpawner and add the following inside the class:
internal int currentRow; internal int column; [SerializeField] private AudioClip shooting; [SerializeField] private GameObject bulletPrefab; [SerializeField] private Transform spawnPoint; [SerializeField] private float minTime; [SerializeField] private float maxTime; private float timer; private float currentTime; private Transform followTarget; internal void Setup() { currentTime = Random.Range(minTime, maxTime); followTarget = InvaderSwarm.Instance.GetInvader(currentRow, column); } private void Update() { transform.position = followTarget.position; timer += Time.deltaTime; if (timerHere's a code breakdown:
currentTime
represents the time to wait until shooting the next bullet. It's set to a random value betweenminTime
andmaxTime
.currentRow
andcolumn
link a bullet spawner with an invader. Thecolumn
is set once and won't change. But, as you'll see later,currentRow
updates if the player's bullets hit this spawner.- Inside
Setup()
, you setfollowTarget
by callingGetInvader
from theInvaderSwarm
instance usingcurrentRow
andcolumn
. You also set an initial value tocurrentTime
.- Inside
Update
, you update the position of the bullet spawner to match thefollowTarget.position
. In addition, you increment thetimer
until it reachescurrentTime
. When this happens, you create a bullet atspawnPoint.position
while playing theshooting
sound effect, followed by resettingtimer
andcurrentTime
.Save everything. Return to Unity and open BulletSpawner in Prefab Mode. Ensure the following values are set for Bullet Spawner:
- Shooting to InvaderBullet located at RW/Sounds.
- Bullet Prefab to EnemyBullet located at RW/Prefabs.
- Spawn Point to the SpawnPoint Transform which is the only child of BulletSpawner.
- Min Time to 1 and Max Time to 10.
To use the BulletSpawner, you need to go back to the InvaderSwarm.cs script.
First, paste the following line at the end of all the variable declarations:
[SerializeField] private BulletSpawner bulletSpawnerPrefab;Then, inside
Start
, add the following lines at the end:for (int i = 0; iIn this code, you create a bullet spawner and set it up. You instantiate
bulletSpawner
for each column of the swarm and set itscolumn
andcurrentRow
followed by calling itsSetup
method. You also parent thebulletSpawner
to the Swarm to prevent clutter in the Hierarchy.Save everything and go back to Unity. Select Game Controller from the Hierarchy and set the Bullet Spawner Prefab for Invader Swarm to BulletSpawner, located in RW/Prefabs.
Save and Play. You now have invaders who shoot bullets at the player.
Notice that both bullets disappear when an invader bullet and cannon bullet collide. The invader bullet disappears when it hits the cannon and the cannon bullet disappears when it hits a bullet spawner. They disappear because
OnCollisionEnter2D
callsDestroySelf
insideBullet
.There's one thing missing, however, and that's explosions. :] You'll add them next.
Adding Explosions
Navigate to RW/Prefabs. Notice the Explosion prefab. It's a simple GameObject with a pre-made sprite animation you'll use for the explosion visuals.
Now, open GameManager.cs again. Add the following after declaring
sfx
:[SerializeField] private GameObject explosionPrefab; [SerializeField] private float explosionTime = 1f; [SerializeField] private AudioClip explosionClip; internal void CreateExplosion(Vector2 position) { PlaySfx(explosionClip); var explosion = Instantiate(explosionPrefab, position, Quaternion.Euler(0f, 0f, Random.Range(-180f, 180f))); Destroy(explosion, explosionTime); }
CreateExplosion
creates an explosion atposition
with a random rotation along the Z-axis, and destroys it afterexplosionTime
seconds.To use
CreateExplosion
, add the following line at the end ofDestroySelf
inside theBullet
class:GameManager.Instance.CreateExplosion(transform.position);Save all. Return to Unity and select Game Controller from the Hierarchy. For the Game Manager set:
- Explosion Prefab to Explosion located in RW/Prefabs.
- Explosion Clip to Explosion located in RW/Sounds.
Save the scene and Play. You now have explosions. :]
Currently, the bullets don't affect the invaders or the cannon. In the following sections, you'll add scores and lives.
Adding Scores and Lives
Did you notice those super retro UI labels in the game view? Right now they're just deadweight. It's time to get them working so that there are goals and consequences to give this game meaning.
Implementing Player Lives
Open GameManager.cs and add the following code after the variable declarations:
[SerializeField] private int maxLives = 3; [SerializeField] private Text livesLabel; private int lives; internal void UpdateLives() { lives = Mathf.Clamp(lives - 1, 0, maxLives); livesLabel.text = $"Lives: {lives}"; }Calling
UpdateLives
decrements thelives
variable by one and updates the UI label to reflect the change. Currently, nothing happens whenlives
reaches zero but you'll change that later.Add the following at the end of
Awake
:lives = maxLives; livesLabel.text = $"Lives: {lives}";This code sets the default value for
lives
and also updates the UI label.Now, open CannonControl and paste the following lines after the variable declarations:
[SerializeField] private float respawnTime = 2f; [SerializeField] private SpriteRenderer sprite; [SerializeField] private Collider2D cannonCollider; private Vector2 startPos; private void Start() => startPos = transform.position;Then, add the following lines after
Update
:private void OnCollisionEnter2D(Collision2D other) { GameManager.Instance.UpdateLives(); StopAllCoroutines(); StartCoroutine(Respawn()); } System.Collections.IEnumerator Respawn() { enabled = false; cannonCollider.enabled = false; ChangeSpriteAlpha(0.0f); yield return new WaitForSeconds(0.25f * respawnTime); transform.position = startPos; enabled = true; ChangeSpriteAlpha(0.25f); yield return new WaitForSeconds(0.75f * respawnTime); ChangeSpriteAlpha(1.0f); cannonCollider.enabled = true; } private void ChangeSpriteAlpha(float value) { var color = sprite.color; color.a = value; sprite.color = color; }Here's what's happening:
ChangeSpriteAlpha
changes the opacity of the cannon sprite.- When a bullet hits the cannon,
GameManager.UpdateLives
decrements the total lives and theRespawn
coroutine starts.Respawn
first disables thecannonCollider
and makes the cannon sprite invisible. After a few moments, it makes the cannon sprite slightly transparent and sets cannon's position back tostartPos
. Finally, it restores the opacity of the sprite and enables the collider again.Save everything and return to Unity. Select Game Controller and set the Game Manager's Lives Label to Lives Text, which is a child of Canvas.
For the Cannon Control on CANNON, set Sprite to the Sprite Renderer on Sprite, a child GameObject of CANNON. Set Collider to the Box Collider 2D on the CANNON.
Now, save and Play. You'll see the respawn sequence as well as the lives update whenever a bullet hits the cannon.
The bullets don't seem to affect the invaders. You'll work on that in the next section.
Implementing Score and Game Over
Before you do anything else, open the MusicControl.cs script. You want to stop the music at game over, so paste the following code inside the class:
[SerializeField] private AudioSource source; internal void StopPlaying() => source.Stop();
StopPlaying
stops the audio source when called.Now, open the GameManager.cs script and add the following after the variable declarations:
[SerializeField] private MusicControl music; [SerializeField] private Text scoreLabel; [SerializeField] private GameObject gameOver; [SerializeField] private GameObject allClear; [SerializeField] private Button restartButton; private int score; internal void UpdateScore(int value) { score += value; scoreLabel.text = $"Score: {score}"; } internal void TriggerGameOver(bool failure = true) { gameOver.SetActive(failure); allClear.SetActive(!failure); restartButton.gameObject.SetActive(true); Time.timeScale = 0f; music.StopPlaying(); }Then, paste the following lines at the end of
UpdateLives
:if (lives > 0) { return; } TriggerGameOver();Finally, add the following at the end of
Awake
:score = 0; scoreLabel.text = $"Score: {score}"; gameOver.gameObject.SetActive(false); allClear.gameObject.SetActive(false); restartButton.onClick.AddListener(() => { SceneManager.LoadScene(SceneManager.GetActiveScene().name); Time.timeScale = 1f; }); restartButton.gameObject.SetActive(false);Here's what this code does:
allClear
stores a reference to the All Clear panel, which displays when the player eliminates all the invaders.gameOver
references the Game Over panel that shows when the player runs out of lives.UpdateScore
incrementsscore
by thevalue
passed to it and updates the UI label to reflect the changes.TriggerGameOver
shows the Game Over panel iffailure
is true. Otherwise, it shows the All Clear panel. It also enables therestartButton
, pauses the game and stops the music.Awake
handles theonClick
event for the restart button. It reloads the scene when clicked.Open InvaderSwarm.cs and add the following inside the class after the variable declarations:
private int killCount; private System.Collections.Generic.DictionarypointsMap; internal void IncreaseDeathCount() { killCount++; if (killCount >= invaders.Length) { GameManager.Instance.TriggerGameOver(false); return; } } internal int GetPoints(string alienName) { if (pointsMap.ContainsKey(alienName)) { return pointsMap[alienName]; } return 0; } Then paste the following line right above
int rowIndex = 0;
insideStart
:pointsMap = new System.Collections.Generic.Dictionary(); Below the line right under
var invaderName = invaderType.name.Trim();
add the following:pointsMap[invaderName] = invaderType.points;Here's a code breakdown:
pointsMap
is a Dictionary (a map of string to integer). It maps the invader typename
with itspoints
value.IncreaseDeathCount
keeps track of and updateskillCount
when the player eliminates an invader. When the player eliminates all of the invaders,TriggerGameOver
receivesfalse
and displays the All Clear panel.GetPoints
returns the points associated with an invader type by passing in its name as the key.Finally, open BulletSpawner.cs to handle collision detection for the invaders. Paste the following right after
Update
:private void OnCollisionEnter2D(Collision2D other) { if (!other.collider.GetComponent()) { return; } GameManager.Instance. UpdateScore(InvaderSwarm.Instance.GetPoints(followTarget.gameObject.name)); InvaderSwarm.Instance.IncreaseDeathCount(); followTarget.GetComponentInChildren ().enabled = false; currentRow = currentRow - 1; if (currentRow <0)> Here's what this code does:
OnCollisionEnter2D
returns without doing anything if the object that hit the bullet spawner wasn't of typeBullet
.- If the cannon bullet hit the spawner, the score and kill count update. Also, the current
followTarget
's Sprite Renderer disables, then updates thecurrentRow
.- If there aren't any rows left, the GameObject is disabled. Otherwise, you call
Setup
to update thefollowTarget
.Wow! That was a lot of work. Save everything and jump back into Unity to finish this step.
Select Music and set Source of Music Control to the Audio Source component on the same GameObject.
Then, select Game Controller. For Game Manager, set:
- Music to Music Control of Music.
- Score Label to Score Text.
- Game Over to Game Over Panel.
- All Clear to All Clear.
- Restart Button to Restart Button.
Save and Play. Now you can kill the invaders! Enable Gizmos in the Game View and select the Swarm to see how the bullet spawners update.
Kill all the invaders to see the All Clear, or let the lives run out to see the Game Over. Then, you can use the Restart button to reload the scene.
The invaders are a bit slow and have the same speed throughout. In the next section, you'll update their speed with the tempo of the music.
Adding Simple Dynamic Audio
Go to MusicControl.cs. Open it inside your code editor. Add the following line at the top of the class:
private readonly float defaultTempo = 1.33f;This line represents the music's default beats per second. You can calculate this value by considering the music has four beats and is three seconds long.
Now, paste the following above
StopPlaying
:[SerializeField] internal int pitchChangeSteps = 5; [SerializeField] private float maxPitch = 5.25f; private float pitchChange; internal float Tempo { get; private set; }Then, add the following lines after the definition for
StopPlaying
:internal void IncreasePitch() { if (source.pitch == maxPitch) { return; } source.pitch = Mathf.Clamp(source.pitch + pitchChange, 1, maxPitch); Tempo = Mathf.Pow(2, pitchChange) * Tempo; } private void Start() { source.pitch = 1f; Tempo = defaultTempo; pitchChange = maxPitch / pitchChangeSteps; }Here's how the code works:
- Inside
Start
,source.pitch
andTempo
are set to their default values.IncreasePitch
increments the the source audio's pitch by an amount dictated bypitchChange
, which in turn is the ratio ofmaxPitch
andpitchChangeSteps
.maxPitch
also puts an upper limit on the pitch.- After changing the pitch, you can calculate the tempo based on the following formula:
Now open the InvaderSwarm.cs script and add the following at the end of variable declarations:
[SerializeField] private MusicControl musicControl; private int tempKillCount;In
IncreaseDeathCount
, paste the following lines at the end:tempKillCount++; if (tempKillCountNow
IncreaseDeathCount
tracks the variabletempKillCount
to check whether it exceedsinvaders.Length / musicControl.pitchChangeSteps
. If it does, it callsIncreasePitch
andtempKillCount
resets.This means when the player eliminates almost
invaders.Length / musicControl.pitchChangeSteps
invaders, both audio pitch and tempo increase. The variableTempo
insideMusicControl
keeps track of the updated tempo.Finally, inside
Update
, replacexIncrement = speedFactor * Time.deltaTime;
with:xIncrement = speedFactor * musicControl.Tempo * Time.deltaTime;This line ensures the
xIncrement
considers the music tempo, and the invaders move faster as the music gets faster as the player eliminates more and more invaders.Save everything. Return to Unity and select Game Controller. Set the Invader Swarm's Music Control to Music.
Save and Play.
Try shooting down the invaders. You'll notice they start moving faster.
There's still one minor issue: If you miss any invaders, they continue moving once they reach the bottom of the screen. It would be better to trigger Game Over if the swarm reaches the bottom.
To do this, open InvaderSwarm.cs and add the following at the end of variable declarations:
[SerializeField] private Transform cannonPosition; private float minY; private float currentY;Then, paste the following lines at the beginning of
Start
:currentY = spawnStartPoint.position.y; minY = cannonPosition.position.y;Then add the following at the end of
ChangeDirection
:currentY -= ySpacing; if (currentYHere's what this code does:
- Inside
Start
, you setminY
to the Y position of the cannon andcurrentY
to the Y position ofspawnStartPoint
.- Whenever called,
ChangeDirection
decrementscurrentY
until it becomes less thanminY
, at which point the game ends and shows Game Over.Save everything. Return to Unity and select Game Controller. Set Cannon Position of Invader Swarm to the Transform of CANNON.
Save and Play. Now, you'll see that Game Over panel is triggered if the swarm goes below the cannon's position.
And you're done!
Where to Go From Here?
You can use the Download Materials button at the top and bottom of this tutorial to download both the starter and final projects.
You may have noticed you didn't get to add any torchkas. Try adding them as a challenge.
Everything you need is already there. You may need to examine the code inside TorchkasManager and the Torchka scripts. If you get stuck, take a look at the Final project for the solution. Good luck!
Thanks for taking the time to read this article. I hope you had fun and learned something new. Please feel free to join the forum below for any questions or comments.
Special thanks to opengameart user jlunesc for some of the CC-BY assets used in the project.
0)>
}}