인스턴스생성
대부분의 멀티플레이어 게임에서는 일부 게임 오브젝트들을 생성하고 동기화 해야 할 필요가 있습니다.
게임 오브젝트는 룸에 있는 모든 클라이언트들이 가지고 있어야 할 캐릭터가 될 수 있으며, 유닛 또는 몬스터일 수 있습니다. PUN은 이 작업을 수행하기 위한 아주 편리한 방법을 제공하고 있습니다.
일반적으로 유니티에서는 게임 오브젝트의 생명주기를 관리하기 위해 Instantiate
와 Destroy
를 사용합니다. PUN 2는 게임 오브젝트를 풀을 사용하여 생성(그리고 리턴)합니다. 각 네트워크 게임 객체는 반드시 네트워크 상에서 식별자료 사용되는 PhotonView 컴포넌트 (그리고 ViewID)를 가지고 있어야 합니다.
이 페이지와 다음 페이지에서는 네트워크 객체의 생성, 동기화 그리고 사용 방법에 대해 설명합니다.
PhotonNetwork.Instantiate
네트워크 객체를 생성하기 위해서는 유니티의 Object.Instantiate
대신 PhotonNetwork.Instantiate
를 사용합니다. 룸 안에 있는 모든 클라이언트는 제어를 하게 될 객체 생성을 위해 이 호출을 받습니다.
C#
PhotonNetwork.Instantiate("MyPrefabName", new Vector3(0, 0, 0), Quaternion.identity, 0);
PhotonNetwork.Instantiate
의 첫 번째 파라미터는 인스턴스를 생성하기 위한 "프리팹" 입니다. PUN은 내부적으로 PhotonNetwork.PrefabPool
에서 GameObject를 가져와, 네트워크용으로 설정하고 사용할 수 있도록 합니다. 객체가 생성 될 위치와 회전은 반드시 설정되어야 합니다. 나중에 참가한 플레이어들이 이미 이동 했을지라도 내부적으로 이 장소에서 객체 생성을 할 것 입니다.
모든 프리팹은 PhotonView
컴포넌트가 있어야합니다. 이 컴포넌트는 ViewID(네트워크 메시지용 식별자), 객체의 소유자, 네트워크의 변화사항("관찰" 목록)을 어떤 스크립트로 쓰고 읽을지와 이러한 갱신사항들이 어떻게 전송되는지에 대한 방식("Observe option")을 가지고 있습니다. PhotonView 설정을 위해 에디터의 인스펙터를 확인해 보시기 바랍니다. 네트워크 객체는 하나 이상의 PhotonView를 가질 수 있지만 성능상의 이유로 한개만 사용하는 것을 권장 합니다.
기본값으로, PUN은 Resources 폴더에서 프리팹을 로드하고 나중에 GameObject를 파괴하는 DefaultPool을 사용합니다. 좀 더 복잡한 IPunPrefabPool 구현은 파괴할 때 객체를 반환할 수 있고 인스턴스 생성에 재사용할 수 있습니다. 이런 경우에, GameObjects는 Instantiate 에서 실제 생성되지 않으며, 이 의미는 이러한 경우에 있어 유니티가 Start()
를 호출하지 않는다는 의미입니다. 이 이유로 인하여, 네트워크 게임 객체들에 있는 스크립트들은 OnEnable
과 OnDisable
을 반드시 구현 해 주어야 합니다.
게임 오브젝트들의 인스턴스가 생성 되었을 때 설정하기 위해서는, 반드시 스크립트에서 IPunInstantiateMagicCallback
을 구현 해야 합니다. PUN은 이 인터페이스가 컴포넌트에서 구현되었는지를 확인하고 인스턴스가 사용될 때 OnPhotonInstantiate(PhotonMessageInfo info)
를 호출합니다. 누가 언제 게임 오브젝트를 생성 했는지에 대한 정보가 포함되어 있습니다.
노트: IPunInstantiateMagicCallback
구현을 찾는 것은 비용이 많이 드는 액션이므로, 따라서 PUN은 인터페이스를 사용하지 않는 프리팹을 캐시하고 이 프리팹을 다시 사용할 때는 찾는 것을 스킵합니다.
예를들어, 플레이어의 Tag
객체로 인스턴스가 생성된 GameObject를 설정할 수 있습니다:
C#
void OnPhotonInstantiate(PhotonMessageInfo info)
{
// e.g. store this gameobject as this player's charater in Player.TagObject
info.sender.TagObject = this.GameObject;
}
PhotonNetwork.Instantiate
는 나중에 참가한 플레이어를 위해 서버에서 발생된 이벤트를 저장하고 있습니다.
네트워크 객체의 생명주기
기본적으로 PhotonNetwork.Instantiate
메소드에 의해서 생성된 게임 오브젝트들은 동일한 룸내에 있으면 존재 합니다.
만약 룸을 변경하면 유니티의 화면 전환처럼 네트워크 객체들은 변경된 룸으로 이동하지 않습니다.
만약 클라이언트가 룸을 떠나게 되면 클라이언트 플레이어에게 소유 및 생성된 모든 게임 오브젝트들은 사라지게 됩니다. 이러한 것이 게임 로직과 맞지 않는 다면 이 단계는 건너뛰어도 됩니다.
게임에서 RoomOptions.CleanupCacheOnLeave
값을 false 로 설정하시기 바랍니다.
선택적으로 마스터 클라이언트는 PhotonNetwork.InstantiateSceneObject()
메소드를 이용하여 룸의 생명주기를 가진 게임오브젝트들을 생성할 수 있습니다. 이 방식으로 생성된 게임오브젝트는 마스터 클라이언트가 아닌 룸과 연관되어 있습니다. 기본적으로 마스터 클라이언트가 이러한 게임오브젝트들을 제어하지만 photonView.TransferOwnership()
메소드를 통하여 제어권을 넘길 수 도 있습니다. 소유권 이전에 대한 데모를 확인 해 보시기 바랍니다.
네트워크 씬 객체들
PhotonView는 씬내의 객체들 위에 위치시키는 것이 가장 좋습니다.
기본적으로 PhotonView들은 마스터 클라이언트에 의해 제어되며 "neutral" 객체를 통해 게임 방 관련 RPC(원격프로시져콜) 전송에 매우 유용 할 수 있습니다.
중요: 네트워크 객체들을 가진 씬을 로드 할 때 게임방으로 들어가기 전에는 일부 PhotonView 값 들을 사용할 수 없습니다.
예를들어, 게임 룸에 들어가기 전에는 Awake() 메소드에서 isMine
으로 체크할 수 없습니다!
씬 전환
일반적으로 유니티에서 씬을 로드 했을 때 현재 계층 내의 모든 게임오브젝트들은 없어집니다.
이 제거 작업에는 네트워크 오브젝트들도 포함되며 상황에 따라 혼란스러울 수 있습니다.
예:메뉴 씬 에서 게임 방에 참가하고 다른 씬을 로드 합니다.
그 게임 방에 들어 갈 수 있고 게임 룸의 초기 메시지를 받을 수 도 있습니다.
PUN은 네트워크 객체들을 생성하기 시작하지만 로직은 다른 씬을 로드하고 네트워크 객체들은 사라지게 됩니다.
씬을 로드할 때 이러한 문제를 없애기 위해서는 PhotonNetwork.AutomaticallySyncScene
값을 true 로 설정하고 PhotonNetwork.LoadLevel()
메소드를 사용하여 화면 전환을 할 수 있습니다.
PrefabPool 사용하기
기본적으로 PUN은 게임 오브젝트들을 생성하고 파괴하기 위해 간단한 DefaultPool
을 사용합니다. 프리팹을 로드하기 위해 Resources 폴더를 사용하고 파괴된 (사용 단순성을 위해) 객체들은 풀 되지 않습니다. 게임 성능에 부정적인 영향을 준다면, 사용자 지정 PrefabPool 을 설정해야 할 시점 입니다.
사용자 정의 풀 클래스는 반드시 두 개의 메소드가 있는 IPunPrefabPool
인터페이스를 구현해야 합니다:
GameObject Instantiate(string prefabId, Vector3 position, Quaternion rotation)
는 프리팹의 인스턴스를 받습니다. PhotonView 를 가진 유용하고 사용 할 수 없는 게임 오브젝트를 리턴해야 합니다.
Destroy(GameObject gameObject)
는 프리팹의 인스턴스를 파괴(또는 방금 리턴)하기 위해 호출 됩니다. 게임 오브젝트는 이미 비활성되어 있으며 나중에 객체 생성을 위해 풀은 리셋되고 캐시됩니다.
참고: 사용자 정의 IPunPrefabPool
이 사용 될 때, PhotonNetwork.Instantiate
는 게임 오브젝트를 생성하지 않을 것이고 Start()
는 호출되지 않습니다. 이에 따라 OnEnable
및 OnDisable
을 사용하고 그렇지 않으면 계속 실행될 수 있는 물리적 또는 기타 구성 요소를 비활성화합니다!
수동 인스턴스생성
네트워크를 통한 객체 생성을 resources 폴더에 의존하고 싶지 않다면 이 섹션 마지막에 나오는 예제처럼 수동으로 생성해야 합니다.
어떤 객체가 인스턴스 생성(프리팹 이름)이 되는지와 어떻게 식별하는지(ViewID)를 클라이언트에게 알려주어야 할 필요가 있습니다.
PhotonView.ViewID
는 올바른 게임오브젝트/스크립트로 네트워크 메시지를 라우팅 해 주는 키 입니다.
룸에 있는 모든 플레이어는 새로운 객체상의 동일한 ID로 설정해야합니다.
수동 인스턴스 생성 이벤트는 버퍼링되어야 함을 명심하십시오:
나중에 연결하는 클라이언트들도 스폰 명령을 수신해야 합니다.
C#
public void SpawnPlayer()
{
GameObject player = Instantiate(PlayerPrefab);
PhotonView photonView = player.GetComponent<PhotonView>();
if (PhotonNetwork.AllocateViewID(photonView))
{
object[] data = new object[]
{
player.transform.position, player.transform.rotation, photonView.ViewID
};
RaiseEventOptions raiseEventOptions = new RaiseEventOptions
{
Receivers = ReceiverGroup.Others,
CachingOption = EventCaching.AddToRoomCache
};
SendOptions sendOptions = new SendOptions
{
Reliability = true
};
PhotonNetwork.RaiseEvent(CustomManualInstantiationEventCode, data, raiseEventOptions, sendOptions);
}
else
{
Debug.LogError("Failed to allocate a ViewId.");
Destroy(player);
}
}
우리는 로컬에서 플레이어 프리팹을 먼저 인스턴스 생성을 하고 있습니다. 객체의 PhotonView 컴포넌트를 참조하기 위해 필요한 부분입니다.
PhotonView 에 대한 ID를 성공적으로 할당했다면, 다른 클라이언트들에게 보낼 모든 데이터를 수집하고, 이 데이터를 객체의 배열에 저장합니다. 이 예제에서는 인스턴스 생성된 객체의 위치와 회전 그리고 - 가장 중요한 할당된 ViewID
를 전송합니다.
그후 RaiseEventOptions
과 SendOptions
을 생성합니다. 로컬에서 객체를 이미 인스턴스 생성을 하였기 때문에, RaiseEventOptions
로 이 이벤트가 룸의 캐시에 추가되고 다른 클라이언트들에게만 보내 진다는 것을 확인합니다. 정의한 SendOptions
으로 이 이벤트는 신뢰할 수 있는 것으로 전송됩니다.
마지막으로 PhotonNetwork.RaiseEvent(...)
를 사용하여 사용자 정의 이벤트를 서버로 전송합니다. 이 경우에 특정 이벤트를 나타내는 바이트 값을 단순화하는 CustomManualInstantiationEventCode
를 사용합니다.
PhotonView 에 대한 ID 할당에 실패했다면, 에레 메시지를 로깅하고 이전에 인스턴스 생성되었던 객체를 파괴합니다.
PhotonNetwork.RaiseEvent
를 사용하고 있기 때문에, OnEvent
콜백 핸들러를 사용해야 합니다. 올바르게 등록해야 한다는 것을 잊지 마세요. 동작 방법을 보기 위해서는, RPCs 그리고 RaiseEvent 문서 페이지를 참고하실 수 있습니다. 이 예제에서는 OnEvent
는 다음과 같습니다:
C#
public void OnEvent(EventData photonEvent)
{
if (photonEvent.Code == CustomManualInstantiationEventCode)
{
object[] data = (object[]) photonEvent.CustomData;
GameObject player = (GameObject) Instantiate(PlayerPrefab, (Vector3) data[0], (Quaternion) data[1]);
PhotonView photonView = player.GetComponent<PhotonView>();
photonView.ViewID = (int) data[2];
}
}
여기서는 수신된 이벤트가 사용자 정의 수동 인스턴스화 이벤트인지 여부를 간단히 확인할 수 있습니다.
그렇다면, 수신한 위치와 회전 정보를 가지고 플레이어의 프리팹를 인스턴스화합니다.
이후 객체의 PhotonView 컴포넌트에 대한 참조를 얻고 수신한 ViewID
를 지정합니다.
에셋 번들을 사용하여 네트워크 객체를 로드하려면, 자신의 에셋 번들 로드 코드를 추가하고 예제에서 PlayerPrefab
을 에셋 번들의 프리팹으로 교체하기만 하면 됩니다.