Build a 2D runner-style game in Unity where a dino jumps with the spacebar and the ground and clouds scroll. You will use Rigidbody2D for jumping, Physics2D.OverlapCircle for ground detection, Animator for run/jump states, and texture offset plus object movement for scrolling.
Game Structure
The project uses two main scripts:
| Script | Role |
|---|---|
DinoController |
Jump input, ground detection, Rigidbody2D and Animator control |
Scroll |
Ground texture scrolling; cloud movement and respawn from the right |
Project Folder Structure
content/sources/Assets/
├── 01.Scenes/
│ └── GameScene.unity
├── 02.Scripts/
│ ├── DinoController.cs
│ └── Scroll.cs
├── 03.Images/
│ ├── Dino_Run_1.png, Dino_Jump.png, Dino_Down.png, ...
│ ├── Cactus_1~4.png, Pterosaur_1~2.png, Cloud.png, Ground.png
│ └── Materials/
│ └── Ground.mat
├── 04.Sounds/
└── 05.Animations/
├── Dino.controller
├── Dino_Run.anim, Dino_Jump.anim, Dino_Down.anim, Dino_Die.anim
└── ...
DinoController – Jump and Ground Check
This script handles the dino’s jump, ground detection, and animation (run vs jump).
Full Code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace DinoRun2D
{
public class DinoController : MonoBehaviour
{
[SerializeField] private float jumpForce;
[SerializeField] private bool isGrounded;
[SerializeField] private Transform groundCheckPoint;
[SerializeField] private LayerMask whatIsGround;
[SerializeField] private float groundCheckRadius = 0.2f;
private Animator anim;
private Rigidbody2D rb;
private static readonly int IsGroundHash = Animator.StringToHash("isGround");
void Start()
{
rb = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
anim.SetBool(IsGroundHash, true);
}
void Update()
{
isGrounded = Physics2D.OverlapCircle(groundCheckPoint.position, groundCheckRadius, whatIsGround);
if (Input.GetKeyDown(KeyCode.Space) && isGrounded)
{
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
}
anim.SetBool(IsGroundHash, isGrounded);
}
void OnDrawGizmos()
{
if (groundCheckPoint == null) return;
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(groundCheckPoint.position, groundCheckRadius);
}
}
}
Key Concepts
1. Ground detection – Physics2D.OverlapCircle
isGrounded = Physics2D.OverlapCircle(groundCheckPoint.position, groundCheckRadius, whatIsGround);
- OverlapCircle checks a circle in 2D space; it returns true if any Collider2D on the given LayerMask overlaps that circle.
- groundCheckPoint: A Transform (e.g. empty GameObject under the dino’s feet) defines the center of the circle.
- whatIsGround: Set to a “Ground” layer so only the floor is counted; this avoids double jumps and unintended triggers.
2. Jump – set velocity
if (Input.GetKeyDown(KeyCode.Space) && isGrounded)
{
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
}
- GetKeyDown(KeyCode.Space): true only on the frame the key is pressed.
- velocity: Keeping
rb.velocity.xand setting onlyytojumpForcegives a consistent jump height and preserves horizontal movement (e.g. from scrolling).
3. Animator – hash for parameters
private static readonly int IsGroundHash = Animator.StringToHash("isGround");
anim.SetBool(IsGroundHash, isGrounded);
- StringToHash turns the parameter name into an int; SetBool(hash, value) is faster than using the string every frame and avoids typos.
- In the Animator, add a bool isGround and use it in Transitions between Run and Jump.
4. Gizmos
void OnDrawGizmos()
{
if (groundCheckPoint == null) return;
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(groundCheckPoint.position, groundCheckRadius);
}
- Draws the ground-check circle in the Scene view so you can tune position and radius.
Scroll – Background (Ground and Clouds)
Ground: scroll by moving the texture offset. Clouds: move left and respawn on the right when they leave the screen.
Full Code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace DinoRun2D
{
public class Scroll : MonoBehaviour
{
[SerializeField] private float groundScrollSpeedX = 2f;
private float cloudScrollSpeedX;
private Renderer quadRenderer;
[SerializeField] private bool isCloud;
[SerializeField] private float leftBoundary = -11f;
[SerializeField] private float rightBoundary = 11f;
[SerializeField] private float cloudMinY = 0.5f;
[SerializeField] private float cloudMaxY = 4f;
private Material materialInstance;
void Start()
{
quadRenderer = GetComponent<Renderer>();
if (isCloud)
cloudScrollSpeedX = Random.Range(0.05f, 0.09f);
else
materialInstance = quadRenderer.material;
}
void Update()
{
if (isCloud)
{
transform.position = new Vector3(
transform.position.x - cloudScrollSpeedX * Time.deltaTime,
transform.position.y,
transform.position.z
);
if (transform.position.x <= leftBoundary)
transform.position = new Vector3(rightBoundary, Random.Range(cloudMinY, cloudMaxY), 0f);
}
else
{
float offsetX = Time.time * groundScrollSpeedX;
materialInstance.mainTextureOffset = new Vector2(offsetX, 0);
}
}
void OnDestroy()
{
if (materialInstance != null)
Destroy(materialInstance);
}
}
}
Key Concepts
1. Ground – texture offset
float offsetX = Time.time * groundScrollSpeedX;
materialInstance.mainTextureOffset = new Vector2(offsetX, 0);
- mainTextureOffset shifts the material’s main texture; increasing it over time makes the ground look like it’s moving.
- materialInstance: Cached copy of the material used for scrolling; the original is not modified. Destroy it in OnDestroy to avoid leaking cloned materials.
2. Clouds – move and respawn
transform.position = new Vector3(
transform.position.x - cloudScrollSpeedX * Time.deltaTime, ...
);
if (transform.position.x <= leftBoundary)
transform.position = new Vector3(rightBoundary, Random.Range(cloudMinY, cloudMaxY), 0f);
- Move left with Time.deltaTime for frame-rate independent speed.
- When past leftBoundary, teleport to rightBoundary with a random Y between cloudMinY and cloudMaxY for variety.
- Random.Range(0.05f, 0.09f) per cloud in Start gives each cloud a slightly different speed for a simple parallax feel.
3. Material cleanup
- Destroying the cached materialInstance in OnDestroy prevents memory leaks from runtime material copies.
Scene Setup Summary
| Item | Recommendation |
|---|---|
| Dino | Rigidbody2D, Collider2D, Animator, DinoController; groundCheckPoint = empty under feet |
| Ground | Quad (or similar) with Ground texture/material, Scroll (isCloud = false); use “Ground” layer if needed |
| Clouds | Scroll (isCloud = true), set leftBoundary/rightBoundary to match camera bounds |
| Layer | Create “Ground” layer and assign it to DinoController’s whatIsGround |
With this setup you get a dino that jumps with the spacebar and scrolling ground and clouds. You can extend the game with obstacles, collision, scoring, and game over using other tutorials (Instantiate, 2D collision, etc.).