히가츠류의 보금자리

[Unity Photon] PhotonView, RPC, Hashtable 수치 값 동기화에 대한 고민과 정리 본문

Programming/Unity 3D

[Unity Photon] PhotonView, RPC, Hashtable 수치 값 동기화에 대한 고민과 정리

HiGaTsu Ryu 2022. 7. 20. 14:05

 Photon 정말 대단하다. 간단한 코드 몇줄로 네트워크가 가능하게 해놨다... 정말 너무 편한데..

대신 편한 만큼 커스텀이 어렵고 모든게 숨겨져있거나 자동 처리되는 경우가 많아서 골치가 아프다.

 

현재 프로젝트 팀에서 Photon RND가 제대로 되어있지 않다보니 건들 때마다 골칫덩이인데,

오늘은 비교적 간단하게 해결 가능한 선택지가 많지만,

선택지가 많아서 더 고민하게 되는 동기화 부분에 대해 정리해두려 한다.

 

냅두면 까먹으니까.. 생각 날 때마다 블로그 적어야지...

 


[Photon 데이터 동기화 방법]

 현재 알아낸 것으론 데이터 동기화에 3가지의 방법이 있다. (더 있으면 제보 부탁드립니다.)

 

1. Photon View 관련 컴포넌트를 를 사용하는 방법

2. Photon RPC 함수를 사용하는 방법

3. NetworkManager.Instance.localPlayer의 CustomProperties를 사용하는 방법

 

 

 첫번째 방법은 Photon View 관련 컴포넌트들을 사용하는 것이다.

 현재 찾은 Photon 관련 View들은 다음과 같다.

photon에서 지원해주는 컴포넌트 목록

 - Photon Animator View : 애니메이션 동기화 관련

                                          anim 자체를 동기화 한다기 보단.. Parameters를 동기화 해준다. 따라서 버그 생길 확률이 높다.

                                          실제로 3명의 캐릭터가 똑같은 춤을 각자 다른 시간에 실행해도,

                                          나중에 들어온 플레이어는 3명의 캐릭터가 똑같은 춤을 같은 시간에 실행하는 것으로 보인다.

 - Photon Rigidbody View :  Rigidbody 동기화 관련

                                            Rigidbodt 전반적인 값을 체크하며, 특히 속도와 회전 속도 등을 담당함

 - Photon Tansform View : Transform 동기화 관련 

                                          Transform 전반적인 값을 체크하며, 특히 Position, Rotation, Scale을 담당함

 - Photon View : 모든걸 관리하는 총집합체 같은 느낌. 하는 일 많음.

                           View ID, isMine, Controller, Owner, Creator, Observables, Observed Componets 등 뿐만 아니라

                           Hp, 외형, 개인정보 등의 특별한 변수들도 여기서 다 관리한다.

 

 Photon View의 IPunObservable - OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) 를 통해

stream.Send, stream.Receive 을 사용하면 해결 될 것 같다.

		IPunObservable 필수.

        public int testInt = 3;
        private float time = 0;

        private void Update()
        {
            time += Time.deltaTime;
            if (time > 3f)
            {
                time = 0;
                testInt = Random.Range(0, 100);
            }
        }

        public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
        {
            if (stream.IsWriting)
            {
                Debug.Log("stream.IsWriting==true : " + testInt);
                stream.SendNext(testInt);
            }
            else
            {
                testInt = (int)stream.ReceiveNext();
                Debug.Log("stream.IsWriting==false : " + testInt);
            }
        }

테스트 시, Room에 나만 있을 경우 Debug.Log가 출력되지 않았고,

누군가 들어와있는 경우 Debug.Log가 출력되었다.

 

>> 해당 방법은 지속적인 값 관리에 대한 이슈 확인이 필요하다. 정보 있다면 제보 부탁드립니다.

 

 


 두번째 방법은 Photon RPC 함수를 사용하는 방법이다.

 RPC는 원격 프로시져 호출(Remote Procedure Calls)의 약자이며 이름 그대로

"같은 Room"의 "원격 클라이언트"에게 있는 메소드를 호출하는 것이다.

 

메소드에 대해서 원격 호출을 사용하려면 [PunRPC]를 사용해야 가능하다.

 

말보다는 구현이 이해가 빠를 것이다.

해당 방법은 대충 구현만 해놓고 아직 RND가 끝나지 않아서 가물가물 한데.. 예시 코드 첨부하겠다.

...
{
	if(photonView.IsMine)		// IsMine Check
    {
	PhotonView?.RPC("ActiveInteraction", RpcTarget.AllBuffered, index);
	}
}

