Source file unity-csharp/arcade-classics/frogger/FroggerGame.cs from the GDnD code examples. Download raw file

// GDnD wiki example
// Demonstrates: building a row layout from data, mapping a y position to a lane type, and owning score/lives
// Related pages: [[overview-arcade-classics-as-learning-projects]], [[csharp-enums]], [[unity-gamemanager-pattern]], [[ui-design]]
 
using UnityEngine;
 
// Builds the board from a list of lane definitions and answers the one question
// the frog keeps asking: "what kind of lane is at this height?". Row 0 (bottom)
// and the top row are always safe; the lanes in between are road or river. Also
// owns score and lives.
public class FroggerGame : MonoBehaviour
{
    public enum LaneType { Safe, Road, River }
 
    [System.Serializable]
    public struct LaneDef
    {
        public LaneType type;
        [Tooltip("Units per second. Sign sets direction; ignored for Safe lanes.")]
        public float speed;
        [Tooltip("Seconds between spawns; ignored for Safe lanes.")]
        public float spawnInterval;
    }
 
    [Header("Layout (bottom row up, excluding the safe start and goal rows)")]
    [SerializeField] private LaneDef[] lanes;
    [SerializeField] private float rowHeight = 0.9f;
 
    [Header("Prefabs")]
    [Tooltip("Hazard on the Vehicle layer.")]
    [SerializeField] private Hazard carPrefab;
    [Tooltip("Wide hazard on the Log layer.")]
    [SerializeField] private Hazard logPrefab;
 
    [SerializeField] private FrogController frog;
    [SerializeField] private int startingLives = 3;
    [Tooltip("Optional on-screen HUD. If empty, messages print to the Console.")]
    [SerializeField] private ArcadeHud hud;
 
    public float RowHeight => rowHeight;
    public float StartY { get; private set; }
    public float GoalY { get; private set; }
    public int Score { get; private set; }
    public int Lives { get; private set; }
 
    private int totalRows;
 
    private void Start()
    {
        Build();
    }
 
    private void Build()
    {
        // Safe start row + lanes + safe goal row, centred vertically on the camera.
        totalRows = lanes.Length + 2;
        StartY = -((totalRows - 1) * rowHeight) / 2f;
        GoalY = ((totalRows - 1) * rowHeight) / 2f;
 
        for (int i = 0; i < lanes.Length; i++)
        {
            int rowIndex = i + 1; // row 0 is the safe start
            float y = StartY + rowIndex * rowHeight;
 
            GameObject laneObject = new GameObject($"Lane {rowIndex} ({lanes[i].type})");
            laneObject.transform.SetParent(transform);
 
            Hazard prefab = lanes[i].type switch
            {
                LaneType.Road => carPrefab,
                LaneType.River => logPrefab,
                _ => null,
            };
 
            laneObject.AddComponent<Lane>().Configure(lanes[i].type, lanes[i].speed, lanes[i].spawnInterval, prefab, y);
        }
 
        Score = 0;
        Lives = startingLives;
        frog.Bind(this);
        frog.ResetToStart();
        RefreshHud();
    }
 
    // Round the y position to the nearest row and report that row's type.
    public LaneType GetLaneType(float y)
    {
        int index = Mathf.RoundToInt((y - StartY) / rowHeight);
        if (index <= 0 || index >= totalRows - 1)
        {
            return LaneType.Safe;
        }
 
        return lanes[index - 1].type;
    }
 
    public void OnFrogReachedGoal()
    {
        Score += 100;
        RefreshHud();
        ShowMessage($"Home! Score {Score}");
        frog.ResetToStart();
    }
 
    public void OnFrogDied()
    {
        Lives--;
        ShowMessage($"Frog lost. Lives remaining: {Lives}");
 
        if (Lives <= 0)
        {
            ShowMessage($"Game over. Final score: {Score}");
            Score = 0;
            Lives = startingLives;
        }
 
        RefreshHud();
        frog.ResetToStart();
    }
 
    // HUD helpers. The game owns the numbers; ArcadeHud only displays them.
    // With no HUD assigned, messages fall back to the Console instead of throwing.
    private void RefreshHud()
    {
        if (hud != null)
        {
            hud.SetStatus($"Score {Score}   Lives {Lives}");
        }
    }
 
    private void ShowMessage(string message)
    {
        if (hud != null)
        {
            hud.ShowMessage(message);
        }
        else
        {
            Debug.Log(message);
        }
    }
}