Teqvault.study

Unity Mobile Game Dev

Master 2D, 3D, and 2.5D game development for mobile โ€” from your first scene to shipping on Android & iOS.

๐ŸŽฎ ๐Ÿ•น๏ธ ๐Ÿ“ฑ ๐ŸŽฏ โš”๏ธ
Course Progress
0%
MODULE 01

Getting Started with Unity

Set up Unity Hub, create your first mobile project, and understand how Unity's project structure maps to real game architecture.

๐ŸŽฎ What is Unity?

Unity is a cross-platform game engine used by millions of indie and AAA studios. It runs on C# and supports 2D, 3D, AR, VR, and mobile targets โ€” all from one editor. Games like Pokรฉmon GO, Among Us, Hollow Knight, and Last Day on Earth were built in Unity.

why unity for mobile
๐Ÿ“ฑ

Android + iOS

One codebase builds to both platforms with platform-specific modules.

๐Ÿช

Asset Store

Thousands of free and paid assets โ€” animations, shaders, UI kits.

โšก

C# Scripting

Full .NET runtime with modern C# features and excellent IDE support.

๐Ÿ†“

Free Tier

Personal license is free for projects earning under $200k/year.

๐Ÿ“ฅ Installation Checklist
  1. Download Unity Hub from unity.com/download โ€” this manages all your Unity versions.
  2. In Unity Hub โ†’ Installs โ†’ Install Editor โ†’ choose Unity 6 (LTS recommended for mobile).
  3. When installing, check: Android Build Support, Android SDK & NDK Tools, iOS Build Support (Mac only), and OpenJDK.
  4. Install Visual Studio 2022 Community or JetBrains Rider as your IDE โ€” both integrate with Unity's debugger.
  5. In Unity Hub โ†’ Licenses โ†’ Get a free Personal license.
๐Ÿ“ Creating Your First Mobile Project
  1. Unity Hub โ†’ New Project โ†’ Select "3D (URP)" template โ€” URP (Universal Render Pipeline) is the standard for mobile.
  2. Name it TheDeathRoad, choose a save location, click Create.
  3. Once open: File โ†’ Build Settings โ†’ Android โ†’ click Switch Platform. This tells Unity your target is mobile from day one.
  4. Go to Edit โ†’ Project Settings โ†’ Player โ†’ set Company Name, Product Name, and Minimum API Level: Android 8.0 (API 26).
๐Ÿ’ก
URP vs Built-inURP (Universal Render Pipeline) is Unity's recommended pipeline for mobile โ€” it's faster, uses Shader Graph, and has better batching. Always start new projects in URP.
๐Ÿ—‚๏ธ Project Folder Structure
Assets/ โ”œโ”€โ”€ _Scenes/ โ† all .unity scene files โ”œโ”€โ”€ Scripts/ โ† your C# MonoBehaviours โ”‚ โ”œโ”€โ”€ Player/ โ”‚ โ”œโ”€โ”€ Enemies/ โ”‚ โ””โ”€โ”€ UI/ โ”œโ”€โ”€ Prefabs/ โ† reusable GameObjects โ”œโ”€โ”€ Art/ โ”‚ โ”œโ”€โ”€ Sprites/ โ† 2D textures โ”‚ โ”œโ”€โ”€ Models/ โ† 3D .fbx / .obj โ”‚ โ””โ”€โ”€ Animations/ โ”œโ”€โ”€ Audio/ โ”œโ”€โ”€ Materials/ โ””โ”€โ”€ Resources/ โ† runtime-loaded assets
โš ๏ธ
Never rename folders Unity needsKeep Resources/, StreamingAssets/, Plugins/, and Editor/ named exactly โ€” Unity treats them specially.
๐Ÿง  Quick Check
Which render pipeline should you use for a new Unity mobile game?
MODULE 02

Unity Core Concepts

Before touching the interface or writing a script, learn the five words every Unity tutorial assumes you already know โ€” Scene, GameObject, Component, Prefab, and the MonoBehaviour lifecycle. Everything else in this course builds on these.

๐ŸŽฌ Scene โ€” The File You're Editing

A Scene is a single level or screen in your game โ€” a menu, a battle arena, a shop. It's saved as a .unity file. A game is usually many Scenes loaded one at a time (or streamed together for open worlds). Everything you place, move, or script lives inside whichever Scene is currently open.

๐Ÿ“ฆ GameObject โ€” An Empty Box That Does Nothing

A GameObject is the base building block of everything in a Scene โ€” a player, an enemy, a light, a UI button, even an empty organizational folder. On its own, a fresh GameObject does absolutely nothing. It has no shape, no image, no behavior. It's just a named container with a position in the world.

๐Ÿ’ก
Where you see themEvery row in the Hierarchy panel (bottom-left by default) is a GameObject. Click one and its details show up in the Inspector panel (right side) โ€” that's where you'll spend most of your time.
๐Ÿงฉ Component โ€” What Actually Gives It Behavior

A Component is a piece of functionality you attach to a GameObject. This is the part people find confusing at first: Unity doesn't have a "Player" class or an "Enemy" class built in. Instead, you build up behavior by stacking components onto an empty GameObject:

GameObject: "Player" โ”œโ”€โ”€ Transform โ† position/rotation/scale โ€” every GameObject has this one โ”œโ”€โ”€ SpriteRenderer โ† makes it visible on screen โ”œโ”€โ”€ Rigidbody2D โ† makes it affected by physics โ”œโ”€โ”€ BoxCollider2D โ† gives it a physical shape to collide with โ””โ”€โ”€ PlayerMovement.cs โ† your own script โ€” also a component!

Every one of those is a separate, swappable piece. Want the player to stop being affected by gravity? Remove the Rigidbody. Want an invisible trigger zone? Keep the Collider, remove the SpriteRenderer. This "stack components" approach is called composition, and it's the core idea behind how Unity works โ€” very different from traditional class inheritance.

๐Ÿ“œ Your Script Is Just Another Component

When you write a C# script and drag it onto a GameObject, it becomes a component exactly like SpriteRenderer or Rigidbody2D. Every script you write in Unity inherits from a base class called MonoBehaviour, which is what lets Unity call your code automatically at the right moments.

โฑ๏ธ The MonoBehaviour Lifecycle โ€” Awake, Start, Update

