Game Development Jan 1, 2026

Lesson 5: Player Controller & Movement Systems

Build responsive player movement with C++ and Blueprint, implement camera controls and input handling, create character controller systems, and add movement animations for multiplayer battle royale.

By GamineAI Team

Lesson 5: Player Controller & Movement Systems

Welcome to Core Gameplay Development!

Excellent work completing Phase 1! Now we're diving into Phase 2: Core Development. In this lesson, you'll build the foundation of player interaction - responsive movement, smooth camera controls, and polished character controller systems that feel great in multiplayer.

Learning Objectives

By the end of this lesson, you will:

  • ✅ Build responsive player movement with C++ and Blueprint
  • ✅ Implement camera controls and input handling
  • ✅ Create character controller systems with proper replication
  • ✅ Add movement animations and states
  • ✅ Implement jump, sprint, and crouch mechanics
  • ✅ Test movement in multiplayer environment
  • ✅ Optimize movement for network performance

Part 1: Character Controller Setup

Step 1: Enhance BRCharacter Class

1.1 Update Character Header

Open BRCharacter.h and add movement properties:

UCLASS()
class MULTIPLAYERBATTLEROYALE_API ABRCharacter : public ACharacter
{
    GENERATED_BODY()

public:
    ABRCharacter(const FObjectInitializer& ObjectInitializer);

protected:
    virtual void BeginPlay() override;
    virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
    virtual void Tick(float DeltaTime) override;

    // Movement properties
    UPROPERTY(Replicated, BlueprintReadOnly, Category = "Movement")
    float MaxWalkSpeed = 600.0f;

    UPROPERTY(Replicated, BlueprintReadOnly, Category = "Movement")
    float MaxSprintSpeed = 900.0f;

    UPROPERTY(Replicated, BlueprintReadOnly, Category = "Movement")
    float MaxCrouchSpeed = 300.0f;

    UPROPERTY(Replicated, BlueprintReadOnly, Category = "Movement")
    bool bIsSprinting = false;

    UPROPERTY(Replicated, BlueprintReadOnly, Category = "Movement")
    bool bIsCrouching = false;

    // Camera properties
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
    class USpringArmComponent* SpringArm;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
    class UCameraComponent* Camera;

    // Input functions
    void MoveForward(float Value);
    void MoveRight(float Value);
    void Turn(float Value);
    void LookUp(float Value);
    void StartJump();
    void StopJump();
    void StartSprint();
    void StopSprint();
    void StartCrouch();
    void StopCrouch();

    // Server RPCs
    UFUNCTION(Server, Reliable, WithValidation)
    void ServerStartSprint();

    UFUNCTION(Server, Reliable, WithValidation)
    void ServerStopSprint();

    UFUNCTION(Server, Reliable, WithValidation)
    void ServerStartCrouch();

    UFUNCTION(Server, Reliable, WithValidation)
    void ServerStopCrouch();

    // Replication
    virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

    // Movement update
    void UpdateMovementSpeed();
};

1.2 Implement Character Class

Open BRCharacter.cpp:

#include "BRCharacter.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Net/UnrealNetwork.h"

ABRCharacter::ABRCharacter(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
    PrimaryActorTick.bCanEverTick = true;

    // Enable replication
    bReplicates = true;
    SetReplicatingMovement(true);

    // Set default values
    MaxHealth = 100.0f;
    Health = MaxHealth;
    MaxWalkSpeed = 600.0f;
    MaxSprintSpeed = 900.0f;
    MaxCrouchSpeed = 300.0f;
    bIsSprinting = false;
    bIsCrouching = false;

    // Create spring arm component
    SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
    SpringArm->SetupAttachment(RootComponent);
    SpringArm->TargetArmLength = 300.0f;
    SpringArm->bUsePawnControlRotation = true;
    SpringArm->bInheritPitch = true;
    SpringArm->bInheritYaw = true;
    SpringArm->bInheritRoll = false;

    // Create camera component
    Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
    Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);
    Camera->bUsePawnControlRotation = false;

    // Configure character movement
    GetCharacterMovement()->MaxWalkSpeed = MaxWalkSpeed;
    GetCharacterMovement()->JumpZVelocity = 600.0f;
    GetCharacterMovement()->AirControl = 0.35f;
    GetCharacterMovement()->bOrientRotationToMovement = true;
    GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f);
}

void ABRCharacter::BeginPlay()
{
    Super::BeginPlay();
    UpdateMovementSpeed();
}

void ABRCharacter::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
}

void ABRCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);

    // Movement bindings
    PlayerInputComponent->BindAxis("MoveForward", this, &ABRCharacter::MoveForward);
    PlayerInputComponent->BindAxis("MoveRight", this, &ABRCharacter::MoveRight);
    PlayerInputComponent->BindAxis("Turn", this, &ABRCharacter::Turn);
    PlayerInputComponent->BindAxis("LookUp", this, &ABRCharacter::LookUp);

    // Action bindings
    PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ABRCharacter::StartJump);
    PlayerInputComponent->BindAction("Jump", IE_Released, this, &ABRCharacter::StopJump);
    PlayerInputComponent->BindAction("Sprint", IE_Pressed, this, &ABRCharacter::StartSprint);
    PlayerInputComponent->BindAction("Sprint", IE_Released, this, &ABRCharacter::StopSprint);
    PlayerInputComponent->BindAction("Crouch", IE_Pressed, this, &ABRCharacter::StartCrouch);
    PlayerInputComponent->BindAction("Crouch", IE_Released, this, &ABRCharacter::StopCrouch);
}

void ABRCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    DOREPLIFETIME(ABRCharacter, MaxWalkSpeed);
    DOREPLIFETIME(ABRCharacter, MaxSprintSpeed);
    DOREPLIFETIME(ABRCharacter, MaxCrouchSpeed);
    DOREPLIFETIME(ABRCharacter, bIsSprinting);
    DOREPLIFETIME(ABRCharacter, bIsCrouching);
}

// Movement functions
void ABRCharacter::MoveForward(float Value)
{
    if (Controller != nullptr && Value != 0.0f)
    {
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);
        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
        AddMovementInput(Direction, Value);
    }
}

void ABRCharacter::MoveRight(float Value)
{
    if (Controller != nullptr && Value != 0.0f)
    {
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);
        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
        AddMovementInput(Direction, Value);
    }
}

void ABRCharacter::Turn(float Value)
{
    AddControllerYawInput(Value);
}

void ABRCharacter::LookUp(float Value)
{
    AddControllerPitchInput(Value);
}

void ABRCharacter::StartJump()
{
    bPressedJump = true;
}

void ABRCharacter::StopJump()
{
    bPressedJump = false;
}

void ABRCharacter::StartSprint()
{
    if (HasAuthority())
    {
        bIsSprinting = true;
        UpdateMovementSpeed();
    }
    else
    {
        ServerStartSprint();
    }
}

void ABRCharacter::StopSprint()
{
    if (HasAuthority())
    {
        bIsSprinting = false;
        UpdateMovementSpeed();
    }
    else
    {
        ServerStopSprint();
    }
}

void ABRCharacter::StartCrouch()
{
    if (HasAuthority())
    {
        bIsCrouching = true;
        Crouch();
        UpdateMovementSpeed();
    }
    else
    {
        ServerStartCrouch();
    }
}

void ABRCharacter::StopCrouch()
{
    if (HasAuthority())
    {
        bIsCrouching = false;
        UnCrouch();
        UpdateMovementSpeed();
    }
    else
    {
        ServerStopCrouch();
    }
}

void ABRCharacter::UpdateMovementSpeed()
{
    if (GetCharacterMovement())
    {
        if (bIsCrouching)
        {
            GetCharacterMovement()->MaxWalkSpeed = MaxCrouchSpeed;
        }
        else if (bIsSprinting)
        {
            GetCharacterMovement()->MaxWalkSpeed = MaxSprintSpeed;
        }
        else
        {
            GetCharacterMovement()->MaxWalkSpeed = MaxWalkSpeed;
        }
    }
}

// Server RPCs
void ABRCharacter::ServerStartSprint_Implementation()
{
    bIsSprinting = true;
    UpdateMovementSpeed();
}

bool ABRCharacter::ServerStartSprint_Validate()
{
    return true;
}

void ABRCharacter::ServerStopSprint_Implementation()
{
    bIsSprinting = false;
    UpdateMovementSpeed();
}

bool ABRCharacter::ServerStopSprint_Validate()
{
    return true;
}

void ABRCharacter::ServerStartCrouch_Implementation()
{
    bIsCrouching = true;
    Crouch();
    UpdateMovementSpeed();
}

bool ABRCharacter::ServerStartCrouch_Validate()
{
    return true;
}

void ABRCharacter::ServerStopCrouch_Implementation()
{
    bIsCrouching = false;
    UnCrouch();
    UpdateMovementSpeed();
}

bool ABRCharacter::ServerStopCrouch_Validate()
{
    return true;
}

Part 2: Input Mapping

Step 2: Configure Input Actions

2.1 Create Input Actions

  1. In Unreal Editor, go to EditProject Settings

  2. Navigate to EngineInput

  3. Create these Action Mappings:

    • Jump → Space Bar
    • Sprint → Left Shift
    • Crouch → Left Control
  4. Create these Axis Mappings:

    • MoveForward → W (Scale: 1.0), S (Scale: -1.0)
    • MoveRight → D (Scale: 1.0), A (Scale: -1.0)
    • Turn → Mouse X (Scale: 1.0)
    • LookUp → Mouse Y (Scale: -1.0)

2.2 Test Input

  • Play in Editor and test all movement inputs
  • Verify camera rotation works smoothly
  • Test sprint and crouch toggles

Part 3: Movement Animation Setup

Step 3: Create Animation Blueprint

3.1 Create Animation Blueprint

  1. Right-click in Content Browser → AnimationAnimation Blueprint
  2. Select AnimInstance as parent class
  3. Select your character's skeleton
  4. Name it: ABP_BRCharacter

