Skip to main content
The camera system provides smooth player tracking, keeping the player centered in the viewport throughout gameplay.

CameraController

A straightforward camera controller that follows the player’s position in 2D space.

Configuration

player
PlayerInput
required
Reference to the player’s PlayerInput component for position tracking

Implementation

CameraController.cs:3-22
public class CameraController : MonoBehaviour
{
    Vector3 target;

    [SerializeField]
    private PlayerInput player;

    void Start()
    {
        target = new Vector3(player.transform.position.x, player.transform.position.y, -10);
    }

    void Update()
    {
        target.x = player.transform.position.x;
        target.y = player.transform.position.y;

        transform.position = target;
    }
}

How It Works

Initialization

On startup, the camera initializes its target position:
CameraController.cs:10-12
void Start()
{
    target = new Vector3(player.transform.position.x, player.transform.position.y, -10);
}
The Z-coordinate is set to -10 to position the camera in front of 2D sprites (which are typically at Z=0).
In Unity’s 2D mode, negative Z values place the camera in front of sprites. The value -10 is a common convention that ensures proper rendering without being too far away.

Position Tracking

Every frame, the camera updates to match the player’s position:
CameraController.cs:14-20
void Update()
{
    target.x = player.transform.position.x;
    target.y = player.transform.position.y;

    transform.position = target;
}
The camera:
  1. Reads the player’s X and Y coordinates
  2. Maintains its Z-coordinate at -10
  3. Instantly moves to the target position
This implementation provides instantaneous following without smoothing. The camera position updates every frame in Update() rather than FixedUpdate() for smooth visual tracking independent of physics updates.

Setup in Unity

  1. Main Camera Setup
    • Select the Main Camera in the Hierarchy
    • Add the CameraController component
    • Set the camera’s Projection to Orthographic (for 2D)
    • Adjust Size to set the zoom level (e.g., 5-10)
  2. Player Reference
    • Drag the Player GameObject into the player field
    • The player must have a PlayerInput component
  3. Camera Positioning
    • Initial camera position doesn’t matter - it will snap to the player in Start()
    • Ensure Z position is negative (handled automatically)

Customization Options

While the base implementation is simple, here are common enhancements:

Smooth Following (Lerp)

Add interpolation for smoother camera movement:
public float smoothSpeed = 0.125f;

void Update()
{
    target.x = player.transform.position.x;
    target.y = player.transform.position.y;

    // Smoothly interpolate to target
    transform.position = Vector3.Lerp(transform.position, target, smoothSpeed);
}

Camera Bounds

Prevent the camera from moving outside level boundaries:
public float minX, maxX, minY, maxY;

void Update()
{
    target.x = Mathf.Clamp(player.transform.position.x, minX, maxX);
    target.y = Mathf.Clamp(player.transform.position.y, minY, maxY);

    transform.position = target;
}

Dead Zone

Only move the camera when the player moves beyond a certain distance:
public float deadZoneX = 1f;
public float deadZoneY = 1f;

void Update()
{
    float deltaX = player.transform.position.x - transform.position.x;
    float deltaY = player.transform.position.y - transform.position.y;

    if (Mathf.Abs(deltaX) > deadZoneX)
    {
        target.x = player.transform.position.x;
    }

    if (Mathf.Abs(deltaY) > deadZoneY)
    {
        target.y = player.transform.position.y;
    }

    transform.position = target;
}

Look-Ahead

Offset the camera slightly in the player’s movement direction:
public float lookAheadDistance = 2f;

void Update()
{
    Vector2 playerVelocity = player.GetComponent<Rigidbody2D>().linearVelocity;
    Vector2 lookAhead = playerVelocity.normalized * lookAheadDistance;

    target.x = player.transform.position.x + lookAhead.x;
    target.y = player.transform.position.y + lookAhead.y;

    transform.position = target;
}

Y-Axis Only

For side-scrollers where horizontal position is automatic:
void Update()
{
    // Only follow Y position
    target.y = player.transform.position.y;
    
    // X moves automatically or stays fixed
    target.x = transform.position.x + autoScrollSpeed * Time.deltaTime;

    transform.position = target;
}

Integration with Player System

The camera system integrates simply with the player:
// Camera reads player position every frame
target.x = player.transform.position.x;
target.y = player.transform.position.y;

// Player movement (from PlayerInput and PlayerJump) 
// automatically updates player.transform.position
// which the camera then reads
The camera uses PlayerInput as its reference, but only accesses the Transform component. It doesn’t call any PlayerInput methods or modify player state.

Performance Considerations

Update vs LateUpdate

For smoother following, consider using LateUpdate():
void LateUpdate() // Instead of Update()
{
    target.x = player.transform.position.x;
    target.y = player.transform.position.y;

    transform.position = target;
}
LateUpdate() runs after all Update() calls, ensuring the camera moves after the player has finished moving for the frame.

Vector3 Allocation

The current implementation modifies the same target vector, which is efficient:
// Good - reuses existing Vector3
target.x = player.transform.position.x;
target.y = player.transform.position.y;

// Avoid - creates new Vector3 every frame
target = new Vector3(player.transform.position.x, player.transform.position.y, -10);

Common Issues

Camera Jitter

Problem: Camera appears to jitter or shake Solution: Use LateUpdate() instead of Update() so the camera moves after player physics

Player Off-Center

Problem: Player doesn’t appear centered in the camera view Solution: Verify the camera’s Z position is negative (e.g., -10) and the player’s Z position is 0

No Camera Movement

Problem: Camera stays at starting position Solution: Check that:
  • The player field is assigned in the Inspector
  • The player GameObject is actually moving
  • The CameraController script is enabled

Best Practices

  1. Use LateUpdate - Move the camera in LateUpdate() for smoother following
  2. Maintain Z-coordinate - Always keep Z at -10 to ensure proper 2D rendering
  3. Reference the player correctly - Ensure the player reference is assigned before Play mode
  4. Consider smoothing - Instant following can feel jerky; consider adding smooth damping
  5. Set appropriate camera size - Adjust orthographic size based on your game’s scale
  6. Test camera bounds - Ensure the camera doesn’t show areas outside your level
  7. Profile performance - Camera updates run every frame; keep the logic simple

Comparison with Cinemachine

Unity’s Cinemachine package provides advanced camera features:
FeatureCameraControllerCinemachine
Simple following
SmoothingManualBuilt-in
Dead zonesManualBuilt-in
Camera shakeManualBuilt-in
Multiple targetsManualBuilt-in
ComplexityLowHigh
PerformanceOptimalGood
Setup timeMinutesHours
The simple CameraController is perfect for prototypes and small games. For production games with complex camera requirements, consider upgrading to Cinemachine.

Build docs developers (and LLMs) love