[PunRPC]
public void ActiveInteraction(int index)
{
	interactiveObject.Init(index);
}

    1. PhotonView.RPC로를 [PunRPC] 함수 실행 (매개변수 전달 가능)

    2. 특정 작업 실행하면, 룸에 모든 사람들에게 반영 됨.

 

RpcTarget에 따라 전송 방법과 대상도 다르다. enum 참고 바람.

    public enum RpcTarget
    {
        /// <summary>Sends the RPC to everyone else and executes it immediately on this client. Player who join later will not execute this RPC.</summary>
        All,

        /// <summary>Sends the RPC to everyone else. This client does not execute the RPC. Player who join later will not execute this RPC.</summary>
        Others,

        /// <summary>Sends the RPC to MasterClient only. Careful: The MasterClient might disconnect before it executes the RPC and that might cause dropped RPCs.</summary>
        MasterClient,

        /// <summary>Sends the RPC to everyone else and executes it immediately on this client. New players get the RPC when they join as it's buffered (until this client leaves).</summary>
        AllBuffered,

        /// <summary>Sends the RPC to everyone. This client does not execute the RPC. New players get the RPC when they join as it's buffered (until this client leaves).</summary>
        OthersBuffered,

        /// <summary>Sends the RPC to everyone (including this client) through the server.</summary>
        /// <remarks>
        /// This client executes the RPC like any other when it received it from the server.
        /// Benefit: The server's order of sending the RPCs is the same on all clients.
        /// </remarks>
        AllViaServer,

        /// <summary>Sends the RPC to everyone (including this client) through the server and buffers it for players joining later.</summary>
        /// <remarks>
        /// This client executes the RPC like any other when it received it from the server.
        /// Benefit: The server's order of sending the RPCs is the same on all clients.
        /// </remarks>
        AllBufferedViaServer
    }

아래는 번역기 돌린 것.

All   RPC를 다른 모든 사용자에게 보내고 이 클라이언트에서 즉시 실행합니다. 나중에 가입한 플레이어는 이 RPC를 실행하지 않습니다.
Others   다른 모든 사용자에게 RPC를 보냅니다. 이 클라이언트는 RPC를 실행하지 않습니다. 나중에 가입한 플레이어는 이 RPC를 실행하지 않습니다.
MasterClient 
 RPC를 MasterClient에만 보냅니다. 주의: 마스터 클라이언트는 RPC를 실행하기 전에 연결이 끊길 수 있으며 이로 인해 RPC가 손실될 수 있습니다.
AllBuffered 
 RPC를 다른 모든 사용자에게 보내고 이 클라이언트에서 즉시 실행합니다. 새로운 플레이어는 RPC가 버퍼링될 때 RPC를 받습니다(이 클라이언트가 떠날 때까지).
OthersBuffered 
 모든 사용자에게 RPC를 보냅니다. 이 클라이언트는 RPC를 실행하지 않습니다. 새로운 플레이어는 RPC가 버퍼링될 때 RPC를 받습니다(이 클라이언트가 떠날 때까지).
AllViaServer 
 서버를 통해 모든 사용자(클라이언트 포함)에게 RPC를 보냅니다. 이 클라이언트는 서버로부터 RPC를 수신할 때 다른 클라이언트와 마찬가지로 RPC를 실행합니다.
 이 클라이언트는 서버로부터 RPC를 수신할 때 다른 클라이언트와 마찬가지로 RPC를 실행합니다.

- 이점 : 서버의 RPC 전송 순서는 모든 클라이언트에서 동일합니다.
AllBufferedViaServer   서버를 통해 RPC를 모든 사용자(클라이언트 포함)에게 보내고 나중에 참여하도록 버퍼링합니다.
 이 클라이언트는 서버로부터 RPC를 수신할 때 다른 클라이언트와 마찬가지로 RPC를 실행합니다.

- 이점 : 서버의 RPC 전송 순서는 모든 클라이언트에서 동일합니다.
티스토리 칸 수 왜 이렇게 이상하지..
아무튼 설명은 그러하다.

 

그 외 특수 조건이 있었는지 모르겠다. 나머지는 공식문서 참고 바람.

 

- RPCs 와 RaiseEvent 문서 : https://doc.photonengine.com/ko-kr/pun/current/gameplay/rpcsandraiseevent

 

 


 마지막으로 PhotonNetwork.LocalPlayer의 CustomProperties를 사용하는 방법이다.

 해당 방법은 PhotonNetwork의 LocalPlayer에 데이터를 저장하고 방법으로..

기본적으로 "키"-"값"의 형태이다. 값은 클라이언트에서 동기화되고 캐시되기 때문에 사용전에 fetch 할 필요가 없다.

