Welcome to CodeBench

Five short coding problems to test what you really know about Unity game programming. The questions get progressively harder and target the specific concepts that separate beginners from intermediate game developers. Take your time — wrong answers come with explanations.

5
Problems
~15
Minutes
⭐→⭐⭐⭐⭐
Difficulty
Problem 1 of 5 ⭐ Easy

Frame-Rate Independent Movement

Update() Time.deltaTime Movement

Problem

You're writing a script to move an enemy forward at exactly 5 units per second, regardless of whether the game runs at 30 FPS or 144 FPS. Which expression correctly fills the blank?

EnemyMove.cs
void Update()
{
    // Fill in the blank to move 5 units/sec forward
    transform.position += ________________;
}
Choose the correct expression
  • Vector3.forward * 5
  • Vector3.forward * 5 * Time.deltaTime
  • Vector3.forward * Time.deltaTime
  • Vector3.forward * 5 / Time.deltaTime
Hint Update() runs once per frame. Time.deltaTime is the number of seconds since the last frame — multiplying by it converts "per frame" into "per second."

Why this matters

Without Time.deltaTime, your enemy moves the same distance every frame. If the game runs at 60 FPS, that's 5 × 60 = 300 units per second. At 30 FPS, it's 150 units per second. The same script behaves completely differently depending on hardware.

Multiplying by Time.deltaTime means "5 units × (seconds per frame)" — which works out to exactly 5 units per second regardless of frame rate. This is the most common beginner bug in Unity.

The exception: when you're using Rigidbody.MovePosition() in FixedUpdate(), the physics engine already handles fixed timing — you don't need Time.deltaTime in that case.

Problem 2 of 5 ⭐ Easy

MonoBehaviour Lifecycle Order

Awake OnEnable Start Update

Problem

The script below is attached to an active GameObject and the script is enabled. What is the order of the first letters printed to the Console? Type your answer as four letters with no spaces or punctuation (e.g., ASUE).

LifecycleDemo.cs
void Awake()     { Debug.Log("A"); }
void Start()     { Debug.Log("S"); }
void Update()    { Debug.Log("U"); }
void OnEnable()  { Debug.Log("E"); }
Your Answer
Just the four letters in order — case doesn't matter, spaces and commas are ignored.
Hint Awake runs first when the script loads. OnEnable runs whenever the script becomes enabled (including right after Awake). Start runs once before the first frame. Update runs every frame after that.

Why this matters

The correct order is AESU: AwakeOnEnableStartUpdate.

Knowing this order tells you where to put your setup code:

  • Awake — cache your own components (GetComponent on yourself). Safe even when the GameObject is inactive.
  • OnEnable — subscribe to events. Pairs with OnDisable for cleanup.
  • Start — access OTHER objects. All other Awakes have already run, so their references are ready.
  • Update — runs every frame thereafter.

Putting GameManager.Instance.AddScore(...) in Awake can fail because the GameManager's Awake may not have run yet — but the same call in Start is safe.

Problem 3 of 5 ⭐⭐ Medium

Complete the Coroutine

Coroutine WaitForSeconds yield return

Problem

Complete the coroutine so the GameObject is destroyed exactly 3 seconds after the coroutine starts. Type only the missing line of code (the one that goes where the dashes are).

DelayedDestroy.cs
IEnumerator DestroyAfterDelay()
{
    ________________________________
    Destroy(gameObject);
}
Your Code
Whitespace and semicolons are flexible. Both 3, 3f, and 3.0f are acceptable.
Hint Coroutines pause at a yield return statement. Unity provides a special object that, when yielded, pauses the coroutine for a number of seconds. Its name describes exactly what it does.

Why this matters

The answer is yield return new WaitForSeconds(3);

Coroutines are Unity's way of writing time-based logic without blocking the main thread or creating new threads. When the coroutine hits yield return new WaitForSeconds(3), it:

  • Pauses the coroutine right at that line
  • Lets Update() and every other script keep running normally
  • Resumes 3 in-game seconds later, on the next available frame

Important distinction: WaitForSeconds respects Time.timeScale — pausing the game with Time.timeScale = 0 also pauses the coroutine. Use WaitForSecondsRealtime when you need real wall-clock time (e.g., timers in a pause menu).

To actually run the coroutine, you need StartCoroutine(DestroyAfterDelay()) somewhere in your code — typically in Start() or in response to an event.

Problem 4 of 5 ⭐⭐⭐ Hard

The Phantom Death Bug

Destroy() Update() State flags

Problem

The enemy script below should run its death sequence once when health reaches zero. But your QA team reports that the death sound plays multiple times per kill and the score sometimes increments by 30 or 40 instead of 10. The script compiles without errors. Why?