You'll see these three method names in almost every script in this course. They're not interchangeable โ€” Unity calls each one at a different, specific moment:

MethodCalled WhenUse It For
Awake()The instant the GameObject loads, before anything else runsGrabbing references to your own components (GetComponent<Rigidbody2D>())
Start()Once, right before the first frame โ€” after all Awakes have runSetup that depends on other objects already existing
Update()Every single frame, continuously, for as long as the object existsInput checks, movement, anything that needs to happen constantly
C# โ€” Seeing the Order for Yourself
using UnityEngine;

public class LifecycleDemo : MonoBehaviour
{
    void Awake()  { Debug.Log("1. Awake โ€” I exist"); }
    void Start()  { Debug.Log("2. Start โ€” the scene is ready"); }
    void Update() { Debug.Log("3. Update โ€” this repeats every frame"); }
}
โš ๏ธ
Common beginner bugTrying to reference another object's component in Awake() before that object has initialized its own fields is a classic source of null reference errors. If something depends on another GameObject being fully set up first, put it in Start(), not Awake().
๐Ÿงฌ Prefab โ€” A Saved, Reusable GameObject

Once you've built a GameObject with the right components attached (say, a fully working enemy with a Rigidbody, Collider, sprite, and AI script), you can drag it from the Hierarchy into the Project window to save it as a Prefab. From then on, that Prefab is a template โ€” you can spawn as many copies as you want at runtime with Instantiate(), and editing the original Prefab updates every copy in every Scene that uses it.

๐Ÿง  Quick Check
You need to grab a reference to this object's own Rigidbody2D before anything else runs. Which method do you use?
MODULE 03

The Unity Interface

Learn every panel of the Unity editor โ€” Scene, Game, Hierarchy, Inspector, Project, and Console โ€” and how they work together.

๐Ÿ–ผ๏ธ

Scene View

Where you place and position GameObjects visually in the world.

๐Ÿ“บ

Game View

What the player's camera sees. Hit Play to run your game here.

๐ŸŒฒ

Hierarchy

A tree of every GameObject in the current scene, with parent-child relationships.

๐Ÿ”

Inspector

Shows all Components on the selected GameObject and lets you tweak values.

๐Ÿ“‚

Project

Your Assets folder โ€” drag files here to import them into your project.

๐Ÿ’ฌ

Console

Debug.Log(), errors, and warnings appear here. Your best debugging friend.

๐ŸŽฎ Core Concepts: GameObjects & Components

Everything in a Unity scene is a GameObject โ€” an empty container. GameObjects do nothing by themselves. You attach Components to give them behavior.

GameObject: "Player" โ”œโ”€โ”€ Transform โ† position, rotation, scale (EVERY object has this) โ”œโ”€โ”€ SpriteRenderer โ† draws the sprite โ”œโ”€โ”€ Rigidbody2D โ† gives it physics โ”œโ”€โ”€ Collider2D โ† defines hit box โ””โ”€โ”€ PlayerController โ† your custom C# script
๐Ÿง 
The Component MindsetUnity uses an Entity-Component pattern. Instead of big class hierarchies, you compose behavior by snapping components together โ€” like LEGO bricks. A "Player" is just a container; components make it move, draw, and collide.
โŒจ๏ธ Essential Shortcuts
ShortcutAction
WMove tool (translate)
ERotate tool
RScale tool
Ctrl+PPlay / Stop
Ctrl+DDuplicate selected object
FFocus camera on selected object
Ctrl+Shift+NNew empty GameObject
Ctrl+ZUndo (works in most editor actions)
๐Ÿ“ Your First Script

In Unity, scripts are MonoBehaviours โ€” C# classes that attach to GameObjects. Two key lifecycle methods:

C# โ€” MonoBehaviour Lifecycle
using UnityEngine;

public class HelloWorld : MonoBehaviour
{
    // Called ONCE when the GameObject first appears in the scene
    void Start()
    {
        Debug.Log("Scene started!");
    }

    // Called EVERY FRAME (~60 times/sec on mobile)
    void Update()
    {
        // Game logic goes here
    }
}
  1. In Project panel โ†’ right-click Scripts/ โ†’ Create โ†’ C# Script โ†’ name it HelloWorld.
  2. Double-click to open in your IDE. Paste the code above.
  3. Drag the script from Project onto a GameObject in the Hierarchy. Hit Play. Check Console.
MODULE 04

2D Foundations

Sprites, tilemaps, cameras, sorting layers, and the complete 2D pipeline โ€” everything you need to build a 2D mobile game world.

๐Ÿ–ผ๏ธ Sprites: The Building Blocks

A Sprite is a 2D image displayed in your game. Unity handles the import, slicing, packing, and rendering.

  1. Drag a PNG into your Assets/Art/Sprites/ folder.
  2. Select it in Project โ†’ in Inspector set Texture Type: Sprite (2D and UI).
  3. Set Pixels Per Unit (PPU) โ€” typically 32, 64, or 100. Keep it consistent across all art. PPU determines how many pixels = 1 Unity unit (meter).
  4. For spritesheets: set Sprite Mode: Multiple โ†’ open Sprite Editor โ†’ Slice โ†’ Grid By Cell Size โ†’ enter your frame size.
  5. Drag the sprite into the Scene or onto a GameObject. Unity auto-adds a SpriteRenderer.
๐Ÿ’ก
Mobile Texture CompressionSet texture compression to ASTC for Android/iOS. In the Inspector, Platform tab โ†’ Android โ†’ Format: ASTC 6x6. This cuts texture memory in half with minimal quality loss.
๐Ÿ—บ๏ธ Tilemaps โ€” Building Your World

Unity's Tilemap system lets you paint levels like pixels on a grid โ€” perfect for top-down survival games.

  1. Hierarchy โ†’ right-click โ†’ 2D Object โ†’ Tilemap โ†’ Rectangular. This creates a Grid parent with a Tilemap child.
  2. Window โ†’ 2D โ†’ Tile Palette โ†’ Create New Palette โ†’ name it "World".
  3. Drag your tileset sprites into the Tile Palette. Now you can paint tiles onto the scene grid.
  4. Create multiple Tilemaps for layering: Ground, Decoration, Walls, Shadows โ€” each as a child of the same Grid.
  5. Add a TilemapCollider2D + CompositeCollider2D to the Walls tilemap for efficient collision.