3.2 Set Up State Machine

Create these states in the Animation Blueprint:

Idle State:

  • Default state
  • Plays idle animation
  • Transitions to Walk when speed > 0

Walk State:

  • Plays walk animation
  • Transitions to Idle when speed = 0
  • Transitions to Sprint when sprinting

Sprint State:

  • Plays sprint animation
  • Transitions to Walk when not sprinting

Jump State:

  • Plays jump animation
  • Transitions to Idle when grounded

Crouch State:

  • Plays crouch animation
  • Transitions to Idle when standing

3.3 Blend Spaces

Create blend spaces for smooth transitions:

  • Walk Blend Space: Forward/Backward, Left/Right
  • Sprint Blend Space: Forward/Backward, Left/Right

Part 4: Network Optimization

Step 4: Optimize for Multiplayer

4.1 Movement Replication Settings

In BRCharacter.cpp constructor:

// Optimize movement replication
GetCharacterMovement()->SetIsReplicated(true);
GetCharacterMovement()->SetNetUpdateFrequency(30.0f); // 30 Hz for movement
GetCharacterMovement()->SetNetServerMoveTickEnabled(true);

4.2 Client Prediction

Unreal handles client prediction automatically, but ensure:

  • Movement is server-authoritative
  • RPCs validate on server
  • Smooth interpolation enabled

4.3 Network Smoothing

Enable network smoothing in Character Movement Component:

  • Network Smoothing: Enabled
  • Network Smoothing Server Max Distance: 200.0
  • Network Smoothing Server Max Distance (Debug): 500.0

Part 5: Testing & Polish

Step 5: Test Movement

5.1 Single Player Testing

  1. Test all movement inputs
  2. Verify camera controls feel smooth
  3. Test sprint and crouch mechanics
  4. Check animation transitions

5.2 Multiplayer Testing

  1. Launch Play in Editor with 2+ players
  2. Test movement replication
  3. Verify other players move smoothly
  4. Test sprint/crouch replication
  5. Check for lag or jitter

5.3 Performance Testing

  1. Monitor frame rate during movement
  2. Check network bandwidth usage
  3. Verify no performance drops with multiple players
  4. Test with 50+ simulated players if possible

Mini Challenge: Create Advanced Movement System

Task: Enhance the movement system with advanced features.

Requirements:

  1. Add sliding mechanic (slide when sprinting + crouch)
  2. Add wall-running or wall-jumping
  3. Add stamina system for sprinting
  4. Add movement speed modifiers (injured, carrying items)
  5. Create smooth camera transitions
  6. Add footstep sound effects based on surface
  7. Test all features in multiplayer

Deliverables:

  • Enhanced character controller with new mechanics
  • Stamina system implementation
  • Sound integration
  • Multiplayer testing results

Pro Tips:

  • Use Blueprint for rapid iteration on movement feel
  • Test movement feel extensively before moving on
  • Get feedback from playtesters early
  • Optimize network updates for large player counts
  • Use animation montages for special moves
  • Consider adding movement abilities (dash, roll, etc.)

Common Mistakes to Avoid

1. Not Testing in Multiplayer

  • ❌ Only testing single-player movement
  • ✅ Always test movement with multiple clients

2. Ignoring Network Performance

  • ❌ Not optimizing replication frequency
  • ✅ Set appropriate update frequencies for movement

3. Poor Input Handling

  • ❌ Not validating input on server
  • ✅ Always validate movement input server-side

4. Missing Animation States

  • ❌ Not handling all movement states
  • ✅ Create complete animation state machine

5. Camera Issues

  • ❌ Camera lag or jitter
  • ✅ Use smooth camera interpolation

Troubleshooting

Q: Movement feels laggy in multiplayer A: Check network update frequency, enable network smoothing, and verify server tick rate.

Q: Sprint doesn't replicate to other clients A: Ensure sprint state is replicated and RPCs are called correctly.

Q: Camera rotation is too fast/slow A: Adjust mouse sensitivity in input settings or add sensitivity multiplier.

Q: Animations don't play correctly A: Check animation blueprint state machine transitions and blend spaces.

Q: Character falls through ground A: Check collision settings, ensure capsule component is properly configured.

Key Takeaways

Character Controller: Build responsive movement with C++ and Blueprint ✅ Input System: Configure input mappings for all movement actions ✅ Camera Controls: Implement smooth camera rotation and positioning ✅ Movement States: Create sprint, crouch, and jump mechanics ✅ Animation System: Set up animation blueprints and state machines ✅ Network Optimization: Optimize movement replication for multiplayer ✅ Testing: Test extensively in single-player and multiplayer

What's Next?

In Lesson 6: Multiplayer Networking & Replication, we'll:

  • Deep dive into Unreal's replication system
  • Implement advanced replication techniques
  • Create reliable RPC systems
  • Optimize network bandwidth
  • Handle lag compensation and prediction
  • Build robust multiplayer synchronization

Get ready to master the networking systems that make multiplayer games work!

Additional Resources


Ready to continue? Move on to Lesson 6 to master multiplayer networking!