Learning how to make a 2D top down game in Unity comes down to getting four systems right: player movement, the tile world, collisions and the camera. Get those working in an empty scene and you have the skeleton of a Zelda-like, a twin-stick shooter or a farming sim. This guide covers each system with working C# code, points out the mistakes that trip up most beginners, and shows where Sorceress fits when you’d rather spend your evenings on design than on boilerplate and pixel pushing.
What is a 2D top down game?
A top-down game shows the world from above, so the player moves freely on two axes: left and right on X, up and down on Y. Nothing pulls the character toward a floor, and that’s the big mechanical difference from a platformer. Classic examples include early Zelda games, Pokémon-style RPGs and arena shooters. Browser battle royales use the same view: ZombsRoyale.io pairs simple 2D graphics with a top-down perspective on a large shared map.
You’ll see a few terms over and over:
- Sprite: a 2D image drawn in the game world, such as the player, a coin or a tree.
- Sprite sheet: one image that holds many sprites or animation frames in a grid. Unity slices it into separate pieces.
- Tileset: a sheet of small square images (grass, wall, water edge) designed to snap together on a grid.
- Tilemap: Unity’s grid component. You paint tiles onto it to build a level.
- Rigidbody2D: the component that hands an object to Unity’s 2D physics system so it can collide with walls.
Unity is a cross-platform engine from Unity Technologies, first released in 2005, and its 2D tooling is mature: Tilemaps, sprite slicing, 2D physics and 2D lights. A game engine is the framework that handles rendering, physics, input and audio, so you only write the rules of your game. That’s why figuring out how to make a 2D top down game in Unity takes surprisingly little code. Most of the work is setting things up correctly in the editor.
How to make a 2D top down game in Unity: project setup
1. Install Unity and create a 2D project
Install the Unity Hub, add a recent editor version, then create a new project from a 2D template. In Unity 6 the usual pick is Universal 2D. It uses the Universal Render Pipeline (URP) with the 2D Renderer and comes with the Tilemap and Sprite packages. Choose it unless a tutorial you’re following needs the built-in pipeline.
2. Organise the project
Create folders for Art, Tiles, Scripts, Prefabs and Scenes on day one. It feels fussy, but a top-down game generates dozens of tile assets quickly, and a clean structure saves you from digging through the Project window later.
3. Import your sprites correctly
Drag your character sheet and tileset into the Art folder. Then select each one and check these settings in the Inspector:
- Texture Type: Sprite (2D and UI).
- Sprite Mode: Multiple for sheets. Then open the Sprite Editor and slice by cell size.
- Pixels Per Unit: match your tile size. For 16×16 pixel tiles, use 16 so one tile equals one Unity unit.
- Filter Mode: Point (no filter) for pixel art. Any other setting makes sprites look blurry.
- Compression: None for small pixel-art sheets, so colours don’t smear.
4. Add the packages you need
Open the Package Manager, check that the Input System package is installed, then add Cinemachine for the follow camera. That’s the whole toolkit. Everything else in this guide is built into Unity.

Player movement in C# for a top-down game
Create an empty GameObject called Player and add a Sprite Renderer with your idle frame. Then add a Rigidbody2D and a CapsuleCollider2D. On the Rigidbody2D, set Gravity Scale to 0, tick Freeze Rotation Z and set Interpolate to Interpolate for smooth motion. Shrink the collider so it covers only the character’s feet. In a top-down view the feet are what bump into walls, and a full-body collider makes the player catch on every doorway.
Unity scripts are written in C#. Create PlayerMovement.cs in your Scripts folder and attach it to the Player:
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(Rigidbody2D))]
public class PlayerMovement : MonoBehaviour
{
[SerializeField] float moveSpeed = 5f;
[SerializeField] Animator animator; // optional
Rigidbody2D rb;
InputAction moveAction;
Vector2 moveInput;
void Awake()
{
rb = GetComponent<Rigidbody2D>();
rb.gravityScale = 0f;
rb.freezeRotation = true;
moveAction = new InputAction("Move", InputActionType.Value);
moveAction.AddCompositeBinding("2DVector")
.With("Up", "<Keyboard>/w")
.With("Down", "<Keyboard>/s")
.With("Left", "<Keyboard>/a")
.With("Right", "<Keyboard>/d");
moveAction.AddCompositeBinding("2DVector")
.With("Up", "<Keyboard>/upArrow")
.With("Down", "<Keyboard>/downArrow")
.With("Left", "<Keyboard>/leftArrow")
.With("Right", "<Keyboard>/rightArrow");
moveAction.AddBinding("<Gamepad>/leftStick");
}
void OnEnable() => moveAction.Enable();
void OnDisable() => moveAction.Disable();
void OnDestroy() => moveAction.Dispose();
void Update()
{
moveInput = Vector2.ClampMagnitude(moveAction.ReadValue<Vector2>(), 1f);
if (animator != null)
{
animator.SetFloat("Speed", moveInput.sqrMagnitude);
if (moveInput != Vector2.zero)
{
animator.SetFloat("MoveX", moveInput.x);
animator.SetFloat("MoveY", moveInput.y);
}
}
}
void FixedUpdate()
{
rb.MovePosition(rb.position + moveInput * moveSpeed * Time.fixedDeltaTime);
}
}
Three details matter here. Input is read in Update, which runs every frame, while movement happens in FixedUpdate, which runs in step with the physics engine. Moving through MovePosition instead of setting transform.position means Unity checks collisions along the way, so the player stops at walls rather than passing through them. And clamping the input vector keeps diagonal movement from being faster than straight movement, a bug players spot within seconds.
If Unity reports that the Input System isn’t enabled, open Project Settings → Player and set Active Input Handling to Input System Package (or Both).
Adding walk animations
Create an Animator Controller and add a blend tree set to 2D Simple Directional, driven by MoveX and MoveY. Put one walk clip at each of the four directions: (0, 1), (0, −1), (−1, 0) and (1, 0). Build a matching idle blend tree, then switch between the two with transitions on the Speed parameter. The script only updates MoveX and MoveY while the player is moving, so the character keeps facing its last direction when it stops. Players notice small touches like that.





