StartInGame()

StartScouting()이 60초 타이머를 걸어두고 있었으니 StartInGame()에서 

1. 정찰 모드 끔

2. CurrentPhase를 InGame으로 전환

3. AliveHiderCount 저장

4. 300초 후 EndMatch() 예약

 

void AFDGameMode::StartInGame() // 본게임 시작
{
    // 정찰 모드 종료 — Scouting에서 켰던 걸 대칭으로 끔
    for (APlayerState* PS : GameState->PlayerArray)
    {
       const AFDPlayerState* FDPS = Cast<AFDPlayerState>(PS);
       if (!FDPS || FDPS->RoleTag != EFDRole::Tagger) continue;

       if (AFDTaggerCharacter* TaggerChar = Cast<AFDTaggerCharacter>(FDPS->GetPawn()))
       {
          TaggerChar->SetScoutingMode(false);
       }
    }
    
    AFDGameState* FDGameState = GetGameState<AFDGameState>();
    if (!FDGameState) return;

    FDGameState->CurrentPhase = EMatchPhase::InGame;
    UE_LOG(LogTemp, Warning, TEXT("[GameMode] InGame 단계 시작"));

    // 생존한 숨는사람 수 저장해두기 
    int32 HiderCount = 0;
    for (APlayerState* PS : GameState->PlayerArray)
    {
       if (const AFDPlayerState* FDPS = Cast<AFDPlayerState>(PS))
       {
          if (FDPS->RoleTag == EFDRole::Hider)
          {
             ++HiderCount;
          }
       }
    }
    FDGameState->AliveHiderCount = HiderCount; // 그걸 GameState의 replication 되는 alivehidercount에 넣음
    
    // 300초 후 게임 종료
    GetWorldTimerManager().SetTimer(PhaseTimerHandle, this, &AFDGameMode::EndMatch, 300.f, false);
}

 

포획 판정

캐릭터 쪽엔 이미 겹침 감지 → 포획 시퀀스 로직이 짜여 있었음 (팀원 작성 로직)

void AFDTaggerCharacter::OnCaptureCollisionOverlap(...)
{
    if (CapturedHider) return;
    ACharacter* HiderCharacter = Cast<ACharacter>(OtherActor);
    if (!HiderCharacter) return;

    Internal_StartCaptureSequence(HiderCharacter);
}

 

 

근데 판정은 GameMode에서 하는 게 더 안전할 것 같아서 팀원에게 양해 구하고 수정

Overlap → GameMode->RequestCaptureJudgement → 승인되면 → StartCaptureSequence → Internal_
void AFDGameMode::RequestCaptureJudgement(AFDTaggerCharacter* TaggerCharacter, ACharacter* HiderCharacter)
{
    if (!TaggerCharacter || !HiderCharacter) return;

    AFDGameState* FDGameState = GetGameState<AFDGameState>();
    if (!FDGameState || FDGameState->CurrentPhase != EMatchPhase::InGame) return;
    // 본게임 중이 아니면 판정 자체를 시작 안 함

    TaggerCharacter->StartCaptureSequence(HiderCharacter);
}

 

 

판정 종료

판정은 포획/ 봐주기/ 타이머 만료 3개로 나뉘는데 세 경로 모두 공통으로 해야 하는 일이 있음

판정 타이머 정리, 양쪽 입력 복구, GameMode에 결과 통보, CapturedHider 초기화

그래서 이 공통 로직을 하나의 함수에 묶어서 넣음 

void AFDTaggerCharacter::Internal_ResolveCaptureLocally(bool bWasCaptured) 
// 아웃이든 봐주기든 똑같이 실행되어야 하는건 하나로 묶음 
{
    // 타이머 끄기 (결과가 정해졌으니 자동 만료 타이머 필요 없음)
    GetWorldTimerManager().ClearTimer(CaptureJudgeTimerHandle);
    
    // 양쪽 입력 원복시킴
    if (APlayerController* TaggerPC = Cast<APlayerController>(GetController()))
    {
       TaggerPC->EnableInput(TaggerPC);
    }
    if (APlayerController* HiderPC = Cast<APlayerController>(CapturedHider->GetController()))
    {
       HiderPC->EnableInput(HiderPC);
    }
    
    // 게임모드에 알림
    if (AFDGameMode* FDGameMode = GetWorld()->GetAuthGameMode<AFDGameMode>())
    {
       FDGameMode->ResolveCapture(CapturedHider, bWasCaptured);
    }
    
    CapturedHider = nullptr; 
}

