Responsabilidades na prática: quem guarda o quê?
Para implementar pontuação com regras claras e persistência durante a partida, é essencial separar responsabilidades entre GameMode, GameState e PlayerState. Essa separação evita “gambiarras” como guardar pontuação no personagem (que pode ser destruído ao morrer) ou no HUD (que é apenas visual).
| Classe | Onde existe | Responsabilidade prática para pontuação | O que NÃO fazer |
|---|---|---|---|
GameMode | Somente no servidor (autoridade) | Regras: como pontua, quando pontua, validação, reset de rodada, condições de vitória | Guardar estado que o HUD precisa ler diretamente (cliente não acessa) |
GameState | Servidor e clientes | Estado global da partida: pontuação do time, tempo, objetivo global, fase do jogo | Guardar pontuação individual de cada jogador (use PlayerState) |
PlayerState | Servidor e clientes | Estado individual persistente durante a partida: pontuação do jogador, nome, mortes, ping | Fazer lógica de regra (isso é do GameMode) |
Regra de ouro para pontuação
- Regras e validação ficam no
GameMode. - Valor da pontuação do jogador fica no
PlayerState. - HUD lê do
PlayerState(ou de um binding/evento que venha dele) e apenas exibe. - Estado global (ex.: pontuação total do time) fica no
GameState.
Estrutura sugerida (Blueprints)
Você pode implementar com Blueprints derivados das classes padrão:
BP_MyGameMode(deriva de GameModeBase ou GameMode)BP_MyGameState(deriva de GameStateBase ou GameState)BP_MyPlayerState(deriva de PlayerState)
Em Project Settings > Maps & Modes, defina essas classes como padrão do seu mapa/modo.
Passo a passo: pontuação ao coletar itens
1) Criar a variável de pontuação no PlayerState
No BP_MyPlayerState:
- Crie a variável
ScorePoints(tipoInteger). - Crie uma função
AddScorecom entradaDelta(Integer).
Implementação da função AddScore:
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
ScorePoints = ScorePoints + DeltaOpcional (recomendado para rastreabilidade): dispare um evento/dispatcher quando a pontuação mudar.
- Crie um
Event DispatcherchamadoOnScoreChangedcom parâmetroNewScore(Integer). - No final de
AddScore, chameOnScoreChangedpassandoScorePoints.
2) Centralizar a regra no GameMode (validação e aplicação)
No BP_MyGameMode, crie uma função AwardScoreForPickup com entradas:
PlayerController(tipoPlayerController)Points(Integer)
Fluxo recomendado:
- Validar se
PlayerControlleré válido. - Obter o
PlayerStatedo controller. - Cast para
BP_MyPlayerState. - Chamar
AddScore(Points).
Function AwardScoreForPickup(PC, Points): If PC is not valid: return PS = PC->PlayerState Cast PS to BP_MyPlayerState as MyPS If cast failed: return MyPS->AddScore(Points)Por que aqui? Porque o GameMode é o lugar natural para aplicar regras: “quanto vale cada item”, “pode pontuar agora?”, “há multiplicador?”, “limite por tempo?”, etc.
3) Item coletável chama o GameMode (sem referência frágil)
No Blueprint do item coletável (ex.: BP_Pickup), você normalmente tem um evento de overlap/uso. Ao confirmar a coleta, em vez de tentar acessar o HUD ou o personagem diretamente, faça:
- Descobrir quem coletou (geralmente o
Other Actordo overlap). - Obter o
Controllerdo jogador (se for um Character/Pawn). - Chamar o
GameModepara conceder pontos.
Exemplo de fluxo (conceitual):
OnComponentBeginOverlap(OtherActor): Pawn = OtherActor (cast para seu Pawn/Character) PC = Pawn->GetController (cast para PlayerController) GM = GetGameMode (cast para BP_MyGameMode) GM->AwardScoreForPickup(PC, PointsValue) DestroyActor (o pickup)Observação importante: o item coletável não deve “guardar” referência fixa para o jogador, HUD ou PlayerState. Ele resolve tudo no momento do evento (overlap/uso), reduzindo acoplamento e evitando referências quebradas.
Atualização do HUD: ler do PlayerState (e não do GameMode)
Como o HUD é local do jogador, a fonte mais estável para pontuação individual é o PlayerState do jogador local. Duas abordagens comuns:
Abordagem A: Atualização por evento (recomendado para rastreabilidade)
No Widget do HUD (ex.: WBP_HUD):
- No
Event Construct, obtenha oOwning Player(PlayerController). - Do controller, obtenha
PlayerStatee cast paraBP_MyPlayerState. - Faça
Bindno dispatcherOnScoreChanged. - Quando o evento disparar, atualize o texto de pontuação.
WBP_HUD::Event Construct: PC = GetOwningPlayer PS = PC->PlayerState MyPS = Cast to BP_MyPlayerState Bind Event to MyPS.OnScoreChanged (chamar também UpdateScoreText(MyPS.ScorePoints) para inicializar) OnScoreChanged(NewScore): ScoreTextBlock->SetText(NewScore as text)Vantagens: o fluxo é explícito (quem mudou, quando mudou) e fácil de depurar.
Abordagem B: Binding direto (simples, mas menos rastreável)
Você pode fazer binding do TextBlock para uma função que lê ScorePoints do PlayerState. Funciona, mas pode atualizar com frequência e torna mais difícil rastrear “quem disparou a mudança”.
Reset de pontuação ao reiniciar o nível
Ao reiniciar o nível (ex.: Open Level no mesmo mapa), uma nova instância de GameMode e GameState será criada, e os PlayerState também tendem a ser recriados (dependendo do fluxo). Para garantir reset consistente:
Opção 1: Reset automático pela recriação (com validação)
- Defina
ScorePointscom valor padrão 0 noBP_MyPlayerState. - Garanta que o HUD sempre leia do PlayerState atual (re-bind no Construct).
Essa opção funciona bem quando reiniciar significa recarregar o mapa.
Opção 2: Reset explícito no GameMode (útil para “Restart” sem trocar de mapa)
No BP_MyGameMode, crie uma função ResetMatchScore:
- Obter todos os PlayerStates (via GameState).
- Para cada PlayerState, setar
ScorePoints = 0(ou chamar uma funçãoResetScoreno PlayerState). - Disparar
OnScoreChangedpara atualizar HUD imediatamente.
ResetMatchScore: GS = GetGameState (cast para BP_MyGameState ou GameStateBase) ForEach PlayerState in GS.PlayerArray: MyPS = Cast to BP_MyPlayerState If valid: MyPS.ScorePoints = 0 MyPS.OnScoreChanged.Broadcast(0)Se você tiver também pontuação global (time/partida), resete no GameState aqui.
Usando GameState para pontuação global (opcional)
Se o jogo tiver uma pontuação compartilhada (ex.: “pontuação da equipe” ou “itens coletados no total”), crie no BP_MyGameState:
- Variável
TeamScore(Integer). - Função
AddTeamScore(Delta)que soma e (opcionalmente) dispara dispatcherOnTeamScoreChanged.
O GameMode pode, ao conceder pontos individuais, também atualizar o total global no GameState quando fizer sentido.
Etapa de validação: evitar referências frágeis e tornar o fluxo rastreável
Checklist de robustez (anti-acoplamento)
- O pickup não guarda referência fixa para HUD/PlayerState/Character. Ele resolve o coletor no evento e chama o GameMode.
- O HUD não busca o GameMode para ler pontuação (GameMode não existe no cliente). O HUD lê do
PlayerState. - A pontuação não fica no Character. Se o Pawn morrer/trocar, a pontuação permanece no
PlayerState. - Regras ficam no GameMode: o pickup não decide “se vale ponto” além do seu valor; quem valida é o GameMode.
Checklist de rastreabilidade (debug do fluxo)
Adicione logs com Print String (ou um sistema de log próprio) em pontos-chave, com mensagens padronizadas:
- No pickup, ao coletar:
[Pickup] Collected by PC=... Points=... - No GameMode, ao conceder:
[GameMode] AwardScore PC=... Points=... PS=... - No PlayerState, ao somar:
[PlayerState] AddScore Delta=... NewScore=... - No HUD, ao atualizar:
[HUD] OnScoreChanged NewScore=...
Validação prática:
- Teste reiniciar o nível e confirme que a pontuação volta a 0 (ou ao valor esperado).
- Teste coletar múltiplos itens rapidamente e verifique se a ordem dos logs faz sentido (Pickup → GameMode → PlayerState → HUD).
- Force um cenário onde o Pawn é destruído/trocado (ex.: respawn) e confirme que a pontuação permanece, pois está no PlayerState.