언리얼엔진/개념·이론

[Unreal Engine 네트워크] HasAuthority()와 IsLocallyControlled()는 무엇이 다른가?

write76465 2026. 7. 15. 20:52

Unreal Engine 멀티플레이를 처음 구현할 때 가장 혼동하기 쉬운 함수가 다음 두 가지다.

HasAuthority()
IsLocallyControlled()

두 함수 모두 “이 코드를 여기서 실행해도 되는가?”를 판단할 때 사용하지만, 확인하는 대상은 완전히 다르다.

  • HasAuthority()는 Actor의 상태를 결정할 권한을 확인한다.
  • IsLocallyControlled()는 Pawn을 현재 컴퓨터에서 직접 조종하는지 확인한다.

이 차이를 명확히 이해하지 못하면 다음과 같은 문제가 발생한다.

  • 클라이언트에서만 Actor가 사라짐
  • 호스트에서는 UI가 보이지만 원격 클라이언트에서는 보이지 않음
  • 모든 플레이어 화면에 동일한 UI가 나타남
  • 서버와 클라이언트의 Actor 상태가 달라짐
  • 리슨 서버에서는 정상인데 전용 서버에서 동작하지 않음
  • RPC를 호출했지만 기대한 컴퓨터에서 실행되지 않음

이번 글에서는 두 함수의 정의뿐만 아니라 Authority, Ownership, Local Control이 각각 무엇을 의미하는지 근본적인 구조부터 정리한다.


1. 멀티플레이에서는 Actor가 하나만 존재하지 않는다

싱글플레이에서는 월드에 생성된 캐릭터가 하나의 프로그램 안에 하나만 존재한다.

하지만 네트워크 게임에서는 논리적으로 동일한 캐릭터가 서버와 각 클라이언트에 따로 존재한다.

플레이어 A와 B가 접속한 게임을 예로 들면 다음과 같다.

서버
├─ Player A 캐릭터
└─ Player B 캐릭터

클라이언트 A
├─ Player A 캐릭터 복제본
└─ Player B 캐릭터 복제본

클라이언트 B
├─ Player A 캐릭터 복제본
└─ Player B 캐릭터 복제본

코드상으로는 모두 APlayerCharacter일 수 있지만, 실제로는 서로 다른 프로세스 또는 월드에 존재하는 별개의 C++ 객체다.

따라서 아래 함수가 실행됐다는 사실만으로는 충분하지 않다.

void APlayerCharacter::Die()
{
}

네트워크 환경에서는 다음 질문이 추가로 필요하다.

어느 컴퓨터에서 실행됐는가?
서버의 Actor인가, 클라이언트의 복제본인가?
이 Actor의 권한은 누구에게 있는가?
현재 컴퓨터가 이 Pawn을 직접 조종하는가?

멀티플레이의 어려움은 같은 코드가 서로 다른 역할을 가진 여러 Actor 인스턴스에서 실행될 수 있다는 점에서 시작한다.


2. Authority란 무엇인가?

Authority는 특정 Actor의 최종 상태를 결정할 권한이다.

일반적인 서버 권한형 멀티플레이 게임에서는 서버에 존재하는 Actor가 Authority를 가진다.

if (HasAuthority())
{
    // 이 Actor의 권한을 가진 인스턴스에서 실행
}

HasAuthority()를 단순히 “현재 프로그램이 서버인가?”라고 이해할 수도 있지만, 더 정확한 질문은 다음과 같다.

현재 이 Actor 인스턴스가 이 Actor의 상태를 결정할 권한을 가지고 있는가?

Actor 기준으로 판단한다는 것이 중요하다.

일반적인 복제 Actor라면 다음과 같은 관계가 만들어진다.

서버의 Actor
→ Authority 보유

클라이언트의 Actor 복제본
→ Authority 없음

이를 코드로 확인하면 다음과 같다.

if (HasAuthority())
{
    UE_LOG(LogTemp, Warning, TEXT("Authority Actor"));
}
else
{
    UE_LOG(LogTemp, Warning, TEXT("Remote Actor Copy"));
}

3. 왜 서버가 Authority를 가져야 하는가?

멀티플레이 게임에서 모든 클라이언트가 상태를 마음대로 결정할 수 있다면 동일한 게임 상태를 유지할 수 없다.

예를 들어 클라이언트가 자신의 체력을 직접 수정할 수 있다고 가정해 보자.