Tilemap Layers Setup (Hierarchy)
Grid (GameObject)
โ”œโ”€โ”€ Tilemap: Ground        Order: 0
โ”œโ”€โ”€ Tilemap: Shadows       Order: 1
โ”œโ”€โ”€ Tilemap: Objects       Order: 2
โ””โ”€โ”€ Tilemap: Walls         Order: 3 + TilemapCollider2D
๐Ÿ“ท 2D Camera Setup

For a mobile top-down game, your camera needs to follow the player and fit different screen sizes.

C# โ€” SmoothCameraFollow.cs
using UnityEngine;

public class CameraFollow : MonoBehaviour
{
    public Transform target;        // Drag player here
    public float    smoothSpeed = 8f;
    public Vector3  offset = new Vector3(0, 0, -10);

    void LateUpdate()
    {
        Vector3 desired   = target.position + offset;
        Vector3 smoothed  = Vector3.Lerp(transform.position, desired,
                                          smoothSpeed * Time.deltaTime);
        transform.position = smoothed;
    }
}
๐Ÿ“ฑ
Camera Size for MobileOn the Camera component, set Projection: Orthographic and Size: 5 as a starting point. Use Canvas Scaler: Scale With Screen Size with reference 1080ร—1920 for portrait mobile games.
๐Ÿ”ข Sorting Layers โ€” Draw Order

In 2D, Unity needs to know what draws in front of what. Sorting Layers give you explicit control.

  1. Edit โ†’ Project Settings โ†’ Tags and Layers โ†’ Sorting Layers. Add: Background, Ground, Shadows, Characters, Projectiles, UI.
  2. On each SpriteRenderer and Tilemap Renderer, set the correct Sorting Layer.
  3. Use Order In Layer for fine-grained control within the same layer (higher = in front).
  4. For characters in a top-down game, dynamically set Order in Layer based on Y position: lower Y = in front.
C# โ€” Y-Sort (draw order by Y position)
void Update()
{
    // Multiply by -100 so lower Y = higher sort order
    GetComponent<SpriteRenderer>().sortingOrder =
        Mathf.RoundToInt(-transform.position.y * 100);
}
MODULE 05

2D Physics & Colliders

Rigidbodies, colliders, triggers, layers, and the full physics pipeline โ€” make objects collide, bounce, and interact.

โš™๏ธ Rigidbody2D โ€” Physics Bodies

Add a Rigidbody2D component to any object that should be affected by physics (gravity, forces, velocity).

Body TypeUse Case
DynamicPlayer, enemies, projectiles โ€” fully simulated
KinematicMoving platforms, doors โ€” controlled by script, not forces
StaticWalls, ground โ€” never moves, super performant
๐Ÿ“ฑ
Top-Down SetupFor a top-down survival game, set Gravity Scale: 0 on the player's Rigidbody2D. You handle all movement in script โ€” no falling.
C# โ€” Top-Down Player Movement
public class PlayerMovement : MonoBehaviour
{
    public float speed = 5f;
    private Rigidbody2D rb;
    private Vector2 moveInput;

    void Awake() => rb = GetComponent<Rigidbody2D>();

    void Update()
    {
        // Keyboard (replaced by touch joystick on mobile)
        moveInput.x = Input.GetAxisRaw("Horizontal");
        moveInput.y = Input.GetAxisRaw("Vertical");
        moveInput.Normalize();
    }

    void FixedUpdate()
    {
        // Always move physics in FixedUpdate
        rb.linearVelocity = moveInput * speed;
    }
}
๐Ÿ“ฆ Colliders โ€” Collision Shapes
โฌœ

BoxCollider2D

Rectangular. Best for most characters, tiles, and pickups.

โญ•

CircleCollider2D

Perfectly round. Great for projectiles and rolling objects.

๐Ÿ”ท

CapsuleCollider2D

Pill shape. Ideal for humanoid characters.

๐Ÿ”บ

PolygonCollider2D

Matches sprite shape. Most accurate, most expensive โ€” use sparingly.

โš ๏ธ
Is TriggerCheck Is Trigger on a collider to make it detect overlaps without physical response โ€” perfect for pickups, zones, and damage areas.
๐Ÿ“ž Collision Callbacks
C# โ€” Collision & Trigger Events
// Called when physics bodies touch (Is Trigger = false)
void OnCollisionEnter2D(Collision2D col)
{
    if (col.gameObject.CompareTag("Enemy"))
        TakeDamage(10);
}

// Called when entering a trigger zone (Is Trigger = true)
void OnTriggerEnter2D(Collider2D other)
{
    if (other.gameObject.CompareTag("Pickup"))
    {
        CollectItem(other.gameObject);
        Destroy(other.gameObject);
    }
}

// Continuously while inside trigger
void OnTriggerStay2D(Collider2D other) { }

// When leaving trigger
void OnTriggerExit2D(Collider2D other) { }
๐ŸŽญ Physics Layers โ€” Controlling Who Hits Who
  1. Edit โ†’ Project Settings โ†’ Tags and Layers โ†’ Layers. Add: Player, Enemy, Projectile, Ground, Pickup.
  2. Assign layers to GameObjects in the Inspector (top-right dropdown).
  3. Edit โ†’ Project Settings โ†’ Physics 2D โ†’ Layer Collision Matrix. Uncheck pairs that shouldn't collide (e.g. Enemy-Enemy, Projectile-Projectile).
MODULE 06

2D Animation

Animator Controller, Blend Trees, Animation Events, and smooth state machines โ€” bring your sprites to life.

๐ŸŽž๏ธ Animation Workflow
  1. Select sliced sprite frames in Project โ†’ drag them all into the Scene. Unity auto-creates an Animation clip and a basic Animator.
  2. Open Window โ†’ Animation โ†’ Animation to see the timeline. Adjust Sample Rate (12 fps for pixel art, 24 for smooth).
  3. Open Window โ†’ Animation โ†’ Animator to see the state machine. This controls which animation plays when.
  4. Add Parameters (float Speed, bool IsAttacking, trigger Hit) to drive transitions between states.
C# โ€” Driving the Animator
private Animator anim;
private Rigidbody2D rb;

void Awake() {
    anim = GetComponent<Animator>();
    rb   = GetComponent<Rigidbody2D>();
}

