Skip to main content
The player system handles all player-related mechanics including movement, jumping, collision detection, and death animations. It uses Unity’s physics system for realistic movement and implements advanced features like double jumping and wall sliding.

Architecture Overview

The player system is divided into four specialized components:
  • PlayerInput - Handles horizontal movement and character orientation
  • PlayerJump - Manages jump mechanics, gravity, wall sliding, and double jumping
  • CollisionDetection - Detects ground and wall collisions using overlap checks
  • PlayerDeath - Handles death animations via events
This system uses a physics-based approach where gravity is dynamically calculated based on desired jump height and distance, providing consistent and tunable jump arcs.

PlayerInput

Handles horizontal player movement using Unity’s Input System and manages character sprite flipping.

Configuration

speed
float
default:"5.0"
Movement speed in units per second
Animator
Animator
required
Reference to the player’s Animator component for walk animations

Implementation

The movement system uses FixedUpdate() for physics-based movement:
PlayerInput.cs:20-30
private void FixedUpdate()
{
    Vector2 velocity = rigid2d.linearVelocity;
    velocity.x = horizontalDirection * speed;
    rigid2d.linearVelocity = velocity;

    Animator.SetFloat("Walk", Mathf.Abs(horizontalDirection));

    if ((horizontalDirection > 0) && !facingRight ||
        (horizontalDirection < 0) && facingRight) FlipCharacter();
}

Input Handling

Movement input is received through Unity’s Input System:
PlayerInput.cs:32-36
private void OnMove(InputValue value)
{
    Vector2 input = value.Get<Vector2>();
    horizontalDirection = input.x;
}

Character Flipping

The character sprite automatically flips to face the movement direction:
PlayerInput.cs:38-44
private void FlipCharacter()
{
    Vector3 currentScale = gameObject.transform.localScale;
    currentScale.x *= -1;
    gameObject.transform.localScale = currentScale;
    facingRight = !facingRight;
}

PlayerJump

Implements a sophisticated jumping system with physics-based gravity calculation, variable jump height, wall sliding, and double jump mechanics.

Configuration

JumpHeight
float
Maximum height the player can reach when jumping
PressTimeToMaxJump
float
Time in seconds the jump button must be held to reach maximum height
DistanceToMaxHeight
float
Horizontal distance traveled to reach jump apex
SpeedHorizontal
float
Horizontal speed used for gravity calculations
WallSlideSpeed
float
Maximum falling speed when sliding down a wall
Animator
Animator
required
Reference to the player’s Animator for jump animations

Physics-Based Gravity

The system calculates gravity dynamically based on desired jump characteristics:
PlayerJump.cs:96-100
private void SetGravity()
{
    float gravity = 2 * JumpHeight * (SpeedHorizontal * SpeedHorizontal) / (DistanceToMaxHeight * DistanceToMaxHeight);
    rb2D.gravityScale = gravity / 9.81f;
}
This formula ensures consistent jump arcs regardless of platform or level design.

Jump Force Calculation

PlayerJump.cs:107-110
private float GetJumpForce()
{
    return 2 * JumpHeight * SpeedHorizontal / DistanceToMaxHeight;
}

Variable Jump Height

Players can control jump height by holding or releasing the jump button:
PlayerJump.cs:69-73
public void OnJumpFinished()
{
    float fractionOfTimePassed = 1 / Mathf.Clamp01((Time.time - jumpStartedTime) / PressTimeToMaxJump);
    rb2D.gravityScale *= fractionOfTimePassed;
}
Releasing the jump button early increases gravity, resulting in a shorter jump.

Jump Initiation

PlayerJump.cs:49-67
public void OnJumpStarted()
{
    if (isGrounded || isWallSliding)
    {
        SetGravity();
        Jump();

        jumpStartedTime = Time.time;

        doubleJumpDelay = Time.time + 0.2f;
        doubleJumpDone = false;
    }

    else if (!doubleJumpDone && (Time.time > doubleJumpDelay))
    {
        doubleJumpDone = true;
        Jump();
    }
}
Double jump has a 0.2-second delay after the initial jump to prevent accidental activation.