CurrentHealth = 999999.f;

서버 검증 없이 이 값이 인정된다면 치팅에 매우 취약해진다. 또한 서로 다른 클라이언트가 각자 다른 값을 가지고 있을 수도 있다.

따라서 중요한 게임 상태는 서버가 결정한다.

대표적인 서버 권한 로직은 다음과 같다.

  • 피해량 계산
  • 체력 변경
  • 사망 판정
  • 탄약 소비
  • 아이템 획득
  • 점수 및 킬 카운트
  • Actor 생성과 제거
  • 충돌 판정
  • 리스폰
  • 게임 승패 판정
void ACharacter::ApplyDamage(float Damage)
{
    if (!HasAuthority())
    {
        return;
    }

    CurrentHealth -= Damage;

    if (CurrentHealth <= 0.f)
    {
        Die();
    }
}

서버가 값을 확정한 뒤, 필요한 상태를 클라이언트에 복제한다.

클라이언트 입력
→ 서버에 요청
→ 서버가 유효성 검사
→ 서버가 상태 변경
→ 변경된 상태를 클라이언트에 복제

4. IsLocallyControlled()란 무엇인가?

IsLocallyControlled()는 Pawn 또는 Character가 현재 컴퓨터의 로컬 Controller에 의해 조종되고 있는지 확인한다.

if (IsLocallyControlled())
{
    // 이 컴퓨터에서 직접 조종하는 Pawn
}

클라이언트 A에는 자신의 캐릭터뿐만 아니라 다른 플레이어의 캐릭터 복제본도 존재한다.

클라이언트 A

Player A 캐릭터
→ 클라이언트 A가 직접 조종
→ IsLocallyControlled() == true

Player B 캐릭터
→ 네트워크를 통해 관찰하는 복제본
→ IsLocallyControlled() == false

따라서 Local Control은 서버 권한이 아니라 입력과 화면의 주체를 구분하기 위한 개념이다.

다음과 같은 로직에 주로 사용된다.

  • HUD 생성
  • 개인 결과 UI
  • 카메라 흔들림
  • 크로스헤어 표시
  • 로컬 입력 처리
  • 마우스 커서 변경
  • 카메라 FOV 변경
  • 로컬 전용 사운드
  • 1인칭 전용 메시 표시
if (IsLocallyControlled())
{
    ShowCrosshair();
    ApplyCameraRecoil();
}

다른 플레이어가 총을 발사했다고 내 화면의 카메라까지 흔들리면 안 되기 때문에 이러한 로컬 구분이 필요하다.


5. 두 함수는 반대 조건이 아니다

가장 흔한 오해는 다음과 같다.

HasAuthority() == 서버
IsLocallyControlled() == 클라이언트

이렇게 외우면 리슨 서버에서 문제가 생긴다.

HasAuthority()와 IsLocallyControlled()는 반대되는 개념이 아니다. 서로 다른 기준을 검사한다.

HasAuthority()
→ 상태를 결정할 권한이 있는가?

IsLocallyControlled()
→ 현재 컴퓨터에서 직접 조종하는가?

따라서 두 값이 동시에 참일 수도 있고 동시에 거짓일 수도 있다.

실행 환경대상 PawnHasAuthority()IsLocallyControlled()
리슨 서버 호스트의 Pawn True True
리슨 서버 원격 플레이어 Pawn True False
클라이언트 A 자신의 Pawn False True
클라이언트 A 다른 플레이어 Pawn False False
전용 서버 모든 플레이어 Pawn True False

이 표는 Unreal 네트워크 코드를 이해하는 데 매우 중요하다.


6. 리슨 서버에서 특히 혼동되는 이유

리슨 서버는 서버 역할을 수행하면서 동시에 로컬 플레이어도 가지고 있다.

따라서 호스트의 Pawn은 다음 조건을 모두 만족한다.

HasAuthority() == true
IsLocallyControlled() == true

이 때문에 잘못된 코드도 호스트에서는 정상처럼 보일 수 있다.

예를 들어 UI를 Authority 조건 안에서 생성했다고 가정해 보자.

if (HasAuthority())
{
    CreateResultWidget();
}

리슨 서버 호스트는 Authority를 가지면서 화면도 존재하므로 UI가 정상적으로 나타난다.

그러나 원격 클라이언트의 Pawn은 Authority가 없다.

리슨 서버 호스트
HasAuthority = true
→ UI 표시

