본문 바로가기
개발/Unity 내일배움캠프 TIL

Unity 기존 게임을 포톤 PUN2로 동기화하기

by 석시 2023. 10. 18.



포톤으로 기존 싱글플레이 게임들을 멀티플레이 게임으로 만들어보자.

물론 기존에 만들어놨던 게임 구조가 너무나도 복잡하면…. 좀 힘들 수 있다.

주로 상속받는 부분 변경과 함수 변경에 대해 이야기해보고자 한다.


MonobehaviourPun vs MonobehaviourPunCallbacks

일단 제일 먼저 하게되는 일은 Monobehaviour를 상속받고 있던 기존의 스크립트를 MonobehaviourPun이나 MonobehaviourPunCallbacks를 상속받는 걸로 바꿔주면서 시작한다.

둘의 차이는 뭐냐 하면, 오버라이드를 하는지 안하는지에 따라 다르다.

포톤으로 세팅하는 과정에서 포톤의 기능함수들을 오버라이드해서 사용할 일이 있다.

방을 세팅한다거나, 만들어진 방에 참가한다거나 하는 등의 기능이다.

따라서 보통 로비를 제작하여 플레이어들을 매칭하는 기능을 만드려고 할 때 주로 이 MonobehaviourPunCallbacks를 사용하는 편이고 그게 아니라면 MonobehaviourPun을 주로 사용한다.

보통은 PhotonView로 동기화를 해준다.

대부분의 동기화는 이 PhotonView에서 해결이 된다.

자세한 내용은 다음 글을 참고하자.

Unity 포톤 (Photon)
포톤 기본 기능PhotonNetwork1) ConnectUsingSettings()2) CreateRoom() & JoinRoom()3) Instatiate()MonoBehaviourPunCallbacks1) OnConnectedToMaster()2) OnJoinedRoom() & OnPlayerEnteredRoom()포톤뷰 (PhotonView) 컴포넌트PhotonViewObserved Component 추가 PhotonView1) PhotonTransformView2) PhotonAnimatorView3) PhotonRigidbodyView 포톤 (Photon) 은 멀티플레이를 구현하는 솔루션 중 가장 유명한 네트워크 엔진 중 하나이다. 여러 엔진에 적용 가능하고, 크로스플랫폼 간 통신도 지원하는 등..
https://seoksii.tistory.com/59#b07f8fc0-dceb-4f79-ab02-dd4199922524

하지만, Transform, Rigidbody, Animator 외에 우리가 직접 작성한 스크립트의 변수라던지 저 세 가지에 해당되지 않는 컴포넌트를 동기화할 일이 있을 것이다.

그것을 위해 여러 방법들을 사용한다.


로컬 vs 리모트

현재 스크립트가 달려 있는 오브젝트가, 내가 조작하는 것인지 다른 플레이어가 조작하는 것인지 구분해줄 필요가 있다.

PhotonView는 부착할 때마다 고유의 ViewID를 부여해주는데, 이 ViewID의 소유권으로 로컬인지 리모트인지를 구분하게 된다.

메서드 상으로는 매우 간단한데, photonView.IsMine으로 확인하면 된다.

photonView.IsMine은 해당 객체를 내가 생성한 것인지 아닌지를 반환하는 프로퍼티이다.

photonView.AmOwner와 헷갈릴 수 있는데, photonView.AmOwner는 이 PhotonView의 Ownership(소유권)을 내가 가지고 있는지 아닌지를 반환해준다.

따라서 내가 생성하지 않은 객체라도 소유권을 넘겨받으면 내가 제어할 수 있도록 하기 위해 존재하는 프로퍼티이다.

따라서 로컬에서만 작동하는 메서드의 경우 앞에 이러한 구문을 붙여준다.

if (!photonView.IsMine) return;


변수 동기화: IPunObservable

IPunObservable은 스크립트 내에서 특정 변수들을 동기화 할 일이 있을 때 사용하게 되는 인터페이스이다.

상속 시 필수 구현부는 단 하나밖에 없다.

OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)라는 함수이다.

public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
    if (stream.IsWriting)
    {
        // 변수를 변경하는 부분 (통신을 보내는)
        stream.SendNext(changedInt); // 변수1
        stream.SendNext(changedVec); // 변수2
    }
    else
    {
        // 바뀐 변수를 읽어오는 기능 (클론이 통신을 받도록)
        changedInt = (int)stream.ReceiveNext(); // 변수1
        changedVec = (Vector3)stream.ReceiveNext(); // 변수2
    }
}


함수 동기화: PunRPC

함수는 기본적으로 동기화가 안된다.

동기화하듯이 모든 클라이언트가 영향을 받도록 함수를 호출해주려면 서버에서 호출하면 된다.

클라이언트에서 서버의 함수를 호출하는 것을 Remote Procedure Calls, 줄여서 RPC라고 부른다.

주로 이벤트 발생 시에 사용한다.

서버에서 호출하고 싶은 함수 위에 [PunRPC]라는 키워드를 붙인다.

[PunRPC]
void RemoteFunction(string param1, string param2)
{
    
}

호출할 때는 PhotonView.RPC() 메서드를 사용한다.

if (photonView.IsMine)
    photonView.RPC("RemoteFunction", RpcTarget.All, "param1", "param2");

RPC()의 두번째 파라미터는 어느 클라이언트들에게 함수 실행을 전달해줄지에 대한 타겟을 정하는 부분이다.

이 타겟설정에 따라 실행 결과가 굉장히 많이 바뀐다.

타겟종류를 정리하자면 다음과 같다.

All자신의 클라이언트는 함수를 바로 실행하고 다른 클라이언트들에게 전달 로컬 클라이언트가 리모트 클라이언트보다 빠르게 실행됨
AllViaServer모두가 서버를 거쳐서 원격 함수 호출을 전달받는다. 로컬과 리모트의 함수 실행 타이밍이 동일하다.
AllBufferedAll은 호출하는 시기에 통신을 수행하고 바로 사라진다. 따라서 방에 나중에 들어온 사람들은 그 결과를 받지 못한다. AllBuffered는 원격 호출을 버퍼에 저장해두어 나중에 방에 들어온 사람들도 그 결과를 받아볼 수 있다.
AllBufferedViaServer원격 호출 버퍼링 & 모두가 서버를 거쳐서 호출 통신
MasterClient마스터 클라이언트 (방장) 에게만 전달
Others나를 제외한 모두에게 전달
OtherBuffered나를 제외한 모두에게 버퍼링을 거쳐 전달


지금까지 설명한 것들을 조합하여 동기화를 구현할 수 있을 것이다.

RaiseEvent라는 것도 있는데, 이것은 PhotonView를 거치지 않는 이벤트이다.

많이 남발하면 느려질 위험이 있어 잘 쓰지 않는다길래, 여기에는 따로 정리하지 않았다.

포톤을 통한 동기화가 여기저기 많이 필요할 것 같은데, 막상 쉽게 정리된 정보들은 여기저기 흩어져 있어 한 번 모아서 정리해보았다.

포톤 PUN2를 쓸 때마다 두고두고 볼 예정이다.


Uploaded by N2T