PUN Classic(PUN1이라고도 불립니다)은 PUN의 첫 버전입니다.         현재는 리팩토링 및 기능 확장에 의해 PUN2로 새롭게 바뀌었습니다.          새 프로젝트에는 PUN2를 이용해 주시고, 기존의 프로젝트도 가능하면 PUN1에서 PUN2로 옮기는 것을 권장합니다.  자세한 내용은: "마이그레이션 노트". PUN Classic은 곧 점검이 시작됩니다.        중요한 버그의 수정과 Unity의 신버전의 지원 등을 예정하고 있습니다. 신기능의 추가는 PUN2에서만 이루어지므로 주의해 주십시오.

Oculus Avatar SDK 사용하기

이 지침서는 PUN에서 Oculus Avatar SDK의 사용법을 보여드립니다. 따라서 신규 유니티 프로젝트를 시작하고 다음의 패키지들을 임포트해주시기 바랍니다:

시작하기

임포트가 완료되었으면 기존의 컴포넌트 확장부터 시작할 수 있습니다. 첫 번째 단계는 'LocalAvatar' 와 'RemoteAvatar' 두 개의 프리팹이 있는 'Assets/OvrAvatar/Content/Prefabs' 로 가는 것 입니다: 다음 단계는 두 개의 프리팹을 사용하거나 복사본을 생성하는 것 입니다..

중요: 두 프리팹은 모두 'Resources' 폴더에 위치시켜야 합니다.
이 경우에 있어서는 각 프리팹을 'Assets/Resources' 에 위치해 놓습니다.

Back To Top

Avatar 동기화

다음 단계는 여러 개의 클라이언트 간에 동기화를 처리하는PhotonView (우리는 나중에 붙일 것 입니다) 컴포넌트에 의해 관찰될 스크립트를 구현하는 것 입니다. 따라서 새로운 스크립트를 생성하여 PhotonAvatarView 라고 이름을 지어주고 코드에 다음 세 개의 레퍼런스를 추가 합니다:

    private PhotonView photonView;
    private OvrAvatar ovrAvatar;
    private OvrAvatarRemoteDriver remoteDriver;

또한 아바타의 데이터를 실제로 다른 클라이언트에 보내기 전에 데이터를 저장하기 위한 byte-array의 리스트가 필요합니다.

    private List<byte[]> packetData;

유니티의 Awake 함수를 사용하여 이전의 모든 레퍼런스와 객체들을 설정할 수 있습니다.

    public void Awake()
    {
        photonView = GetComponent<PhotonView>();
        if (photonView.isMine)
        {
            ovrAvatar = GetComponent<OvrAvatar>();

            packetData = new List<byte[]>();
        }
        else
        {
            remoteDriver = GetComponent<OvrAvatarRemoteDriver>();
        }
    }

PhotonView 컴포넌트에 대한 레퍼런스를 얻은 후에 isMine 조건을 직접 사용하여 'Local'과 'Remote Avatar'사이를 명확하게 구분할 수 있습니다. 인스턴스화 된 객체가 우리의 객체라면 우리는 OvrAvatar 컴포넌트(다음 단계에서 이것을 사용)에 대한 레퍼런스를 얻고, 네트워크를 통해 이 데이터를 전송하기 전에 모든 아바타 관련 입력 이벤트를 저장하는 byte-array 리스트를 인스턴스화합니다. 객체가 다른 클라이언트에 속하면 OvrAvatarRemoteDriver 컴포넌트에 대한 레퍼런스를 얻어 이 컴포넌트를 나중에 다른 클라이언트가 제스처를 볼 수 있도록 입력을 모방하는 데 사용됩니다. 다음에는 Unity의 OnEnableOnDisable 메소드를 통해 제스처의 기록 시작과 중지하는데 사용합니다.

    public void OnEnable()
    {
        if (photonView.isMine)
        {
            ovrAvatar.RecordPackets = true;
            ovrAvatar.PacketRecorded += OnLocalAvatarPacketRecorded;
        }
    }

    public void OnDisable()
    {
        if (photonView.isMine)
        {
            ovrAvatar.RecordPackets = false;
            ovrAvatar.PacketRecorded -= OnLocalAvatarPacketRecorded;
        }
    }