원격 클라이언트
HasAuthority = false
→ UI 표시 안 됨

개발자가 호스트 화면만 확인하면 이 코드가 정상이라고 오해할 수 있다.

전용 서버에서는 화면 자체가 없으므로 Authority 조건 안에서 Widget을 생성해도 사용자에게 표시할 방법이 없다.


7. 전용 서버와 리슨 서버의 차이

리슨 서버

한 플레이어의 게임 프로세스가 서버와 클라이언트 역할을 동시에 수행한다.

리슨 서버 프로세스
├─ 서버 역할
└─ 호스트 플레이어의 로컬 화면과 입력

장점은 테스트와 소규모 멀티플레이 구성이 간단하다는 것이다. 단점은 Authority와 Local Control이 같은 프로세스에 존재하기 때문에 잘못된 실행 위치를 발견하기 어렵다는 것이다.

전용 서버

플레이어의 화면이나 입력 없이 서버 역할만 수행한다.

전용 서버
├─ 게임 규칙
├─ Actor 상태
├─ 판정
└─ 복제

화면 없음
로컬 플레이어 없음

전용 서버의 Pawn은 일반적으로 다음 상태다.

HasAuthority() == true
IsLocallyControlled() == false

따라서 UI와 카메라 코드는 전용 서버에서 실행할 필요가 없다.


8. Authority, Ownership, Local Control은 서로 다르다

Unreal 네트워크를 이해하기 어려운 가장 큰 이유는 비슷해 보이는 세 개념이 따로 존재하기 때문이다.

Authority

Actor의 최종 상태를 결정할 권한이다.

누가 상태를 변경할 수 있는가?

일반적으로 서버가 가진다.

Ownership

네트워크상에서 특정 Actor가 어느 Connection에 속하는지를 나타낸다.

이 Actor의 소유 클라이언트는 누구인가?

Ownership은 RPC 전달 방향과 연관된다. 특히 Client RPC와 Server RPC가 어느 연결을 통해 전달될지 결정할 때 중요하다.

Local Control

현재 컴퓨터에서 해당 Pawn을 직접 조종하는지를 나타낸다.

이 Pawn의 입력과 카메라가 현재 컴퓨터에 있는가?

이 세 개념을 한 문장으로 정리하면 다음과 같다.

Authority
→ 결정권

Ownership
→ 네트워크 연결의 소유 관계

Local Control
→ 현재 컴퓨터의 조작 관계

9. Authority와 Ownership이 다른 사례

서버는 모든 캐릭터에 대한 Authority를 가진다. 하지만 각각의 캐릭터는 서로 다른 클라이언트 Connection에 소유될 수 있다.

서버
├─ Player A Pawn — Authority 있음, Client A 소유
└─ Player B Pawn — Authority 있음, Client B 소유

따라서 서버가 Player A의 Controller에 Client RPC를 호출하면 Client A에 전달되고, Player B에게 호출하면 Client B에 전달된다.

UFUNCTION(Client, Reliable)
void Client_OpenResultUI();
PlayerAController->Client_OpenResultUI();

Authority는 서버에 있지만, RPC가 도착할 클라이언트는 Ownership으로 결정된다.


10. UI에는 왜 IsLocallyControlled()가 적합한가?

UI는 일반적으로 각 플레이어의 화면에만 존재해야 한다.

if (IsLocallyControlled())
{
    OpenInventoryUI();
}

클라이언트의 월드에는 여러 Pawn이 존재하더라도 현재 화면의 UI를 조작해야 하는 것은 로컬 Pawn뿐이다.

예를 들어 사망 UI를 아무 조건 없이 실행하면 다른 플레이어가 사망할 때도 내 화면에 결과 UI가 뜰 수 있다.

void ACharacter::OnDeath()
{
    OpenDeathUI(); // 모든 복제 Character에서 실행될 가능성
}

로컬 조건을 추가하면 자신의 Pawn에 대해서만 실행된다.

void ACharacter::OnDeath()
{
    if (IsLocallyControlled())
    {
        OpenDeathUI();
    }
}

다만 게임 설계에 따라 서버가 소유 PlayerController에 Client RPC를 보내 UI를 열도록 구성할 수도 있다.

if (HasAuthority())
{
    PlayerController->Client_OpenDeathUI();
}

두 방식 모두 가능하지만 책임을 섞지 않는 것이 중요하다.


11. Client RPC와 로컬 함수는 구분하는 것이 좋다

