[Project Arc] 네트워크 환경에서 플레이어 카메라 Activate 문제 해결
오늘은 언리얼 멀티플레이 환경에서 플레이어 카메라 시점이 보이지 않는 문제를 Activate로 해결한 과정과, 아직 남아 있는 카메라 회전(Input_Look) 문제를 원인 중심으로 정리하고자 합니다.
오늘은 언리얼 멀티플레이 환경에서 플레이어 카메라 시점이 보이지 않는 문제를 Activate로 해결한 과정과, 아직 남아 있는 카메라 회전(Input_Look) 문제를 원인 중심으로 정리하고자 합니다.
문제 상황 요약
- 환경: UE5, 멀티플레이(Host + Client), C++ 기반 캐릭터/컨트롤러, BP 기반 GameplayCamera 사용
- 증상 1: Client 입장에서 플레이어 Pawn 은 스폰되고 Possess 도 되지만, 카메라 시점이 비어 있거나 이상한 위치를 비춤
- 증상 2: Host 에서는 정상 동작하는 것처럼 보이지만, Client 쪽에서만 카메라 관련 문제 발생
- 추가 증상: 시점을 보이게 만드는 문제는 해결했지만, 마우스 입력에 따른 카메라 상하좌우 회전은 아직 제대로 동작하지 않음
원인 분석: 카메라 시점(Activate) 문제
1. ViewCamera vs GameplayCamera 사용 불일치
- C++
ACMPlayerCharacterBase안에는ViewCamera(UCameraComponent)와CameraBoom(USpringArmComponent)가 존재CameraBoom->bUsePawnControlRotation = true;ViewCamera->bAutoActivate = false;
- 하지만 실제 게임에서는 캐릭터 BP 에서 별도의 GameplayCamera 컴포넌트(UGameplayCameraComponent 파생 BP) 를 메인 카메라로 사용 중
- 결과적으로:
- C++에서
ViewCamera->Activate()를 호출해도 실제로 화면에 쓰이는 카메라는 GameplayCamera 이므로, 시점 문제는 그대로 유지
- C++에서
2. 네트워크에서 Possess/BeginPlay 타이밍 차이
- 서버:
- GameMode 에서 Pawn 생성 →
NewPlayer->Possess(NewPawn)실행 - 서버 기준에서는 Possess 타이밍이 확실
- GameMode 에서 Pawn 생성 →
- 클라이언트:
- Pawn 이 복제되고
BeginPlay가 먼저 호출된 뒤에, - 컨트롤러 소유 정보가 들어오고
PawnClientRestart/OnRep_Controller등이 호출됨
- Pawn 이 복제되고
BeginPlay기준으로 카메라 Setup 을 시도하면:- Host(리스너 서버)는 우연히 잘 동작할 수 있지만,
- 순수 Client 에서는 아직 로컬 컨트롤러가 붙지 않은 상태일 수 있어서
IsLocallyControlled()가 false 인 경우가 많음 - 이 경우, 로컬 전용 카메라 Activate 로직이 실행되지 않음
해결 전략: PawnClientRestart에서 GameplayCamera Activate
선택한 기준 시점
- 네트워크 플레이에서 로컬 클라이언트가 이 Pawn 을 실제로 조종할 준비가 된 시점은
APawn::PawnClientRestart()에서 가장 확실 - 이유:
- 컨트롤러/Owner 정보가 세팅된 뒤 호출됨
- 입력 매핑(Enhanced Input)도 이 시점에 다시 셋업하는 패턴이 일반적
- Host / Client 양쪽에서 동일한 타이밍 보장
구현 아이디어
ACMPlayerCharacterBase에 카메라 셋업 전용 헬퍼 함수 추가UGameplayCameraComponent를FindComponentByClass로 찾아온다.- 해당 컴포넌트에 대해
Activate()(또는SetupCamera()) 를 호출한다.
PawnClientRestart()에서:- 기존 입력 매핑 설정 후
- 바로 이 헬퍼 함수를 호출하여, 로컬 플레이어의 카메라를 활성화한다.
적용한 헬퍼 함수 소스코드
1. 헤더 선언 (ACMPlayerCharacterBase.h)
- 전방 선언 및 private 헬퍼 함수 선언
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class UGameplayCameraComponent;
UCLASS()
class CRIMSONMOON_API ACMPlayerCharacterBase : public ACMCharacterBase
{
GENERATED_BODY()
// ...existing code...
private:
// BeginPlay 이후, 로컬 플레이어 기준으로 GameplayCamera를 셋업하는 헬퍼 함수
void SetupGameplayCamera_Helper();
// ...existing code...
};
2. 구현부 (ACMPlayerCharacterBase.cpp)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void ACMPlayerCharacterBase::SetupGameplayCamera_Helper()
{
UE_LOG(LogTemp, Warning,
TEXT("SetupGameplayCamera_Helper: Called. IsLocallyControlled=%d"),
IsLocallyControlled() ? 1 : 0);
// 로컬 플레이어가 소유한 Pawn 에서만 카메라 셋업
if (!IsLocallyControlled())
{
UE_LOG(LogTemp, Warning,
TEXT("SetupGameplayCamera_Helper: Aborted - not locally controlled"));
return;
}
// BP 에서 추가된 UGameplayCameraComponent 를 찾아 SetupCamera/Activate 호출
if (UGameplayCameraComponent* GameplayCameraComp = FindComponentByClass<UGameplayCameraComponent>())
{
UE_LOG(LogTemp, Warning,
TEXT("SetupGameplayCamera_Helper: Found GameplayCameraComponent on %s, calling SetupCamera"),
*GetName());
// 현재는 Activate로 시점을 살려두었음 (필요 시 SetupCamera()로 교체 가능)
GameplayCameraComp->Activate();
// GameplayCameraComp->SetupCamera();
}
else
{
UE_LOG(LogTemp, Warning,
TEXT("SetupGameplayCamera_Helper: GameplayCameraComponent not found on %s"),
*GetName());
}
}
PawnClientRestart에서 호출
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void ACMPlayerCharacterBase::PawnClientRestart()
{
Super::PawnClientRestart();
if (const APlayerController* OwningPlayerController = GetController<APlayerController>())
{
UEnhancedInputLocalPlayerSubsystem* PlayerSubsystem =
OwningPlayerController->GetLocalPlayer()->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
check(PlayerSubsystem);
PlayerSubsystem->RemoveMappingContext(InputConfigDataAsset->DefaultMappingContext);
PlayerSubsystem->AddMappingContext(InputConfigDataAsset->DefaultMappingContext, 0);
}
// 로컬 클라이언트가 이 Pawn 을 다시 사용할 준비가 된 시점에 카메라 셋업 시도
SetupGameplayCamera_Helper();
}
현재 상태: 시점 문제는 해결, 회전 문제는 미해결
해결된 부분
- Host / Client 모두에서:
- GameplayCameraComponent 를 로컬 클라이언트 기준으로 Activate 하도록 변경
- PawnClientRestart 기준으로 실행되기 때문에, 네트워크 타이밍 문제(컨트롤러 미소유 상태)는 피함
- 결과적으로:
- 카메라 시점이 비어 있거나, 이상한 곳을 비추던 문제는 해결됨
- 캐릭터를 정상적으로 화면에 비추고, 이동/전투 플레이가 가능해짐
아직 해결되지 않은 부분: 마우스 입력에 따른 카메라 회전
- 남아 있는 문제:
- 마우스 입력(
Input_Look)에 따라 카메라가 상하좌우로 자연스럽게 회전해야 하는데, - 지금은 시점은 잡히지만, 회전이 작동하지 않음
- 마우스 입력(
- 추정 원인:
- C++ 측에서는
Input_Look에서AddControllerYawInput,AddControllerPitchInput을 호출하고 있음 - 하지만 실제로 화면에 쓰이는 것은 BP 기반 GameplayCamera 이고,
- 이 GameplayCamera/SpringArm 이 컨트롤러 회전을 제대로 반영하지 않거나,
bUsePawnControlRotation설정이 비활성화되어 있거나,- 자체 로직으로만 회전을 제어하고 있을 가능성이 큼
- 따라서 컨트롤러 회전 값은 변하지만, 카메라 컴포넌트가 그 값을 사용하지 않는 상태일 수 있음
- C++ 측에서는
- 요약:
- “카메라가 안 보인다” 문제는
UGameplayCameraComponentActivate 로 해결 - “카메라가 마우스 입력대로 돌지 않는다” 문제는 GameplayCamera의 회전 설정/로직 조정이 추가로 필요
- “카메라가 안 보인다” 문제는
마치며
이번 작업에서는 네트워크 환경에서 플레이어 카메라 시점이 비정상적으로 비춰지는 문제를, PawnClientRestart 시점에 UGameplayCameraComponent 를 찾아 Activate 하는 방식으로 해결하였습니다. 특히 BeginPlay, PossessedBy, PawnClientRestart 간의 호출 시점과 IsLocallyControlled() 여부가 네트워크 환경에서 어떻게 달라지는지 다시 한 번 정리할 수 있었고, 로컬 전용 카메라 로직은 PawnClientRestart 에서 처리하는 것이 안전하다는 점을 확인했습니다.
다만, 아직 마우스 입력에 따른 카메라 상하좌우 회전이 기대한 대로 동작하지 않는 문제가 남아 있습니다. 이는 C++ 단의 Input_Look 처리가 아니라, 실제 뷰를 담당하고 있는 GameplayCamera 컴포넌트의 회전 설정과 로직이 컨트롤러 회전을 어떻게 받아들이는지에 더 가깝기 때문에, 다음 단계에서는 해당 BP/컴포넌트의 bUsePawnControlRotation 설정, SpringArm 사용 여부, ViewTarget 세팅 방식을 집중적으로 점검하고 수정할 예정입니다.

