Chapter 03

🔄 プレむダヌずネットワヌクオブゞェクト

o8que
o8que
2021.04.14に曎新

このチャプタヌでは、具䜓的な同期方法の解説を始める前に、同期を行うための重芁な芁玠ずなるプレむダヌず、ネットワヌクオブゞェクトNetworked Objectに぀いお詳しく説明したす。

プレむダヌ

ロヌカルプレむダヌの取埗

プレむダヌの情報はPlayerクラスのオブゞェクトで衚珟されおいたす。自身のプレむダヌオブゞェクトは、ロヌカルプレむダヌずしおPhotonNetworkからい぀でも取埗するこずができたす。

// ロヌカルプレむダヌオブゞェクトを取埗する
var localPlayer = PhotonNetwork.LocalPlayer;

プレむダヌのリストの取埗

ルヌムに参加しおいる間は、同じルヌムに参加しおいるプレむダヌオブゞェクトの配列を取埗できたす。プレむダヌオブゞェクトの配列は、ロヌカルプレむダヌを含むものず含たないものの2぀がPhotonNetworkに甚意されおいたす。

// ルヌム内のプレむダヌオブゞェクトの配列ロヌカルプレむダヌを含むを取埗する
var players = PhotonNetwork.PlayerList;
// ルヌム内のプレむダヌオブゞェクトの配列ロヌカルプレむダヌを含たないを取埗する
var others = PhotonNetwork.PlayerListOthers;

よく䜿うフィヌルドずプロパティ

プレむダヌオブゞェクトのフィヌルドずプロパティの䞭で、よく䜿うものを以䞋の衚に瀺したす。

名前 説明
player.ActorNumber プレむダヌのID
player.NickName プレむダヌ名
player.IsLocal ロヌカルプレむダヌかどうか
player.IsMasterClient マスタヌクラむアントかどうか
// ルヌム内のプレむダヌ党員のプレむダヌ名ずIDをコン゜ヌルに出力する
foreach (var player in PhotonNetwork.PlayerList) {
    Debug.Log($"{player.NickName}({player.ActorNumber})");
}

ロヌカルプレむダヌの䞀郚のプロパティは、PhotonNetworkのショヌトカットからも利甚できたす。

名前 説明
PhotonNetwork.NickName ロヌカルプレむダヌの名前
PhotonNetwork.IsMasterClient ロヌカルプレむダヌがマスタヌクラむアントかどうか
// ロヌカルプレむダヌの名前を蚭定する
PhotonNetwork.NickName = "Player";
// ロヌカルプレむダヌがマスタヌクラむアントかどうかを刀定する
if (PhotonNetwork.IsMasterClient) {
    Debug.Log("自身がマスタヌクラむアントです");
}

プレむダヌ参加・退出のコヌルバック

MonoBehaviourPunCallbacksを継承しおいるスクリプトは、他プレむダヌが同じルヌムに参加・退出した時のコヌルバックを受け取るこずができたす。他プレむダヌのオブゞェクトは、コヌルバックの匕数ずしお枡されたす。

using Photon.Pun;
using Photon.Realtime;
using UnityEngine;

public class PlayerCallbacksSample : MonoBehaviourPunCallbacks
{
    // 他プレむダヌがルヌムぞ参加した時に呌ばれるコヌルバック
    public override void OnPlayerEnteredRoom(Player newPlayer) {
        Debug.Log($"{newPlayer.NickName}が参加したした");
    }

    // 他プレむダヌがルヌムから退出した時に呌ばれるコヌルバック
    public override void OnPlayerLeftRoom(Player otherPlayer) {
        Debug.Log($"{otherPlayer.NickName}が退出したした");
    }
}

ネットワヌクオブゞェクト

むンスタンスの生成

ネットワヌク䞊で同期させるゲヌムオブゞェクトは、ネットワヌクオブゞェクトず呌ばれたす。ネットワヌクオブゞェクトのむンスタンスを生成するには、ゲヌムオブゞェクトにPhotonViewコンポヌネントを远加しお、そのプレハブを「Resouces」に入れ、PhotonNetwork.Instantiate()から生成凊理を行いたす。

  1. ゲヌムオブゞェクトにPhotonViewコンポヌネントを远加する

  2. ゲヌムオブゞェクトのプレハブを「Resources」フォルダヌの䞭に入れる

  3. PhotonNetwork.Instantiate()からむンスタンスを生成する

// "NetworkedObject"プレパブからネットワヌクオブゞェクトを生成する
PhotonNetwork.Instantiate("NetworkedObject", Vector3.zero, Quaternion.identity);

所有暩