다음 함수가 있다고 가정해 보자.

UFUNCTION(Client, Reliable)
void Client_OpenResultUI();

Client RPC의 가장 명확한 사용 방식은 서버에서 소유 클라이언트를 대상으로 호출하는 것이다.

if (HasAuthority())
{
    PlayerController->Client_OpenResultUI();
}

실제 Widget 생성은 클라이언트의 _Implementation()에서 처리한다.

void AMyPlayerController::Client_OpenResultUI_Implementation()
{
    CreateResultWidget();
}

반면 이미 소유 클라이언트에서 실행 중이고 RPC가 필요 없다면 일반 로컬 함수를 분리하는 편이 의미가 명확하다.

void AMyPlayerController::OpenResultUILocal()
{
    CreateResultWidget();
}
if (IsLocallyControlled())
{
    PlayerController->OpenResultUILocal();
}

권장 구조는 다음처럼 역할을 분리하는 것이다.

UFUNCTION(Client, Reliable)
void Client_OpenResultUI();

void OpenResultUILocal();
void AMyPlayerController::Client_OpenResultUI_Implementation()
{
    OpenResultUILocal();
}

이렇게 하면 서버는 Client RPC를 호출하고, 클라이언트 내부에서는 로컬 함수를 재사용할 수 있다.


12. Actor 제거에는 왜 Authority가 필요한가?

복제 Actor를 클라이언트에서만 제거하면 서버에는 여전히 Actor가 존재할 수 있다.

if (IsLocallyControlled())
{
    Weapon->Destroy(); // 올바른 네트워크 생명주기가 아님
}

클라이언트에서는 무기가 사라진 것처럼 보이더라도 서버와 다른 클라이언트에는 계속 존재할 수 있다.

복제 Actor의 생성과 제거는 서버가 담당해야 한다.

if (HasAuthority() && IsValid(Weapon))
{
    Weapon->Destroy();
    Weapon = nullptr;
}

정상적인 Actor 생명주기는 다음과 같다.

서버가 Actor 생성
→ 클라이언트에 생성 복제
→ 서버가 Actor 제거
→ 클라이언트에 제거 복제

클라이언트가 시각적으로 먼저 감춰야 한다면 로컬 Hide와 서버 Destroy를 함께 사용할 수 있지만, 두 작업의 목적은 다르다.

Hide
→ 즉각적인 시각 처리

Destroy
→ 네트워크 Actor의 실제 생명주기 종료

13. SetVisibility()와 Destroy()의 차이

다음 두 코드는 의미가 완전히 다르다.

MeshComponent->SetVisibility(false);
Actor->Destroy();

SetVisibility(false)

  • Component가 화면에 렌더링되지 않도록 한다.
  • Actor는 여전히 월드에 존재한다.
  • Tick, Controller, 네트워크 참조 등이 유지될 수 있다.
  • 충돌은 별도로 끄지 않으면 유지될 수 있다.

Destroy()

  • Actor의 생명주기를 종료한다.
  • 서버에서 복제 Actor를 제거하면 클라이언트에도 제거가 전달된다.
  • 기존 참조는 유효성을 다시 확인해야 한다.
  • Pawn을 제거하면 Controller의 Possess 관계에도 영향을 줄 수 있다.

따라서 사라져 보이게 하는 것과 실제로 제거하는 것은 별개의 설계 결정이다.


14. Replication은 함수를 자동으로 실행해 주는 시스템이 아니다

변수를 Replicated로 설정하면 서버의 값이 클라이언트에 전달된다.

UPROPERTY(ReplicatedUsing = OnRep_IsDead)
bool bIsDead;
void ACharacter::OnRep_IsDead()
{
    if (bIsDead)
    {
        PlayDeathAnimation();
    }
}

여기서 복제되는 것은 bIsDead라는 상태다. 서버에서 실행된 PlayDeathAnimation() 함수 자체가 자동으로 클라이언트에서 다시 실행되는 것은 아니다.

따라서 네트워크 코드는 보통 다음과 같이 구성한다.

서버가 상태 변경
→ 상태 복제
→ 클라이언트의 OnRep 함수 실행
→ 클라이언트가 상태에 맞는 연출 실행

이러한 상태 기반 구조는 RPC 한 번에 모든 것을 의존하는 것보다 뒤늦게 접속한 클라이언트나 네트워크 지연 상황에 대응하기 쉽다.


15. OnRep의 역할