또한 새로운 패킷이 기록 될 때 발생되는 이벤트 핸들러를 설정합니다. 다음 단계에서 이 패킷은 PUN에 의해 지원되는 byte-array로 직렬화됩니다. 그런 다음, 전에 생성된 리스트에 추가되어 네트워크를 통해 전송할 준비가 됩니다. 아래 확인:

    private int localSequence;

    public void OnLocalAvatarPacketRecorded(object sender, OvrAvatar.PacketEventArgs args)
    {
        using (MemoryStream outputStream = new MemoryStream())
        {
            BinaryWriter writer = new BinaryWriter(outputStream);

            var size = Oculus.Avatar.CAPI.ovrAvatarPacket_GetSize(args.Packet.ovrNativePacket);
            byte[] data = new byte[size];
            Oculus.Avatar.CAPI.ovrAvatarPacket_Write(args.Packet.ovrNativePacket, size, data);

            writer.Write(localSequence++);
            writer.Write(size);
            writer.Write(data);

            packetData.Add(outputStream.ToArray());
        }
    }

이제 시리얼라이저가 있으므로 수신 된 패킷을 비직렬화 할 디시리얼라이저가 필요합니다. 따라서 다음 작업은 이 기능을 구현하는 것 입니다. 예제에서는 DeserializeAndQueuePacketData 입니다:

    private void DeserializeAndQueuePacketData(byte[] data)
    {
        using (MemoryStream inputStream = new MemoryStream(data))
        {
            BinaryReader reader = new BinaryReader(inputStream);
            int remoteSequence = reader.ReadInt32();

            int size = reader.ReadInt32();
            byte[] sdkData = reader.ReadBytes(size);

            System.IntPtr packet = Oculus.Avatar.CAPI.ovrAvatarPacket_Read((System.UInt32)data.Length, sdkData);
            remoteDriver.QueuePacket(remoteSequence, new OvrAvatarPacket { ovrNativePacket = packet });
        }
    }

이 함수는 들어오는 byte-array를 디시리얼라이즈하고 제스처를 리플레이 하기 위해 OvrAvatarRemoteDriver 컴포넌트에 대기중인 패킷 데이터를 재 생성합니다. 이 부분의 마지막 코딩은 기록된 패킷 교환을 추가하는 것입니다. 이 경우 우리는OnPhotonSerializeView를 사용합니다. 왜냐하면 정기적으로 자동 호출되기 때문에 정기적인 업데이트와 자연스럽게 보이게 됩니다.

    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if (stream.isWriting)
        {
            if (packetData.Count == 0)
            {
                return;
            }

            stream.SendNext(packetData.Count);

            foreach (byte[] b in packetData)
            {
                stream.SendNext(b);
            }

            packetData.Clear();
        }

        if (stream.isReading)
        {
            int num = (int)stream.ReceiveNext();

            for (int counter = 0; counter < num; ++counter)
            {
                byte[] data = (byte[])stream.ReceiveNext();

                DeserializeAndQueuePacketData(data);
            }
        }
    }

좀 더 이해를 돕기 위해서 두 파트로 분리하였습니다. 첫 번째 파트는 게임 오브젝트의 소유자에 의해 실행 될 isWriting 조건입니다. 먼저 데이터를 보내야 하는지 확인합니다. 데이터를 보내야하는 경우 첫 번째로 보낸 값은 패킷 개수입니다. 이 정보는 수신측에서 중요한 것 입니다. 결국 우리는 더 이상 필요하지 않기 때문에 기록되고 직렬화 된 모든 패킷을 보내고 목록에 저장된 이전 패킷 데이터를 지웁니다. 마지막에는 기록되고 직렬화된 모든 패킷을 전송하고, 더 이상 필요가 없으므로 리스트에 저장된 이전 패킷 데이터를 삭제합니다.

그러나 isReading 조건은 객체를 소유하지 않은 원격 클라이언트에서만 실행됩니다. 먼저, 처리해야 하는 패킷 개수를 확인한 다음 이전에 구현된 함수를 호출하여 모든 패킷 데이터를 단계적으로 비직렬화 및 대기열에 넣습니다.

