Open the Third Person template and you get a Character Blueprint and a GameMode. Nothing tells you that the GameMode quietly spawns three more classes for you, or that each one has a job. So the score goes into the Character, the respawn goes into the Level Blueprint, and the first time the player dies, the score is back to zero. This post is the map that ends the guessing.
The five classes in one sentence each
- GameMode — the rules. Who spawns where, when a round ends, what happens on death. One per level, and it exists only on the server.
- GameState — the public scoreboard. Match timer, team score, the list of players. Everyone can read it.
- PlayerController — the player's will. Input that isn't about the body, the HUD, the camera, menus. It survives when the Pawn dies.
- PlayerState — the player's record. Name, kills, score, team. Also survives death, and other players can see it.
- Pawn / Character — the body. Movement, health, the mesh that gets shot. Expendable.
Everything else follows from one question: should this survive the body dying, and does anyone else need to see it?
Where does the score go?
Per-player score goes on the PlayerState. Team or match score goes on the GameState.
The typical beginner setup puts a Points variable on the Character. Then the Character dies, gets destroyed, a fresh one spawns, and its Points start at 0. The PlayerState doesn't have that problem: it belongs to the Controller, not the Pawn, so it's still there after the respawn.
CB0
Reading it back in any Blueprint: Get Player State → Cast To BP_MyPlayerState → Points. (Don't call it Score: PlayerState already has a built-in, Blueprint-read-only Score, and the name is taken.) The GameState is one Get Game State away, and it already holds every player's PlayerState in its Player Array, which is what a scoreboard loops over.
Where does the health bar go?
Two answers, because there are two things here.
The health value lives on the Character. It is the body's health, and when the body is gone, there's nothing left to be healthy. Health that should persist through respawn (say, a max-health upgrade) is a record, so that part goes on the PlayerState.
The health bar widget gets created by the PlayerController. Event BeginPlay → Create Widget → Add to Viewport. In multiplayer, put an Is Local Controller check in front of it: the server holds a copy of every player's controller, and you don't want it building widgets for all of them.
The catch: after a respawn the widget still points at the dead Character. When the new Pawn is possessed, hand the widget the new reference. In a single-player game, Event On Possess in the PlayerController gives you the possessed Pawn directly (it only runs on the server, so multiplayer needs a client-side hook, which belongs in a later post on replication). Listening for health changes with an Event Dispatcher instead of a binding keeps that hand-off to one node.
Where does the respawn go?
In the GameMode. Death is a rules question: how long to wait, where to spawn, whether you're out of lives.
CB1
Restart Player takes the Controller and spawns a new Default Pawn Class at a PlayerStart. Destroy the old Character first. If the Controller still has a Pawn, Restart Player won't spawn a new one. The Controller and its PlayerState stay untouched the whole time, which is exactly why the score survives.
Don't let the Character respawn itself. It's about to be destroyed, and a Blueprint that destroys itself halfway through its own logic is a classic source of Accessed None errors (see common Blueprint errors).
And the GameInstance?
The GameInstance isn't one of the five, and that's the point. Everything above is rebuilt when you call Open Level: GameMode, GameState, Controller, PlayerState, Pawn. The GameInstance is created when the game starts and lives until it closes.
Use it for what has to cross a level change: the selected character, the save slot in use, settings. Keep it small. It's a hand-off point, not a second GameMode. Anything that has to survive a restart of the game belongs in a SaveGame, not here (see save/load).
Single-player? It still matters
"I'm not making a multiplayer game" is the usual reason to ignore all this. Two problems with that. First, the score-reset bug above has nothing to do with networking. Second, Get Game Mode returns None on a client. If you ever add co-op, every widget that reads the score from the GameMode breaks. GameState and PlayerState replicate to every client; set your variables to Replicated and their values come along. That's what those two classes are for.
Common pitfalls
- Your GameMode changes do nothing. Check the map's
World Settings→GameMode Override. It wins over the default inProject Settings→Maps & Modes, and it's easy to forget you set it. - You spawn as the wrong Pawn. Either
Default Pawn Classin the GameMode is wrong, or there's a Character placed in the level withAuto Possess Playerset toPlayer 0, which takes over before the GameMode spawns yours. - Mixing Base and non-Base classes.
GameModeBasegoes withGameStateBase,GameModegoes withGameState. Mix them and some framework functions and events silently stop being called. - Game state in the Level Blueprint. It can't easily be reached from other Blueprints and doesn't carry over to the next level. Rules go in the GameMode.
- Casting to the GameMode from everywhere. One
Cast Toper Blueprint is fine. Ten Blueprints all hard-referencing the GameMode is the dependency web from interfaces vs casting.
The 30-second recap
- GameMode = rules and respawn, server-only, one per level.
- GameState = public match data like the timer and team score.
- PlayerController = HUD, menus, camera; it survives death.
- PlayerState = per-player score and stats; also survives death.
- Character = body and health, created and destroyed with every life.
- GameInstance = the only one that survives
Open Level.
Next Sunday, votes permitting: collision presets and channels — Block, Overlap, Ignore, and the checklist for the trigger that never fires.
— Marco
Comments
Leave a comment