void Update() {
    // Speed param drives Idle โ†” Walk blend
    anim.SetFloat("Speed", rb.linearVelocity.magnitude);

    // Trigger one-shot attack animation
    if (Input.GetKeyDown(KeyCode.Space))
        anim.SetTrigger("Attack");
}
๐ŸŒ€ Blend Trees โ€” 8-Direction Movement

For top-down movement, use a 2D Blend Tree to blend idle/walk animations based on movement direction.

  1. In Animator, right-click โ†’ Create State โ†’ From New Blend Tree.
  2. Double-click the Blend Tree โ†’ set Blend Type: 2D Simple Directional.
  3. Parameters: MoveX (float) and MoveY (float).
  4. Add Motion fields for each direction: walk_up (0,1), walk_down (0,-1), walk_right (1,0), walk_left (-1,0). Add diagonals for 8-way.
  5. In script: anim.SetFloat("MoveX", moveInput.x) and anim.SetFloat("MoveY", moveInput.y).
MODULE 07

3D Fundamentals

Meshes, materials, lighting, cameras, and the 3D pipeline โ€” the foundation for your Death Road characters and world objects.

๐ŸงŠ 3D Objects in Unity
๐Ÿ“ฆ

Primitives

Cube, Sphere, Capsule, Cylinder โ€” great for prototyping before art is ready.

๐Ÿ—ฟ

Mesh + MeshRenderer

Import .fbx/.obj from Blender. MeshRenderer applies materials to it.

๐Ÿ’€

Skinned Mesh

Characters with bones/armatures. Used with Animator for 3D animation.

๐ŸŽญ

Prefabs

Saved reusable GameObjects. Instantiate enemies, pickups, effects from prefabs.

๐ŸŽจ Materials & URP Shaders

Materials define how a surface looks โ€” color, texture, shininess, transparency.

  1. Project โ†’ right-click โ†’ Create โ†’ Material. Name it PlayerMat.
  2. In Inspector, Shader dropdown โ†’ Universal Render Pipeline/Lit for realistic, or URP/Unlit for flat (great for 2.5D characters).
  3. Drag your character texture into the Base Map slot.
  4. Drag the material onto your mesh in the Scene or Inspector.
๐Ÿ’ก
For 2.5D CharactersUse URP/Unlit on 3D characters โ€” no lighting calculations means consistent look regardless of world lighting, and much better mobile performance.
๐Ÿ“ท 3D Camera Modes
ModeUse CaseFOV
PerspectiveStandard 3D games โ€” depth visible60ยฐ
Orthographic2.5D, isometric โ€” no perspective distortionSize-based
IsometricPerspective rotated 45ยฐ/35ยฐ โ€” like Diablo40โ€“50ยฐ
โš™๏ธ 3D Physics โ€” Rigidbody & Colliders
C# โ€” 3D Character Controller
public class CharController3D : MonoBehaviour
{
    public float speed = 4f;
    private Rigidbody rb;

    void Awake() => rb = GetComponent<Rigidbody>();

    void FixedUpdate()
    {
        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");
        Vector3 dir = new Vector3(h, 0, v).normalized;
        rb.MovePosition(rb.position + dir * speed * Time.fixedDeltaTime);
    }
}
MODULE 08

Lighting & Materials

URP lighting, baked vs realtime, normal maps, emissives, and mobile-optimized lighting setups for your game world.

๐Ÿ’ก Light Types in URP
โ˜€๏ธ

Directional Light

The sun. Infinite range, affects everything. One per scene max.

๐Ÿ’ก

Point Light

Like a lightbulb โ€” spherical falloff from a point. For torches, lamps.

๐Ÿ”ฆ

Spot Light

Cone of light. Flashlights, car headlights, dramatic effect.

๐Ÿ“ฆ

Area Light

Bake-only โ€” large soft light from a rectangular area. Windows, panels.

๐Ÿž Baked vs Realtime Lighting
TypeMobile CostDynamic ObjectsBest For
BakedNear zero at runtimeโŒ Static onlyEnvironment, terrain
MixedModerateโœ… Dynamic shadowsMain directional light
RealtimeHighโœ… Fully dynamicFlashlights, pickups
๐Ÿ“ฑ
Mobile Lighting BudgetOn mobile, limit yourself to: 1 Directional (Mixed or Baked), max 2-3 realtime Point/Spot lights visible at once. Bake everything static. This keeps frame rate smooth on mid-range phones.
๐ŸŽจ PBR Maps โ€” Making Materials Look Real
MapControls
Base Map (Albedo)The base color/texture
Normal MapFake surface bumps without extra geometry
Metallic MapHow metal-like the surface is
SmoothnessHow shiny/rough (rough=matte, smooth=mirror)
Emission MapSelf-illuminated โ€” glows without needing a light
Occlusion MapBaked ambient shadow in crevices
MODULE 09 โญ

2.5D โ€” The Death Road Way

3D characters on a 2D world plane โ€” exactly how Last Day on Earth works. Depth sorting, isometric cameras, billboard sprites, and the full hybrid pipeline.

๐ŸŽฎ What Is 2.5D?

2.5D means characters and objects are 3D meshes rendered on a constrained plane โ€” players can't move into/out of the screen. The camera is locked at an angle. The result feels dimensional but plays like a top-down 2D game.

Real Game Example

Last Day on Earth: Survival uses 3D characters with skeletal animation on an isometric fixed camera. The world is a flat plane. Characters have real 3D depth (you can see their back when facing away). It looks great, runs fast, and is much easier to animate than pure sprite sheets.

๐Ÿ“ท Setting Up the 2.5D Camera
  1. Create a Camera. Set Projection: Perspective, Field of View: 50.
  2. Rotate the camera: X = 45ยฐ, Y = 45ยฐ for isometric. Or X = 60ยฐ, Y = 0ยฐ for a top-down tilted view like LDOE.
  3. Position camera above and behind the player โ€” experiment with Z = -10, Y = 8.
  4. Attach a camera follow script that locks to player XZ position (never Y โ€” no vertical movement).
C# โ€” 2.5D Camera Follow
public class IsoCamera : MonoBehaviour
{
    public Transform target;
    public Vector3   offset = new Vector3(0, 10, -7);
    public float    smooth = 6f;

