Weekend Project: Build a Quiz Game for Android in Unity With Timer, Score, and High Score

Unity Mini Projects

On PlayMobile.online, author George Jones walks you through a compact weekend project to build a simple Android quiz loop in Unity. Start with mobile constraints in mind: battery life, small screens, draw calls, and font atlas size affect your app early, so design for performance from minute one.

Weekend goal: a playable loop with a countdown timer, score, and persistent high score. Keep question data separate from scripts (JSON/CSV/ScriptableObject) as TonyLi advised on the Unity forum so you can scale beyond “hello world.”

Minimal scene checklist: Canvas + TextMeshPro TMP_Text questionText + TMP_Text timerText + four answer Buttons + one Pause Button. Wire these in the Inspector exactly as fields below.

Drop-in GameManager (C#):

using TMPro; using UnityEngine; using UnityEngine.UI; using System.Collections.Generic;

public class GameManager : MonoBehaviour {
  public TMP_Text questionText;
  public TMP_Text timerText;
  public Button[] answerButtons; // assign 4 buttons
  public Button pauseButton;
  float timer = 30f;
  int score = 0;
  int highScore = 0;

  void Start(){
    highScore = PlayerPrefs.GetInt("highScore", 0); // Unity PlayerPrefs docs
    ShowQuestion(GetNextQuestion());
    pauseButton.onClick.AddListener(TogglePause);
  }

  void ShowQuestion(Question q){
    questionText.text = q.text;
    timer = q.timeLimit;
    for(int i=0;i OnAnswerSelected(idx));
      btn.GetComponentInChildren().text = q.answers[i];
    }
  }

  void Update(){
    timer = Mathf.Max(0, timer - Time.deltaTime);
    timerText.text = Mathf.CeilToInt(timer).ToString();
  }

  void OnAnswerSelected(int idx){ /* scoring & next question logic */ }
  void TogglePause(){ /* pause logic */ }
}

Timing model: use Time.deltaTime in Update or a coroutine, clamp at zero, and avoid per-frame allocations to save battery. Decide if wrong answers subtract time; this snippet is ready to accept data objects so you won’t hard‑code strings in scripts—a common beginner mistake.

Follow Unity UI Manual and PlayerPrefs docs for correctness, and apply the GDC practice: make the smallest playable loop, playtest, then iterate.

Build the core loop fast: UI, timer, answer buttons, and scoring

Focus on the smallest playable loop: present a question, run a countdown, accept one input, update the score, then load the next item. Keep the scene tiny so you can iterate quickly and test on a device.

Create the Canvas and wire fields in the Editor

Create a Canvas (Screen Space – Overlay). Add a TextMeshProUGUI for the question, four Buttons for answers, and small TMP labels for timer, score, and high score. Verify exactly one EventSystem exists in the scene.

Wiring and data separation

Store the four answer Buttons in an array and assign them indexes 0–3. Route all clicks to one OnAnswerSelected(int index) handler to avoid duplicated logic. Keep question text out of scripts: read from a Question object (string text, string[] answers, int correctIndex) so you can swap JSON, CSV, or ScriptableObject later without touching UI logic.

Core loop, timer, and pause behavior

  1. Load question.
  2. Render UI and enable input.
  3. Start countdown (use a coroutine or Time.deltaTime).
  4. On input, disable buttons, update score, then transition.

Use a paused flag or Time.timeScale = 0 for pause. If you use timeScale, remember coroutines and UI updates need explicit handling.

Common beginner mistakes and fixes

  • Multiple listeners: call button.onClick.RemoveAllListeners() before adding, or set listeners once in Awake.
  • Stale references: avoid GameObject.Find at runtime; use serialized fields and OnValidate checks.
  • Update() spam: update the timer label at a fixed cadence (for example, 10 Hz) to reduce TMP and Canvas rebuilds.

Question data and results screens using unity quiz game android source code patterns

Keep your question banks separate from gameplay logic to make updates and moderation simple.

Practical data options

  • ScriptableObject: fast authoring inside the Editor, best when content ships with the app.
  • JSON / CSV: editable in spreadsheets and easy to swap without rebuilding the app.
  • Backend-driven: use a server when you need rotating content, events, or moderated uploads.

Android loading and performance

Bundle small banks as a TextAsset for quick startup. For larger files use StreamingAssets and load via UnityWebRequest on Android to avoid blocking file I/O.

Always parse once at startup and cache question objects. Reuse lists and avoid per-question string allocations to prevent GC spikes during play.

Backend flow and offline strategy

A simple server flow: pull a set, cache locally, and post high scores. Realtime multiplayer is usually overkill, but the same auth and leaderboard patterns scale if you add rooms later. Ship a local fallback so the app stays playable when the server is unreachable.

Results screen and review mode

Switch panels (QuestionPanel → ResultsPanel) instead of instantiating UI each round. For review, reuse the answer buttons, color-code correct and selected entries, and disable interaction to avoid extra draw calls.

Option When to pick Tradeoffs
ScriptableObject Editor-driven content Fast, builds into APK
JSON/CSV Frequent non-dev edits Editable, needs parser
Backend Live updates & moderation Network complexity, offline concerns

Android-ready build, persistence, and mobile performance guardrails

Prepare your build for real phones by locking down persistence, performance, and safe-area layout before the first release.

High score persistence and versioning

Persist high scores with PlayerPrefs using explicit keys. Use HS_V1 for your first key, then HS_V2 when you change format.

Migration pattern: read HS_V1 once, write HS_V2, then remove HS_V1. This preserves player progress across updates.

Mobile performance checklist

  • Avoid frequent Canvas rebuilds: separate static and dynamic UI into different canvases.
  • Reduce draw calls: share TMP font materials and use sprite atlases with consistent textures.
  • Plan font atlases: limit glyph sets and keep styles consistent to avoid atlas churn.
  • Battery-friendly timing: prefer coroutines or timed ticks over heavy Update loops; cap frame rate if practical.

Screen sizes, safe areas, and testing

Use Canvas Scaler (Scale With Screen Size), proper anchors, and safe-area padding to keep touch targets stable on tall screens and notches.

Item Action Why
IL2CPP vs Mono Choose IL2CPP for release Better performance and smaller runtime bugs
API level Target current Android SDK Compatibility and Play Store requirements
Device test Test on low/mid hardware Catch overdraw, font and memory issues

Before shipping, check for duplicate EventSystem entries, async-load question banks instead of blocking I/O, and split large sprite sheets. If you add a backend or server sync, version endpoints and support offline retries so you don’t spam the server.

Follow the Unity UI Manual and PlayerPrefs documentation, then prototype → playtest → iterate as recommended at GDC: test time-per-question, readability, and whether scoring feels fair, then refine.

Conclusion

Before you call the build complete, confirm the playable loop runs cleanly on device and that your data pipeline is swappable between ScriptableObject, JSON, or CSV.

Checklist — verify these on a real phone: listeners are not duplicated; timer stops at zero; results panel shows totals; high score uses a versioned PlayerPrefs key; UI respects safe areas.

Top 5 issues to re-check: hard‑coded content, TMP/UI rebuild spikes, Update() doing heavy work, blocking file or network I/O, and touch targets misaligned by safe area errors.

Next steps: add categories (Science/History), enable question shuffling, add review mode, and consider backend sync only if you need rotating banks. Follow the official Unity UI and PlayerPrefs docs as your source of truth. Credit: George Jones on PlayMobile.online and related tutorial files from Jimmy Vegas for a solid starter project.

Leave a Reply

Your email address will not be published. Required fields are marked *