OnRep 함수는 복제 변수가 클라이언트에서 변경됐을 때 후속 처리를 실행하기 위해 사용한다.

UPROPERTY(ReplicatedUsing = OnRep_Health)
float CurrentHealth;
UFUNCTION()
void OnRep_Health();
void ACharacter::OnRep_Health()
{
    UpdateHealthBar();
}

대표적인 사용 사례는 다음과 같다.

  • 체력 UI 갱신
  • 사망 애니메이션 재생
  • 장착 무기 외형 변경
  • 캐릭터 상태 이펙트 적용
  • 문 개폐 상태 반영

서버는 실제 값을 결정하고 클라이언트는 복제된 값을 기반으로 시각적 결과를 구성한다.


16. RPC와 Replication의 차이

Replication

현재 상태를 전달한다.

현재 체력은 70이다.
현재 사망 상태는 true다.
현재 장착 무기는 Rifle이다.

상태가 중요한 경우에 적합하다.

RPC

특정 동작 또는 이벤트의 실행을 요청한다.

서버에 발사를 요청한다.
소유 클라이언트에게 UI를 열도록 요청한다.
모든 클라이언트에게 폭발 이펙트를 재생하도록 요청한다.

RPC 종류는 다음과 같다.

UFUNCTION(Server, Reliable)
void Server_Fire();

UFUNCTION(Client, Reliable)
void Client_OpenUI();

UFUNCTION(NetMulticast, Unreliable)
void Multicast_PlayFireEffect();

간단히 정리하면 다음과 같다.

방식의미
Replication 현재 상태 전달
Server RPC 클라이언트가 서버에 요청
Client RPC 서버가 특정 소유 클라이언트에 전달
Multicast RPC 서버가 관련 인스턴스 전체에 이벤트 전달

17. Reliable을 항상 사용하면 안 되는 이유

RPC에 Reliable을 붙이면 반드시 전달돼야 하는 메시지로 처리된다.

UFUNCTION(Server, Reliable)
void Server_ConfirmPurchase();

구매, 리스폰 요청, 중요한 상태 전환처럼 누락되면 안 되는 이벤트에 적합하다.

하지만 총알 트레이서나 반복 발소리처럼 빈번하게 발생하고 한두 번 누락돼도 게임 상태에 영향이 없는 이벤트에 Reliable을 남용하면 네트워크 큐에 부담이 생긴다.

UFUNCTION(NetMulticast, Unreliable)
void Multicast_PlayTracerEffect();

기준은 다음과 같다.

누락되면 게임 상태가 깨지는가?
→ Reliable 고려

누락돼도 다음 프레임이나 다음 효과로 복구되는가?
→ Unreliable 고려

18. 네트워크 디버깅이 어려운 근본적인 이유

멀티플레이 버그는 단일 실행 흐름으로 재현되지 않는다.

동일한 함수가 서버와 여러 클라이언트에서 각각 실행될 수 있으며, 로그도 서로 다른 프로세스에 출력될 수 있다.

따라서 단순히 아래 로그만 남기면 정보가 부족하다.

UE_LOG(LogTemp, Warning, TEXT("Function Called"));

다음 정보를 함께 출력하는 것이 좋다.

UE_LOG(
    LogTemp,
    Warning,
    TEXT(
        "Actor=%s | Authority=%d | Local=%d | LocalRole=%d | RemoteRole=%d"
    ),
    *GetName(),
    HasAuthority(),
    IsLocallyControlled(),
    static_cast<int32>(GetLocalRole()),
    static_cast<int32>(GetRemoteRole())
);

PlayerController나 네트워크 모드를 추가로 출력할 수도 있다.

UE_LOG(
    LogTemp,
    Warning,
    TEXT("NetMode=%d | Controller=%s"),
    static_cast<int32>(GetNetMode()),
    *GetNameSafe(GetController())
);

이렇게 해야 다음을 구분할 수 있다.

  • 서버에서 실행된 로그
  • 소유 클라이언트에서 실행된 로그
  • 다른 플레이어의 복제본에서 실행된 로그
  • 리슨 서버 호스트에서 실행된 로그

19. 자주 발생하는 실수

UI를 Authority 조건으로만 처리

if (HasAuthority())
{
    OpenUI();
}

리슨 서버 호스트에서는 보일 수 있지만 원격 클라이언트 화면에는 표시되지 않을 수 있다.