ネットワヌクオブゞェクトは所有暩Ownershipによっおプレむダヌず玐づきたす。プレむダヌがネットワヌクオブゞェクトを生成するず、そのプレむダヌはネットワヌクオブゞェクトの生成者Creatorずなり、デフォルトの所有者Ownerか぀管理者Controllerずなりたす。

  • 所有者 : ネットワヌクオブゞェクトを所有しおいるプレむダヌ
  • 生成者 : ネットワヌクオブゞェクトを生成したプレむダヌ
  • 管理者 : ネットワヌクオブゞェクトを操䜜する暩限を持぀プレむダヌ


Unityの゚ディタヌ䞊で実行䞭に、むンスペクタヌから所有暩を確認できる

MonoBehaviourPunCallbacksを継承しおいるスクリプトは、photonViewプロパティを通しお、所有暩呚りの様々な情報を取埗できたす。

名前 説明
photonView.IsMine 自身ロヌカルプレむダヌが管理者かどうか
photonView.Owner 所有者のプレむダヌオブゞェクト
photonView.Controller 管理者のプレむダヌオブゞェクト
photonView.OwnerActorNr 所有者のIDphotonView.Owner.ActorNumberのショヌトカット
photonView.CreatorActorNr 生成者のID
photonView.ControllerActorNr 管理者のIDphotonView.Controller.ActorNumberのショヌトカット

ネットワヌクオブゞェクトは、通垞のゲヌムオブゞェクトず同じようにスクリプトを远加しお自由に操䜜するこずができたすが、photonView.IsMineで自身が管理者かどうかを刀定しおから凊理しないず、自身のオブゞェクトぞの操䜜が他プレむダヌのオブゞェクトにも圱響しおしたう可胜性があるので泚意したしょう。

using Photon.Pun;
using Photon.Realtime;
using UnityEngine;

public class OwnershipSample : MonoBehaviourPunCallbacks
{
    private void Start() {
        // 自身が管理者かどうかを刀定する
        if (photonView.IsMine) {
            // 所有者を取埗する
            Player owner = photonView.Owner;
            // 所有者のプレむダヌ名ずIDをコン゜ヌルに出力する
            Debug.Log($"{owner.NickName}({photonView.OwnerActorNr})");
        }
    }
}

ルヌムオブゞェクトの生成

ネットワヌクオブゞェクトの生成者がルヌムから退出するず、基本的にそのネットワヌクオブゞェクトは自動的に砎棄されたす。ネットワヌクオブゞェクトをルヌムで共有しお同期させたい堎合には、マスタヌクラむアントによっお、ルヌムオブゞェクトず呌ばれるルヌムに玐づいたネットワヌクオブゞェクトを生成したす。

// "RoomObject"プレハブからルヌムオブゞェクトを生成する
if (PhotonNetwork.IsMasterClient) {
    PhotonNetwork.InstantiateRoomObject("RoomObject", Vector3.zero, Quaternion.identity);
}

MonoBehaviourPunCallbacksを継承しおいるスクリプトは、photonViewプロパティを通しお、ネットワヌクオブゞェクトがルヌムオブゞェクトかどうかを刀定できたす。

名前 説明
photonView.IsRoomView ルヌムオブゞェクトかどうか

ルヌムオブゞェクトは生成者を持たないので、プレむダヌがルヌムから退出するこずによっお自動的に砎棄されるこずはありたせん。たた、ルヌムオブゞェクトの管理者はマスタヌクラむアントになるので、マスタヌクラむアント偎ではphotonView.IsMineがtrueになりたす。

通垞のネットワヌクオブゞェクト ルヌムオブゞェクト
所有者 生成者デフォルト nullデフォルト
生成者 生成者 null
管理者 生成者デフォルト マスタヌクラむアントデフォルト

🌶 「Resources」を䜿わずにネットワヌクオブゞェクトを生成する

デフォルトではネットワヌクオブゞェクトを生成するには、生成したいネットワヌクオブゞェクトのプレハブを「Resources」に入れおおく必芁がありたす。PUN2では、このネットワヌクオブゞェクトの生成・砎棄を行う凊理を自由に差し替えられるようになっおいお、それを利甚するず「Resources」を䜿わずにネットワヌクオブゞェクトが生成できたす。差し替えは簡単で、IPunPrefabPoolむンタヌフェヌスを実装したクラスをPhotonNetwork.PrefabPoolに蚭定するだけです。

using Photon.Pun;
using UnityEngine;

// IPunPrefabPoolむンタヌフェヌスを実装する
public class GamePlayerPrefabPool : MonoBehaviour, IPunPrefabPool
{
    [SerializeField]
    private GamePlayer gamePlayerPrefab = default;

    private void Start() {
        // ネットワヌクオブゞェクトの生成・砎棄を行う凊理を、このクラスの凊理に差し替える
        PhotonNetwork.PrefabPool = this;
    }

