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

// GDnD wiki example
// Demonstrates: the game loop owning score and serve state, and subscribing to a ball event
// Related pages: [[overview-arcade-classics-as-learning-projects]], [[unity-gamemanager-pattern]], [[observer-pattern]], [[ui-design]]
 
using UnityEngine;
 
// Minimal match controller: owns the two scores, serves the ball, and re-serves
// after each point. This is the smallest version of the central-state ownership
// idea that grows into [[unity-gamemanager-pattern]] in later projects.
public class PongMatch : MonoBehaviour
{
    [SerializeField] private Ball2D ball;
    [SerializeField] private int pointsToWin = 11;
    [Tooltip("Optional on-screen HUD. If empty, messages print to the Console.")]
    [SerializeField] private ArcadeHud hud;
 
    public int LeftScore { get; private set; }
    public int RightScore { get; private set; }
 
    private void OnEnable()
    {
        // Subscribe in OnEnable / unsubscribe in OnDisable to avoid leaks — see [[observer-pattern]].
        if (ball != null)
        {
            ball.Scored += OnScored;
        }
    }
 
    private void OnDisable()
    {
        if (ball != null)
        {
            ball.Scored -= OnScored;
        }
    }
 
    private void Start()
    {
        RefreshHud();
        ServeTowardRandomSide();
    }
 
    private void OnScored(int direction)
    {
        // direction +1 → ball exited right, so the left (player) scores, and vice versa.
        if (direction > 0)
        {
            LeftScore++;
        }
        else
        {
            RightScore++;
        }
 
        if (LeftScore >= pointsToWin || RightScore >= pointsToWin)
        {
            ShowMessage(LeftScore > RightScore ? "Left player wins!" : "Right player wins!");
            LeftScore = 0;
            RightScore = 0;
        }
 
        RefreshHud();
 
        // Serve toward the player who just conceded.
        ball.Serve(direction > 0 ? +1 : -1);
    }
 
    private void ServeTowardRandomSide()
    {
        ball.Serve(Random.value < 0.5f ? -1 : +1);
    }
 
    // HUD helpers. The match 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($"{LeftScore}   :   {RightScore}");
        }
    }
 
    private void ShowMessage(string message)
    {
        if (hud != null)
        {
            hud.ShowMessage(message);
        }
        else
        {
            Debug.Log(message);
        }
    }
}