Actor 상태를 Local Control 조건으로 변경

if (IsLocallyControlled())
{
    CurrentHealth = 0.f;
}

클라이언트 로컬 값만 변경되고 서버의 권위 상태와 충돌할 수 있다.

Multicast RPC를 클라이언트에서 호출

Multicast_PlayEffect();

NetMulticast RPC는 일반적으로 서버에서 호출해야 관련 클라이언트로 전달된다. 클라이언트에서 호출하면 전체에 전파되지 않고 로컬 실행에 그칠 수 있다.

리슨 서버 호스트에서만 테스트

호스트 Pawn은 Authority와 Local Control을 동시에 가지므로 실행 위치가 잘못된 코드를 발견하기 어렵다.

모든 것을 Reliable RPC로 구현

빈번한 연출 이벤트까지 Reliable로 전송하면 패킷 지연과 RPC 큐 적체를 유발할 수 있다.


20. 네트워크 로직을 작성할 때 먼저 물어야 할 질문

코드부터 작성하기 전에 해당 기능의 책임을 정해야 한다.

첫 번째 질문

이 값이 게임 결과에 영향을 주는가?

그렇다면 서버 Authority가 결정해야 한다.

체력, 피해, 탄약, 아이템, 점수, 사망, 리스폰
→ 서버

두 번째 질문

특정 플레이어의 화면에만 필요한가?

그렇다면 Local Control 또는 소유 Client RPC가 적합하다.

HUD, 카메라, 입력 모드, 개인 결과 UI
→ 소유 클라이언트

세 번째 질문

다른 플레이어에게도 보여야 하는 연출인가?

복제 변수, OnRep 또는 서버 Multicast RPC를 고려한다.

사망 애니메이션, 발사 이펙트, 폭발, 피격 연출
→ 관련 클라이언트

네 번째 질문

현재 상태인가, 순간 이벤트인가?

상태
→ Replication

순간 이벤트
→ RPC 또는 복제 상태 변경에 대한 OnRep

21. 권장 책임 분리 예시

void ACharacter::Die()
{
    if (!HasAuthority())
    {
        return;
    }

    bIsDead = true;
    OnRep_IsDead();

    AMyPlayerController* MyController =
        Cast<AMyPlayerController>(GetController());

    if (IsValid(MyController))
    {
        MyController->Client_OpenDeathUI();
    }
}
void ACharacter::OnRep_IsDead()
{
    if (bIsDead)
    {
        PlayDeathAnimation();
    }
}
void AMyPlayerController::Client_OpenDeathUI_Implementation()
{
    OpenDeathUILocal();
}

이 구조에서는 책임이 분명하다.

서버
→ 사망 상태 결정

Replication과 OnRep
→ 모든 관련 화면에 사망 상태 반영

Client RPC
→ 소유 플레이어에게만 UI 표시

다만 사망 애니메이션이 끝난 뒤 UI를 열어야 한다면 서버 시간 기준으로 UI를 요청하거나, 소유 클라이언트가 복제 상태를 받은 뒤 로컬 연출 종료 시 UI를 표시하는 구조를 선택할 수 있다.

어느 방식이든 중요한 것은 실행 주체를 의도적으로 결정하는 것이다.


마무리

HasAuthority()와 IsLocallyControlled()는 모두 조건문에 사용되지만 해결하는 문제가 다르다.

HasAuthority()
→ 누가 게임 상태를 결정하는가?

IsLocallyControlled()
→ 누가 이 Pawn을 현재 컴퓨터에서 조종하는가?

그리고 여기에 Ownership까지 더해진다.

Ownership
→ 이 Actor가 어느 클라이언트 Connection에 속하는가?

멀티플레이 개발에서 어려운 점은 문법이 아니라 하나의 기능이 여러 컴퓨터에 걸쳐 실행된다는 사실이다. 같은 Actor 클래스와 같은 함수라도 실행 위치에 따라 역할과 결과가 달라진다.

따라서 네트워크 코드를 작성할 때는 항상 다음을 먼저 정해야 한다.

누가 결정하는가?
누구에게 보여야 하는가?
누가 입력하는가?
어느 인스턴스에서 실행되는가?
결과를 상태로 복제할 것인가, 이벤트로 전달할 것인가?

이 기준을 명확히 하면 Authority, Local Control, Ownership, RPC, Replication을 개별 문법이 아니라 하나의 네트워크 책임 모델로 이해할 수 있다.