void AFDTaggerCharacter::ForceOut() // 아웃 누르면 호출될 함수
{
    if (!CapturedHider) return; // 판정 중인 게 없으면 무시
    
    Internal_ResolveCaptureLocally(true);
}

void AFDTaggerCharacter::RequestSpare() // 봐주기 누르면 호출될 함수
{
    if (!CapturedHider) return;

    ACharacter* SparedHider = CapturedHider;
    
    // 봐주기 버프: 속도 증가 
    if (UCharacterMovementComponent* Movement = SparedHider->GetCharacterMovement())
    {
       Movement->MaxWalkSpeed = 1200.f;
    }
    // 무적처리 
    
    // 10초 뒤 버프 해제(대략 적음)
    FTimerHandle SpareTimerHandle;
    GetWorldTimerManager().SetTimer(SpareTimerHandle, [this, SparedHider]()
    {
       OnSpareExpired(SparedHider);
    }, 10.f, false);
    
    Internal_ResolveCaptureLocally(false); // 봐줬으니까 capture은 false로
}

 

RequestSquare 함수에서 Internal_ResolveCaptureLocally를 호출하고 나면 CapturedHider가 nullptr이 되는데

봐주기 버프 해제 타이머(OnSpareExpired)는 5초 뒤에도 그 캐릭터를 알아야 함

그래서 Internal_ 호출 전에 SparedHider라는 별도 변수로 미리 캐릭터를 저장해두고 그걸 넘김

 

GameMode 쪽 ResolveCapture는 카운트 감소와 종료 조건 체크를 담당한다.

void AFDGameMode::ResolveCapture(ACharacter* HiderCharacter, bool bWasCaptured)
{
    if (!HiderCharacter || !bWasCaptured) return; // 봐줌이면 GameMode가 할 일 없음

    AFDGameState* FDGameState = GetGameState<AFDGameState>();
    if (!FDGameState) return;

    --FDGameState->AliveHiderCount;
    if (FDGameState->AliveHiderCount <= 0)
    {
        EndMatch();
    }
}

 

전체 흐름 정리

술래의 콜리전 안에 숨는 사람 들어왔을 때 공격 버튼 누름 (마우스 좌클릭)

-> Server_TryCapture() 호출

-> Server_TryCapture_Implementation() 실행

-> CaptureCollision 콜리전 켬

-> 그 사이에 하이더랑 물리적으로 겹치면 OnCaptureCollisionOverlap() 실행

-> GameMode->RequestCaptureJudgement() 호출

-> 승인되면 캐릭터의 StartCaptureSequence() 호출 -> Internal_StartCaptureSequence()

-> // 여기서부터는 UI 작업 해야 디버깅 가능해짐

Client_ShowCapturePopup() (술래 화면에 UI 팝업)

  1. 아웃 선택시 Server_RequestOut (Onclicked으로 함수 넣어야함) -> ForceOut() -> Internal_ResolveCaptureLocally(true)
  2. 봐주기 선택시 Server_RequestSpare (마찬가지) -> RequestSpare() -> Internal_ResolveCaptureLocally(false)
  3. 시간초과시 -> OnCaptureTimerExpired() -> Internal_ResolveCaptureLocally(true)

 

알게된 것

RPC는 클라이언트 -> 서버로 물리적으로 다른 프로세스 경계를 건널 때만 필요함

서버 프로세스 안에서 캐릭터가 GameMode를 부르는 건 RPC 말고 그냥 GameMode-> 이렇게 불러도 됨

+ Recent posts