PUN Classic(PUN1とも呼ばれます)はPUNのはじめの主要バージョンです。         現在は、リファクタリングおよび機能拡張されたPUN2に切り替わっています。         新しいプロジェクトはPUN2をご利用いただき、また可能であれば既存のプロジェクトについてもPUN1からPUN2へ移行していただくよう強く推奨しています。 こちらをご参照ください: "移行情報". PUN Cloassicは今後数か月メンテナンスされます。       重要なバグの修正や新しいUnityバージョンのサポートは行いますが、新機能の追加はPUN2のみとなります。

Oculus Avatarの使用

このガイドでは、PUNでOculus Avatar SDKを使用する方法を説明します。 まずは、新しいUnityプロジェクトを使い、次のパッケージをインポートしましょう:

はじめに

インポートが完了したら、既存のコンポーネントを拡張することができます。 まず、'Assets/OvrAvatar/Content/Prefabs'で2つのプレハブを確認します:'LocalAvatar'と 'RemoteAvatar'です。 次にこれらの2つのプレハブを使用するか、それらのコピーを作成します。

重要: 両方のプレハブを 'Resources'フォルダの中に配置する必要があります。 この場合、各プレハブのコピーは'Assets/Resources'にあります。

Back To Top

Avatarの同期

次のステップでは、複数のクライアント間で同期を処理するPhotonView(後で添付します)コンポーネントによって監視されるスクリプトを実装する必要があります。 このため、新しいスクリプトを作成してPhotonAvatarViewと名付け、次の3つの参照をコードに追加します:

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

さらに、実際に他のクライアントに送信する前に、Avatarからのデータを格納するために使用するバイト配列のリストが必要です。

    private List<byte[]> packetData;

UnityのStart関数を使うことで、以前のすべての参照とオブジェクトを設定できます。

    public void Start()
    {
        photonView = GetComponent<PhotonView>();

        if (photonView.isMine)
        {
            ovrAvatar = GetComponent<OvrAvatar>();
            ovrAvatar.RecordPackets = true;
            ovrAvatar.PacketRecorded += OnLocalAvatarPacketRecorded;

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

PhotonViewコンポーネントへの参照を取得した後、私たちはisMine条件を直接使用して、 'Local'と 'Remote Avatar'の間を明確にすることができます。 インスタンス化されたオブジェクトが私たちのものであれば、次のステップでこれを使用して OvrAvatarコンポーネントへの参照を取得し、このデータをネットワーク経由で送信する前にすべてのアバター関連の入力イベントを格納するバイト配列のリストをインスタンス化します。 オブジェクトが別のクライアントに属している場合は、後でOvrAvatarRemoteDriverコンポーネントへの参照を取得します。OvrAvatarRemoteDriverコンポーネントは、後で他のクライアントがジェスチャーを参照できるように入力を模倣するために使用されます。 次に、私たちのジェスチャーを含む記録パケットを開始したり停止したりするために使用する、UnityのOnDisableメソッドが必要です。

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

次のステップでは、このパケットはPUNがサポートするバイト配列にシリアル化されます。 その後、これは以前に作成したリストに追加され、ネットワーク上で送信される準備が整います。不要なデータの送信を避け、またメッセージサイズの超過によって発生する可能性のある切断を防止するには、関数のほかの部分を処理する必要があるか確認する条件をまず実装します。

以下を参照してください:

    private int localSequence;

    public void OnLocalAvatarPacketRecorded(object sender, OvrAvatar.PacketEventArgs args)
    {
        if (!PhotonNetwork.inRoom || (PhotonNetwork.room.PlayerCount < 2))
        {
            return;
        }

        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 });
        }
    }

この関数は受信バイト配列を非直列化してパケットデータを再作成し、パケットデータを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);
            }
        }
    }

分かりやすくするために、2つのパートに分けます。 最初のパートは、ゲームオブジェクトのオーナーによって実行されるisWriting条件です。 これはデータを送信する必要があるかどうかを最初に確認します。データを送信する必要がある場合、最初に送信される値はパケット数です。 この情報は受信側にとって重要です。

最終的には、記録されシリアル化されたすべてのパケットを送信し、リストに格納されている以前のパケットデータを消去します。このパケットデータはもう不要です。

ただしisReading条件は、オブジェクトを所有していないリモートクライアントでのみ実行されます。 まず、処理しなければならないパケットの数を確認してから、以前に実装した関数を呼び出します。その後、すべてのパケットデータを段階的に非直列化してキューに入れます。

Don't forget to add the PhotonAvatarView component to the observed components of the PhotonView. 最後のステップは、両方のプレハブにPhotonViewコンポーネントと実装されたPhotonAvatarViewを添付することです。 PhotonViewの監視されたコンポーネントにPhotonAvatarViewコンポーネントを追加してください。

Back To Top

Avatarのインスタンス化

残念ながら、PhotonNetwork.Instantiateを呼び出すだけではネットワークAvatarをインスタンス化することができません。 これは2種類のアバターをインスタンス化する必要があるためです: 1つはインスタンス化するプレイヤーのための'LocalAvatar'で、もう1つは他の人のための'RemoteAvatar'です。 したがって、手動インスタンス化を使用する必要があります。 以下のコードは、たとえば既存のNetwork Managerや、ネットワークロジックを処理するその他のスクリプトに設定することができます。このコードは、以前に作成したPhotonAvatarViewスクリプトには属しません。この例では、まず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を比較します: これら2つが同じ場合、このクライアントもイベントを発生させ、さらに'LocalAvatar'レハブをインスタンス化する必要があることがわかります。 IDが同じでない場合、クライアントは 'RemoteAvatar'プレハブをインスタンス化する必要があります。 RaiseEvent関数と、それに対応するOnEventコールバックについての詳細は、ドキュメントのRPCとRaiseEvent ページを参照してください。

    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;
                }
            }
        }
    }

これはクライアントがすでにルームに入っているか、後に参加するかに関わらず、各接続クライアントで正しいAvatarをインスタンス化します。 カスタムイベントが実行された点を確認する必要があります。このためには、以前に実装したOnEventコールバックを登録しなければなりません。この処理は、UnityのOnEnableおよびOnDisable関数によって処理されます(後にクリーンアップをおこないます)。

    public void OnEnable()
    {
        PhotonNetwork.OnEventCall += OnEvent;
    }

    public void OnDisable()
    {
        PhotonNetwork.OnEventCall -= OnEvent;
    }

最後に、プレイヤーがゲームを離れたときに、各クライアントでAvatarが破壊されるようにしてください。

Back To Top

テスト

Avatarの同期をテストするには、2つの方法があります。

最初の方法には少なくとも2つの個別のコンピュータが必要で、それぞれにOculusデバイス(RiftとTouch Controller)が接続されている必要があります。 Unityエディターからゲームを開始するか、または最初にビルドを作成してから両方のマシンで実行して、ジェスチャーの同期に問題がないことを確認できます。

別のアプローチは、第2のテストアプリケーションを構築することです。 RemoteAvatarがインスタンス化される地点を見るためのメインカメラを配置して回転させる「空白の」プロジェクトとすることができます(このページの冒頭で言及したプラグインが必要です)。 この場合、カメラは(0/0/0)を映すべきです。Photonに接続するときは、必ず同じAppIdとAppVersionを使用してください。また、インスタンス化イベントを処理するため、OnEventコールバックを実装して登録してください。 このアプローチの利点は、1つのOculusデバイス(RiftおよびTouch Controller)を備えた1台のコンピュータのみが必要である点です。

ドキュメントのトップへ戻る