U
tab 01Basics11 lessons — tap to open
11 / 11 · 20 min
Basics★ exercise

Exercise: the arena takes shape

your project so far

Assets/

Scenes/

Arena.unity

FloorKnightMageArcherCanvas

Scripts/

CharacterSwitcher.csSwapCamera.csGameManager.csArenaHud.csGridSpawner.cs→ArenaBuilder.cs

Prefabs/

P_Cube

you already havethis lesson addsthis lesson retires

exercise60–90 min

Build the arena the rest of the course takes place in. One scene called Arena: a floor, a grid of blocks generated by code — the blocks you will be jumping between in tab 02 — three characters you can swap between, and a small on-screen readout.

what you reuse

int / float / boolStart / Updatepublic field → Inspectorif / for / ListPlane + materialPrefab + InstantiateGetComponent
animated diagram

This is what the finished scene should look and behave like.

What to build, step by step

Part 1 — the stage

  • A new scene named Arena, saved in Assets/Scenes. Every lesson from here to the end happens in this one file — the only other scene you ever make is the intro screen.

  • A Plane floor at 0,0,0 scaled to 3,1,3 with your own dark material.

  • Camera at roughly 0, 14, -16 rotated 38, 0, 0 so the whole floor is in frame.

  • Directional Light rotated 50, -30, 0 with soft shadows.

Part 2 — the block grid

Write one script that spawns the grid. Do not place a single cube by hand — if you find yourself dragging cubes, go back and fix the loop.

ArenaBuilder.cs — fill in the TODOs
using System.Collections.Generic;
using UnityEngine;

public class ArenaBuilder : MonoBehaviour
{
    [Header("Grid")]
    [SerializeField] private GameObject blockPrefab;
    [SerializeField] private int columns = 4;
    [SerializeField] private int rows = 4;
    [SerializeField] private float spacing = 1.6f;
    [SerializeField] private Transform blockParent;

    // every block we made, so we can count and clear them
    private readonly List<GameObject> blocks = new List<GameObject>();

    public int BlockCount => blocks.Count;

    void Start()
    {
        Build();
    }

    void Update()
    {
        // press R to rebuild with fresh random colours
        if (Input.GetKeyDown(KeyCode.R))
            Build();
    }

    public void Build()
    {
        Clear();

        float offsetX = (columns - 1) * spacing * 0.5f;
        float offsetZ = (rows - 1) * spacing * 0.5f;

        for (int x = 0; x < columns; x++)
        {
            for (int z = 0; z < rows; z++)
            {
                Vector3 pos = new Vector3(
                    x * spacing - offsetX,
                    0.5f,
                    z * spacing - offsetZ);

                GameObject b = Instantiate(blockPrefab, pos, Quaternion.identity, blockParent);
                b.name = $"Block_{x}_{z}";

                // TODO 1: give it a random colour
                // TODO 2: give it a random height between 0.5 and 2.5

                blocks.Add(b);
            }
        }
    }

    public void Clear()
    {
        foreach (GameObject b in blocks)
            if (b != null) Destroy(b);

        blocks.Clear();
    }
}

Part 3 — three characters

  • Three capsules at Y = 1, in three different colours, all children of one empty called Characters.

  • SimpleMover on all three, CharacterSwitcher on a GameManager, keys 1 / 2 / 3 and Tab.

  • The camera follows whichever character is currently active.

Part 4 — the readout

A tiny HUD proves your scripts can talk to each other. This is the first time two of your own scripts read from a third.

ArenaHud.cs — attach to any object, no UI package needed
using UnityEngine;

public class ArenaHud : MonoBehaviour
{
    [SerializeField] private ArenaBuilder builder;
    [SerializeField] private CharacterSwitcher switcher;

    // OnGUI is the quick-and-dirty way to draw text. Fine for an exercise,
    // never ship it — real UI lives on a Canvas, like the HudPanel from the last lesson.
    void OnGUI()
    {
        GUI.skin.label.fontSize = 20;
        GUI.Label(new Rect(20, 20, 400, 30), "Blocks: " + builder.BlockCount);
        GUI.Label(new Rect(20, 50, 400, 30), "Active: " + switcher.ActiveCharacter.name);
        GUI.Label(new Rect(20, 80, 400, 30), "R = rebuild");
    }
}

Done when

  • ✓Pressing Play shows a floor with 16 blocks of random colour and height, none of them placed by hand.
  • ✓Pressing R rebuilds the grid with new random values and the count stays correct.
  • ✓Keys 1, 2, 3 hand control to a different character; only one moves at a time.
  • ✓The camera follows the active character smoothly, with no jitter.
  • ✓The HUD shows the live block count and the active character's name.
  • ✓The Console is empty — no errors, no warnings from your own scripts.
  • ✓Changing columns, rows or spacing in the Inspector changes the result without touching code.

If you want more

bonus 1

Make the blocks fall in with physics: add a Rigidbody and spawn them 5 m up.

bonus 2

Colour the blocks by distance from the centre instead of randomly.

bonus 3

Add a fourth character and make the switcher handle any list length automatically.

bonus 4

Let the active character push blocks: give the blocks a Rigidbody, move the character with a CharacterController, and push them from OnControllerColliderHit.