네트워크 시스템의 HLAPI에서 플레이어는 특수한 종류의 오브젝트입니다. 이 오브젝트는 서버 상에 위치한 플레이어를 나타내므로, 클라이언트에서 서버로 보내는 보안 원격 프로시저 호출인 커맨드를 실행할 수 있습니다. 서버 권한 시스템에서 다른 비플레이어 서버측 오브젝트는 오브젝트에서 클라이언트로 직접 커맨드를 받을 수 없습니다. 이는 보안을 유지하고 분산된 환경에서 작업의 복잡성을 줄이기 위함입니다. 플레이어 오브젝트를 통해 사용자의 입력 커맨드를 수신하면 메시지가 올바른 위치에서 올바른 클라이언트에게 수신되도록 하며 중앙에서 처리할 수 있게 됩니다.
NetworkManager를 사용하면 클라이언트가 서버에 연결되는 경우 자동으로 플레이어가 추가됩니다. 하지만 몇몇 상황에서는 입력 이벤트가 발생하기 전까지 플레이어 추가가 지연되야 하는 경우가 있으므로, NetworkManager의 AutoCreatePlayer 체크박스를 해제하여 이 동작을 끌 수 있습니다. 플레이어가 추가되면 PlayerPrefab에서 오브젝트를 인스턴스화하며 연결과 연관을 짓게 됩니다. 이는 실제로 NetworkManager가 NetworkServer.AddPlayerForConnection를 호출하여 이루어집니다. 이 동작은 NetworkManager.OnServerAddPlayer를 오버라이드하는 방법으로 수정할 수 있습니다. OnServerAddPlayer 디폴트 구현은 PlayerPrefab에서 새로운 플레이어 인스턴스를 인스턴스화하며 NetworkServer.AddPlayerForConnection를 호출하여 새로운 플레이어 인스턴스를 스폰합니다. OnServerAddPlayer 커스텀 구현 역시 NetworkServer.AddPlayerForConnection를 호출해야 하지만, 필요한 다른 초기화 과정 역시 수행할 수 있습니다. 아래는 플레이어의 컬러를 커스텀화하는 예시입니다.
class Player : NetworkBehaviour
{
[SyncVar]
public Color color;
}
class MyManager : NetworkManager
{
public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId)
{
GameObject player = (GameObject)Instantiate(playerPrefab, Vector3.Zero, Quaternion.Identity);
player.GetComponent<Player>().color = Color.Red;
NetworkServer.AddPlayerForConnection(conn, player, playerControllerId);
}
}
OnServerAddPlayer에서 NetworkServer.AddPlayerForConnection 함수를 호출할 필요는 없습니다. 올바른 연결 오브젝트와 playerControllerId가 전달된 경우 OnServerAddPlayer가 반환된 이후에 호출될 수도 있습니다. 이 경우 원격 데이터 소스에서 플레이어 데이터를 로드하는 것과 같은 비동기적 과정을 중간에 실행하도록 할 수 있습니다.
HLAPI는 플레이어와 클라이언트를 별도의 오브젝트로 취급합니다. 대부분의 경우 한 클라이언트에는 한 플레이어가 있습니다. 하지만 다수의 컨트롤러가 한 개의 콘솔 시스템에 연결된 경우와 같은 상황에서는 단일 연결에 다수의 플레이어 오브젝트가 있을 수 있습니다. 이러한 경우 playerControllerId 프로퍼티를 통해 서로를 구분할 수 있습니다. 연결에 대해서만 한정되는 식별자가 존재합니다. 따라서 이 프로퍼티는 해당 클라이언트 플레이어에 관련된 컨트롤러 ID에 매핑됩니다.
서버의 NetworkServer.AddPlayerForConnection에 전달된 플레이어 오브젝트는 시스템에 의해 자동으로 스폰되므로, 플레이어에 대해 NetworkServer.Spawn를 호출할 필요는 없습니다. 플레이어가 준비되면 씬에서 활성화된 NetworkIdentity 오브젝트가 플레이어의 클라이언트에 스폰됩니다. 따라서 게임의 모든 네트워크 오브젝트는 해당 클라이언트에서 최종 상태로 생성되므로, 다른 게임 참여자와 동기화됩니다.
플레이어 오브젝트를 만들기 위해 NetworkManager의 playerPrefab을 사용해야만 하는 것은 아닙니다. 다른 메서드를 사용하여 각각의 플레이어를 생성할 수도 있습니다.
AddPlayerForConnection 함수는 OnServerAddPlayer에서 호출될 필요는 없습니다. 데이터베이스가 어떤 플레이어 유형이 생성되었는지에 대한 정보를 반환하는 것과 같은 다른 서비스에 대한 요청이 진행되는 시점 등에서 비동기적으로 호출할 수도 있기 때문입니다.
플레이어와 더불어 클라이언트 연결 역시 “준비” 상태가 있습니다. 준비된 클라이언트는 스폰된 오브젝트와 상태 동기화 업데이트를 보낸 경우입니다. 준비되지 않은 클라이언트는 이들 업데이트를 보내지 않은 경우입니다. 클라이언트가 최초로 서버에 연결하면 준비된 상태가 아닙니다. 이 경우 클라이언트는 씬 로딩, 아바타 선택, 로그인 상자 입력 등과 같이 서버 시뮬레이션과 실시간으로 상호작용을 하지 않아도 되는 작업을 할 수 있습니다. 클라이언트가 게임 전 작업을 전부 진행하였고 모든 에셋을 로딩한 경우, ClientScene.Ready를 호출하여 준비 상태에 진입할 수 있습니다. 위의 예제 역시 올바르게 작동하는데, NetworkServer.AddPlayerForConnection를 통해 플레이어를 추가하면 클라이언트가 준비 상태가 아닌 경우 준비 상태로 전환하기 때문입니다.
클라이언트는 준비 상태가 아니더라도 네트워크 메시지를 송수신할 수 있으며, 활성화된 플레이어가 없어도 가능합니다. 따라서 메뉴나 선택 화면에 있는 클라이언트 역시 플레이어 오브젝트가 없더라도 게임에 연결하여 상호작용이 가능합니다. 커맨드와 RPC 호출을 사용하지 않고 메시지를 보내는 방법은 아래 섹션에 있습니다.
연결용 플레이어 오브젝트는 NetworkServer.ReplacePlayerForConnection로 대체할 수 있습니다. 이는 게임 이전 로비 화면과 같은 특정 상황에서 플레이어가 내릴 수 있는 커맨드를 제한하는 데 유용합니다. 이 함수는 AddPlayerForConnection와 동일한 인자를 가지지만, 해당 연결에 플레이어가 있더라도 가능하다는 점이 다릅니다. 변경 전 플레이어 오브젝트를 제거할 필요는 없습니다. NetworkLobbyManager는 이 방법을 통해 로비에서 모든 플레이어가 준비되면 LobbyPlayer를 게임플레이 플레이어로 전환합니다.
이 방법은 오브젝트가 제거된 이후 플레이어를 다시 스폰하는 데 사용할 수 있습니다. 일부 경우 오브젝트를 비활성화한 후 재스폰 시점에서 게임 특성을 초기화하는 것이 좋지만, 아래와 같은 코드를 사용하여 새로운 오브젝트로 대체할 수도 있습니다.
class GameManager
{
public void PlayerWasKilled(Player player)
{
var conn = oldPlayer.connectionToClient;
var newPlayer = Instantiate<GameObject>(playerPrefab);
Destroy(oldPlayer.gameObject);
NetworkServer.ReplacePlayerForConnection(conn, newPlayer, 0);
}
}
연결용 플레이어 오브젝트가 제거된 경우 해당 클라이언트는 커맨드를 실행할 수 없습니다. 하지만 네트워크 메시지를 보낼 수는 있습니다.
ReplacePlayerForConnection을 사용하려면 오브젝트와 클라이언트 간 관계를 생성하기 위해 해당 플레이어 클라이언트에 대한 NetworkConnection 오브젝트가 필요합니다. 이는 보통 NetworkBehaviour 클래스상의 connectionToClient 프로퍼티이지만, 만일 기존 플레이어가 이미 제거된 경우 사용하지 못할 수도 있습니다.
연결을 찾기 위해 몇몇 리스트를 사용할 수도 있습니다. NetworkLobbyManager를사용하는 경우 로비 플레이어는 lobbySlots에서 찾을 수 있습니다. 또한, NetworkServer는 connections와 localConnections 리스트를 가지고 있습니다.