Photon Unity Networking(PUN)とは
Unity でマルチプレイが簡単にできるようになるアセット。
オブジェクトを動的に生成するためにはあらかじめプレハブ化しておく方法と、ViewID などを手動で与えてあげてごにょごにょする方法がありますが、後者の情報がどこにもなかったのでメモ程度に残しておきます。
動作環境
- Unity2017.4.7f1
- Photon Unity Networking Free v1.91
それぞれダウンロードしてインポートしておいてください。
またPUNのセットアップも完了させておいてください。コチラのサイト様などが参考になるかと思います。
Cube を動的に生成してみる
準備ができたら早速スクリプトを書いていきたいと思います。
今回は Cube をスクリプトから生成して、ViewID その他もろもろを与えて同期オブジェクトとして存在させたいと思います。
適当なオブジェクトに以下のスクリプトと PhotonView をアタッチして実行してみてください。
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!");
}
}
そうすると以下のような結果になるかと思います。
PhotonでCubeを動的に生成して同期するテスト① pic.twitter.com/lakhSPHttw
— karukaru@C94三日目O-44b (@_karukaru_) 2018年7月13日
1人だけで実行しているのでわかりづらいですが、ビルドして同時に実行すれば生成スピードが倍になってるのがわかるかと思います。
PhotonでCubeを動的に生成して同期するテスト②
— karukaru@C94三日目O-44b (@_karukaru_) 2018年7月13日
最初の1個がめっちゃ同期ズレしてるけど気にしないで pic.twitter.com/ZT1izdnGk4
解説
先ほどのスクリプトを(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フレーム経過したら自身を削除するスクリプトを追加します。
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のドキュメント を読んでください。
これを実行すると以下のようになります。
PhotonでCubeを動的に生成して同期するテスト③
— karukaru@C94三日目O-44b (@_karukaru_) 2018年7月13日
動的に生成した同期オブジェクトを削除してあげる pic.twitter.com/57xL31Lyl9
使わなくなったオブジェクトは削除しないとネットワークを圧迫してしまうので、ちゃんと削除してあげましょう。