술래에게 잡힌 하이더는 맵에서 사라지고 그 사람 화면은 아직 살아있는 다른 하이더의 시점으로 넘어가야 함

 

그리고 ResolveCapture는 GameMode 안에 있어서 이미 서버 권한 컨텍스트 

네트워크 처리를 얹기에도 자리가 맞음 -> 여기에 구현

설계 결정

언리얼에서 죽어서 시점이 넘어간다를 구현하는 길이 크게 둘

SpectatorPawn 컨트롤러가 폰을 놓고 유령 카메라(ASpectatorPawn)를 새로 소유 맵을 자유롭게 날아다니는 자유 관전
SetViewTarget 컨트롤러는 그대로, 카메라가 바라볼 대상만 다른 플레이어로 교체 조종은 못 하지만 그 사람 어깨 너머 화면이 보임

 

자유 관전을 시키면 맵을 탈출할수도 있으니 후자로 구현

 

순서

  1. 죽음 상태 기록
  2. 살아있는 사람으로 시점 먼저 이동
  3. 그 다음 잡힌 폰 제거
  4. 남은 하이더 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);
}

+ Recent posts