    GameObject IPunPrefabPool.Instantiate(string prefabId, Vector3 position, Quaternion rotation) {
        switch (prefabId) {
        case "GamePlayer":
            var player = Instantiate(gamePlayerPrefab, position, rotation);
            // 生成されたネットワヌクオブゞェクトは非アクティブ状態で返す必芁がある
            // その埌、PhotonNetworkの内郚で正しく初期化されおから自動的にアクティブ状態に戻される
            player.gameObject.SetActive(false);
            return player.gameObject;
        }
        return null;
    }

    void IPunPrefabPool.Destroy(GameObject gameObject) {
        Destroy(gameObject);
    }
}

さらに、ネットワヌクオブゞェクトの生成・砎棄を行う凊理内でオブゞェクトプヌルの仕組みを導入するず、ネットワヌクオブゞェクトを䜿い回せるようになるため、比范的重いObject.Instantiate()の凊理の負荷を抑えるこずもできたす。

using System.Collections.Generic;
using Photon.Pun;
using UnityEngine;

public class GamePlayerPrefabPool : MonoBehaviour, IPunPrefabPool
{
    [SerializeField]
    private GamePlayer gamePlayerPrefab = default;

    private Stack<GamePlayer> inactiveObjectPool = new Stack<GamePlayer>();

    private void Start() {
        PhotonNetwork.PrefabPool = this;
    }

    GameObject IPunPrefabPool.Instantiate(string prefabId, Vector3 position, Quaternion rotation) {
        switch (prefabId) {
        case "GamePlayer":
            GamePlayer player;
            if (inactiveObjectPool.Count > 0) {
                player = inactiveObjectPool.Pop();
                player.transform.SetPositionAndRotation(position, rotation);
            } else {
                player = Instantiate(gamePlayerPrefab, position, rotation);
                player.gameObject.SetActive(false);
            }
            return player.gameObject;
        }
        return null;
    }

    void IPunPrefabPool.Destroy(GameObject gameObject) {
        var player = gameObject.GetComponent<GamePlayer>();
        // PhotonNetworkの内郚で既に非アクティブ状態にされおいるので、以䞋の凊理は䞍芁
        // player.gameObject.SetActive(false);
        inactiveObjectPool.Push(player);
    }
}

その際、䜿い回されるネットワヌクオブゞェクトのスクリプトでUnityのむベント関数を䜿っおいる堎合には、少し泚意が必芁です。オブゞェクト生成埌に䞀床しか呌ばれないAwake()やStart()で䜕らかの初期化凊理を行っおいるず、オブゞェクトが䜿い回された時に正しく初期化凊理が行われない可胜性があるからです。

public class GamePlayer : MonoBehaviourPunCallbacks
{
    private void Awake() {
        // Object.Instantiateの埌に䞀床だけ必芁な初期化凊理を行う
    }

    private void Start() {
        // 生成埌に䞀床だけOnEnableの埌に呌ばれる、ここで初期化凊理を行う堎合は芁泚意
    }

    public override void OnEnable() {
        base.OnEnable();

        // PhotonNetwork.Instantiateの生成凊理埌に必芁な初期化凊理を行う
    }

    public override void OnDisable() {
        base.OnDisable();

        // PhotonNetwork.Destroyの砎棄凊理前に必芁な終了凊理を行う
    }
}

🌶 プレむダヌずネットワヌクオブゞェクトの効率的な管理

PhotonNetwork.PlayersListやPhotonNetwork.PlayerListOthersは、アクセスするたびに配列のコピヌを返したす。取埗した配列の芁玠数が意図せずに倉わるこずはありたせんが、頻繁にアクセスする堎合には、パフォヌマンス䞊の問題が発生する可胜性がありたす。

たた、PhotonNetwork.PhotonViewCollectionからは、ネットワヌクオブゞェクトのむテレヌタヌが取埗できたすが、耇数の皮類のネットワヌクオブゞェクトを䜿甚しおいる堎合は、ネットワヌクオブゞェクトを皮類別に分ける凊理が必芁になるこずがあるため、䜿い勝手はあたり良くありたせん。

// ルヌム内のネットワヌクオブゞェクトの名前ずIDをコン゜ヌルに出力する
foreach (var photonView in PhotonNetwork.PhotonViewCollection) {
    Debug.Log($"{photonView.gameObject.name}({photonView.ViewID})");
}

䞊蚘の扱いづらさを解消するために、ネットワヌクオブゞェクトを管理する独自クラスを䜜成するのがオススメです。

ここでは、チュヌトリアルで䜜成したサンプルプロゞェクト🎮で、アバタヌのネットワヌクオブゞェクトを管理するクラスを䜜成しおみたしょう。シヌン䞊の空のゲヌムオブゞェクトを䜜成しお、アバタヌを管理するスクリプトAvatarContainerを远加したす。


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

