35
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Photon Unity Networkingで動的に同期オブジェクトを生成する方法

Last updated at Posted at 2018-07-13

Photon Unity Networking(PUN)とは

Unity でマルチプレイが簡単にできるようになるアセット。
オブジェクトを動的に生成するためにはあらかじめプレハブ化しておく方法と、ViewID などを手動で与えてあげてごにょごにょする方法がありますが、後者の情報がどこにもなかったのでメモ程度に残しておきます。

動作環境

それぞれダウンロードしてインポートしておいてください。
またPUNのセットアップも完了させておいてください。コチラのサイト様などが参考になるかと思います。

Cube を動的に生成してみる

準備ができたら早速スクリプトを書いていきたいと思います。
今回は Cube をスクリプトから生成して、ViewID その他もろもろを与えて同期オブジェクトとして存在させたいと思います。
適当なオブジェクトに以下のスクリプトと PhotonView をアタッチして実行してみてください。

CubeCreator.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(PhotonView))]
public class CubeCreator : MonoBehaviour
{
    [SerializeField]
    Material CubeMaterial;

    int Timer = 0;
    bool isJoinedRoom = false;
    PhotonView PhotonView;

    void Start()
    {
        //RPC のために PhotonView 取得
        PhotonView = GetComponent<PhotonView>();

        //Photon に接続
        PhotonNetwork.ConnectUsingSettings("CubeCreator");
    }

    // ロビーに入室すると呼ばれるイベント
    void OnJoinedLobby()
    {
        Debug.Log("ロビーに入室成功");

        //ルームに入室するか作成する
        PhotonNetwork.JoinOrCreateRoom("CubeCreator", null, null);
    }

    //ルームに入室すると呼ばれる
    void OnJoinedRoom()
    {
        Debug.Log("ルームに入室成功");
        isJoinedRoom = true;
    }

    void Update()
    {
        Timer++;
        //100フレーム経過していない、又はルームに入室していないならスキップ
        if (Timer < 100 || !isJoinedRoom)
            return;

        //Cube を作成する関数を全クライアントで実行
        PhotonView.RPC("CreateCube", PhotonTargets.AllBuffered, PhotonNetwork.AllocateViewID());
        Timer = 0;
    }

    [PunRPC]
    void CreateCube(int viewID)
    {
        //Cube を生成
        var _cube = GameObject.CreatePrimitive(PrimitiveType.Cube);

        //わかりやすいように上空に移動
        _cube.transform.position = new Vector3(0, 10, 0);

        //マテリアルを設定
        _cube.GetComponent<Renderer>().material = CubeMaterial;

        //Rigidbody を追加
        var _rigidbody = _cube.AddComponent<Rigidbody>();

        //PUN を追加
        var _photonView = _cube.gameObject.AddComponent<PhotonView>();
        var _photonTransformView = _cube.gameObject.AddComponent<PhotonTransformView>();
        var _photonRigidbodyView = _cube.gameObject.AddComponent<PhotonRigidbodyView>();

        //PhotonView の ObservedComponents リストを初期化
        _photonView.ObservedComponents = new List<Component>();

        //PhotonView に ViewID を設定
        _photonView.viewID = viewID;

        //到達保証の設定
        //詳しくは https://support.photonengine.jp/hc/ja/articles/224763767-PUN%E3%81%A7%E5%88%B0%E9%81%94%E4%BF%9D%E8%A8%BC%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%82%92%E8%A1%8C%E3%81%86
        _photonView.synchronization = ViewSynchronization.ReliableDeltaCompressed;

        //PhotonTransformView の設定
        //位置の同期を有効にする
        _photonTransformView.m_PositionModel.SynchronizeEnabled = true;

        //回転の同期を有効にする
        _photonTransformView.m_RotationModel.SynchronizeEnabled = true;

        //リストに追加して同期対象に加える
        _photonView.ObservedComponents.Add(_photonTransformView);
        _photonView.ObservedComponents.Add(_photonRigidbodyView);

        Debug.Log("Create!");
    }
}

そうすると以下のような結果になるかと思います。

1人だけで実行しているのでわかりづらいですが、ビルドして同時に実行すれば生成スピードが倍になってるのがわかるかと思います。

解説

先ほどのスクリプトを(PUNの基本的な部分を覗いて)解説します。