    void LateUpdate()
    {
        Vector3 goal = target.position + offset;
        transform.position = Vector3.Lerp(
            transform.position, goal, smooth * Time.deltaTime);
        transform.LookAt(target);
    }
}
๐Ÿง 3D Characters on a Flat Plane
  1. Import your character .fbx from Blender (or use a Unity asset). Set Rig: Humanoid for mecanim animations.
  2. Place the character at Y = 0 (ground plane). Set Rigidbody โ†’ Freeze Rotation XYZ and Freeze Position Y.
  3. Add a CapsuleCollider for physics. Size it to match the character.
  4. Movement is always on the XZ plane (no Y movement unless jumping).
  5. Rotate the character to face movement direction using Quaternion.LookRotation.
C# โ€” 2.5D Player Controller (Full)
public class Player25D : MonoBehaviour
{
    public float speed       = 4f;
    public float rotSpeed    = 15f;
    private Rigidbody rb;
    private Animator  anim;

    void Awake() {
        rb   = GetComponent<Rigidbody>();
        anim = GetComponent<Animator>();
    }

    void FixedUpdate()
    {
        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");
        Vector3 move = new Vector3(h, 0, v).normalized;

        // Move on XZ plane only
        rb.MovePosition(rb.position + move * speed * Time.fixedDeltaTime);

        // Rotate to face movement direction
        if (move != Vector3.zero)
        {
            Quaternion targetRot = Quaternion.LookRotation(move, Vector3.up);
            rb.MoveRotation(Quaternion.Slerp(
                rb.rotation, targetRot, rotSpeed * Time.fixedDeltaTime));
        }

        // Drive animations
        anim.SetFloat("Speed", move.magnitude);
    }
}
๐Ÿ“Š Depth Sorting โ€” Who Draws in Front?

In 2.5D, objects closer to the camera (lower Z in isometric) should draw in front. Unity handles this automatically with the renderer's depth buffer โ€” but for transparent objects (trees, UI) you need manual sorting.

  1. For opaque 3D objects: Unity's depth buffer handles sorting automatically. No extra work needed.
  2. For transparent sprites (foliage, shadows, UI markers): Use the Sorting Group component + set Order based on world Z position.
  3. For ground decals and shadows: use a flat quad with an Unlit transparent material, slightly above Y=0 to avoid Z-fighting.
C# โ€” Dynamic Depth Sort for Transparent Objects
using UnityEngine.Rendering;

public class DepthSort : MonoBehaviour
{
    private SortingGroup sg;

    void Awake() => sg = GetComponent<SortingGroup>();

    void Update()
    {
        // Objects farther from camera (higher Z) get lower order
        sg.sortingOrder = 5000 - Mathf.RoundToInt(transform.position.z * 10);
    }
}
๐ŸŒฟ World Design: Trees, Walls, & Occlusion

In a survival game like Death Road, objects should become transparent when the player walks behind them.

C# โ€” Object Fade When Player Behind
public class OcclusionFader : MonoBehaviour
{
    public float  fadeAlpha   = 0.3f;
    public float  fadeSpeed   = 8f;
    private Renderer   rend;
    private float  targetAlpha = 1f;

    void Awake() => rend = GetComponent<Renderer>();

    // Call this from a trigger on the building
    public void SetFaded(bool faded) =>
        targetAlpha = faded ? fadeAlpha : 1f;

    void Update()
    {
        Color c = rend.material.color;
        c.a = Mathf.Lerp(c.a, targetAlpha, fadeSpeed * Time.deltaTime);
        rend.material.color = c;
    }
}
๐ŸŽฏ Death Road Project Challenge

Build Your First 2.5D Scene

Using everything from Modules 01โ€“08:

  • Set up an isometric camera (X=50, Y=45) above a flat plane
  • Place a 3D capsule as your player with the Player25D script
  • Add 3 cube "buildings" and attach OcclusionFader triggers
  • Add a directional light at 45ยฐ and bake ambient lighting
  • Test that the player walks behind buildings correctly
MODULE 10

Mobile Input & Touch

Virtual joysticks, tap detection, swipe gestures, and Unity's Input System โ€” build controls that feel native on touchscreens.

๐Ÿ‘† Unity's New Input System
  1. Package Manager โ†’ search "Input System" โ†’ Install. When prompted, switch to the New Input System.
  2. Create โ†’ Input Actions asset. Add an Action Map "Player" with actions: Move (Vector2), Attack (Button), Interact (Button).
  3. For Move, add bindings: WASD keyboard + Left Stick gamepad + a virtual joystick touch binding.
  4. Generate C# class: check "Generate C# Class" in the asset Inspector โ†’ Apply.
C# โ€” New Input System Setup
public class PlayerInput : MonoBehaviour
{
    private PlayerControls controls; // generated class
    private Vector2 moveInput;

    void Awake() {
        controls = new PlayerControls();
        controls.Player.Move.performed += ctx =>
            moveInput = ctx.ReadValue<Vector2>();
        controls.Player.Move.canceled  += _ =>
            moveInput = Vector2.zero;
        controls.Player.Attack.performed += _ =>
            DoAttack();
    }
    void OnEnable()  => controls.Enable();
    void OnDisable() => controls.Disable();
}
๐Ÿ•น๏ธ Virtual Joystick (from scratch)
C# โ€” VirtualJoystick.cs
using UnityEngine;
using UnityEngine.EventSystems;

public class VirtualJoystick : MonoBehaviour,
    IPointerDownHandler, IDragHandler, IPointerUpHandler
{
    public RectTransform handle;
    public float maxRadius = 80f;
    public static Vector2 Input;

    private Vector2 center;

    public void OnPointerDown(PointerEventData e) {
        center = e.position;
    }
    public void OnDrag(PointerEventData e) {
        Vector2 delta = e.position - center;
        Vector2 clamped = Vector2.ClampMagnitude(delta, maxRadius);
        handle.anchoredPosition = clamped;
        Input = clamped / maxRadius;
    }
    public void OnPointerUp(PointerEventData e) {
        handle.anchoredPosition = Vector2.zero;
        Input = Vector2.zero;
    }
}

Then in your player script, replace Input.GetAxisRaw() with VirtualJoystick.Input.x / VirtualJoystick.Input.y.

๐Ÿ‘† Tap & Swipe Detection
C# โ€” Simple Tap + Swipe
void Update()
{
    foreach (Touch touch in UnityEngine.Input.touches)
    {
        if (touch.phase == TouchPhase.Began)
            OnTapStart(touch.position);

        if (touch.phase == TouchPhase.Ended)
        {
            if (touch.deltaPosition.magnitude < 10f)
                OnTap(touch.position);      // short tap
            else
                OnSwipe(touch.deltaPosition); // swipe gesture
        }
    }
}
MODULE 11

