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]]
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<Lane>().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();
}
}