● 돌고 돌아서 선택한 불편하지만 확실한 방법
스탠드얼론으로 하든, 네트워크 플레이로 하든 1가지 확실한 것은 플레이어 스테이트 자체는 캐릭터 폰이 생성되면서 덩달아 생성된다는 것이다. 다만, 특히 네트워크 환경이라면 막 생성될 시점에서는 서로 연결할 수 없는 상황이라는 점이다.
그래서 나는 어쩔 수 없지만 확실한 방법을 쓰기로 했다.
void AMainGameCharacter::Attack()
{
//사망하거나 이미 공격 상태라면 공격 불가
if (bIsDeath || CurrentAttackDelay > 0)
{
return;
}
//공격은 경찰만 가능
if (!Cast<AMainGamePlayerState>(GetPlayerState())->bPolice)
{
return;
}
CurrentAttackDelay = AttackDelayTime;
//서버에 공격 요청하기
AttackOrder();
}
내가 헤더에 플레이어 스테이트 변수를 따로 만든 이유는 캐스팅으로 인한 비용을 줄이기 위함이었다. 그런데 아무리 효율적인 구조라고 해도 안된다면 그냥 못쓴다. 그래서 그냥 필요할 때마다 캐스팅을 하는 방식을 사용했다. 코드가 길어진데다가 매 명령마다 캐스팅이 실행되는 문제가 있지만 작동은 확실하게 보장된다.
플레이어 스테이트를 쓰는 다른 코드도 이런 방식을 사용하도록 수정하니 문제가 발생하지 않았다.
● 서버는 빌드된 데이터만 사용한다
문제 해결을 위한 조사를 해보니 알게 된 사실인데, 클라이언트 실행에서는 네비게이션이 올바르게 동작하는 것은 사실 에디터라서 그런 것이다. 에디터에서는 현 상황을 즉시 반영할 수 있어서 그냥 네비메시 볼륨을 얹어놓기만 해도 작동했다. 하지만 서버에게는 빌드된 데이터가 없으니 제대로 작동하지 않는 문제임을 알게 되었다. 지금은 네비 시스템 관련 빌드가 없는 것이니 해당 빌드만 실행하면 될 것이다.

에디터 화면 상단에 있는 '빌드'를 눌러서 나오는 메뉴에서 '빌드 경로'를 선택하면된다.

다시 올바르게 작동하는 것을 볼 수 있다.
● 또 다른 문제를 발견했다
아무래도 데이케이티드 서버가 아닌 리슨 서버로 구동했던 탓인지 착각한 것이 또 발견됐다. 이번에는 달리기를 시도하면 계속 달리기를 멈추고 빠른 이동이 안되는 현상이다.
private:
//서버에게 모든 클라이언트에 있는 이 캐릭터의 이동 속도 변경 요청
UFUNCTION(Server, Reliable)
void ChangeMoveSpeed_Server(const bool bRun);
//서버에서 실행, 각 클라이언트에서 이동 속도 변경
UFUNCTION(NetMulticast, Reliable)
void ChangeMoveSpeed_NetMulticast(const bool bRun);
void AMainGameCharacter::DoRunStart()
{
ChangeMoveSpeed_Server(true);
}
void AMainGameCharacter::DoRunEnd()
{
ChangeMoveSpeed_Server(false);
}
void AMainGameCharacter::ChangeMoveSpeed_Server_Implementation(const bool bRun)
{
ChangeMoveSpeed_NetMulticast(bRun);
}
void AMainGameCharacter::ChangeMoveSpeed_NetMulticast_Implementation(const bool bRun)
{
GetCharacterMovement()->MaxWalkSpeed = bRun ? MovementSpeed.Y : MovementSpeed.X;
}
이에 대하여 내가 선택한 해결 방법은 서버와 클라이언트에서 동시에 이동 속도를 변경하는 것이다. 기존의 시프트 키로 이동 속도를 직접 변경하는 것 대신 속도 변경을 서버에 요청하게 만들고 서버에서는 NetMulticast 함수를 실행한다. 이 NetMulticast 함수에서 이동 속도를 변경한다. NetMulticast 함수는 서버에서 호출하면 서버 및 모든 클라이언트에서 실행되는 것을 이용했다.
이전에는 구현한 것이 의도대로 작동한 이유가 리슨 서버였기 때문이었다. 그래서 내가 한 것이 올바르다고 착각했다. 하지만 지금과 같이 데디케이티스 서버와 클라이언트로 이루어진 구성이라면 당연히 내가 구현했던 기능이 안되는 것이 당연하다. 데디케이티스 서버 방식일 때 클라이언트에서만 변경하면 서버에서 변경될 리가 없고 더 나아가 다른 클라이언트에서도 변경되지 않을 것이다.
● 이제 원래 하려던 것을 해보자
문제를 해결했으니 하려던 것을 계속 해본다. 2명이 접속하면 잠시 후 둘 중 하나가 경찰이 되어 공격이 가능한지 확인해본다.