UI & HUD Systems

Canvas, health bars, inventory slots, damage numbers, menus โ€” build a complete mobile game UI that looks great on any screen size.

๐Ÿ–ผ๏ธ Canvas Setup for Mobile
  1. Hierarchy โ†’ UI โ†’ Canvas. Set Render Mode: Screen Space โ€” Overlay.
  2. Add a Canvas Scaler: UI Scale Mode โ†’ Scale With Screen Size. Reference: 1080 ร— 1920 (portrait) or 1920 ร— 1080 (landscape). Match: Width or Height.
  3. Add a Canvas Group to control alpha/interactability for menu transitions.
  4. Use Safe Area anchoring for notch/island devices: anchor UI to the safe area rect at runtime.
C# โ€” Safe Area (Notch Fix)
public class SafeAreaFitter : MonoBehaviour
{
    void Awake() {
        RectTransform rt   = GetComponent<RectTransform>();
        Rect safe          = Screen.safeArea;
        Vector2 anchorMin  = safe.position / new Vector2(Screen.width, Screen.height);
        Vector2 anchorMax  = (safe.position + safe.size) / new Vector2(Screen.width, Screen.height);
        rt.anchorMin       = anchorMin;
        rt.anchorMax       = anchorMax;
    }
}
โค๏ธ Health Bar
C# โ€” HealthBar.cs
using UnityEngine.UI;

public class HealthBar : MonoBehaviour
{
    public Slider slider;
    public Image  fill;
    public Color  fullColor  = Color.green;
    public Color  emptyColor = Color.red;

    public void SetHealth(float current, float max)
    {
        float t = current / max;
        slider.value = t;
        fill.color   = Color.Lerp(emptyColor, fullColor, t);
    }
}
๐Ÿ’ฌ Floating Damage Numbers
C# โ€” DamagePopup.cs
public class DamagePopup : MonoBehaviour
{
    public TextMeshProUGUI label;
    private float moveY  = 1.5f;
    private float lifetime = 1f;
    private float t;

    public void Init(int dmg) {
        label.text = dmg.ToString();
    }

    void Update() {
        t += Time.deltaTime;
        transform.position += Vector3.up * moveY * Time.deltaTime;
        label.alpha = Mathf.Lerp(1f, 0f, t / lifetime);
        if (t >= lifetime) Destroy(gameObject);
    }

    // Static spawner โ€” call this from anywhere
    public static void Spawn(Vector3 pos, int dmg) {
        DamagePopup popup = Instantiate(Resources.Load<DamagePopup>("DamagePopup"));
        popup.transform.position = pos;
        popup.Init(dmg);
    }
}
MODULE 12

NavMesh & Agent Basics

Bake a walkable surface, drop a NavMeshAgent on an enemy, and get it chasing the player โ€” the foundation every AI behavior builds on.

๐Ÿ—บ๏ธ NavMesh โ€” Unity's Built-In Pathfinding
  1. Install: Package Manager โ†’ AI Navigation (for Unity 6) โ†’ Install.
  2. Select all walkable ground objects โ†’ Inspector โ†’ Static dropdown โ†’ check Navigation Static.
  3. Window โ†’ AI โ†’ Navigation โ†’ Bake tab โ†’ click Bake. A blue overlay shows the walkable area.
  4. Add a NavMeshAgent component to the enemy GameObject. Set Speed, Stopping Distance, and Angular Speed.
C# โ€” Basic Enemy AI with NavMesh
using UnityEngine.AI;

public class ZombieAI : MonoBehaviour
{
    public Transform  player;
    public float     detectionRange  = 8f;
    public float     attackRange     = 1.5f;
    public float     attackCooldown  = 1.2f;
    private NavMeshAgent agent;
    private float     lastAttack;

    enum State { Idle, Chase, Attack }
    private State state = State.Idle;

    void Awake() => agent = GetComponent<NavMeshAgent>();

    void Update()
    {
        float dist = Vector3.Distance(transform.position, player.position);

        state = dist < attackRange    ? State.Attack
               : dist < detectionRange ? State.Chase
               :                           State.Idle;

        switch (state) {
            case State.Chase:
                agent.SetDestination(player.position); break;
            case State.Attack:
                agent.ResetPath();
                if (Time.time > lastAttack + attackCooldown)
                { DoAttack(); lastAttack = Time.time; }
                break;
            default:
                agent.ResetPath(); break;
        }
    }

    void DoAttack() => Debug.Log("Zombie attacks!");
}
๐ŸŽฏ Detection โ€” Sight Cones & Triggers

Distance checks alone make enemies feel like they have x-ray vision. Real detection combines a trigger radius with a line-of-sight check so enemies only "see" what's actually in front of them and not blocked by a wall.

C# โ€” Line-of-Sight Check
bool CanSeePlayer()
{
    Vector3 dir = player.position - transform.position;
    float angle = Vector3.Angle(transform.forward, dir);
    if (angle > fieldOfView * 0.5f) return false;

    if (Physics.Raycast(transform.position + Vector3.up, dir.normalized,
        out RaycastHit hit, detectionRange, obstacleMask))
    {
        return hit.collider.CompareTag("Player");
    }
    return true;
}
๐Ÿ’ก
Mobile perf noteOnly run CanSeePlayer() every few frames (e.g. via InvokeRepeating), not every Update() โ€” raycasts against many enemies add up fast on mid-range Android GPUs.
MODULE 13

Enemy AI State Machines

Go beyond chase-and-attack โ€” build a full Idle โ†’ Wander โ†’ Chase โ†’ Attack โ†’ Flee state machine with a suspicion meter, the pattern Death Road's enemies actually use.

๐Ÿง  State Machine vs Behavior Tree
ApproachBest ForComplexity
Enum State MachineSimple enemies (zombie, guard)Low
ScriptableObject StatesMultiple enemy types sharing logicMedium
Behavior Designer / NodeCanvasComplex NPC with dialogue + combatHigh
๐Ÿ’ก
For Death RoadStart with the enum state machine โ€” Idle, Wander, Chase, Attack, Flee. Add a SuspicionMeter float that increases when the player is nearby and triggers state transitions.
๐Ÿ˜จ The Suspicion Meter

