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

// GDnD wiki example
// Demonstrates: keyboard input, clamped movement, and a one-line "track the ball" AI for the second paddle
// Related pages: [[overview-arcade-classics-as-learning-projects]], [[unity-input]], [[unity-transform]]
 
using UnityEngine;
 
// One script for both paddles. Set controlMode in the Inspector: Player reads an
// input axis; Ai chases the ball's height. The Ai branch is the seed of every
// later "enemy follows target" behaviour in the coding progression.
public class Paddle2D : MonoBehaviour
{
    public enum ControlMode { Player, Ai }
 
    [SerializeField] private ControlMode controlMode = ControlMode.Player;
    [SerializeField] private float speed = 12f;
 
    [Header("Player")]
    [Tooltip("Vertical input axis name (e.g. \"Vertical\" or a custom second-player axis).")]
    [SerializeField] private string verticalAxis = "Vertical";
 
    [Header("AI")]
    [SerializeField] private Transform ball;
    [Tooltip("Caps AI reaction so it is beatable. Lower = lazier paddle.")]
    [SerializeField, Range(0f, 1f)] private float aiResponsiveness = 0.65f;
 
    private float halfHeight;
    private float halfSize;
    private Camera cachedCamera;
 
    private void Awake()
    {
        cachedCamera = Camera.main;
        halfSize = GetComponent<SpriteRenderer>().bounds.extents.y;
    }
 
    private void Update()
    {
        float move = controlMode == ControlMode.Player ? ReadPlayerInput() : ReadAiInput();
        Vector3 position = transform.position;
        position.y += move * speed * Time.deltaTime;
 
        halfHeight = cachedCamera.orthographicSize - halfSize;
        position.y = Mathf.Clamp(position.y, -halfHeight, halfHeight);
        transform.position = position;
    }
 
    private float ReadPlayerInput()
    {
        return Input.GetAxisRaw(verticalAxis);
    }
 
    private float ReadAiInput()
    {
        if (ball == null)
        {
            return 0f;
        }
 
        // Move toward the ball, but scale by responsiveness so the AI lags and
        // the player can win. A deadzone stops jitter when roughly aligned.
        float difference = ball.position.y - transform.position.y;
        if (Mathf.Abs(difference) < 0.1f)
        {
            return 0f;
        }
 
        return Mathf.Sign(difference) * aiResponsiveness;
    }
}