Wall Sliding

When touching a wall, the player’s fall speed is limited:
PlayerJump.cs:91-94
private void SetWallSlide()
{
    rb2D.linearVelocity = new Vector2(rb2D.linearVelocity.x, Mathf.Max(rb2D.linearVelocity.y, -WallSlideSpeed));
}

Jump Events

The system broadcasts jump events for other systems to react:
PlayerJump.cs:26
public static event Action OnJumpChange;
This event is invoked whenever the player jumps:
PlayerJump.cs:75-81
private void Jump()
{
    Vector2 velocity = new(rb2D.linearVelocity.x, GetJumpForce());
    rb2D.linearVelocity = velocity;

    OnJumpChange?.Invoke();
}

CollisionDetection

Detects ground and wall collisions using circular overlap checks at specific transform positions.

Configuration

groundLayer
LayerMask
required
Layer mask defining which layers count as ground or walls
groundCheckPoint
Transform
required
Transform position where ground detection is performed (typically below the player)
frontCheckPoint
Transform
required
Transform position where wall detection is performed (typically in front of the player)

Ground Detection

CollisionDetection.cs:29-33
private void CheckGrounded()
{
    var colliders = Physics2D.OverlapCircleAll(groundCheckPoint.position, checkRadius, groundLayer);
    isGrounded = (colliders.Length > 0);
}
The check radius is set to 0.15 units, providing reliable ground detection.

Wall Detection

CollisionDetection.cs:35-39
private void CheckFront()
{
    var colliders = Physics2D.OverlapCircleAll(frontCheckPoint.position, checkRadius, groundLayer);
    isTouchingFront = (colliders.Length > 0);
}

Public API

Other components query collision state through these methods:
CollisionDetection.cs:41-49
public bool IsTouchingFront()
{
    return isTouchingFront;
}

public bool IsGrounded()
{
    return isGrounded;
}
Collision checks run in FixedUpdate() to stay synchronized with Unity’s physics system.

PlayerDeath

Handles player death animations through an event-driven system.

Configuration

Animator
Animator
required
Reference to the player’s Animator for triggering death animations

Event Subscription

PlayerDeath.cs:7-15
private void OnEnable()
{
    FinishGame.OnDeathEvent += ShowDeathAnimation;
}

private void OnDisable()
{
    FinishGame.OnDeathEvent -= ShowDeathAnimation;
}

Death Animation

PlayerDeath.cs:17-20
private void ShowDeathAnimation()
{
    Animator.SetBool("Dead", true);
}
The death system uses events to decouple death logic from animation, allowing the game state to trigger death from various sources (falling, enemies, hazards) without tight coupling.

Integration Example

All four components work together on the player GameObject:
// Player GameObject has all components:
// - Rigidbody2D (for physics)
// - Animator (for animations)
// - PlayerInput (movement)
// - PlayerJump (jumping)
// - CollisionDetection (collision checks)
// - PlayerDeath (death handling)

// PlayerJump queries CollisionDetection:
private bool isWallSliding => collisionDetection.IsTouchingFront();
private bool isGrounded => collisionDetection.IsGrounded();

// Movement and jumping share the same Rigidbody2D
// Animations are coordinated through Animator parameters

Best Practices

  1. Tune jump parameters together - JumpHeight, DistanceToMaxHeight, and SpeedHorizontal are interdependent. Adjust them as a set for consistent feel.
  2. Layer mask setup - Ensure the groundLayer in CollisionDetection includes all surfaces the player should interact with.
  3. Check point positioning - Position groundCheckPoint slightly below the player’s collider and frontCheckPoint at mid-height on the player’s front edge.
  4. Physics settings - Use FixedUpdate() for all physics modifications to avoid frame-rate dependent behavior.
  5. Event cleanup - Always unsubscribe from events in OnDisable() to prevent memory leaks.

Build docs developers (and LLMs) love