마지막 단계는 두 개의 프리팹에 PhotonView 컴포넌트와 구현 된 PhotonAvatarView 를 붙이는 것 입니다. PhotonView의 관찰되는 컴포넌트에 PhotonAvatarView 컴포넌트를 추가하는 것을 잊지 마세요.

Back To Top

Avatars 인스턴스화

네트워크 아바타를 인스턴스화 하기 위해서는 불행하지만 PhotonNetwork.Instantiate 호출만으로는 되지 않습니다. 이유는 두 개의 다른 Avatar를 인스턴스화 해야 하기 때문입니다. 인스턴스를 생성하는 플레이어의 경우 'LocalAvatar'이며 이외 다른 플레이어의 경우 'RemoteAvatar' 입니다. 따라서 수동 인스턴스화 를 사용할 필요가 있습니다. 예제에서는 첫 번째로 Avatar용 'ViewId' 를 할당하고 다른 클라이언트들에게 알림을 주기 위한 캐싱을 사용하도록 하게하는 RaiseEvent 함수를 사용합니다.

    public readonly byte InstantiateVrAvatarEventCode = 123;

    public void OnJoinedRoom()
    {
        int viewId = PhotonNetwork.AllocateViewID();

        PhotonNetwork.RaiseEvent(InstantiateVrAvatarEventCode, viewId, true, new RaiseEventOptions() { CachingOption = EventCaching.AddToRoomCache, Receivers = ReceiverGroup.All });
    }

이제 올바른 프리팹이 인스턴스화 되었는지를 확인하는 OnEvent콜백 핸들러가 필요합니다. 따라서 전송자 ID와 로컬 클라이언트의 ID를 비교합니다. ID가 같으면 우리는 이 클라이언트가 이벤트를 발생시켰으며 또한 'LocalAvatar'프리 팹을 인스턴스화 해야 한다는 것을 알고 있습니다. ID가 같지 않으면 클라이언트가 'RemoteAvatar' 프리팹을 인스턴스화 해야 합니다.

    private void OnEvent(byte eventcode, object content, int senderid)
    {
        if (eventcode == InstantiateVrAvatarEventCode)
        {
            GameObject go = null;

            if (PhotonNetwork.player.ID == senderid)
            {
                go = Instantiate(Resources.Load("LocalAvatar")) as GameObject;
            }
            else
            {
                go = Instantiate(Resources.Load("RemoteAvatar")) as GameObject;
            }

            if (go != null)
            {
                PhotonView pView = go.GetComponent<PhotonView>();

                if (pView != null)
                {
                    pView.viewID = (int)content;
                }
            }
        }
    }

이렇게 하면 클라이언트가 이미 방에 들어 있거나 이후에 참여한 것과 상관없이 연결된 각각의 클라이언트에서 올바른 아바타가 인스턴스화됩니다. 마지막으로 한가지: 플레이어가 게임을 나갈 때 각 클라이언트에서 아바타가 파괴되는지 확인하십시오.

Back To Top

테스트

Avatar의 동기화 테스트에는 두 가지 옵션이 있습니다.

첫 번째 방법은 적어도 두 개의 별도 컴퓨터가 필요하며 각 컴퓨터에는 Oculus 장치가 연결되어 있어야 합니다(Rift 및 터치 컨트롤러). 유니티 에디터에서 게임을 시작하거나 먼저 빌드를 생성하고 나중에 두 머신에서 실행하여 제스처의 동기화가 잘되는지 확인할 수 있습니다.

또 다른 방식은 두 번째 테스트 어플리케이션을 빌드하는 것 입니다. 이것은 RemoteAvatar가 인스턴스화 될 지점을 보도록 기본 카메라를 배치하고 회전시키는 '빈' 프로젝트(이 페이지의 시작 부분에 언급 된 플러그인은 여전히 필요)일 수 있습니다. 우리의 경우에는 카메라가 (0/0/0)을 보아야 합니다. Photon에 연결할 때는 동일한 AppId 및 AppVersion을 사용해야 합니다. 이 접근법의 장점은 하나의 Oculus 장치 (리프트 및 터치 컨트롤러)가 있는 컴퓨터 한 대만 필요하다는 것입니다.

기술문서 TOP으로 돌아가기