😀

Photon Fusion1で動的にSceneObject(的なもの)を生成する

2024/02/18に公開

Photon FusionでScene Objectは、シーンに一つだけしか必要ないNetworkObjectを生成する場合に便利な機能です。 例えば、ModelやManager等を作成したい場合は、シーンにおいておくだけで目的を達成することができます。
ただし、動的にNetworkObjectを生成したい場合、SceneObjectはシーンロード時に処理が行われるため、開発者側で付与することができません。そのため、SceneObjkectのようなシーンに一つだけのNetworkObjectを生成するにはすこしだけ工夫が必要でした。Tipsとして役に立つ人もいるかもなと思い書き残しておきます。

環境と利用しているモード

タイトルにも書きましたが、利用しているPhotonFusionのバージョンは2.0ではありません。(1.1.8です)ですので、現在RecommendされているFusion2.0とは挙動が異なる可能威勢があります。
また、Host/ServerモードではなくSharedモードを利用しています。

シーンに一つだけのNetworkObjectを作るには

シーンに一つだけのNetworkObjectを生成する時に考えることは、2つです。
一つは、プレイヤーがNetworkObjectであるManagerAをSpawnさせてNetworkObjectとしてシーンに生成すると、Fusion側で自動的に他のプレイヤーのシーンにも同じManagerAが生成されてしまいます。これは、例えばプレイヤーがAvatarなどを生成して自動的に共有する場合は便利ですが、今回のようにシーンに一つしかないものを作りたい場合は対応を考える必要があります。

もう一つは、ManagerAを生成したプレイヤーはそのManagerAのStateAuthorになります。このプレイヤーがいなくなった場合、ManagerAをどのような挙動にするかを設定しておく必要があります。

作成したプレイヤーがいなくなってもシーンに残るnetworkObject

こちらについては、じつは、デフォルトの設定で解決できています。下のNetworkObjectの画像のAllow State Authority Overrideがデフォルトでtrueになっており、これがtrueになっていると、このNetworkObjectを生成したプレイヤーがセッションから離れた場合は違うプレイヤーにStateAuthorityが変更されるようになっています。
逆に、デフォルトの設定だとアバターなどセッションから離れたときに消えてほしいNetworkObjectは残ってしまいます。Despawnの処理を自分で書いてもよいですが、下の画像のDestroy When State Authority LeavesをtrueにするとFusionが勝手にDespawn処理を呼んでくれます。

シーンに一つだけのNetworkObjectを生成する

Singletonのような処理を作ればよいわけですが、NwetworkObjectの破壊にはDespawnが必要です。また、生成されたNetworkObjectがアタッチされたManagerAがNetwork化されるまでにはラグがあるため、Singletonの処理でオブジェクトが消えてしまうとNetwork化されるまえにObjectが消えてしまうなど煩雑な管理をする必要がありそうでした。

なので、今回はManagerAを生成するタイミングでの、ActivePlayerの数をみて生成と非生成を分岐させるという比較的楽な方法で実現しました。

private void SpawnNetworkManagers()
{
    if (_networkRunner.ActivePlayers.Count() <= 1)
    {  // 最初に入ったプレイヤーだけmanagerAをスポーンさせる。
        _networkRunner.Spawn(networkObject);
    }
}

この方法の注意点として、
_networkRunner.ActivePlayers.Count()がセッション開始直後の場合は自身がActivePlayersとして認識されない場合があります。例えば、最初のプレイヤーがせしょんを開始した直後だと0の場合があります。
ActivePlayersの中身を見ると下のようになっているのでSimulation内でActivePlayersが生成されていない場合Emptyのリストが返ってきているようです。なので、遅延処理を少し入れてからSpawnの処理を入れたほうが良いと思います。

/// <summary>
/// Returns the collection of <see cref="T:Fusion.PlayerRef" /> objects for this NetworkRunner's <see cref="T:Fusion.Simulation" />.
/// </summary>
public IEnumerable<PlayerRef> ActivePlayers
{
  get => this._simulation?.ActivePlayers ?? System.Linq.Enumerable.Empty<PlayerRef>();
}

また、もう一つの注意点としてNetworkObjectが動的に生成されるということはScene Objectの時のように最初からシーンに存在するわけではないため最初にセッションを開始したPlayer以外のPlayerはManagerAについてはFusion側から勝手に生成されます。

そのため、シーンオブジェクトの時のようにNetworkBehaviourを継承しているクラスをStartやAwake時に他のクラスから参照しようとすると見つからずにエラーが出ます。NetworkBehaivorがSpanwされてから依存関係を整理するなどの対応をする必要が必要があります。

例えば、ServiceLocatarなどを利用している場合はnetworkObjectはResolveのみを行う側として依存関係を構築すると。動的にNetworkObjectが生成された場合も、他クラスがNetworkObjectのクラスに依存していないため問題なく処理できたりします。

おわりに

Fusion2がリリースされて大幅にSDKが変わっているので、このTipsが使われることがあるかわからないですが、役に立ったら嬉しいです。
ちなみに、今開発しているアプリはFusion2で廃止されるOnChange系でNetworkPropatyを管理しているところが結構あるので、今のところは乗り換えない方針です。(OnChange系が今後追加されたら嬉しいな。。)
ただ、色々機能が増えているので今後Sharedモードで面白い機能が出たりしたら乗り換えも検討するかもしれません。

Discussion