Gameplay Framework
Unreal's gameplay framework classes — Actor, Pawn, Character, controllers, GameMode, GameState, PlayerState, components and subsystems.
23 questions
JuniorTheoryVery commonWhat is the difference between an AActor and an APawn in Unreal Engine?
What is the difference between an AActor and an APawn in Unreal Engine?
Every APawn is an AActor, but a Pawn adds the ability to be possessed by an AController. A Pawn represents an agent — player or AI — that receives input or AI control. A plain Actor is any world object with no possession concept.
Common mistakes
- ✗Thinking a Pawn is unrelated to Actor —
APawnderives fromAActor - ✗Believing only Pawns can have meshes or components — any Actor can
- ✗Assuming a Pawn receives input on its own without being possessed
Follow-up questions
- →What happens to a Pawn's input when its controller unpossesses it?
- →Can two controllers possess the same Pawn at the same time?
JuniorTheoryVery commonWhat is an AActor in Unreal Engine and what makes it special?
What is an AActor in Unreal Engine and what makes it special?
An AActor is any UObject that can be placed or spawned into a level. It can hold components, has a transform via its root component, ticks, replicates, and runs the BeginPlay/EndPlay lifecycle. It is the unit of gameplay content in a world.
Common mistakes
- ✗Thinking an Actor must always have a visible mesh — many Actors are invisible logic holders
- ✗Confusing the Actor with its root component — the Actor's transform comes from the root component
- ✗Assuming Actors only exist on the server; they are instantiated on clients too
Follow-up questions
- →How does an Actor get a world-space transform if components, not the Actor, hold transforms?
- →What is the difference between
Destroy()and letting an Actor go out of scope?
JuniorTheoryVery commonWhat is the difference between an APawn and an ACharacter in Unreal Engine?
What is the difference between an APawn and an ACharacter in Unreal Engine?
ACharacter is an APawn specialized for humanoid bipeds. It ships with a UCapsuleComponent, a skeletal mesh, and a UCharacterMovementComponent giving replicated walking, jumping, and falling. A bare Pawn has none and needs custom movement.
Common mistakes
- ✗Reversing the hierarchy —
ACharacterderives fromAPawn, not the other way round - ✗Thinking any Pawn gets walking/jumping for free — only
ACharacterdoes - ✗Using
ACharacterfor non-humanoid agents like vehicles or turrets
Follow-up questions
- →When would you use a bare
APawninstead of anACharacter? - →What does
UCharacterMovementComponentreplicate, and how?
JuniorTheoryCommonWhat is a UActorComponent in Unreal Engine and what is it used for?
What is a UActorComponent in Unreal Engine and what is it used for?
A UActorComponent is a reusable piece of behaviour or data owned by an AActor. It encapsulates one responsibility — movement, health, audio — so functionality is composed onto Actors. USceneComponent, a subclass, adds a transform.
Common mistakes
- ✗Thinking every component has a transform — only
USceneComponentand its subclasses do - ✗Believing a component can exist in a level without an owning Actor
- ✗Assuming components of the same class share state across Actor instances
Follow-up questions
- →What is the difference between
UActorComponentandUSceneComponent? - →Why is the root component special compared to other scene components?
JuniorTheoryCommonAPlayerController vs APlayerState — what data goes where in Unreal Engine?
APlayerController vs APlayerState — what data goes where in Unreal Engine?
The APlayerController handles a player's input, camera, and HUD and is mostly server-and-owner local. The APlayerState holds replicated per-player data — score, name, team — seen by all clients. Controller for control, PlayerState for stats.
Common mistakes
- ✗Putting replicated score on the controller — other clients can't see it there
- ✗Putting input or HUD logic on the PlayerState instead of the controller
- ✗Thinking the controller is freely replicated to all clients like the PlayerState
Follow-up questions
- →Why can't a remote client read another player's
APlayerController? - →Which object would you query to build a scoreboard, and why?
JuniorCodeCommonImplement a basic health UActorComponent in C++ for Unreal Engine.
Implement a basic health UActorComponent in C++ for Unreal Engine.
Derive from UActorComponent, store MaxHealth and CurrentHealth, expose ApplyDamage/Heal that clamp the value, and broadcast a death event when health hits zero. A multicast delegate lets the owning Actor react without tight coupling.
Common mistakes
- ✗Not clamping health, letting it go negative or above
MaxHealth - ✗Deriving the health holder from
AActorinstead ofUActorComponent - ✗Destroying the owner directly inside the component instead of broadcasting an event
Follow-up questions
- →How would you replicate
CurrentHealthso all clients see the health bar? - →Why broadcast a delegate instead of calling the owner's death function directly?
JuniorTheoryCommonWhat is the APlayerController responsible for in Unreal Engine?
What is the APlayerController responsible for in Unreal Engine?
An APlayerController represents a human player's intent. It receives input, possesses a Pawn, owns the camera and HUD, and is the player's network connection endpoint. It persists across Pawn deaths, so respawn logic and per-player UI live there.
Common mistakes
- ✗Confusing the controller with the Pawn — the controller is not the visible body
- ✗Putting match-wide state on the controller instead of GameState
- ✗Assuming the controller dies with the Pawn — it persists across respawns
Follow-up questions
- →Why is the
APlayerControllera better place for the HUD than the Pawn? - →How does an
AAIControllerdiffer from anAPlayerController?
MiddleTheoryCommonWhen is component composition better than inheritance in Unreal Engine?
When is component composition better than inheritance in Unreal Engine?
Prefer composition when a behaviour — health, inventory, targeting — is needed by unrelated Actor types. A UActorComponent is reusable across any Actor, avoiding deep brittle hierarchies. Inheritance fits only true "is-a" relationships.
Common mistakes
- ✗Building a deep Actor hierarchy to share behaviour instead of extracting components
- ✗Thinking composition is only about performance, not reuse and decoupling
- ✗Using inheritance for orthogonal behaviours that aren't a true is-a relationship
Follow-up questions
- →How can a component expose events to its owning Actor without tight coupling?
- →What problems appear when many Actor types inherit from one giant base class?
MiddleCodeCommonImplement damage application on collision between two Actors in Unreal Engine.
Implement damage application on collision between two Actors in Unreal Engine.
Bind a handler to the collision component's OnComponentHit or OnComponentBeginOverlap delegate, then route damage through UGameplayStatics::ApplyDamage. That fires the target's TakeDamage, keeping damage logic on the receiver.
Common mistakes
- ✗Polling for overlaps in
Tickinstead of binding the collision delegate - ✗Subtracting health directly instead of routing through
ApplyDamage/TakeDamage - ✗Forgetting to verify the hit Actor is not the projectile's own owner
Follow-up questions
- →What is the difference between a Hit event and an Overlap event?
- →Why route damage through
ApplyDamageinstead of a custom interface call?
MiddleDebuggingCommonFind and fix the bug in this ACharacter movement input code in Unreal Engine.
Find and fix the bug in this ACharacter movement input code in Unreal Engine.
The handler calls AddMovementInput with a world-axis vector that ignores the controller's yaw, so the character always moves along world X regardless of where it faces. Fix it by deriving the direction from the control rotation's forward vector.
Common mistakes
- ✗Using a fixed world-axis vector instead of a direction relative to control rotation
- ✗Forgetting to zero the pitch/roll so the character isn't pushed into the ground
- ✗Confusing the Actor's rotation with the controller's control rotation
Follow-up questions
- →Why use the control rotation instead of the Actor's rotation for movement?
- →How would you add strafing (right/left) movement correctly?
MiddleTheoryCommonWhat kind of logic belongs in the AGameModeBase in Unreal Engine?
What kind of logic belongs in the AGameModeBase in Unreal Engine?
The GameMode owns the rules of play: which classes to spawn, where players spawn, win/loss conditions, score authority, and match flow. It exists only on the server, so it is the authoritative place for rule decisions — never for replicated UI state.
Common mistakes
- ✗Storing replicated or per-player state in the GameMode — clients can't see it
- ✗Putting input/camera logic in the GameMode instead of the PlayerController
- ✗Expecting the GameMode to exist on clients for rule reads
Follow-up questions
- →Where should match-wide state visible to clients live instead?
- →What is the difference between
AGameModeBaseandAGameMode?
MiddleTheoryCommonWhy does the AGameModeBase exist only on the server in Unreal Engine?
Why does the AGameModeBase exist only on the server in Unreal Engine?
The GameMode is the authoritative rules arbiter; if clients had their own copy they could decide outcomes locally and cheat. Server-only keeps one source of truth for rules. Shared state clients must see lives in the replicated AGameStateBase.
Common mistakes
- ✗Trying to access the GameMode pointer on a client — it is always null there
- ✗Thinking the GameMode replicates a read-only copy to clients
- ✗Confusing the server-only GameMode with the replicated GameState
Follow-up questions
- →How can a client trigger a rule decision if it has no GameMode?
- →What happens if you call
GetGameMode()on a client?
MiddleTheoryCommonWhat kind of data belongs in the AGameStateBase in Unreal Engine?
What kind of data belongs in the AGameStateBase in Unreal Engine?
The GameState holds match-wide state every client must see: round timer, team scores, current phase, and the list of connected APlayerStates. It is replicated to all clients, the shared observable counterpart to the server-only GameMode.
Common mistakes
- ✗Putting game rules in the GameState — rules belong in the server-only GameMode
- ✗Storing per-player data in the GameState instead of the PlayerState
- ✗Thinking the GameState is server-only like the GameMode
Follow-up questions
- →How does the GameState relate to the GameMode at runtime?
- →How would you replicate a changing round timer through the GameState?
MiddleTheoryCommonWhat kind of data belongs in the APlayerState in Unreal Engine?
What kind of data belongs in the APlayerState in Unreal Engine?
The PlayerState holds per-player data that must survive the Pawn and be visible to all clients: name, score, kills, team, ping. One PlayerState exists per player and is replicated, so other clients can read each player's stats.
Common mistakes
- ✗Storing match-wide state in the PlayerState instead of the GameState
- ✗Putting persistent per-player stats on the Pawn, which dies on respawn
- ✗Thinking the PlayerState is local-only and not replicated to other clients
Follow-up questions
- →Why is the PlayerState a better home for the score than the Pawn?
- →How do you find a specific player's PlayerState from the GameState?
MiddleCodeCommonWrite a C++ function that spawns an AActor at a given location in Unreal Engine.
Write a C++ function that spawns an AActor at a given location in Unreal Engine.
Call GetWorld()->SpawnActor<T>(Class, Transform, Params). Build an FTransform from the location, pass an FActorSpawnParameters, and always null-check the returned pointer — spawning can fail if the collision test rejects the location.
Common mistakes
- ✗Using
NewObjectornewfor Actors — Actors must be created viaSpawnActor - ✗Not null-checking the result — spawn fails when collision handling rejects the spot
- ✗Forgetting
BeginPlayruns as part of spawning, not before it
Follow-up questions
- →What does
ESpawnActorCollisionHandlingMethodcontrol during spawning? - →How does deferred spawning let you set properties before
BeginPlay?
SeniorTheoryCommonBeginPlay fires late, twice, or never across editor, PIE, and replicated spawns — why?
BeginPlay fires late, twice, or never across editor, PIE, and replicated spawns — why?
BeginPlay runs only after the actor and its components are fully spawned and registered and the level has started. Runtime SpawnActor fires it inside the spawn call; level actors get it when the world begins play; on a client it waits for the actor channel and initial replicated state. The constructor and PostInitializeComponents run earlier — never read replicated data there.
Common mistakes
- ✗Reading replicated properties in the constructor or
PostInitializeComponentsinstead ofBeginPlay - ✗Assuming a client's
BeginPlayalready has all initial replicated state available - ✗Expecting the same
BeginPlayordering for level-placed actors and runtime-spawned ones
Follow-up questions
- →Where would you put logic that must run after initial replicated state arrives on a client?
- →How does deferred spawning with
SpawnActorDeferredchange whenBeginPlayfires?
MiddleCodeOccasionalImplement a generic ability cooldown system in C++ for Unreal Engine.
Implement a generic ability cooldown system in C++ for Unreal Engine.
Record the world time when an ability fires, then gate the next use by checking elapsed time against the cooldown. Use GetWorld()->GetTimeSeconds() for the timestamp — comparing times avoids a Tick counter and survives variable frame rates.
Common mistakes
- ✗Counting down in
Tickinstead of comparing timestamps — drifts with frame rate - ✗Using frame counts instead of seconds — breaks at variable FPS
- ✗Forgetting the first use should pass when no timestamp has been recorded yet
Follow-up questions
- →How would you expose remaining cooldown time to drive a UI radial fill?
- →How would you pause cooldowns when the game is paused?
MiddleTheoryOccasionalWhat are Subsystems in Unreal Engine and what kinds of them exist?
What are Subsystems in Unreal Engine and what kinds of them exist?
Subsystems are engine-managed singletons with an automatic lifecycle. Kinds are scoped: UEngineSubsystem, UGameInstanceSubsystem, UWorldSubsystem, ULocalPlayerSubsystem. Each is created and destroyed with its owning scope.
Common mistakes
- ✗Hand-rolling a static singleton instead of using a subsystem with managed lifecycle
- ✗Picking the wrong scope — e.g.
UEngineSubsystemfor world-specific state - ✗Thinking subsystems are Actor components attached in the level
Follow-up questions
- →When would you choose a
UWorldSubsystemover aUGameInstanceSubsystem? - →How do you retrieve a subsystem instance from C++ or Blueprint?
SeniorDesignOccasionalYou are building a reusable cooldown component for player abilities in a competitive multiplayer game. When a player activates an ability it must enter a cooldown and refuse re-activation until it elapses; every connected client must show an accurate remaining-time bar, and the cooldown must survive variable network latency. Cheating clients must not be able to fire faster than the cooldown allows. Describe the component's design and replication strategy. Requirements: the authority over whether an ability may fire is unambiguous; clients display correct remaining time without trusting their own clock drift; the UI stays responsive despite latency; and the bandwidth spent on the cooldown does not grow with frame rate.
You are building a reusable cooldown component for player abilities in a competitive multiplayer game. When a player activates an ability it must enter a cooldown and refuse re-activation until it elapses; every connected client must show an accurate remaining-time bar, and the cooldown must survive variable network latency. Cheating clients must not be able to fire faster than the cooldown allows. Describe the component's design and replication strategy. Requirements: the authority over whether an ability may fire is unambiguous; clients display correct remaining time without trusting their own clock drift; the UI stays responsive despite latency; and the bandwidth spent on the cooldown does not grow with frame rate.
The server is the only authority: it stores the cooldown end time against the replicated GameState's server clock, validates every activation request against it, and rejects calls that arrive too early. Replicate the end timestamp, not a ticking counter — clients derive remaining time locally from the synced clock. The client may predict the cooldown for UI responsiveness, but the server's value stays authoritative and corrects any mismatch.
Common mistakes
- ✗Trusting a client-reported "ready" flag instead of validating against server time
- ✗Replicating a ticking counter every frame instead of a single end timestamp
- ✗Storing the cooldown on the GameMode, which never replicates to clients
Follow-up questions
- →How does client-side prediction coexist with the server's authoritative cooldown value?
- →Why replicate an end timestamp rather than the remaining-seconds value directly?
SeniorDesignOccasionalYou are designing the damage pipeline for a competitive shooter where players run a modifiable client. When a player shoots, damage must be applied to the target, but a tampered client must not be able to inflate the damage it deals, hit targets it has no line of sight to, or fire faster than its weapon permits. The pipeline must still feel responsive to honest players on a high-latency connection. Describe how you would structure the flow between client and server. Requirements: the client never gets to declare the damage value applied; the server independently re-checks that the shot is legitimate before applying anything; the target's health is owned somewhere a client cannot directly write; and honest clients still get responsive feedback.
You are designing the damage pipeline for a competitive shooter where players run a modifiable client. When a player shoots, damage must be applied to the target, but a tampered client must not be able to inflate the damage it deals, hit targets it has no line of sight to, or fire faster than its weapon permits. The pipeline must still feel responsive to honest players on a high-latency connection. Describe how you would structure the flow between client and server. Requirements: the client never gets to declare the damage value applied; the server independently re-checks that the shot is legitimate before applying anything; the target's health is owned somewhere a client cannot directly write; and honest clients still get responsive feedback.
Never let a client report damage as a number — it would just send a huge value. The client sends only intent ("I fired at this target") via a server RPC; the server validates everything — weapon owned and off cooldown, target in range and line-of-sight on the server's world — then computes and applies the damage. Health lives in a server-owned replicated property; clients receive results, never author them.
Common mistakes
- ✗Accepting a damage amount from the client instead of only an intent to act
- ✗Believing a
ReliableRPC is also a trusted or tamper-proof RPC - ✗Letting clients write to their own health rather than receiving a replicated result
Follow-up questions
- →How do you validate a hit fairly when the client and server disagree on the target's position?
- →What server-side checks catch a client firing faster than its weapon allows?
SeniorTheoryOccasionalPlayer score vanishes on respawn or seamless travel — how do you keep APlayerState data?
Player score vanishes on respawn or seamless travel — how do you keep APlayerState data?
APlayerState outlives the Pawn — the Pawn is destroyed and re-spawned on death, but the same PlayerState persists, owned by the APlayerController. So per-player stats like score belong on the PlayerState, not the Pawn. Across seamless travel the engine creates fresh PlayerStates; data you must keep has to be carried forward by overriding CopyProperties, which the engine calls to migrate values onto the new instance.
Common mistakes
- ✗Storing score or other persistent stats on the Pawn, which dies on respawn
- ✗Assuming PlayerState objects survive seamless travel without
CopyProperties - ✗Confusing the Pawn's lifetime with the PlayerState's controller-bound lifetime
Follow-up questions
- →Why does the PlayerState survive a Pawn respawn but not always a seamless travel?
- →What belongs on the PlayerController instead of the PlayerState across travel?
SeniorDebuggingOccasionalA value set on the GameMode never reaches clients — how do you trace the authority flow?
A value set on the GameMode never reaches clients — how do you trace the authority flow?
AGameModeBase exists only on the server, so any property set on it is invisible to clients by design — it never replicates. The fix is to move shared data to AGameStateBase, which replicates to everyone, marking the property Replicated with an entry in GetLifetimeReplicatedProps. Trace it by confirming the value is written on the authority and that clients read it via GetGameState(), not GetGameMode().
Common mistakes
- ✗Putting client-visible state on the GameMode instead of the replicated GameState
- ✗Assuming the GameMode replicates a copy to clients if its property is marked
Replicated - ✗Forgetting to register the GameState property in
GetLifetimeReplicatedProps
Follow-up questions
- →How would you push a one-off event from the GameMode to all clients without a persistent property?
- →Why does
GetGameMode()return null on a client, and what should you call instead?
SeniorPerformanceOccasional5,000 components all tick every frame — how do you diagnose and cut the cost?
5,000 components all tick every frame — how do you diagnose and cut the cost?
Profile first with stat game and Unreal Insights to confirm tick is the hot path. Then cut it: disable tick where unneeded (bCanEverTick = false) and raise TickInterval for slow logic. The structural fix is to stop per-component ticks entirely — move shared logic into one subsystem or manager that iterates the set once per frame, turning thousands of virtual calls into a single tight loop.
Common mistakes
- ✗Optimizing tick before profiling to confirm tick is actually the bottleneck
- ✗Leaving
bCanEverTicktrue on components that have no per-frame work - ✗Keeping thousands of per-component ticks instead of one subsystem-driven loop
Follow-up questions
- →How does a tick group or tick dependency change when your logic runs within a frame?
- →What overhead does a per-component tick carry beyond the work in the tick body itself?