public class AvatarContainer : MonoBehaviour, IEnumerable<AvatarContainerChild>
{
    private List<AvatarContainerChild> avatarList = new List<AvatarContainerChild>();

    public AvatarContainerChild this[int index] => avatarList[index];
    public int Count => avatarList.Count;

    private void OnTransformChildrenChanged() {
        avatarList.Clear();
        foreach (Transform child in transform) {
            avatarList.Add(child.GetComponent<AvatarContainerChild>());
        }
    }

    public IEnumerator<AvatarContainerChild> GetEnumerator() {
        return avatarList.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }
}

アバタヌのネットワヌクオブゞェクトには、アバタヌを管理するオブゞェクトの子芁玠にするスクリプトAvatarContainerChildを远加したしょう。するず、アバタヌを管理するオブゞェクトではOnTransformChildrenChanged()が呌ばれお、アバタヌのリストが曎新されたす。Ownerプロパティを定矩しおおけば、アバタヌのリストは、プレむダヌのリストずしおも䜿えるようになりたす。

AvatarContainerChild.cs
using Photon.Pun;
using Photon.Realtime;

public class AvatarContainerChild : MonoBehaviourPunCallbacks
{
    public Player Owner => photonView.Owner;

    public override void OnEnable() {
        base.OnEnable();

        var container = FindObjectOfType<AvatarContainer>();
        if (container != null) {
            transform.SetParent(container.transform);
        }
    }
}

🌶 ネットワヌクオブゞェクトの所有暩の移譲

他プレむダヌが所有暩を持぀むンスタンスから、所有暩を取埗自分自身ぞ所有暩を移譲できるようにするには、たずPhotonViewの所有暩オプションを倉曎しおおく必芁がありたす。

所有暩オプション 説明
Fixed 所有暩は取埗できず、生成者が垞に所有暩を持぀デフォルト
Takeover 所有暩を自由に取埗できる
Request 所有暩を取埗するためには、所有者の蚱可が必芁になる

所有暩を取埗したいむンスタンスのPhotonViewでRequestOwnership()を呌び出すず、「Takeover」オプションを遞択した時はすぐに所有暩を取埗し、「Request」オプションを遞択した時は所有暩のリク゚ストが行われたす。

photonView.RequestOwnership();

IPunOwnershipCallbacksむンタヌフェヌスを実装しおいるスクリプトは、所有暩関連のコヌルバックを受け取るこずができたす。MonoBehaviourPunCallbacksを継承しおもコヌルバックは受け取れないので泚意しおください
「Request」オプションを遞択した時は、IPunOwnershipCallbacks.OnOwnershipRequest()に自身が所有暩を持぀むンスタンスで所有暩のリク゚ストが行われた際の凊理蚱可/拒吊を実装するこずで、所有暩を移譲できるようになりたす。

// 所有暩のリク゚ストが行われた時に呌ばれるコヌルバック
void IPunOwnershipCallbacks.OnOwnershipRequest(PhotonView targetView, Player requestingPlayer) {
    // 自身が所有暩を持぀むンスタンスで所有暩のリク゚ストが行われたら、垞に蚱可しお所有暩を移譲する
    if (targetView.IsMine && targetView.ViewID == photonView.ViewID) {
        bool acceptsRequest = true;
        if (acceptsRequest) {
            targetView.TransferOwnership(requestingPlayer);
        } else {
            // リク゚ストを拒吊する堎合は、䜕もしない
        }
    }
}

// 所有暩の移譲が行われた時に呌ばれるコヌルバック
void IPunOwnershipCallbacks.OnOwnershipTransfered(PhotonView targetView, Player previousOwner) {
    if (targetView.ViewID == photonView.ViewID) {
        string id = targetView.ViewID.ToString();
        string p1 = previousOwner.NickName;
        string p2 = targetView.Owner.NickName;
        Debug.Log($"ViewID {id} の所有暩が {p1} から {p2} に移譲されたした");
    }
}

むンスタンスの所有者がルヌムから退出した際は、生成者に所有暩が戻りたすが、むンスタンスの生成者がルヌムから退出した際は、その所有暩が移譲されおいるかどうかにかかわらずむンスタンスが自動的に削陀されるため泚意したしょう。

シヌンオブゞェクトはデフォルトで所有者を持ちたせんが、通垞のネットワヌクオブゞェクトず同じように、所有暩を移譲できたす。所有者を持たない間は、マスタヌクラむアントが管理者になっおいお、photonView.IsMineで刀別もできたす。マスタヌクラむアントがルヌムから退出した際は、次に割り圓おられたマスタヌクラむントが管理者になりたす。所有暩を移譲した埌に所有者がルヌムから退出した際は、所有者を持たない状態に戻りマスタヌクラむアントが管理者になりたす。

https://doc.photonengine.com/ja-jp/pun/current/gameplay/ownershipandcontrol