변경 사항들은 SetCustomProperties()를 사용하면 되고, 사용 방식이 단순하므로 RPC처럼 어려운 부분은 없다. 문서에는 다른 플레이어들에게 푸시된다고 써있었는데.. 이 부분은 RND 필요.

- 동기화와 상태 (프로퍼티의 Check와 Swap(CAS)) 문서 : https://doc.photonengine.com/ko-kr/pun/current/gameplay/synchronization-and-state

 

 나는 해당 방법을 코스튬 데이터를 저장하는데 사용하였었고, 생각보다 유용하게 사용했다.

자주 갱신될 필요 없는 데이터를 Save/Load 하는데 적합하다고 생각한다.

아래는 예시 코드이다.

public void SaveData()
{
	// Hashtable이 Unity기본 Hashtable과 충돌할 수 있어서 꼭 명시적 선언 필요함
	ExitGames.Client.Photon.Hashtable hash = new ExitGames.Client.Photon.Hashtable();

	hash.Add("Hair", hairData.ToString());
	hash.Add("Top", topData.ToString());
	hash.Add("Bottom", bottomData.ToString());
	hash.Add("Shoes", shoesData.ToString());

	PhotonNetwork.LocalPlayer.SetCustomProperties(hash);
}

"Hair" 키 값으로 hairData를 ToString()으로 저장.

SetCustomProperties()를 사용하여 localPlayer에 푸시한다.

// 여기서 Hashtable은 ExitGames.Client.Photon.Hashtable이다.
...
{
	...
	Hashtable properties = Photon.Pun.PhotonNetwork.LocalPlayer.CustomProperties;
    
	if (properties.TryGetValue("Hair", out object hair))
	{
		// use hair data
	}
	if (properties.TryGetValue("Top", out object top))
	{
		// use top data
	}
	if (properties.TryGetValue("Bottom", out object bottom))
	{
		// use bottom data
	}
	if (properties.TryGetValue("Shoes", out object shoes))
	{
		// use shoes data
	}
}

그 다음에 Init 시 데이터를 가져온다.

 

저 부분은 구현한지 좀 되어서 추가 필요 조건이 있었는지 헷갈린다..

마찬가지로 공식문서 참고 바람.

 

무슨 방법이든 해당 방법들을 사용하면 해결할 수 있을 것 이다!

필요에 따라 골라쓰자.

 


[정리]

 

1. Photon View 관련 컴포넌트를 를 사용하는 방법

     →   Update처럼 지속적인 갱신 필요할 때 주로 사용

 

2. Photon RPC 함수를 사용하는 방법

     →   필요할 때 호출하여 원격 제어하는데 사용

 

3. NetworkManager.Instance.localPlayer의 CustomProperties를 사용하는 방법

     →   PlayerPrefs와 비슷하게 사용

 

 

 

나중에 수정하게 될진 모르겠는데 일단 마무리..

 

 

구현 방법에는 무엇이든 100% 정답은 없으니 맹신하지 마시고, 의심하시고, 개선하시고..

좋은 방법 찾으실 수 있기를 바랍니다.

 

ㄲㅡㅌ

 

 

 

 

[공식 사이트 정리]

- Photon 공식 홈페이지 : https://www.photonengine.com/ko-KR/Photon

 

글로벌 크로스 플랫폼 실시간 게임 개발 | Photon Engine

Cookie 설정 Photon은 귀하를 로그인 사용자로 식별하고 품질을 개선하고 마케팅을 위해 쿠키를 사용합니다. 아래 Cookie 설정을 확인하고 프라이버시를 관리해 주시기 바랍니다. 당사가 Cookie를 사용

www.photonengine.com

- Photon Unity Networking 2 (v2.30) : https://doc-api.photonengine.com/en/pun/v2/functions.html

- Photon Fusion (v1.1.2) : https://doc-api.photonengine.com/en/fusion/current/namespace_fusion.html

 

[그 외 참고하기 좋았던 블로그들]

 - [Photon] RPC | 포톤 네트워크(Photon Network) | Photon View | PunRPC : https://narasee.tistory.com/243

 - [포톤] 동기화하는 법 : https://mingyu0403.tistory.com/311

 - 자주 쓰는 유니티 포톤 코드 : https://wmmu.tistory.com/entry/%EC%9E%90%EC%A3%BC-%EC%93%B0%EB%8A%94-%EC%9C%A0%EB%8B%88%ED%8B%B0-%ED%8F%AC%ED%86%A4-%EC%BD%94%EB%93%9C

 

잘못 게시된 정보나 추가 정보는 댓글 부탁드립니다!

오류와 버그 없는 코딩 되시고, 좋은 하루 보내세요!

Comments