Instead of snapping straight from Idle to Chase the instant a player is detected, a suspicion meter ramps up gradually โ€” the enemy notices something, gets more alert, then commits to a chase. It reads as far more "alive" than a hard trigger.

  1. While the player is visible, increase suspicion each frame.
  2. While hidden, let it decay back toward zero.
  3. Cross a low threshold โ†’ Wander toward last known position. Cross a high threshold โ†’ Chase.
C# โ€” Full FSM with Suspicion Meter
public class EnemyFSM : MonoBehaviour
{
    enum State { Idle, Wander, Chase, Attack, Flee }
    private State state = State.Idle;

    public float suspicion        = 0f;
    public float suspicionRise    = 25f;
    public float suspicionDecay   = 10f;
    public float chaseThreshold   = 70f;
    public float wanderThreshold  = 20f;
    public float lowHealthFlee    = 0.2f;
    public Health   health;

    void Update()
    {
        suspicion += (CanSeePlayer() ? suspicionRise : -suspicionDecay) * Time.deltaTime;
        suspicion = Mathf.Clamp(suspicion, 0f, 100f);

        if (health.Percent < lowHealthFlee) state = State.Flee;
        else if (suspicion >= chaseThreshold)  state = State.Chase;
        else if (suspicion >= wanderThreshold) state = State.Wander;
        else                                       state = State.Idle;

        RunState();
    }

    void RunState()
    {
        switch (state)
        {
            case State.Idle:   DoIdle();   break;
            case State.Wander: DoWander(); break;
            case State.Chase:  DoChase();  break;
            case State.Attack: DoAttack(); break;
            case State.Flee:   DoFlee();   break;
        }
    }
}
๐Ÿƒ Flee โ€” Running Away Convincingly

Flee isn't just "reverse the chase vector." Run straight away from the player and enemies get stuck backing into corners. Sample a few points around the enemy, keep the one farthest from the player and reachable on the NavMesh.

C# โ€” Flee to Best NavMesh Point
void DoFlee()
{
    Vector3 away = (transform.position - player.position).normalized;
    Vector3 fleeTarget = transform.position + away * 6f;

    if (NavMesh.SamplePosition(fleeTarget, out NavMeshHit hit, 6f, NavMesh.AllAreas))
        agent.SetDestination(hit.position);
}
โš ๏ธ
Common bugIf you skip the NavMesh.SamplePosition check, fleeing enemies will try to path off the NavMesh entirely and get stuck vibrating against a wall. Always validate the flee point first.
MODULE 14

Draw Calls & Batching

Every object you draw costs the GPU a "draw call." On mid-range Android, that budget is small โ€” here's how to spend it wisely.

๐Ÿ“ What's a Draw Call, and Why It's Expensive

Each unique material + mesh combination the GPU renders is a separate draw call. Mobile GPUs choke well before desktop ones โ€” a scene that runs at 500 draw calls on PC can drop a budget Android phone to single-digit fps. The fix isn't fewer objects; it's fewer unique draws.

Device TierSafe Draw Call Budget
Low-end Android (2โ€“3 GB RAM)~80โ€“150 draw calls
Mid-range Android~150โ€“300 draw calls
Flagship / modern iOS~300โ€“500 draw calls
๐Ÿงต Static & Dynamic Batching
  1. Mark non-moving scenery (ground, walls, props) as Static in the Inspector โ†’ enables Static Batching automatically.
  2. For moving objects sharing one material (bullets, small props), Unity applies Dynamic Batching automatically if the mesh is under ~300 vertices.
  3. Batching only merges objects that share the same material โ€” two identical meshes with different materials still cost two draw calls.
๐Ÿ’ก
GPU InstancingFor lots of the same object with slight per-instance variation (color, scale), enable Enable GPU Instancing on the material instead of relying on batching โ€” it handles hundreds of copies in one draw call.
๐Ÿ–ผ๏ธ Texture Atlasing

Combining multiple textures into one shared sheet (an atlas) lets many objects reference the same texture, which is required for batching to actually merge them. This matters most for UI and small props like pickups, particles, and decals.

  1. Group sprites that appear together often (UI icons, item pickups) into one atlas using Unity's Sprite Atlas asset.
  2. Window โ†’ 2D โ†’ Sprite Atlas โ†’ Create New โ†’ drag in your loose sprites.
  3. Enable Include in Build and Unity will pack and reference the atlas automatically at runtime.
๐Ÿ“Š Frame Debugger โ€” See Every Draw Call

The Profiler tells you how much time rendering costs; the Frame Debugger shows which individual draw calls make up a frame, in order.

  1. Window โ†’ Analysis โ†’ Frame Debugger โ†’ Enable.
  2. Step through the list โ€” each entry is one draw call. Look for repeated materials that should have batched but didn't.
  3. If two identical materials appear as separate draw calls, check that both use the exact same material asset โ€” even a duplicated instance breaks batching.
MODULE 15

Profiling & Memory

Target 60fps on mid-range Android devices โ€” the Profiler workflow, object pooling, and the full optimization cheat sheet.

๐Ÿ” The Profiler โ€” Find the Problem First

Before optimizing anything, measure. Unity's Profiler shows exactly where your frame time is going.

  1. Window โ†’ Analysis โ†’ Profiler (Ctrl+7).
  2. Hit Play and watch CPU, GPU, Memory, and Rendering graphs.
  3. Click a spike in the timeline to see the call stack. The longest bar is your problem.
  4. On a real Android device: connect via USB โ†’ Profiler โ†’ Target: [your device].
โ™ป๏ธ Object Pooling โ€” Never Use Destroy()

Creating and destroying GameObjects at runtime (bullets, enemies, FX) causes garbage collection spikes. Pool them instead.

C# โ€” Generic Object Pool
using System.Collections.Generic;

public class ObjectPool<T> where T : MonoBehaviour
{
    private Queue<T>  pool = new Queue<T>();
    private T         prefab;

    public ObjectPool(T prefab, int preload) {
        this.prefab = prefab;
        for (int i = 0; i < preload; i++) CreateNew();
    }

    public T Get() {
        T obj = pool.Count > 0 ? pool.Dequeue() : CreateNew();
        obj.gameObject.SetActive(true);
        return obj;
    }

