# **Recreating Original Pong in Unity 6: A Beginner's Guide (2-Player, New Input System + Authentic Sound Effects)** Welcome to this beginner-friendly tutorial! We'll build a faithful recreation of the 1972 Atari Pong arcade game using Unity 6. This project teaches core 2D concepts like sprites, physics, input, UI, and audio—perfect for your first game. We'll go slow, with clear steps, explanations, and checkpoints to test as you go. If something breaks, check the Console (Window > General > Console) for errors.What We'll Build: - **Visual style**: Black background, white rectangular paddles, white circle ball, thin white outlines for top/bottom board edges, and a vertical dashed "net" in the middle. - **Controls**: Player 1 (left paddle) uses W (up) / S (down); Player 2 (right paddle) uses Up Arrow / Down Arrow. - **Mechanics**: Ball bounces off paddles/walls with velocity inheritance, scoring on side misses, first to 10 points wins, auto-reset. Ball starts/resets with alternating horizontal direction (left/right toggle for fairness). - **Sounds**: Authentic Atari-style beeps (paddle "boink," wall "blip," score "buzz"). - **Sprites**: Custom paddle via Sprite Editor (20x100 pixels for proper size at 1:1:1 scale); default Square for outlines/net; Circle for ball (scaled to 0.2 for retro size; collider radius 1.28 for hit detection). - **Input**: New Input System (modern, flexible). - **UI**: Canvas + TextMeshPro for scores/win text (scalable, no legacy glitches). Estimated Time: 2-3 hours (with testing). Unity Version: 6000.2.10f1 (Tech Stream—stable for beginners).Prerequisites: Setting Up Your Unity Project (15-20 minutes)Before coding, get Unity ready. This ensures no weird errors later. 1. **Install Unity 6:** - Download/open Unity Hub (free from unity.com/hub). - In Hub: Click Installs (left sidebar) > Add > Select Unity 6000.2.10f1 (under "Tech Stream" tab) > Install (includes 2D tools; ~5GB, takes 10-15 min). - Why? Unity 6 has improved 2D physics and Input System for smooth games. 2. **Create a New Project:** - In Hub: Click Projects > New Project > Select 2D Core template (best for sprites/physics). - Name: "Pong" > Choose folder (e.g., Desktop/Pong) > Create Project. - Unity opens to a blank scene. Save immediately: File > Save As > Name "PongScene" > Save in Assets folder. 3. **Configure the Main Camera (Your Game's Viewport):** - In Hierarchy (left panel): Select Main Camera. - In Inspector (right panel): Under Background, set color to black (click color box > RGB 0,0,0). - Under Projection, ensure Orthographic (2D default). Set Size to 5 (zooms to ~10x7 playfield—test by playing; adjust if too zoomed). - Checkpoint: Press Play (top center) > Stop. Black screen? Good! 4. **Install Packages (Add Tools Unity Needs):** - Window (top menu) > Package Manager. - Search "Input System" > Select > Install (version ~1.8.0+; restart Unity if asked—saves settings). - Search "TextMeshPro" > Select > Import TMP Essential Resources (adds UI fonts; click Yes on popup). - Why? Input System handles keys/gamepad; TextMeshPro for pretty text. 5. **Project Settings Tweaks (Fix Common Beginner Bugs):** - Edit > Project Settings > Player (left sidebar) > Configuration tab > Active Input Handling > Set to Input System Package (New) (or Both if testing legacy). - Still in Project Settings > Editor tab > Enter Play Mode Settings > Check Reload Domain and Reload Scene (prevents input "freezing" after Play/Stop). - Why? New Input System can glitch in Editor without this. - Checkpoint: Play/Stop twice—keys should respond (we'll test soon). # Step 0: Add Authentic Sound Effects (10-15 minutes)Pong's charm is those beeps! We'll use free, exact recreations. 1. **Download Sounds:** - Open browser > Search "free pong arcade sound effects WAV" (e.g., Pixabay or Freesound.org). - Download 3 short files (~0.5s each, 8-bit vibe): - paddle_hit.wav (high "boink" for paddle bounces). - wall_bounce.wav (low "blip" for top/bottom walls). - score_buzz.wav (buzzing "miss" for scoring). - Tip: If no downloads, Unity Asset Store > Window > Asset Store > Search "Free Retro Beeps" > Import a pack. 2. **Import to Unity:** - In Project window (bottom panel): Right-click > Import New Asset. - Select your 3 files > Open. - For each: Select in Project > Inspector > AudioClip section > Set Compression Format to Vorbis (small file size) > Apply. - Why? Vorbis keeps sounds crisp without bloating your build. 3. Checkpoint: Files appear in Project as blue audio icons. Drag one to Hierarchy > Play > Hear sound? Great! # Step 1: Create Sprites (Building Your Game's Graphics—15 minutes)Sprites are 2D images. We'll make simple white shapes—no art skills needed. 1. **Paddle Sprite (Tall Rectangle for Paddles):** - Project window: Right-click > Create > 2D > Sprites > Square > Rename "PaddleSprite". - Select PaddleSprite > Sprite Editor button (top Inspector—window opens). - In Sprite Editor: Top-left Sprite Settings > Pixels Per Unit to 100 (sharp scaling) > Apply. - Resize canvas: Top toolbar > Canvas dropdown > Change Canvas Size > Width: 20, Height: 100 > Apply (makes thin paddle). - Fill white: Left toolbar > Brush tool > Set color white (#FFFFFF) > Draw over canvas. - Bottom: Apply > Close Editor. - Why? 20x100 pixels = ~0.2x1 unit in-game (retro size). 2. **Ball Sprite (Circle for Ball):** - Right-click > Create > 2D > Sprites > Circle > Rename "BallSprite" (built-in white circle—no edits needed). - Why? Unity's default is perfect for Pong's dot. 3. **Default Square (For Lines/Net):** - Right-click > Create > 2D > Sprites > Square > Rename "DefaultSquare" (256x256 white—no edits; we'll scale for lines). - Checkpoint: Drag each to Hierarchy > See white shapes? Delete them (right-click > Delete)—we'll use later. # Step 2: Set Up the Playfield Background and Boundaries (Building the Court—20 minutes)Create invisible physics walls + visual lines. Use empty GameObjects (GOs) as containers. 1. **Background (Black Court Floor):** - Hierarchy: Right-click > Create Empty > Rename "Background". - Add Component (bottom Inspector): Search "Sprite Renderer" > Add. - Drag "DefaultSquare" to Sprite field > Color: black (#000000). - Transform (top Inspector): Position (0,0,0) > Scale (10,7,1) (covers playfield). - Add Tag: Inspector > Tag dropdown > Add Tag > + > Name "Background" > Select GO > Assign tag. - Sorting Layer: Sprite Renderer > Sorting Layer dropdown > Add Sorting Layer > Name "Background" (draws first). - Why? Large black square = your game's "floor." 2. **Walls Container (Groups Boundaries):** - Right-click > Create Empty > Rename "Walls" > Position (0,0,0). 3. **Add Individual Walls (4 Boundaries for Bounces/Scores):** - Right-click "Walls" > Create Empty > Rename "LeftWall" > Position (-5.5, 0, 0) > Scale (0.1, 7, 1) (thin vertical). - Add Box Collider 2D (search in Add Component). - For scoring: Check Is Trigger (no physics block, just detect). - Duplicate LeftWall (Ctrl+D) > Rename "RightWall" > Position (5.5, 0, 0) > Same scale/Is Trigger. - Right-click "Walls" > Create Empty > Rename "TopWall" > Position (0, 3.5, 0) > Scale (11.1, 0.1, 1) (wide horizontal). - Add Box Collider 2D (no trigger—solid bounce). - Add Sprite Renderer > Drag "DefaultSquare" > Color: white (#FFFFFF) (visual line). - Duplicate TopWall > Rename "BottomWall" > Position (0, -3.5, 0) > Same setup. - Why? 11.1 width overlaps sides (no gaps). Left/Right invisible (no Sprite Renderer). 4. **Net (Dashed Center Line—Do This Carefully):** - Right-click > Create Empty > Rename "Net" > Position (0,0,0). - Right-click "Net" > Create Empty > Rename "NetDash1" > Add Sprite Renderer > Drag "DefaultSquare" > Color: white > Scale (0.1, 0.2, 1) (short horizontal dash) > Position (0, 2.75, 0). - Duplicate NetDash1 > Rename "NetDash2" > Position (0, 2.25, 0) (0.5 units down). - Efficient Duplication for 12 Dashes: - Select both dashes (Shift-click) > Ctrl+D (duplicate). - Scene view (bottom, tab next to Game): Hold Ctrl (snaps to grid) > Drag pair down 1 unit (to Y=1.75/1.25). - Repeat: Select new pair > Ctrl+D > Ctrl-drag down 1 unit (to 0.75/0.25). - Select last pair > Ctrl+D > Ctrl-drag to -0.75/-1.25. - Tweak manually: Select each > Inspector Position Y to -1.75, -2.25, -2.75 (add extras if needed for 12 total). - Why? Mimics original dotted net. Grid snap = precise spacing. - Checkpoint: Play > See black court with white lines/dashes? Ball will bounce off walls later. # Step 3: Set Up the Input System (Keyboard Controls—15 minutes)New Input System = flexible keys (easy to add gamepad later). 1. **Create Input Actions Asset:** - Project: Right-click > Create > Input Actions > Name "PongInputActions". - Double-click asset (opens Input Actions window). 2. **Build Action Maps & Bindings (Separate for Each Player):** - In window: Right-click empty area > Add Action Map > Name "Player1". - Right-click "Player1" > Add Action > Name "MoveUp" > Inspector (right): Action Type: Value > Control Type: Button. - Under Bindings: Click "+" > Listen (press W) or search "W" > Keyboard > W. - Right-click "Player1" > Add Action > Name "MoveDown" > Same type > Binding: S. - Right-click empty (outside Player1) > Add Action Map > Name "Player2". - Right-click "Player2" > Add Action > Name "MoveUp" > Binding: Up Arrow (search "up"). - Right-click "Player2" > Add Action > Name "MoveDown" > Binding: Down Arrow. - Save (Ctrl+S) > Close window. 3. **Generate Code (Makes Actions Usable in Scripts):** - Select "PongInputActions" in Project > Inspector > Generate C# Class checkbox > Apply (creates "PongInputActions.cs"). - Why? This "bakes" bindings into code (e.g., inputActions.Player1.MoveUp). 4. **Checkpoint Test (Quick Key Check):** - Hierarchy: Right-click > Create Empty > Add New Script > Name "InputTest" > Open (double-click). - Replace code: csharp ```csharp using UnityEngine; using UnityEngine.InputSystem; public class InputTest : MonoBehaviour { void Update() { if (Keyboard.current.wKey.wasPressedThisFrame) Debug.Log("W pressed!"); } } ``` - Save > Back to Unity > Play > Press W > Console (Window > General > Console) shows message? Delete test GO. # Step 4: Create the Paddles (Player Controls—20 minutes)Paddles = moving rectangles that hit the ball. 1. **Left Paddle (Player 1):** - Drag "PaddleSprite" from Project to Hierarchy > Rename "Paddle1" > Transform: Position (-4.5, 0, 0) > Scale (1,1,1) (sprite size = natural). - Add Box Collider 2D (auto-fits sprite bounds for collisions). - Add Rigidbody 2D > Gravity Scale: 0 (no falling) > Then Body Type: Kinematic (player-controlled, not physics-simulated). - Tip: Set Gravity first—it's grayed out after Kinematic. - Tag: Inspector > Tag > Add Tag > + > "Paddle" > Assign to GO. 2. **Right Paddle (Player 2):** - Duplicate Paddle1 (Ctrl+D) > Rename "Paddle2" > Position (4.5, 0, 0). 3. **Add Movement Script (Handles Keys):** - Select Paddle1 > Add New Script > Name "PlayerControls" > Double-click to open. - Replace entire code (explains sections): csharp ```csharp using UnityEngine; // Core Unity features using UnityEngine.InputSystem; // New Input System public class PlayerControls : MonoBehaviour // Attaches to paddle GO { [Header("Input")] // Groups in Inspector public PongInputActions inputActions; // Link to our asset [Header("Player Selection")] public bool isPlayer1 = true; // Toggle for P1 vs P2 maps public float speed = 8f; // How fast paddles move (tune here) public float boundY = 2.5f; // Y limit (playfield edge) private Rigidbody2D rb; // Physics body private InputAction moveUpAction, moveDownAction; // Key checks void Awake() // Runs on start (setup) { rb = GetComponent<Rigidbody2D>(); // Grab physics if (inputActions == null) inputActions = new PongInputActions(); // Auto-create if missing SetupActions(); // Pick map based on bool } private void SetupActions() // Chooses keys per player { if (isPlayer1) { moveUpAction = inputActions.Player1.MoveUp; moveDownAction = inputActions.Player1.MoveDown; inputActions.Player1.Enable(); // Activate map } else { moveUpAction = inputActions.Player2.MoveUp; moveDownAction = inputActions.Player2.MoveDown; inputActions.Player2.Enable(); } } void OnEnable() // When script activates { moveUpAction?.Enable(); // ? = safe check if null moveDownAction?.Enable(); } void OnDisable() // Cleanup { moveUpAction?.Disable(); moveDownAction?.Disable(); } void Update() // Runs every frame (~60x/sec) { var vel = rb.linearVelocity; // Get current speed (Unity 6 style) if (moveUpAction.IsPressed()) vel.y = speed; // Up key else if (moveDownAction.IsPressed()) vel.y = -speed; // Down else vel.y = 0; // Stop rb.linearVelocity = vel; // Apply var pos = transform.position; // Clamp to bounds pos.y = Mathf.Clamp(pos.y, -boundY, boundY); // No going off-screen transform.position = pos; } } ``` - Save > Back to Unity (compiles automatically). - Drag "PongInputActions" from Project to Input Actions field (under "Input" header). - For Paddle2: Drag same asset > Set Is Player 1 to false. - Why? Bool toggles maps—one asset, two players. 4. **Checkpoint**: Play > W/S moves left paddle up/down (stops at bounds); arrows move right. No movement? Check Input Debugger (Window > Analysis > Input Debugger) > Press keys > See events? # Step 5: Create the Ball (Bouncing Physics + Sounds—20 minutes)The ball = circle with momentum. 1. **Ball Setup:** - Drag "BallSprite" to Hierarchy > Rename "Ball" > Position (0,0,0) > Scale (0.2, 0.2, 1) (small dot). - Add Circle Collider 2D > Radius: 1.28 (bigger than scale for easy hits). - Create Material: Project > Right-click > Create > 2D > Physics Material 2D > Name "Bouncy" > Bounciness: 1 (perfect rebound), Friction: 0 (slippery) > Drag to Collider's Material field. - Add Rigidbody 2D > Gravity Scale: 0, Drag: 0 (no slowing/falling). - Tag: "Ball". - Add AudioSource > Play On Awake: false, Spatial Blend: 0 (2D sound). 2. **Ball Script** (Launch, Reset, Bounces + Sounds): - Add New Script > Name "BallControl" > Replace: csharp ```csharp using UnityEngine; // Basics public class BallControl : MonoBehaviour { private Rigidbody2D rb; // Physics public float launchForceX = 10f; // Horizontal push public float launchForceY = 5f; // Vertical wobble [Header("Sounds")] // Inspector fields public AudioClip paddleHitSound; // Boink clip public AudioClip wallBounceSound; // Blip clip private AudioSource audioSource; // Player private static bool alternateDirection = true; // Shared toggle (starts right) void Start() // Initial setup { rb = GetComponent<Rigidbody2D>(); audioSource = GetComponent<AudioSource>(); Invoke(nameof(GoBall), 2f); // Delay launch } void GoBall() // Launches with direction { float xDir = alternateDirection ? launchForceX : -launchForceX; alternateDirection = !alternateDirection; // Flip for next rb.linearVelocity = Vector2.zero; // Reset speed rb.AddForce(new Vector2(xDir, Random.Range(-launchForceY, launchForceY))); // Push! } public void ResetBall() // Called on score { CancelInvoke(nameof(GoBall)); // Stop any waiting launch rb.linearVelocity = Vector2.zero; transform.position = Vector2.zero; Invoke(nameof(GoBall), 1f); // New launch after pause } void OnCollisionEnter2D(Collision2D collision) // Detect hits { if (collision.gameObject.CompareTag("Paddle")) // Paddle hit { var currentVel = rb.linearVelocity; float y = (currentVel.y / 2) + (collision.rigidbody.linearVelocity.y / 3); // Inherit paddle speed rb.linearVelocity = new Vector2(currentVel.x, y); // Apply angle if (paddleHitSound != null) audioSource.PlayOneShot(paddleHitSound, 0.8f); // Boink! } else if (collision.gameObject.name.Contains("Wall") && !collision.gameObject.name.Contains("Side")) // Top/Bottom { if (wallBounceSound != null) audioSource.PlayOneShot(wallBounceSound, 0.6f); // Blip } } } ``` - Save > Drag clips: Paddle Hit Sound = paddle_hit.wav; Wall Bounce = wall_bounce.wav. - Why? Alternating ensures fair play. OnCollision = physics events. 3. **Checkpoint**: Play > Ball launches right (2s delay), bounces off walls/paddles (sounds?). Miss side = ? (We'll add scoring next). # Step 6: Add Scoring and UI (Points + Win Screen—25 minutes)Track scores with modern UI (no old OnGUI bugs). 1. **Canvas** (UI Container): - Hierarchy: Right-click > UI > Canvas > Rename "ScoreCanvas". - Inspector: Render Mode: Screen Space - Overlay (always on top). - Add Canvas Scaler (if missing): UI Scale Mode > Scale With Screen Size > Reference Resolution (1920, 1080) (adapts to windows). 2. **Score Texts:** - Right-click Canvas > UI > Text - TextMeshPro > Rename "ScoreP1" > Text (top field): "0". - Font Size: 72, Color: White, Alignment: Center > Rect Transform: Pos X: -200, Y: 40 > Anchor: Top-Left (holds position). - Duplicate ScoreP1 > Rename "ScoreP2" > Text: "0" > Pos X: 200 > Anchor: Top-Right. 3. **Win Text & Button**: - Right-click Canvas > UI > Text - TextMeshPro > Rename "WinText" > Text: "" (empty) > Font Size: 72, Color: White, Alignment: Center > Pos (0,0,0) > Anchor: Middle-Center > Uncheck active (top checkbox). - Right-click Canvas > UI > Button - TextMeshPro > Rename "RestartButton" > Text (child): "Restart" > Pos (0, -200, 0) > Anchor: Bottom-Center > Uncheck active. 4. **GameManager (Scores Logic + Sounds):** - Right-click > Create Empty > Rename "GameManager" > Add AudioSource > Play On Awake: false, Spatial Blend: 0. - Add New Script > Name "GameManager" > Replace: csharp ```csharp using UnityEngine; // Basics using TMPro; // TextMeshPro public class GameManager : MonoBehaviour { public static int player1Score = 0; // Shared scores public static int player2Score = 0; public GameObject ball; // Link to ball private BallControl ballControl; // Script access public TextMeshProUGUI scoreP1Text, scoreP2Text, winText; // UI links public GameObject restartButton; // Button link [Header("Sounds")] public AudioClip scoreSound; // Buzz clip private AudioSource audioSource; // Player private bool hasPlayer1Won = false, hasPlayer2Won = false; // Prevent repeats void Start() // Setup { ball = GameObject.FindWithTag("Ball"); // Find by tag ballControl = ball.GetComponent<BallControl>(); audioSource = GetComponent<AudioSource>(); scoreP1Text = GameObject.Find("ScoreP1").GetComponent<TextMeshProUGUI>(); // Find UI scoreP2Text = GameObject.Find("ScoreP2").GetComponent<TextMeshProUGUI>(); winText = GameObject.Find("WinText").GetComponent<TextMeshProUGUI>(); restartButton = GameObject.Find("RestartButton"); UpdateScores(); // Initial display } public static void Score(string wallName) // Called on hit { if (wallName == "RightWall") player1Score++; // P1 scores (ball past P2) else if (wallName == "LeftWall") player2Score++; // P2 scores FindObjectOfType<GameManager>().PlayScoreSound(); // Buzz! } void Update() // Every frame { UpdateScores(); // Refresh text CheckWin(); } private void UpdateScores() { scoreP1Text.text = player1Score.ToString(); scoreP2Text.text = player2Score.ToString(); } private void CheckWin() { if (player1Score >= 10 && !hasPlayer1Won) { hasPlayer1Won = true; winText.text = "Player 1 Wins!"; winText.gameObject.SetActive(true); restartButton.SetActive(true); Time.timeScale = 0f; // Freeze game PlayScoreSound(); // Win buzz ballControl.ResetBall(); // One-time reset } else if (player2Score >= 10 && !hasPlayer2Won) { hasPlayer2Won = true; winText.text = "Player 2 Wins!"; winText.gameObject.SetActive(true); restartButton.SetActive(true); Time.timeScale = 0f; PlayScoreSound(); ballControl.ResetBall(); } } public void PlayScoreSound() { if (scoreSound != null) audioSource.PlayOneShot(scoreSound, 0.8f); } public void RestartGame() // Button callback { player1Score = player2Score = 0; hasPlayer1Won = hasPlayer2Won = false; ballControl.ResetBall(); // Alternating relaunch winText.gameObject.SetActive(false); restartButton.SetActive(false); Time.timeScale = 1f; // Unfreeze } } ``` - Save > Drag: Ball to Ball; UI texts to fields; RestartButton to field; score_buzz.wav to Score Sound. 5. **Button Wiring**: - Select RestartButton > Inspector > Button section > On Click () > + > Drag GameManager from Hierarchy > Dropdown: GameManager.RestartGame. 6. **Side Walls Script (**Detects Scores): - Select LeftWall > Add New Script > Name "SideWalls". - Replace: csharp ```csharp using UnityEngine; // Basics public class SideWalls : MonoBehaviour { void OnTriggerEnter2D(Collider2D other) // Trigger hit { if (other.CompareTag("Ball")) // Ball entered? { GameManager.Score(gameObject.name); // Score + buzz other.GetComponent<BallControl>().ResetBall(); // Relaunch } } } ``` - Save > Duplicate script to RightWall (copy-paste via Add Component > search "SideWalls"). - Ensure both colliders: Is Trigger checked. 7. **Checkpoint**: Play > Ball misses side = score updates, buzz plays, 1s pause + relaunch (alternates direction). Hit 10 = win screen/pause. Click Restart = reset. Sounds on all? # Step 7: Polish, Test, and Build (10 minutes) 1. **Quick Fixes**: - All sprites: Inspector > Color white (#FFFFFF). - Playfield fit: Camera Size 5 (adjust if cropped). - Sounds: Tune volumes (0.6-0.8) in AudioSource. 2. **Full Test**: - Play > 2 players: Move paddles, hit ball (boink), bounce walls (blip), score (buzz + reset). Win? Restart works. - Debug: Console errors? Check tags/fields. No sound? Re-drag clips. 3. **Build Your Game** (Shareable Exe): - File > Build Settings > Add Open Scenes (adds PongScene) > Platform: PC, Mac & Linux Standalone > Build > Choose folder > Name "Pong.exe". - Run exe: Full game! (Input/UI perfect outside Editor). ***Congrats—you built Pong!*** This teaches sprites (Step 1), physics (5), input (3-4), UI/audio (6).