// 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]] 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; 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().Configure(lanes[i].type, lanes[i].speed, lanes[i].spawnInterval, prefab, y); } Score = 0; Lives = startingLives; frog.Bind(this); frog.ResetToStart(); Debug.Log($"Frogger ready. Lives {Lives}."); } // 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; Debug.Log($"Home! Score {Score}"); frog.ResetToStart(); } public void OnFrogDied() { Lives--; Debug.Log($"Frog lost. Lives remaining: {Lives}"); if (Lives <= 0) { Debug.Log($"Game over. Final score: {Score}"); Score = 0; Lives = startingLives; } frog.ResetToStart(); } }