Photon에서의 이전 가이드
Photon(PUN2)으로 만든 멀티플레이를 GS2-Realtime의 sync / stateMachine으로 옮기는 절차와 대응표
이 페이지는 Photon PUN2로 만든 멀티플레이를 GS2-Realtime의 오브젝트 동기화(sync / stateMachine)로 옮기기 위한 절차와 대응표입니다. 각 API의 자세한 내용은 Unity 클라이언트를 참조해 주세요.
사고방식의 차이
| 관점 | Photon PUN2 | GS2-Realtime |
|---|
| 룸 찾기 | Photon의 로비와 매치메이킹 | GS2-Matchmaking으로 멤버를 모으고, GS2-Realtime의 룸을 만든다 |
| 룸 접속 | PhotonNetwork.ConnectUsingSettings / JoinRoom | 발행된 IP 주소·포트·암호화 키로 RealtimeSession을 통해 접속한다 |
| 누가 값을 결정하는가 | 각 클라이언트(마스터 클라이언트가 조정 역할) | 룸의 서버가 쓰기 권한과 RPC 허가를 검증한다. stateMachine에서는 게임 진행도 서버의 룰이 결정한다 |
| 동기화하는 데이터 | OnPhotonSerializeView로 자유로운 데이터를 전송한다 | 오브젝트마다 최대 32개의 정수 필드. 위치·회전은 단위 간격으로 반올림하여 전송한다 |
| 통신 | UDP(reliable / unreliable) | WebSocket(확실하게 전달할 것)과 UDP(정기적인 상태 갱신) |
| 과금 | 동시 접속 수 | 룸 서버의 가동 시간과 통신량 |
가장 큰 차이는 룸의 서버가 권한을 검증할 수 있다는 점입니다. 다른 플레이어 오브젝트의 값은 변경할 수 없으며, 오브젝트 카탈로그를 설정하면 등록한 Prefab과 RPC만 통과하도록 검사하게 할 수 있습니다. 마스터 클라이언트에 맡기던 게임 진행(적의 HP, 득점, 승패)은 stateMachine 룰로 옮기면 클라이언트 개조의 영향을 받지 않게 됩니다.
대응표
컴포넌트
| Photon PUN2 | GS2-Realtime |
|---|
PhotonView | NetworkEntity |
PhotonTransformView | NetworkTransform |
IPunObservable / OnPhotonSerializeView | INetworkObservable(OnNetworkWrite / OnNetworkRead) |
PhotonNetwork.PrefabPool / Resources의 Prefab | Prefab Catalog와 NetworkWorld |
씬에 배치한 PhotonView, InstantiateRoomObject | 룸이 만드는 공유 오브젝트(stateMachine 룰, 또는 조작 권한을 대여하는 오브젝트) |
오브젝트
| Photon PUN2 | GS2-Realtime |
|---|
PhotonNetwork.Instantiate(name, pos, rot) | world.Instantiate(prefabId, pos, rot) |
PhotonNetwork.Destroy(go) | entity.Destroy() |
photonView.IsMine | entity.IsMine |
photonView.Owner | entity.OwnerId(접속 ID) |
photonView.TransferOwnership(player) | entity.TransferOwnership(connectionId) |
photonView.RequestOwnership()(OwnershipOption.Takeover) | entity.RequestOwnership()(오브젝트 카탈로그를 설정하는 경우 "ownership": "takeover") |
PhotonNetwork.CleanupCacheOnLeave = false | 카탈로그에서 "keepOnLeave": true(마스터 클라이언트에게 인계) |
photonView.Group / PhotonNetwork.SetInterestGroups | entity.SetGroup / world.SetInterestGroups, SubscribeGroup |
RPC와 이벤트
| Photon PUN2 | GS2-Realtime |
|---|
[PunRPC] | [NetworkRpc] |
photonView.RPC(name, RpcTarget.All, args) | entity.Rpc(name, RpcTarget.All, args) |
RpcTarget.Others / AllBuffered / OthersBuffered | 같은 이름의 RpcTarget |
RpcTarget.AllViaServer | RpcTarget.All(항상 서버를 경유한다) |
RpcTarget.MasterClient | entity.RpcTo(name, world.MasterClientId, args) |
photonView.RPC(name, player, args) | entity.RpcTo(name, connectionId, args) |
PhotonMessageInfo | NetworkRpcInfo(Sender는 호출자의 접속 ID) |
PhotonNetwork.RaiseEvent | world.RaiseEvent / RaiseEventTo(핸들러는 [NetworkEvent]) |
IOnEventCallback.OnEvent | world.AddEventTarget으로 등록한 [NetworkEvent] 메서드, 또는 world.EventReceived |
룸과 플레이어
| Photon PUN2 | GS2-Realtime |
|---|
PhotonNetwork.LocalPlayer.ActorNumber | session.MyConnectionId |
PhotonNetwork.PlayerList, OnPlayerEnteredRoom / OnPlayerLeftRoom | RealtimeSession의 OnJoinPlayer / OnLeavePlayer |
PhotonNetwork.MasterClient / IsMasterClient | world.MasterClientId / IsMasterClient |
PhotonNetwork.SetMasterClient / OnMasterClientSwitched | world.SetMasterClient / MasterClientChanged |
Room.SetCustomProperties / OnRoomPropertiesUpdate | world.SetRoomProperty / RoomPropertyChanged |
Player.SetCustomProperties / OnPlayerPropertiesUpdate | world.SetPlayerProperty / PlayerPropertyChanged |
PhotonNetwork.Time | world.ServerTime |
PhotonNetwork.SendRate / SerializationRate | world.SendRate |
| unreliable 전송 | RealtimeUdpChannel(정기적인 상태 갱신을 UDP로 전송한다) |
이전 절차
- 룸 준비를 대체한다: Photon의 로비와 매치메이킹을 GS2-Matchmaking과 GS2-Realtime의 룸 생성으로 대체합니다. Realtime 네임스페이스는
serverType을 sync(게임 진행도 서버에서 결정한다면 stateMachine)로 합니다. 흐름은 GS2-Realtime 소개와 튜토리얼을 참조해 주세요. - 접속을 대체한다:
RealtimeSession으로 접속하고, NetworkWorld.Attach와 world.Receive를 연결합니다. 룸에 접속하기의 내용대로 작성합니다. - Prefab을 대체한다:
PhotonView를 NetworkEntity로, PhotonTransformView를 NetworkTransform으로 바꿉니다. Prefab Catalog를 만들고 Prefab ID를 할당하여 등록합니다. - 동기화할 값을 대체한다:
OnPhotonSerializeView로 전송하던 값은 동기화 스키마의 필드로 만들어 INetworkObservable로 읽고 씁니다. 값은 32개까지의 정수(float는 단위 간격으로 반올림)이므로, 문자열이나 배열은 RPC나 커스텀 프로퍼티로 전송합니다. - RPC를 대체한다:
[PunRPC]를 [NetworkRpc]로, photonView.RPC를 entity.Rpc로 바꿉니다. 다른 플레이어의 오브젝트에 대해 호출하는 RPC에는 AnyCaller = true를 붙입니다. 인수의 타입이 사용할 수 있는 타입에 포함되는지 확인합니다. - 생성과 삭제를 대체한다:
PhotonNetwork.Instantiate를 world.Instantiate로, PhotonNetwork.Destroy를 entity.Destroy()로 바꿉니다. 씬에 배치했던 오브젝트는 룸이 만드는 공유 오브젝트로 합니다. - 검사하려면 오브젝트 카탈로그를 설정한다: 생성이나 RPC를 룸이 검사하게 하려면 Prefab Catalog 에셋을 우클릭하여 Copy Object Catalog JSON으로 생성한 JSON을 Realtime 네임스페이스의
syncSetting.objectCatalog에 설정합니다. RPC의 이름이나 Prefab을 바꾸면 다시 생성합니다. - 이벤트·프로퍼티·마스터 클라이언트를 대체한다: 대응표대로 대체합니다.
- UDP를 사용한다: WebGL 이외에서는
RealtimeUdpChannel로 정기적인 상태 갱신을 UDP로 전송합니다.
코드 변경 예
RPC
// Photon PUN2
[PunRPC]
void Fire(Vector3 direction, PhotonMessageInfo info) { /* ... */ }
photonView.RPC(nameof(Fire), RpcTarget.Others, direction);
// GS2-Realtime
[NetworkRpc]
void Fire(Vector3 direction, NetworkRpcInfo info) { /* ... */ }
entity.Rpc(nameof(Fire), RpcTarget.Others, direction);
생성과 소유자 판정
// Photon PUN2
var player = PhotonNetwork.Instantiate("Player", spawnPoint, Quaternion.identity);
if (player.GetComponent<PhotonView>().IsMine) { /* 입력을 활성화한다 */ }
// GS2-Realtime(Prefab ID 1을 Prefab Catalog에 등록해 둔다)
var player = world.Instantiate(1, spawnPoint, Quaternion.identity);
if (player.IsMine) { /* 입력을 활성화한다 */ }
입력을 받는 스크립트는 NetworkEntity의 Controlled Behaviours에 등록하면 쓸 수 있는 동안에만 자동으로 활성화됩니다.
값의 동기화
// Photon PUN2
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.IsWriting) stream.SendNext(health);
else health = (float)stream.ReceiveNext();
}
// GS2-Realtime(동기화 스키마에 float의 Health 필드를 선언해 둔다)
public void OnNetworkWrite(NetworkEntity entity) => entity.SetFloat("Health", health);
public void OnNetworkRead(NetworkEntity entity, uint changed)
{
if (entity.HasChanged("Health", changed)) health = entity.GetFloat("Health");
}
이벤트와 프로퍼티
// Photon PUN2
PhotonNetwork.RaiseEvent(GoalCode, team, new RaiseEventOptions { Receivers = ReceiverGroup.All }, SendOptions.SendReliable);
PhotonNetwork.CurrentRoom.SetCustomProperties(new Hashtable { { "stage", "forest" } });
// GS2-Realtime
world.RaiseEvent("Goal", RpcTarget.All, team); // [NetworkEvent] void Goal(int team)를 등록해 둔다
world.SetRoomProperty("stage", "forest");
주의 사항
- 확정은 비동기:
world.Instantiate는 즉시 사용할 수 있지만, 룸이 거부하면 SpawnFailed가 호출되고 삭제됩니다. 커스텀 프로퍼티는 룸을 경유하여 전원에게 도착한 시점에 반영됩니다. - 값은 반올림된다: 위치는 0.01, 회전은 360/4096도의 단위 간격으로 전송합니다. 정밀도를 바꾸려면 동기화 스키마에서 지정합니다.
- 권한은 서버가 검증한다: 소유자 이외의 쓰기는 거부됩니다. 오브젝트 카탈로그를 설정하면 등록하지 않은 Prefab과 RPC, 허가하지 않은 소유권 취득도 거부됩니다.
- RPC 인수의 상한: 합계 4096바이트까지입니다. 큰 데이터는 나누어 전송합니다.
- 전송 순서: UDP로 전송하는 정기적인 갱신과 WebSocket으로 전송하는 RPC나 이벤트 사이의 순서는 보장하지 않습니다. RPC 직전에 변경한 값은 RPC보다 먼저 도착합니다.
- 룸 종료 조건: 참가자 전원으로부터 1분간 통신이 없으면 룸은 종료됩니다. 룸 종료 조건과의 관계를 참조해 주세요.
- WebGL: UDP를 사용할 수 없으므로 모두 WebSocket으로 전송합니다.
- Photon의 다른 제품: Photon Voice나 Photon Chat에 해당하는 기능은 GS2-Realtime에 포함되어 있지 않습니다. 텍스트 채팅에는 GS2-Chat을 사용할 수 있습니다.