체력, 점수, 킬 로그 동기화하기
24강. 체력, 점수, 킬 로그 동기화하기
1. 이번 강의에서 해결할 문제
체력은 맞은 Pawn, 킬 수는 공격 플레이어, 팀 점수와 킬 로그는 경기 전체에 속합니다. 이 값을 Character 하나에 넣으면 Respawn에서 사라지고 모든 내용을 Multicast로 보내면 늦은 참가와 유실에서 불일치합니다. 상태의 소유 단위를 나눠야 합니다.
2. 학습 목표
3. 핵심 개념
체력은 현재 Pawn 수명에 속하고 모든 관찰자가 필요할 수 있어 Component RepNotify가 적합합니다. 킬은 플레이어가 Respawn해도 유지돼 PlayerState에 둡니다. 팀 점수와 최근 공용 사건은 GameState가 전달합니다. ApplyDamage와 OnTakeAnyDamage 처리는 서버에서만 권위 있게 실행합니다.
고빈도 킬 로그에는 Fast Array가 유용하지만 이 과정에서는 2인 게임의 가장 최근 사건 하나와 Sequence를 복제해 기반만 익힙니다. Fast Array는 32강에서 심화 과정으로 연결합니다.
4. 단계별 실습
UCLASS(ClassGroup = "Capture", meta = (BlueprintSpawnableComponent))
class CAPTUREARENA_API UCaptureHealthComponent : public UActorComponent
{
GENERATED_BODY()
public:
UCaptureHealthComponent();
virtual void GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const override;
protected:
virtual void BeginPlay() override;
UPROPERTY(ReplicatedUsing = OnRep_Health)
float Health = 100.0f;
UPROPERTY(EditDefaultsOnly)
float MaxHealth = 100.0f;
UFUNCTION() void OnRep_Health(float PreviousHealth);
UFUNCTION() void HandleDamage(AActor*, float Damage,
const UDamageType*, AController* InstigatorController, AActor*);
private:
bool bDeathHandled = false;
};
UCaptureHealthComponent::UCaptureHealthComponent()
{
PrimaryComponentTick.bCanEverTick = false;
SetIsReplicatedByDefault(true);
}
void UCaptureHealthComponent::BeginPlay()
{
Super::BeginPlay();
if (GetOwner() && GetOwner()->HasAuthority())
{
Health = MaxHealth;
GetOwner()->OnTakeAnyDamage.AddDynamic(
this, &UCaptureHealthComponent::HandleDamage);
}
}
void UCaptureHealthComponent::HandleDamage(
AActor*, float Damage, const UDamageType*,
AController* InstigatorController, AActor*)
{
if (!GetOwner()->HasAuthority() || Damage <= 0.0f || bDeathHandled) return;
const float Previous = Health;
Health = FMath::Clamp(Health - Damage, 0.0f, MaxHealth);
OnRep_Health(Previous);
if (Previous > 0.0f && Health <= 0.0f)
{
bDeathHandled = true;
NotifyGameModeOfDeath(InstigatorController);
}
}
void UCaptureHealthComponent::OnRep_Health(float PreviousHealth)
{
OnHealthChanged.Broadcast(Health, MaxHealth);
}
void UCaptureHealthComponent::GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UCaptureHealthComponent, Health);
}
void ACaptureArenaGameMode::HandlePlayerKilled(
AController* Victim, AController* Killer)
{
if (!HasAuthority() || !Victim) return;
ACaptureArenaPlayerState* KillerState =
Killer ? Killer->GetPlayerState<ACaptureArenaPlayerState>() : nullptr;
ACaptureArenaPlayerState* VictimState =
Victim->GetPlayerState<ACaptureArenaPlayerState>();
if (KillerState && Killer != Victim)
{
KillerState->AddKillOnServer();
GetGameState<ACaptureArenaGameState>()->AddTeamScore(
KillerState->GetTeamId(), 1);
}
GetGameState<ACaptureArenaGameState>()->SetLatestKillEvent(
KillerState, VictimState);
RestartPlayerAfterDelay(Victim);
}
킬 이벤트 구조에는 표시용 PlayerState 참조 또는 안전한 표시 이름과 증가 Sequence를 둡니다. 실명·플랫폼 계정 ID를 사용하지 않고 게임 내 닉네임 정책을 적용합니다.
5. 코드가 동작하는 이유
HealthComponent는 서버 OnTakeAnyDamage에서 한 번만 0 전이를 확정합니다. GameMode가 킬 규칙을 실행하고 PlayerState·GameState Setter가 서버 값을 변경합니다. 각 클라이언트는 RepNotify와 PlayerState/GameState를 읽어 HUD를 갱신하며 Multicast 없이도 최신 체력·점수에 수렴합니다.
6. 자주 하는 실수와 해결법
7. 직접 실습
8. 이해 점검 질문 3개
9. 핵심 요약
체력, 점수, 킬 로그 동기화하기 미니 퀴즈
선택 즉시 정답과 해설을 확인할 수 있습니다. 결과는 이 브라우저에만 저장됩니다.
학습을 마쳤나요?
직접 실습과 점검 질문까지 확인한 뒤 완료로 표시하세요.