
이번에는 멀티플레이 환경에서 게임 시작용 NPC(GameStarter NPC) 를 구현하고, 발생한 문제를 해결했습니다.
목표는 다음과 같습니다.
- NPC와 상호작용(Interact)했을 때 서버 인원 전체를 특정 맵으로 이동시키기
- 이동 전 화면이 서서히 어두워지는 연출(페이드 아웃) 추가
- Interact 로직이 항상 서버(Host) 에서만 실행되도록 보장
- 특히, Remote Client가 조종하는 캐릭터가 Interact를 수행했을 때는 GameStarter 로직이 실행되지 않도록 필터링
특히, 게임 시작이 Host에서만 수행되도록 해야 하지만, Interact가 서버를 통해 수행되어 의도와 다르게 수행됐던 문제를 중점적으로 다뤄보고자 합니다.
UCMPickUpComponentBeginPlay에서 입력 시스템 초기화 시도 (InitializeInputSystem)CurrentInteractableActor)을 계산 및 복제UCMPickUpComponent::OnPickupInput(const FInputActionValue& Value)2번)를 누르면 호출AbilitySystemComponent->TryActivateAbilitiesByTag(InteractAbilityTag, true);UCMAbility_InteractNetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::ServerOnly;ActivateAbility 는 항상 서버에서만 실행AvatarActor (보통 플레이어 캐릭터)를 가져옴AvatarActor에서 UCMPickUpComponent를 찾음PickUpComp->GetCurrentInteractable()로 상호작용 대상 HitActor 획득HitActor가 UCMInteractableInterface 구현 시:ICMInteractableInterface::Execute_Interact(HitActor, AvatarActor);EndAbility)ACMNpcBaseICMInteractableInterface 를 구현ACMNpcGameStarter : public ACMNpcBaseAPawn 의 Controller 를 통해 판별Controller->IsPlayerController() == true 이면, 플레이어가 조종하는 PawnController->IsLocalController() == true → 서버 자신(리슨 서버) 혹은 서버 로컬 컨트롤러Controller->IsLocalController() == false → Remote 클라이언트가 조종 중인 PawnController->IsPlayerController() && !Controller->IsLocalController()이를 GameStarter NPC의 Interact_Implementation 안에서 사용하여 필터링을 구현했습니다.
파일: CMNpcGameStarter.h
변경 사항
Interact_Implementation(AActor* Interactor) 오버라이드 선언PerformInteract() 를 override 로 선언해 GameStarter 전용 로직 수행주요 멤버 요약
virtual void Interact_Implementation(AActor* Interactor) override;virtual void PerformInteract() override;UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "GameStart") FString TravelURL;UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "GameStart") float FadeDuration = 1.0f;UFUNCTION(NetMulticast, Reliable) void MulticastStartFadeOut();void ServerTravelToConfiguredMap();CMNpcGameStarter.cppInteractor 가 Remote 클라가 조종하는 Pawn이면 조용히 return;PerformInteract() 호출if (!HasAuthority() && GetNetMode() != NM_ListenServer) return;APawn* Pawn = Cast<APawn>(Interactor);AController* Controller = Pawn->GetController();Controller->IsPlayerController() && !Controller->IsLocalController() → Remote 클라return; → GameStart 로직 미실행PerformInteract(); 호출HasAuthority() + NetMode 체크로 서버/리슨 서버에서만 진행UWorld* World = GetWorld(); 유효성 확인MulticastStartFadeOut(); 호출 → NetMulticast RPCWorld->GetTimerManager().SetTimer(..., FadeDuration); 로 ServerTravelToConfiguredMap 예약 호출MulticastStartFadeOut_Implementation()NetMulticast, ReliableUWorld* World = GetWorld();APlayerController* PC = World->GetFirstPlayerController();PC->IsLocalController() 인 경우에만 페이드 적용PC->PlayerCameraManager->StartCameraFade(0.0f, 1.0f, FadeDuration, FLinearColor::Black, true, true);TravelURL 을 기준으로 ServerTravel 실행HasAuthority() 체크)TravelURL.IsEmpty() 시 로그만 출력하고 종료World->ServerTravel(TravelURL, /*bAbsolute*/ false);
오늘 작업을 통해 다음과 같은 점을 다시 정리할 수 있었습니다.
Interact_Implementation(AActor* Interactor) 에서 Interactor의 Controller를 검사하여NetMulticast 함수는 월드마다 한 번씩 실행되므로,GetFirstPlayerController() + IsLocalController() 조합을 사용하면앞으로는 이 패턴을 바탕으로, 특정 권한(예: GM 전용, 특정 팀 전용)만 사용할 수 있는 상호작용 오브젝트를 설계할 때, Interactor 기반 필터링을 적극적으로 활용할 계획입니다.