Enemy.cs
public class Enemy : MonoBehaviour
{
    public float health = 100;
    [SerializeField] private AudioClip deathSound;

    void Update()
    {
        if (health <= 0)
        {
            AudioSource.PlayClipAtPoint(deathSound, transform.position);
            GameManager.Instance.AddScore(10);
            Destroy(gameObject);
        }
    }
}
What's the cause?
  • A. Destroy(gameObject) throws a silent MissingReferenceException because the GameObject is still being referenced by the script.
  • B. Destroy() is deferred — it doesn't remove the GameObject immediately. Update() runs again on subsequent frames before destruction completes, executing the if-block multiple times.
  • C. AudioSource.PlayClipAtPoint is asynchronous and creates a race condition with Destroy().
  • D. The health field is public, which lets other scripts decrement it multiple times in the same frame.
Hint Read the Unity docs for Destroy(). The keyword you're looking for is "deferred" — when exactly does the object actually disappear?

Why this matters

Destroy(gameObject) doesn't remove the GameObject immediately. Unity schedules it for the end of the current frame (after all Update() calls finish on every script). Until then, the GameObject is still alive — and so is its Update().

If health is at 0 when the if-block runs, it's still 0 the next time Update() runs on the same frame's late updates, and on the start of the next frame before the actual removal. The whole block fires multiple times.

The fix — a guard flag:

Enemy.cs — fixed
private bool isDying = false;

void Update()
{
    if (health <= 0 && !isDying)
    {
        isDying = true;
        AudioSource.PlayClipAtPoint(deathSound, transform.position);
        GameManager.Instance.AddScore(10);
        Destroy(gameObject);
    }
}

This same pattern (a bool guard) solves a huge class of "executed too many times" bugs — projectiles hitting multiple targets in one frame, triggers firing repeatedly, coroutines stacking on themselves. Recognising it is a hallmark of an intermediate game programmer.

Problem 5 of 5 ⭐⭐⭐⭐ Hard

Enemy Vision Cone

Vector3 Distance Angle AI

Problem

You're writing the AI for a stealth game. A guard can see the player if both:

  • The player is within viewRange units of the guard, AND
  • The player is within fieldOfView degrees of the guard's forward direction (where a 90° FOV means 45° to each side).

Which implementation is correct?

GuardAI.cs
public bool CanSeePlayer(Transform guard, Vector3 playerPos,
                         float viewRange, float fieldOfView)
{
    Vector3 toPlayer = playerPos - guard.position;
    // Which return statement is correct?
}
Pick the correct implementation
  • A.
    return toPlayer.magnitude <= viewRange &&
           Vector3.Angle(guard.forward, toPlayer) <= fieldOfView / 2;
  • B.
    return Vector3.Distance(guard.position, playerPos) <= viewRange ||
           Vector3.Angle(guard.forward, toPlayer) <= fieldOfView;
  • C.
    return toPlayer.magnitude <= viewRange &&
           Vector3.Dot(guard.forward, toPlayer) > 0;
  • D.
    return Vector3.Distance(guard.position, playerPos) <= viewRange &&
           guard.forward.x == toPlayer.x;
Hint Both conditions must hold — that means you need AND, not OR. Vector3.Angle() returns the unsigned angle (0–180°). Think about: if my FOV is 90°, the player can be at most 45° to either side of forward. So the angle from forward must be at most...?

Why this matters

The correct answer is A. Let's see why the others fail:

  • B uses || (OR). The player would be "seen" if EITHER condition holds — so a player 1000 units away but directly in front would be "seen." The angle test also forgets to halve the FOV.
  • C uses Dot > 0 which only checks if the player is in front of the guard at all — even at 89.9° to the side. It ignores the FOV constraint entirely.
  • D compares an arbitrary X coordinate (guard.forward.x == toPlayer.x) — meaningless geometrically. Floating-point equality also rarely holds.

Why A works:

  • magnitude gives the length of toPlayer — which is the distance from guard to player.
  • Vector3.Angle(a, b) returns the unsigned angle (0–180°) between two vectors, regardless of direction.
  • Dividing the FOV by 2 is essential. A "90° vision cone" means 45° to the left AND 45° to the right of forward. The angle from forward to player must be ≤ half the FOV.
  • && (AND) requires both conditions to be true — exactly the spec.

Real-world upgrade: A complete AI also raycasts from guard to player and rejects the sighting if a wall is in the way. The vision cone is the cheap broad-phase check; the raycast is the expensive narrow-phase check. Doing them in that order saves performance.

🏆

Bench Complete

You've worked through the full set. Whatever your score, every problem here is the kind of thing you'll hit in real Unity projects — getting them wrong now means you get them right when they matter.

Loading...

Where to go from here