Cube を作成する関数を全クライアントで実行

        //Cube を作成する関数を全クライアントで実行
        PhotonView.RPC("CreateCube", PhotonTargets.AllBuffered, PhotonNetwork.AllocateViewID());

Cube を生成する関数を全クライアントで実行します。この時 PhotonNetwork.AllocateViewID() を引数として渡すことで、全クライアントに対して同じ ViewID が通知されます。
PhotonNetwork.AllocateViewID は新しい ViewID を生成して返してくる関数です。詳しいことは PUNのドキュメント を読んでください。

PUN を追加

        //PUN を追加
        var _photonView = _cube.gameObject.AddComponent<PhotonView>();
        var _photonTransformView = _cube.gameObject.AddComponent<PhotonTransformView>();
        var _photonRigidbodyView = _cube.gameObject.AddComponent<PhotonRigidbodyView>();

同期に必要なスクリプトをアタッチして 保持します。
後々リストに追加する必要があるので、追加したタイミングで保持しておくのが良いでしょう。

PhotonView の ObservedComponents リストを初期化

        //PhotonView の ObservedComponents リストを初期化
        _photonView.ObservedComponents = new List<Component>();

PhotonView の ObservedComponents リストは同期対象のスクリプト(PhotonTransformView など)を入れるものですが、これは スクリプトをアタッチした段階では初期化されておらず、自分でリストを生成してあげる必要 があります。
これに気づくまでだいぶ時間を浪費しました。

PhotonView に ViewID を設定

        //PhotonView に ViewID を設定
        _photonView.viewID = viewID;

ここで 引数として貰った ViewID を PhotonView に設定 します。これを行うことで全てのクライアントでこの Cube の ViewID が同一のものとなり、PUN上で同一オブジェクトとして認識されます。

PhotonTransformView の設定

        //PhotonTransformView の設定
        //位置の同期を有効にする
        _photonTransformView.m_PositionModel.SynchronizeEnabled = true;

        //回転の同期を有効にする
        _photonTransformView.m_RotationModel.SynchronizeEnabled = true;

PhotonTransformView の位置と回転の同期を有効 にします。これを行わないと Cube が生成されても同期してくれません。

リストに追加して同期対象に加える

        //リストに追加して同期対象に加える
        _photonView.ObservedComponents.Add(_photonTransformView);
        _photonView.ObservedComponents.Add(_photonRigidbodyView);

最後に PhotonView の ObservedComponents リストへ同期対象のスクリプトを追加 します。

以上で重要なポイントの解説は終わりです。

最後に

PUNなんもわからん、どうやったら動的に生成できるんだと泣いている方の助けになれば幸いです。
これを応用すればどんなオブジェクトでも動的に生成して同期することができるようになるので、より自由度が高くなるかと思います。

おまけ

PUNは同期オブジェクトが1000個まで と決まっていまして、先ほどのスクリプトを動かし続けるとそのうちエラーを吐きます。

制限事項
Viewとプレイヤー

パフォーマンス上の理由で、PhotonNetwork APIがサポートするPhotonViewはプレイヤーあたり1000個まで、プレイヤー数は2,147,483人までです(これはハードウェアのサポート限界よりずっと多いのです!)。

Photon Unity Networking: 基本説明 より引用。

これを回避するためには、同期する必要のなくなったオブジェクトをPUNから削除 してあげる必要があります。
例として、生成した Cube に500フレーム経過したら自身を削除するスクリプトを追加します。

CubeDestroyer.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(PhotonView))]
public class CubeDestroyer : MonoBehaviour
{
    int Timer = 0;

    void Update()
    {
        Timer++;
        //500フレーム経過するまでスキップ
        if (Timer < 500)
            return;

        //ViewID を PhotonView から取得
        int _viewID = GetComponent<PhotonView>().viewID;

        //PUN から ViewID を削除
        PhotonNetwork.UnAllocateViewID(_viewID);

        Debug.Log("Destroy!");

        //オブジェクトを削除
        Destroy(this.gameObject);
    }
}

ここで重要なのは PhotonNetwork.UnAllocateViewID(int ViewID) で、これは引数として渡した ViewID をPUNから削除します。
詳しいことは PUNのドキュメント を読んでください。

これを実行すると以下のようになります。

使わなくなったオブジェクトは削除しないとネットワークを圧迫してしまうので、ちゃんと削除してあげましょう。

35
22
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?