예상한대로, 1명만이 공격이 가능하고 그 1명은 다른 하나와 모든 NPC를 죽일 수 있다.
● 더 많은 플레이어 추가

지금까지는 계속 2명으로 테스트 했고 그에 따라 플레이어 스타트가 2개였다. 3명일 때를 위해 플레이어 스타트를 하나 더 추가할 수도 있지만 그러지 말고 무작위 위치에서 시작하도록 만드려고 한다. 그래서 플레이어 스타트를 삭제했다.
플레이어 스타트를 지운 상태로 시작한다면 플레이어 시작 위치는 무조건 (0,0,0)이 된다. 그러면 시작하자 마자 무작위 위치로 옮기면 될 것이다.
private:
//(서버용) 무작위 시작 위치
void RandomStartPosition();
void AMainGameCharacter::BeginPlay()
{
Super::BeginPlay();
CurrentAttackDelay = 0.0f;
bIsDeath = false;
RandomStartPosition();
}
void AMainGameCharacter::RandomStartPosition()
{
//서버용 함수
if (GetNetMode() == NM_Client)
{
return;
}
//네비게이션 시스템을 이용하여 무작위 위치를 지정하고 그 위치로 이동한다.
FNavLocation NewLocation;
UNavigationSystemV1* NavigationSystem = UNavigationSystemV1::GetNavigationSystem(GetWorld());;
if (IsValid(NavigationSystem) && NavigationSystem->GetRandomPoint(NewLocation))
{
SetActorLocation(NewLocation.Location + FVector(0, 0, 90));
}
}
내가 구현한 방법은 네비게이션 시스템을 이용하는 것이다. 단순한 하드 코딩으로 무작위 좌표값 범위를 지정해줄 수도 있지만 그렇게 하는 것은 공부하는 것이 아니다. 그리고 작성한 코드 그대로 다른 맵에다 옮겨놔도 적용이 가능한 코드를 만드는 것이 프로그래머가 갖춰야 할 능력이다.
NPC 생성 액터나 AI캐릭터 클래스 내에서 네비게이션 시스템을 가져온 것처럼 플레이어 캐릭터도 네비게이션 시스템을 가져온다. 다만 제일 처음 시작시 무작위 위치를 정할 때 외에는 쓸 일이 없으니 따로 캐싱용 변수를 선언하지 않고 함수내 지역 변수를 사용한다.
그리고 캐릭터 조종의 주체는 클라이언트라고 해도 캐릭터의 주인은 서버라서 서버에서 위치 변경을 해야 한다. 그래서 무작위 위치 를 정하는 단계 앞에서 클라이언트인지 확인하고 맞다면 실행을 거부하도록 했다.

테스트 해보니 매 실행마다 다른 위치로 가는 것을 확인했다.

그리고 동시에 여러명 접속해보니 서로 다른 위치에서 시작함을 확인했다.

접속한 플레이어가 많아도 경찰은 1명이다. 즉, 공격이 가능한 플레이어는 1명 뿐임을 확인했다.
'언리얼엔진 학습 및 과제' 카테고리의 다른 글
| 액터 리플리케이션을 이용한 게임 제작 (12) (0) | 2026.01.14 |
|---|---|
| 액터 리플리케이션을 이용한 게임 제작 (11) (1) | 2026.01.09 |
| 액터 리플리케이션을 이용한 게임 제작 (9) (0) | 2026.01.06 |
| 액터 리플리케이션을 이용한 게임 제작 (8) (0) | 2026.01.05 |
| 액터 리플리케이션을 이용한 게임 제작 (7) (0) | 2025.12.26 |