In this beginner-friendly Unity 2D tutorial, we'll build a simple space shooter game. You’ll move a spaceship, fire projectiles, and defeat incoming enemies — all step by step. No experience is needed. Just install Unity 6.0 LTS (or any version with 2D support) and follow along.
Every step is short, clear, and calm. You’ll never be rushed. Whether you're learning for fun or starting your first project — welcome aboard.
We’ll begin by creating a new Unity 2D project using the Universal 2D Core template. This gives us a clean space to build in — with everything ready for sprites, physics, and code.
Tip: You can name your project SpaceShooter
or pick
something fun — the name is yours.
Let’s bring our art into Unity.
Download the image file called space-sprites.png
— it
contains all the graphics we’ll need: enemies, player ship,
projectile, and more.
Open your Unity project and import the file:
Once imported, select space-sprites.png in the Assets window. In the Inspector panel, make sure:
Sprite (2D and UI)
Multiple
Then click Apply to confirm the settings.
Tip: “Multiple” means the image contains more than one sprite — like a sheet with multiple characters.
Unity should automatically slice the image when imported. If not, click on Open Sprite Editor, then use the Slice button with Type: Automatic selected. Then click Apply.
Tip: If automatic slicing fails, you can switch to grid-based slicing or adjust slices manually. Just be sure to click Apply before closing the editor.
Let’s build the ship we’ll be flying. In this game, we won’t use keyboard controls — we’ll use the mouse. The ship should fly toward the point we click.
Start by dragging the blue ship sprite into the scene. Unity will
create a new GameObject. Rename it to Player
, and set its
tag to Player.
Now we’ll add physics. With Player
selected, click
Add Component and add:
In the Rigidbody 2D settings:
0
Interpolate
Then create a new C# script named PlayerMove
and attach
it to the Player
object.
using UnityEngine;
public class PlayerMove : MonoBehaviour
{
// Stores the position where the player clicked
Vector3 clickPos;
// Vector that points from the ship toward the clicked position
Vector3 move;
// Movement speed of the ship
public float speed = 1f;
// Reference to this object's Rigidbody2D component
Rigidbody2D rb;
void Start()
{
// Get the Rigidbody2D component on this object
rb = GetComponent<Rigidbody2D>();
// Start with no movement
clickPos = transform.position;
}
void Update()
{
// If left mouse button is pressed, update the target position
if (Input.GetMouseButton(0))
{
// Convert screen coordinates to world coordinates
clickPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
}
// Calculate the direction vector to move toward the click
move = clickPos - transform.position;
}
void FixedUpdate()
{
// Set the velocity of the ship to move toward the click
// We use only X and Y for 2D movement
rb.linearVelocity = new Vector2(move.x, move.y) * speed;
}
}
Save the script and return to Unity. In the Inspector, set
Speed
to 1
. Press Play and test it — the
ship should now move to wherever you click.
Tip: The mouse position is given in screen coordinates — in pixels. To
convert it into a usable position in the scene, use
Camera.ScreenToWorldPoint
. It returns a point in the
world, but note: the Z-value will match the camera’s
Z-position. Since we’re working in 2D, this is usually fine — we only
care about X and Y.
Now we’ll give our ship something to shoot. Let’s add a laser.
Drag the orange laser sprite into the scene. Unity will create a new
GameObject. Rename it to Laser
. In the
Inspector, set its Order in Layer to
-10
so it's drawn behind the ship. Resize it if needed —
just make sure it looks small and clean.
Add the following components to the Laser
:
0
Now create a new C# script and name it LaserShot
. This
script will push the laser upwards when it spawns, and remove it when
it leaves the screen.
using UnityEngine;
public class LaserShot : MonoBehaviour
{
// Reference to this object's Rigidbody2D
Rigidbody2D rb;
// Speed of the laser
public float force = 5f;
void Start()
{
// Get the Rigidbody2D component
rb = GetComponent<Rigidbody2D>();
// Create a vector pointing up and apply force
Vector3 direction = new Vector3(0, force, 0);
rb.AddForce(direction, ForceMode2D.Impulse);
}
// Called when the laser goes off-screen
void OnBecameInvisible()
{
// Destroy the laser to keep the scene clean
Destroy(gameObject);
}
}
Attach this script to the Laser
object and set the
Force
value to something like 5
.
Finally, drag the Laser
GameObject into the
Assets window to create a prefab. Unity will generate
it automatically. You can now delete the laser from the scene — we’ll
spawn it later by script.
The laser can fly — now let’s make the ship shoot it.
We’ll use a new script to spawn a laser whenever the player presses the right mouse button. To prevent too many lasers spawning at once, we’ll add a short delay between shots using a coroutine.
Create a new C# script and name it PlayerShoot
. This
script should look like this:
using UnityEngine;
using System.Collections;
public class PlayerShoot : MonoBehaviour
{
// Laser prefab to spawn
public GameObject laser;
// Delay between shots
public float delayTime = 0.5f;
// Can the player shoot right now?
bool canShoot = true;
void Update()
{
// Check if right mouse button is pressed and shooting is allowed
if (canShoot && Input.GetMouseButton(1))
{
// Disable shooting for now
canShoot = false;
// Spawn a laser at the player's position
Instantiate(laser, transform.position, transform.rotation);
// Start cooldown
StartCoroutine(NoFire());
}
}
// Wait for a short delay before allowing the next shot
IEnumerator NoFire()
{
yield return new WaitForSeconds(delayTime);
canShoot = true;
}
}
Attach the PlayerShoot
script to the player object. Drag
the Laser
prefab into the script's
Laser field. Set Delay Time to
0.5
.
Press Play and try it — your ship should now fire a laser when you right-click!
Now let’s add some danger. We’ll use a small enemy mine that floats downward toward the player.
Drag the mine sprite into the scene. Unity will create a new
GameObject — rename it to Mine
. Set its
Order in Layer to -5
(between the ship
and laser). Resize it if needed.
Add these components to the mine:
0
Add a new tag named Enemy
and assign it to the mine.
Now create a new C# script and name it MineMove
. It will
move the mine downward, destroy it when it leaves the screen, and
reset the scene if it hits the player.
using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;
public class MineMove : MonoBehaviour
{
// Speed of the mine
public float speed = 1f;
// Rigidbody2D reference
Rigidbody2D rb;
void Start()
{
// Get Rigidbody2D and set downward movement
rb = GetComponent<Rigidbody2D>();
Vector3 move = new Vector3(0, -1, 0);
rb.linearVelocity = move * speed;
}
void OnBecameInvisible()
{
// Destroy the mine if it's off screen
Destroy(gameObject);
}
void OnCollisionEnter2D(Collision2D collision)
{
// If the mine hits the player, reset the game
if (collision.gameObject.tag == "Player")
{
Destroy(gameObject);
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
}
Attach the script to the Mine
object. Set
Speed to 1
. Then drag the mine into the
Assets window to create a prefab. You can now delete
it from the scene — we’ll spawn it later.
Now let’s automate the enemy waves. We’ll set up a spawner that creates new mines at random positions.
Go to GameObject → Create Empty. Unity will create a
new GameObject in the scene. Rename it to MineSpawner
.
You can assign it a custom icon to make it easier to see in the Scene
view.
Move the MineSpawner
to the top-left of the visible
screen area. Make sure its Z position is
0
, since this is a 2D game.
With MineSpawner
selected, press
Ctrl + D (or right-click → Duplicate). Rename the new
object to RightPosition
and make it a
child of MineSpawner. Move it to the top-right of the
screen. Set its Z position to 0
as well.
Now create a new C# script and name it ObjectSpawner
. It
will randomly pick an X-position between the two points and spawn
mines there on a delay.
using UnityEngine;
using System.Collections;
public class ObjectSpawner : MonoBehaviour
{
// Rightmost point used to calculate random X position
public Transform RightPosition;
// Delay between spawns
public float spawnDelay = 5f;
// The object to spawn
public GameObject Item;
void Start()
{
// Call the spawn function repeatedly
InvokeRepeating("Spawn", spawnDelay, spawnDelay);
}
void Spawn()
{
// Choose a random X position between spawner and right edge
Vector3 spawnPos = new Vector3(
Random.Range(transform.position.x, RightPosition.position.x),
transform.position.y,
0);
// Spawn the object at the calculated position
Instantiate(Item, spawnPos, transform.rotation);
}
}
Attach this script to the MineSpawner
object. Drag
RightPosition
into the
Right Position field, and drag the
Mine
prefab into the Item field. Set
Spawn Delay to 5
.
Press Play — mines will now appear at random positions from the top of the screen.
Right now, lasers fly through the mines without any effect. Let’s fix that by giving mines health points and making lasers deal damage.
Create a new C# script and name it HpController
. This
script holds a health value and removes the object when it reaches
zero.
using UnityEngine;
public class HpController : MonoBehaviour
{
// Health points
public int hp = 3;
// Call this method to apply damage
void MakeDamage(int damage)
{
hp -= damage;
// If health reaches zero, destroy this object
if (hp <= 0)
{
Destroy(gameObject);
}
}
}
Select the Mine
prefab in the Assets window and add the
HpController
script to it. Set hp to
3
.
Now open the LaserShot
script. We’ll check for collisions
and send damage to the enemy.
using UnityEngine;
public class LaserShot : MonoBehaviour
{
// Rigidbody2D reference
Rigidbody2D rb;
// Force applied to move the laser
public float force = 10f;
// Damage dealt to enemies
public int damage = 1;
void Start()
{
rb = GetComponent<Rigidbody2D>();
Vector3 direction = new Vector3(0, force, 0);
rb.AddForce(direction, ForceMode2D.Impulse);
}
void OnBecameInvisible()
{
Destroy(gameObject);
}
void OnTriggerEnter2D(Collider2D other)
{
// Check if we hit an enemy
if (other.gameObject.tag == "Enemy")
{
// Send damage to the target, if it has a damage handler
other.gameObject.SendMessage("MakeDamage", damage, SendMessageOptions.DontRequireReceiver);
// Destroy the laser
Destroy(gameObject);
}
}
}
Select the Laser
prefab and set the
Damage value in the Inspector to 1
.
Press Play and test the scene. Mines should now take 3 hits to be destroyed.
Let’s add an enemy that shoots back. We’ll reuse our existing setup and make a few changes.
Drag the Mine
prefab into the scene. Rename it to
Enemy-1
. Change its sprite to the enemy ship image. Set
HP to 1
.
Right-click on the Circle Collider 2D and remove it.
Add a new Box Collider 2D instead. Then drag the
Enemy-1
into the Assets window to create a new prefab.
You can delete it from the scene after that.
Now let’s give this enemy a rocket. Drag the rocket sprite into the
scene and rename it to Rocket
. Set its
Order in Layer to -10
. Add a
Box Collider 2D and enable
Is Trigger. Add a Rigidbody2D with
Gravity Scale = 0 and
Interpolate = Interpolate.
Create a new C# script and name it EnemyBullet
. Here’s
what it should do:
using UnityEngine;
public class EnemyBullet : MonoBehaviour
{
GameObject player;
Rigidbody2D rb;
public float force = 3f;
public int damage = 1;
void Start()
{
rb = GetComponent<Rigidbody2D>();
player = GameObject.FindWithTag("Player");
if (player != null)
{
// Get direction to player
Vector3 dir = player.transform.position - transform.position;
// Point the rocket toward the target
transform.up = dir;
// Push the rocket forward
rb.AddRelativeForce(transform.up * force, ForceMode2D.Impulse);
}
else
{
// If player is not found, remove the rocket
Destroy(gameObject);
}
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "Player")
{
// Apply damage to the player (if possible)
other.gameObject.SendMessage("MakeDamage", damage, SendMessageOptions.DontRequireReceiver);
// Destroy the rocket
Destroy(gameObject);
}
}
void OnBecameInvisible()
{
// Clean up when the rocket leaves the screen
Destroy(gameObject);
}
}
Add the EnemyBullet
script to the Rocket
.
Set Force to 3
and
Damage to 1
. Create a prefab by dragging
it into the Assets window. Then delete the rocket from the scene.
Now it’s time to give the enemy ship some firepower. We’ll add a shooting script that periodically spawns a rocket and lets the rocket handle the rest.
Create a new C# script and name it EnemyShoot
.
using UnityEngine;
using System.Collections;
public class EnemyShoot : MonoBehaviour
{
// Prefab of the rocket to shoot
public GameObject bullet;
// Time between shots
public float fireDelay = 2f;
// Reference to the player
GameObject player;
// Can this enemy shoot right now?
bool canShoot = true;
void Start()
{
// Find player in the scene by tag
player = GameObject.FindWithTag("Player");
}
void Update()
{
// Only shoot if player exists and we are allowed
if (canShoot && player != null)
{
canShoot = false;
// Spawn the rocket at enemy’s current position
Instantiate(bullet, transform.position, Quaternion.identity);
// Start cooldown before next shot
StartCoroutine(firePause());
}
}
IEnumerator firePause()
{
// Wait for delay, then allow shooting again
yield return new WaitForSeconds(fireDelay);
canShoot = true;
}
}
Select the Enemy-1
prefab in the Assets window. Add the
EnemyShoot
script to it. In the
Bullet field, drag the Rocket
prefab.
Set Fire Delay to 2
.
Our Enemy-1
prefab is ready. Now let’s make them appear
in the scene just like the mines. Instead of creating a second
spawner, we’ll improve the existing one and allow it to choose
randomly between multiple objects.
Open the ObjectSpawner
script and update it to use an
array of prefabs:
using UnityEngine;
public class ObjectSpawner : MonoBehaviour
{
// Position used to define the right border for random spawn
public Transform RightPosition;
// Delay between each spawn
public float spawnDelay;
// List of objects to spawn (e.g. mines, enemies)
public GameObject[] Item;
void Start()
{
// Call the spawn function again and again
InvokeRepeating("Spawn", spawnDelay, spawnDelay);
}
void Spawn()
{
// Choose random horizontal position between spawner and right point
Vector3 spawnPos = new Vector3(
Random.Range(transform.position.x, RightPosition.position.x),
transform.position.y,
0
);
// Pick a random object from the list
int i = Random.Range(0, Item.Length);
// Spawn the selected object
Instantiate(Item[i], spawnPos, transform.rotation);
}
}
In the MineSpawner
object (in the scene), look at the
updated Item list. Add the Mine
and
Enemy-1
prefabs to the array. Unity will handle the size
of the list automatically.
Our player still has no visible health. Let's add a simple UI to show the current health on screen. We’ll create a horizontal health bar using Unity’s built-in UI system.
Go to GameObject → UI → Image. This will create a new
Canvas
with an Image
object inside it.
Rename the Image to HealthBar
.
Select the Canvas
. In the Inspector, under the
Canvas Scaler component, set:
UI Scale Mode to Scale With Screen Size
.
Now double-click on the Canvas
in Hierarchy — the scene
view will zoom out and show the UI area. Select the
HealthBar
and use the Rect Tool to
stretch it into a bar shape. You can place it wherever it fits your
layout.
With HealthBar
selected:
Background
.Filled
.Horizontal
.Left
.
Let’s now connect the health bar to actual gameplay. We’ll write a script that keeps track of the player's HP and updates the health bar accordingly.
Create a new C# script and name it PlayerHp
:
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class PlayerHp : MonoBehaviour
{
// Reference to the UI Image (health bar)
public GameObject HealthBar;
// Internal reference to the Image component
Image img;
// Current and maximum health
public int hp;
float maxHp;
void Start()
{
// Get the Image component from the assigned object
img = HealthBar.GetComponent<Image>();
// Set max HP
maxHp = hp;
// Set full health visually
img.fillAmount = hp / maxHp;
}
// Called when the player takes damage
void MakeDamage(int damage)
{
hp -= damage;
// If HP runs out, restart the scene
if (hp <= 0)
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
// Update health bar fill amount
img.fillAmount = hp / maxHp;
}
}
Now go to the scene and select your Player object.
Add the PlayerHp
script to it. Drag the
HealthBar
(from inside the Canvas) into the Health Bar
field. Set the HP value to 10
.
Let’s start by turning the background of our scene to deep space
black. Select Main Camera in the Hierarchy. In the
Inspector, go to the Environment section and set the
Background color to black
.
For a nice space effect, we’ll use Unity’s built-in Particle System to simulate moving stars.
Stars
.10
and
X Rotation to 90
.
0.2
(or as
preferred).
Box
.15
— this makes the
starfield wide.
-100
so
stars render far behind the scene.
Let’s give the enemy’s rocket a proper shooting sound. When a rocket is spawned, we will play a short sound clip using Unity’s built-in audio system.
You can download the sound effects used in this tutorial here:
Open the EnemyBullet
script and add this variable:
//variable for sound clip
public AudioClip BulletSound;
Then, inside the Start()
function, add this line after
launching the rocket:
AudioSource.PlayClipAtPoint(BulletSound, transform.position);
This will create a small temporary audio object in the scene that plays the clip and then disappears.
Here is the complete updated EnemyBullet
script with
sound support:
using UnityEngine;
public class EnemyBullet : MonoBehaviour
{
GameObject player;
Rigidbody2D rb;
public float force = 3f;
public int damage = 1;
public AudioClip BulletSound;
void Start()
{
rb = GetComponent<Rigidbody2D>();
player = GameObject.FindWithTag("Player");
if (player != null)
{
// Get direction to player
Vector3 dir = player.transform.position - transform.position;
// Point the rocket toward the target
transform.up = dir;
// Push the rocket forward
rb.AddRelativeForce(transform.up * force, ForceMode2D.Impulse);
// Play shooting sound
AudioSource.PlayClipAtPoint(BulletSound, transform.position);
}
else
{
// Remove the rocket if no player found
Destroy(gameObject);
}
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "Player")
{
// Send damage to the player
other.gameObject.SendMessage("MakeDamage", damage, SendMessageOptions.DontRequireReceiver);
// Destroy the rocket
Destroy(gameObject);
}
}
void OnBecameInvisible()
{
// Remove rocket if off-screen
Destroy(gameObject);
}
}
Now select the Rocket prefab. Assign
LaserSound
to the Bullet Sound field in
the EnemyBullet
script.
💡 You can replace this sound with your own by importing any
.wav
file and assigning it to the
AudioClip field.
Just like the enemy rockets, we want to add sound to the player's
laser shot. Open the LaserShot
script and update it as
follows:
using UnityEngine;
public class LaserShot : MonoBehaviour
{
// Rigidbody2D reference
Rigidbody2D rb;
// Force applied to move the laser
public float force = 10f;
// Damage dealt to enemies
public int damage = 1;
// Sound effect for laser shot
public AudioClip LaserSound;
void Start()
{
rb = GetComponent<Rigidbody2D>();
// Move the laser up
Vector3 direction = new Vector3(0, force, 0);
rb.AddForce(direction, ForceMode2D.Impulse);
// Play shooting sound
AudioSource.PlayClipAtPoint(LaserSound, transform.position);
}
void OnBecameInvisible()
{
Destroy(gameObject);
}
void OnTriggerEnter2D(Collider2D other)
{
// Check if we hit an enemy
if (other.gameObject.tag == "Enemy")
{
// Send damage to the target, if it has a damage handler
other.gameObject.SendMessage("MakeDamage", damage, SendMessageOptions.DontRequireReceiver);
// Destroy the laser
Destroy(gameObject);
}
}
}
After updating the script, go to the Laser prefab in
the Assets window and assign the LaserSound.wav
clip to
the new Laser Sound field.
💡 If you want to use your own sound effects instead of the ones
provided, you can drag your custom .wav
file into the
Assets/sound
folder and assign it in the same way.
We want destroyed enemies and mines to play a short explosion sound
when they disappear. For this, we'll slightly extend the
HpController
script by adding an audio field and playing
it when health reaches zero.
using UnityEngine;
public class HpController : MonoBehaviour
{
// Health points
public int hp = 3;
// Explosion sound effect
public AudioClip ExplosionsSound;
// Call this method to apply damage
void MakeDamage(int damage)
{
hp -= damage;
// If health reaches zero, destroy this object
if (hp <= 0)
{
// Play explosion sound
AudioSource.PlayClipAtPoint(ExplosionsSound, transform.position);
Destroy(gameObject);
}
}
}
Select both Mine and Enemy-1 prefabs
in the Assets
window. In their
HpController component, assign the
Boom.wav
file to the
Explosions Sound field.
That's it! Now every time an enemy or mine is destroyed, the game gives you satisfying audio feedback.
💡 You can use your own custom explosion sound too — just drag it into
the Assets/sound
folder and assign it the same way.
We should add a particle effect when an Enemy (like a Mine or Enemy-1)
is destroyed. Create a new Particle System and rename
it to KiavoBoom
. Set Position and
Rotation all to 0
. In the Inspector,
make the following adjustments:
Go to the Emission module, add a Burst at
time 0.0
with count 30
.
In the Shape module, change Shape
to
Circle
and Radius
to 0.1
.
Enable Color over Lifetime. Open the color editor,
set the final Alpha to 0
, and choose colors you
like at the start and end. In the Renderer module,
set Order in Layer to 10
(this setting
is in the Renderer section).
Add the script TimeDestroyer
to the KiavoBoom object and
set Time To Destroy to 2
. Then make it a
prefab and remove it from the scene.
You can experiment with more dramatic settings or colors later — this is just a basic explosion for now.
We have now an explosion prefab. We should spawn it when an enemy
(like a Mine or Enemy-1) is destroyed. Open the
HpController
script and modify it like this:
using UnityEngine;
public class HpController : MonoBehaviour
{
// Health points
public int hp = 3;
// here explosion prefab
public GameObject Explosion;
// Explosion sound effect
public AudioClip ExplosionsSound;
// Call this method to apply damage
void MakeDamage(int damage)
{
hp -= damage;
// If health reaches zero, destroy this object
if (hp <= 0)
{
// Play explosion sound
AudioSource.PlayClipAtPoint(ExplosionsSound, transform.position);
// place explosion on gameobject position
Instantiate(Explosion, transform.position, Quaternion.identity);
// destroy this object
Destroy(gameObject);
}
}
}
Now select the Mine and
Enemy-1 prefabs. In the
HpController
component, assign your explosion prefab
(e.g. KiavoBoom
) to the Explosion field.
KiavoBoom
explosion is now linked in the
HpController
of each enemy prefab. When destroyed,
they’ll disappear with sound and particles.
Our game is now complete — it has movement, enemies, shooting, sound, and UI. This tutorial focused on building a simple but working 2D space shooter using core Unity features.
Of course, you can expand it further: add explosions, visual effects, waves of enemies, score, lives, menus, boss fights, background music… even flying chickens or space tea ☕🛸
But our goal was to show you a clean and simple foundation — and now you have it.
💡 Keep experimenting, have fun, and build your own version from here!