술래에게 잡힌 하이더는 맵에서 사라지고 그 사람 화면은 아직 살아있는 다른 하이더의 시점으로 넘어가야 함
그리고 ResolveCapture는 GameMode 안에 있어서 이미 서버 권한 컨텍스트
네트워크 처리를 얹기에도 자리가 맞음 -> 여기에 구현
설계 결정
언리얼에서 죽어서 시점이 넘어간다를 구현하는 길이 크게 둘
| SpectatorPawn | 컨트롤러가 폰을 놓고 유령 카메라(ASpectatorPawn)를 새로 소유 | 맵을 자유롭게 날아다니는 자유 관전 |
| SetViewTarget | 컨트롤러는 그대로, 카메라가 바라볼 대상만 다른 플레이어로 교체 | 조종은 못 하지만 그 사람 어깨 너머 화면이 보임 |
자유 관전을 시키면 맵을 탈출할수도 있으니 후자로 구현
순서
- 죽음 상태 기록
- 살아있는 사람으로 시점 먼저 이동
- 그 다음 잡힌 폰 제거
- 남은 하이더 0명이면 EndMatch
근데 내가 A를 구경하고 있는데 A도 잡히면 A의 폰이 사라지면서 내 시점 대상이 없어짐
-> 누군가 죽을 때마다 죽어있는 플레이어 전원의 시점을 지금 살아있는 사람으로 다시 계산해서 붙여야 함
- FindLivingViewTarget() : 살아있는 하이더의 폰을 하나 반환
- UpdateSpectators() : bIsAlive == false인 모든 플레이어의 카메라를 위 대상으로 재배치
APawn* AFDGameMode::FindLivingViewTarget() const
{
APawn* TaggerPawn = nullptr; // 살아있는 하이더가 하나도 없을 때 쓸 폴백
for (APlayerState* PS : GameState->PlayerArray)
{
const AFDPlayerState* FDPS = Cast<AFDPlayerState>(PS);
if (!FDPS) continue;
// 술래는 폴백으로만 붙잡아두고 계속 진행 (하이더를 우선 찾음)
if (FDPS->RoleTag == EFDRole::Tagger)
{
TaggerPawn = FDPS->GetPawn();
continue;
}
if (FDPS->RoleTag == EFDRole::Hider && FDPS->bIsAlive)
{
if (APawn* HiderPawn = FDPS->GetPawn())
{
return HiderPawn;
}
}
}
// 하이더 전멸 시 술래 시점으로 폴백 (곧 EndMatch라 방어용)
return TaggerPawn;
}
UpdateSpectators()로 카메라를 옮기고, UnPossess() 후 Destroy()로 폰을 제거
UpdateSpectators(); // 카메라 먼저
if (AController* HiderController = HiderCharacter->GetController())
{
HiderController->UnPossess();
}
HiderCharacter->Destroy(); // 그 다음 제거
근데 이렇게 하니까 시점이 안 옮겨가고 그냥 가만히 멈춰있음
순서도 지켰는데 시점이 안 넘어감
원인
폰이 파괴되면 엔진이 내부적으로 APlayerController::PawnPendingDestroy를 자동 호출하고, 컨트롤러 상태를 Spectating으로 전환한다. 이때 엔진이 자기 기준으로 뷰 타겟을 새로 잡는다. SpectatorPawn을 새로 스폰하거나 컨트롤러 자신을 대상으로 삼는다.
-> 순서 문제가 아니라 폰을 파괴하는 순간 언리얼의 관전 상태머신이 개입해서 주도권을 가져가는 것이었음
그리고 UpdateSpectators()는 GameMode -> 서버에서 돈다
거기서 원격 클라이언트의 PlayerController에 대고 SetViewTargetWithBlend를 직접 불렀음
-> Client RPC로 변경
변경
컨트롤러는 자기 폰을 계속 소유한 채로 둠 (Spectating 상태로 안 넘어가게)
폰은 안 보이게 + 충돌 없게만 만들기
HiderCharacter->SetActorHiddenInGame(true);
HiderCharacter->SetActorEnableCollision(false);
// 안 보이는 몸이 돌아다니지 않도록 이동 입력 차단
if (AFDPlayerController* DeadPC = Cast<AFDPlayerController>(HiderCharacter->GetController()))
{
DeadPC->Client_LockMovement();
}
시점 이동은 Client RPC로
void AFDPlayerController::Client_SetSpectateTarget_Implementation(AActor* NewViewTarget)
{
if (!NewViewTarget) return;
// bAutoManageActiveCameraTarget: 켜져 있으면 폰을 소유/해제할 때마다
// 엔진이 뷰 타겟을 자동으로 자기 폰으로 되돌림.
// 관전 중엔 정한 대상이 유지돼야 하므로 꺼둠
bAutoManageActiveCameraTarget = false;
SetViewTargetWithBlend(NewViewTarget, 0.5f);
}
if (AFDPlayerController* FDPC = Cast<AFDPlayerController>(FDPS->GetOwningController()))
{
if (FDPC->GetPawn() == ViewTarget) continue;
// 해당 클라에게 RPC로 지시
FDPC->Client_SetSpectateTarget(ViewTarget);
}'프로젝트 > Funny Or Die (멀티플레이 술래잡기 게임)' 카테고리의 다른 글
| FunnyorDie - 멀티플레이 술래잡기 게임 개발 정리 (0) | 2026.07.20 |
|---|---|
| [트러블슈팅] 하드 트래블 재접속 레이스 잡기 (0) | 2026.07.18 |
| 게임 시작 UI 디자인 (0) | 2026.07.18 |
| 순위창 UI 제작 (0) | 2026.07.18 |
| 순위표 WBP 생성 (0) | 2026.07.18 |
