개인 연습 프로젝트, 알고리즘 2문제
NetDriver->ServerConnection == nullptr
클라이언트는 서버로 가는 단 하나의 Serverconnection을 가지고, 서버는 접속한 클라이언트마다 ClientConnections에 UNetConnection을 하나씩 가진다.
ServerConnection == nullptr은 서버라고 판단 할 수 있다.
단, Standalone 모드에서는 NetDriver 자체가 없을 수 있다. RistenServer도 ServerConnection == nullptr이기때문에 무조건 nullptr만 보고 서버라고 판단하면 위험하다.
NetDriver는 서버에도 있고 클라이언트에도 있다. 서버 NetDriver는 클라이언트들을 받기 위해 있고, 클라이언트 NetDriver는 서버와 통신하기 위해 있다.
GetWorld()->GetNetMode()
// 결과는 아래와 같다.
NM_Standalone 네트워크 없이 혼자 실행
NM_Client 클라이언트
NM_ListenServer 플레이어가 직접 호스트하는 서버
NM_DedicatedServer 전용 서버
서버 전용 프로그램이고 화면 렌더링도 없다.
한 플레이어의 게임 클라이언트가 동시에 서버 역할도 한다. 즉 호스트는 플레이어이면서 서버다.
Minecraft server.jar로 열면 게임 클라이언트가 동시에 서버 역할도하며, 호스트는 플레이어면서 서버다. 하지만 Dedicated Server처럼 구동기로 연다. 이건 어떤 상태일까?
“내 싱글 월드를 열고 친구들이 접속하는 구조”에 더 가깝다.
그러므로 Listen Server다.
-> 클라이언트는 서버로부터 오브젝트의 상태를 받아 업데이트합니다. 서버에서 처리해서 뿌려주고 다시 서버로 보낼 수 있다.
-> 서버로부터 받기만 하고 보낼 수는 없다.
APlayerController::BeginPlay에서 NetRole을 확인해보면 SimulatedProxy로 보인다. 하지만 다음 APlayerController::PossessedBy에서는 AutonomousProxy로 표시된다.
클라이언트의 Pawn은 처음 생성될 때 바로 AutonomousProxy가 아닐 수 있다. 특히 BeginPlay() 시점에는 아직 possession 정보나 owner connection 정보가 완전히 반영되지 않아서 SimulatedProxy처럼 보일 수 있다.
이건 다음과 같은 흐름으로 진행된다.
서버에서 Pawn 생성
-> 서버에서 Controller가 Pawn을 Possess
-> 서버 Pawn은 Authority
-> 소유 클라이언트로 possession 정보 복제
-> 소유 클라이언트의 Pawn이 AutonomousProxy가 됨
-> 다른 클라이언트의 Pawn은 SimulatedProxy
HasAuthority()는 단순히 “서버인가?”를 확인하는 함수가 아니다.
정확히는 현재 실행 중인 Actor 인스턴스가 이 Actor의 최종 상태를 결정할 권한을 가지고 있는지 확인하는 함수다.
멀티플레이에서는 같은 Actor가 서버와 클라이언트에 각각 존재할 수 있다.
서버의 Monster → 원본
클라이언트의 Monster → 복사본
보통 서버가 Actor의 진짜 상태를 결정하므로, 서버 쪽 Actor는 HasAuthority()가 true, 클라이언트 복사본은 false가 된다.
if (HasAuthority())
{
// HP 감소, 죽음 판정, 보상 지급, 스폰, Destroy 같은 최종 판정 처리
}
중요한 점은 HasAuthority()가 무조건 서버 체크는 아니라는 것이다.
클라이언트에서 로컬로만 만든 Actor라면 그 클라이언트 안에서는 HasAuthority()가 true일 수 있다.
하지만 서버가 모르는 Actor이므로 멀티플레이 전체에서 인정되는 상태는 아니다.
정리하면:
HasAuthority()
= 이 Actor의 원본이 지금 여기 있는가?
IsLocallyControlled()
= 이 Pawn이 내 입력으로 조작되는 대상인가?
따라서 게임의 진짜 판정은 HasAuthority()에서 처리하고, 입력·카메라·UI 같은 로컬 처리는 IsLocallyControlled() 기준으로 처리한다.
언리얼에서 LocalRole과 RemoteRole을 나눈 이유는 같은 Actor를 보는 위치가 서버냐 클라이언트냐에 따라 역할이 달라지기 때문이다.
LocalRole은 지금 이 코드가 실행되는 머신에서 이 Actor가 어떤 역할인지를 나타낸다.
반대로 RemoteRole은 반대편 클라이언트에서 이 Actor가 어떤 역할로 존재하는지를 나타낸다.
예를 들어 서버에서 몬스터를 보면:
서버의 Monster
LocalRole = Authority
RemoteRole = SimulatedProxy
즉, 서버 입장에서는 “나는 원본이고, 클라이언트들은 이 Actor를 복사본으로 따라간다”는 뜻이다.
반대로 클라이언트에서 같은 몬스터를 보면:
클라이언트의 Monster
LocalRole = SimulatedProxy
RemoteRole = Authority
즉, 클라이언트 입장에서는 “나는 복사본이고, 원본은 서버에 있다”는 뜻이다.
이렇게 나눈 이유는 단순히 서버/클라를 구분하려는 게 아니라, 현재 실행 중인 Actor 인스턴스가 원본인지, 입력 가능한 복사본인지, 그냥 따라가는 복사본인지 구분하기 위해서다.
Authority
→ 원본. 최종 상태 결정 가능
AutonomousProxy
→ 클라이언트가 직접 조작하는 복사본
SimulatedProxy
→ 서버 상태를 받아 따라가는 복사본
특히 플레이어 캐릭터는 서버가 최종 권한을 가지지만, 소유한 클라이언트도 입력과 예측을 해야 하므로 AutonomousProxy 가 된다. 반면 몬스터나 다른 플레이어 캐릭터는 내 클라이언트 입장에서 직접 조작하지 않으므로 보통 SimulatedProxy가 된다.
LocalRole
= 지금 내 입장에서 이 Actor의 역할
RemoteRole
= 반대편에서 이 Actor가 가지는 역할
결국 이 둘을 나눈 이유는 같은 Actor라도 서버, 소유 클라이언트, 다른 클라이언트에서 해야 할 일이 다르기 때문이다.
서버는 판정하고, 소유 클라이언트는 입력과 예측을 하고, 다른 클라이언트는 결과를 따라간다.