    public void Return(T obj) {
        obj.gameObject.SetActive(false);
        pool.Enqueue(obj);
    }

    private T CreateNew() {
        T obj = Object.Instantiate(prefab);
        obj.gameObject.SetActive(false);
        pool.Enqueue(obj);
        return obj;
    }
}
๐Ÿงฎ GC Allocation โ€” The Silent Killer

Object pooling fixes Instantiate/Destroy spikes, but garbage collection also creeps in from allocations you don't notice โ€” LINQ queries, string concatenation, and new inside Update().

Common GC TrapFix
list.Where(x => ...) in UpdateUse a plain for-loop instead of LINQ
"Score: " + score every frameOnly update the string when score actually changes
new Vector3(...) in a hot loopReuse a cached field where possible (structs still allocate on the stack, but repeated boxing/closures don't)
Closures/lambdas capturing variables in UpdateMove the lambda outside the loop or cache it once
๐Ÿ’ก
Spot it in the ProfilerOpen the Memory module and watch the GC Alloc column per frame. Anything consistently non-zero in Update() is worth hunting down before it becomes a stutter on low-end devices.
โšก Optimization Cheat Sheet
ProblemFix
Too many draw callsEnable GPU Instancing + Static Batching
Overdraw (transparent layers)Reduce transparent objects, use URP Shader Graph alpha clip
Physics spikesReduce FixedUpdate rate to 0.02 โ†’ 0.033, fewer colliders
Memory spikes (GC)Object pooling, avoid LINQ in Update, pre-allocate lists
Texture memoryASTC compression, Texture Atlases, Mip Maps
Shader costURP/Lit โ†’ URP/Simple Lit โ†’ URP/Unlit (cheapest)
Shadow costBake shadows, reduce shadow distance, use blob shadows
MODULE 16

Build & Ship

Generate APK/AAB for Android, set up signing, configure IL2CPP, and submit to Google Play and the App Store.

๐Ÿ“ฆ Android Build Settings
  1. File โ†’ Build Settings โ†’ Android โ†’ Switch Platform.
  2. Player Settings โ†’ Other Settings โ†’ set Scripting Backend: IL2CPP (faster, smaller, required for 64-bit).
  3. Target Architectures: check ARM64 (required by Google Play). ARM7 optional for older devices.
  4. Set Minify: Release for both Java and IL2CPP. This shrinks the APK.
  5. Build โ†’ choose .aab (Android App Bundle) for Google Play, .apk for sideload/testing.
๐Ÿ” Keystore Signing
โš ๏ธ
Never Lose Your KeystoreOnce you publish with a keystore, every update to that app MUST use the same keystore. Losing it means you can never update your published app. Back it up to Google Drive, external drive, and email.
  1. Player Settings โ†’ Publishing Settings โ†’ Keystore Manager โ†’ Create New.
  2. Fill in alias, password, organization. Save the .keystore file somewhere safe.
  3. Unity will sign every build with this key automatically.
๐Ÿš€ Google Play Submission
  1. Build a Release .aab with IL2CPP + ARM64 + minify on.
  2. Google Play Console โ†’ Create App โ†’ upload the .aab to Internal Testing first.
  3. Required: Privacy Policy URL, Content Rating questionnaire, Target Audience & Content form.
  4. Add screenshots: phone (minimum 2), feature graphic (1024ร—500).
  5. Move from Internal โ†’ Closed โ†’ Open testing โ†’ Production over several days.
๐ŸŽฏ Checkpoint Challenge

Ship a Death Road Prototype

Build and sideload a prototype APK containing:

  • 2.5D isometric scene with a playable character
  • At least 2 zombie NavMesh enemies
  • Virtual joystick + attack button
  • Health bar HUD
  • Object pooled bullet/swing VFX
  • Builds without errors in Release mode with IL2CPP + ARM64
MODULE 17 ๐Ÿ

Capstone โ€” Ship a Vertical Slice

Everything from this course, combined into one small playable loop: a 2.5D scene, a NavMesh enemy with a real state machine, pooled effects, a UI HUD, and a signed release build. This is the project that proves you can actually ship.

๐Ÿงฉ What a Vertical Slice Is (and Isn't)

A vertical slice is a small, complete loop โ€” not a big level, not more content, just every system from this course touching each other in one playable minute. It's the difference between "I finished the modules" and "I can put pieces together into something that runs on a phone."

๐Ÿ—‚๏ธ The Build Plan
  1. Scene: One 2.5D area (Module 8) โ€” a small enclosed space with the isometric camera locked to the player.
  2. Player: The 2.5D character controller (Module 8) with virtual joystick input (Module 9) and an attack button.
  3. Enemy: One NavMesh-baked enemy (Module 11) running the full state machine โ€” Idle โ†’ Wander โ†’ Chase โ†’ Attack โ†’ Flee (Module 12) โ€” not just a chase script.
  4. Effects: Attack swing or bullet VFX drawn from an object pool (Module 14) โ€” no Instantiate/Destroy in the play loop.
  5. UI: A health bar HUD (Module 10) that updates when the player takes damage.
  6. Performance: Static-batch the environment (Module 13), confirm draw calls stay inside the mid-range budget (~150โ€“300) using the Frame Debugger.
  7. Build: A signed, Release-mode .apk with IL2CPP + ARM64 (Module 15) that installs and runs on a real Android device.
โœ… Definition of Done
SystemPasses whenโ€ฆ
Enemy AIIt idles, notices the player at a distance (not instantly), chases, attacks, and flees below a health threshold
PoolingThe Profiler shows no Instantiate/Destroy GC spikes during combat
PerformanceFrame Debugger shows draw calls within your device tier's budget
BuildInstalls from a signed .apk on a physical Android device without errors
๐Ÿ Final Challenge

Ship the Slice

Combine every checkpoint above into a single scene and sideload it to a real device. This is your first shippable proof of concept โ€” the same shape of project you'd hand to a publisher or drop into a Play Store closed test.

  • 2.5D isometric scene with a playable character
  • At least 1 enemy running the full Idle/Wander/Chase/Attack/Flee state machine
  • Virtual joystick + attack button
  • Health bar HUD that reflects real damage
  • Object-pooled attack/bullet VFX โ€” zero runtime Instantiate/Destroy
  • Builds without errors in Release mode with IL2CPP + ARM64, signed and installable
Roadmap