2 - シーンをセットアップする
概要
Fusion 102は基本のネットワークセットアップ方法を説明します。
このセクションの終了時には、プロジェクトには以下が含まれています。
- マッチを開始したりマッチに参加するための基本のFusion Runner
- 操作することができるキャラクター
ネットワークインプットの詳細の説明に関するマニュアルをお読みください
Fusionを開始する
Fusionを開始するには、Fusion NetworkRunner
でStartGame
メソッドを呼び出す必要があります。そのため、アプリケーションではシーン内にこのコンポーネントがあるか、コードから追加しておく必要があります。多くのネットワーキングロジックでは、どちらにせよ大量のコードを記述しなければならないので、このチュートリアルはコード中心で進めていきます。
UnityデフォルトのシーンでUnityを開きます。
- 新しい空白のGameObjectを作成する
- 新しいスクリプトコンポーネントを上記に追加する
- 上記のスクリプトの名前を
BasicSpawner
とする - スクリプトを開き、
BasicSpawner
クラスへ、要求されるすべてのメソッドのスタブとともにINetworkRunnerCallbacks
インターフェースを追加する。:
public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks
{
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player) { }
public void OnPlayerLeft(NetworkRunner runner, PlayerRef player) { }
public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input) { }
public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason) { }
public void OnConnectedToServer(NetworkRunner runner) { }
public void OnDisconnectedFromServer(NetworkRunner runner) { }
public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token) { }
public void OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason) { }
public void OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message) { }
public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList) { }
public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary<string, object> data) { }
public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ArraySegment<byte> data) { }
public void OnSceneLoadDone(NetworkRunner runner) { }
public void OnSceneLoadStart(NetworkRunner runner) { }
}
INetworkRunnerCallbacks
を実装すると、FusionのNetworkRunner
がBasicSpawner
クラスと連携できるようになります。NetworkRunner
はFusionの最も大事なコアの部分で、実際のネットワークシミュレーションを実行します。
NetworkRunner
は、BasicSpawner
がINetworkRunnerCallbacks
を実装したことを自動的に検出し、そのメソッドを呼び出します。StartGame
メソッド内で同じゲームオブジェクトが追加されることになるためです。
async void StartGame(GameMode mode)
{
// Create the Fusion runner and let it know that we will be providing user input
_runner = gameObject.AddComponent<NetworkRunner>();
_runner.ProvideInput = true;
// Start or join (depends on gamemode) a session with a specific name
await _runner.StartGame(new StartGameArgs()
{
GameMode = mode,
SessionName = "TestRoom",
Scene = SceneManager.GetActiveScene().buildIndex,
SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
});}
StartGame
メソッドは、まず Fusion NetworkRunner
を作成し、このクライアントがインプットを提供するであろうことをNetworkRunner
に伝えます。その後、ハードコードされた名前を持ち、ゲームモードを指定された新しいセッションを開始します。現在のシーンインデックスがパスされてきますが、クライアントはホストによって指定されたシーンの使用を強制されるため、シーンインデックスはホストに対してのみ関係があります。最後に、デフォルトのSceneObjectProviderが加えて指定されます。SceneObjectProviderはシーンに直接置かれたNetworkObjectsのインスタンス化を行います。厳密にいうとこの例ではこのようなオブジェクトが存在しないため、これは必要なものではありません。
Fusionはいくつかのネットワークトポロジに対応していますが、このイントロダウションではHosted Mode(ホストモード)
に絞ってご紹介したいと思います。ホストモードでは、1つのネットワークピアがサーバーおよびクライアントとなり、ネットワークセッションを作成します。残りのピアは依存のセッションに参加するのみとなります。
これを行うには、ユーザーがゲームをホストするか、既存のセッションに参加するかを選択する方法が必要です。簡単にするため、以下の例ではUnity IMGUIを使用します。以下のメソッドをBasicSpawner.cs
に追加します。
private NetworkRunner _runner;
private void OnGUI()
{
if (_runner == null)
{
if (GUI.Button(new Rect(0,0,200,40), "Host"))
{
StartGame(GameMode.Host);
}
if (GUI.Button(new Rect(0,40,200,40), "Join"))
{
StartGame(GameMode.Client);
}
}
}
このアプリケーションを実行すると、ユーザーが新しいセッションをホストできるようになり、追加のインスタンスをそのセッションに参加させることができるようになります。しかし連携もなくデータが転送されていないため、まるでシングルプレイヤーのように見えることになります。
プレイヤーアバターを作成する
これをゲームにするには、各プレイヤーが入力の提供方法が与えられており、プレイヤーアバターなどとしてワールド内に存在していることが必要です。
Unityのエディターで以下を行います。
- 空白のGameObjectを作成し、
PlayerPrefab
と名前を付ける。 - 上記に
NetworkObject
コンポーネントを追加する。
これによりネットワークアイデンティティが与えられ、全てのピアがそのネットワークアイデンティティを参照できるようになります。ユーザーが子のアバターをコントロールしているため、NetworkCharacterController
も必要になります。これは要件ではありませんが、多くのプレイヤーがコントロールするオブジェクトの良い入り口となるため、やってみることをお勧めします。
一般的に、ネットワークオブジェクトとビジュアル表現を分けて、ネットワークオブジェクトがネットワークステートをスナップしているときでも、ビジュアル表現がスムーズに内挿されるようにしておくことを推奨します。
これを実現するには、以下を行います。
- 標準のUnity
Cube
を子オブジェクトとしてPlayerPrefab
に追加する。 - 上記の名前を
Body
に変更する。 Collider
を削除する。Body
ゲームオブジェクトを親のNetworkCharacterController
のInterpolation Target
プロパティにドラッグする。
アバター構成は以下の様に表示されているはずです。

プロジェクトをSave(保存)
し、Fusionが新しいネットワークオブジェクトをベイクするようにします。その後、プロジェクトフォルダへとドラッグしてアバタープレハブを作成し、シーンからオブジェクトを消去します。
アバターをスポーンする
ゲームがホストモードで実行されているため、新しいあ部ジェクトをスポーンする権限を持っているのはホストのみとなります。つまり、全てのプレイヤーアバターは、セッションへの参加時にホストがスポーンする必要があるということです。正にこれを行うため、INetworkRunnerCallbacks
インターフェイスのOnPlayerJoined
メソッドが呼び出されます。
同様に、プレイヤーが切断するとOnPlayerLeft
メソッドが呼び出されます。
空白の OnPlayerJoined
スタブとOnPlayerLeft
スタブを以下のコードで置き換えます。
[SerializeField] private NetworkPrefabRef _playerPrefab;
private Dictionary<PlayerRef, NetworkObject> _spawnedCharacters = new Dictionary<PlayerRef, NetworkObject>();
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
if (runner.IsServer)
{
// Create a unique position for the player
Vector3 spawnPosition = new Vector3((player.RawEncoded%runner.Config.Simulation.DefaultPlayers)*3,1,0);
NetworkObject networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player);
// Keep track of the player avatars so we can remove it when they disconnect
_spawnedCharacters.Add(player, networkPlayerObject);
}
}
public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
{
// Find and remove the players avatar
if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject))
{
runner.Despawn(networkObject);
_spawnedCharacters.Remove(player);
}
}
基本的にはUnityのInstantiate()
メソッドを、最後以外同様のパラメータセットであるrunner.Spawn()
で置き換えているので、見覚えがあるかと思います。最後のパラメータは、アバターにインプットを許可する、プレイヤーへの参照です。オブジェクトを「占拠」するのとは異なる点に注意することが重要です。
Unityエディタに戻り、作成したプレハブアバターをBasicSpawner
のPlayer Prefab
フィールドにドラッグドロップするのを忘れないようにしてください。
インプットを収集する
入力オーソリティを持っていても、直接クライアントにオブジェクトのネットワークステートの調整を許可するわけではありません。ネットワークステートを更新するために、ホストが解釈する入力構造の提供をします。
クライアントはローカルで入力を適用し、ユーザーに簡易フィードバックを提供することもあります。ですが、これはローカルの予測に過ぎず、ホストによって書き換えられる可能性があります。
入力がユーザーによって回収される前に、入力を保持するようにデータ構造を定義する必要があります。新しいファイルを作成し、NetworkInputData.cs
と名前を付けて以下の構造を入れておきましょう。
using Fusion;
using UnityEngine;
public struct NetworkInputData : INetworkInput
{
public Vector3 direction;
}
この例では、簡潔にするためにベクトルを使用して希望する動作の方向を示しています。ただし、これを行うには帯域幅の消費量をより少なく抑える方法もあります。例えば、方向毎の1ビットのビットフィールド。Fusionではインプットを圧縮し実際に変更するデータのみ送信する点に留意いただき、時期尚早な最適化に躍起にならないようにしてください。
クライアントは、OnInputコールバックでFusionにポーリングされると、ユーザーからインプットを収集する必要があります。BasicSpawner.cs
に戻り、 OnInput()
スタブを以下と置き換えてください。
public void OnInput(NetworkRunner runner, NetworkInput input)
{
var data = new NetworkInputData();
if (Input.GetKey(KeyCode.W))
data.direction += Vector3.forward;
if (Input.GetKey(KeyCode.S))
data.direction += Vector3.back;
if (Input.GetKey(KeyCode.A))
data.direction += Vector3.left;
if (Input.GetKey(KeyCode.D))
data.direction += Vector3.right;
input.Set(data);
}
繰り返しになりますが、かなり見覚えがあるものに合っているはずです。ハンドラがUnityのインプットを使用して、前もって定義されていた構造のローカルクライアントからのインプットを収集し、保存しています。このメソッドの最終行はプレビルドのインプット構造をFusionにパスしており、ホストやこのクライアントがインプットオーソリティを持つオブジェクトがそれを使用できるようにしています。
インプットを適用する
最終ステップは、収集したインプットデータのプレイヤーアバターへの適用です。
PlayerPrefab
を選択する。- 上記に
Player
という新しいスクリプトコンポーネントを追加する。 - 新しいスクリプトを開き、MonoBehaviourをNetworkBehaviourで置き換える
FixedUpdateNetwork()
を実装する。挙動がFusionシミュレーションループに参加できるようになる。
using Fusion;
public class Player : NetworkBehaviour
{
public override void FixedUpdateNetwork(){}
}
シミュレーションティックごとにFixedUpdateNetworkが呼び出されます。Fusionが古いネットワークステートを適用し、そのティックから最新の(予測)ローカルティックへと再シミュレーションするため、これはレンダリングフレームごとに複数回発生することがあります。
インプットはFixedUpdateNetwork
に適用され、正しいインプットが各ティックに適用されるようにする必要があります。FusionはGetInput()
という名前のシンプルなメソッドを提供することで、関連するティックのインプットを獲得します。インプットが取得されると、NetworkCharacterController
が呼び出され、実際の動作がアバターに適用されるようになります。
全体のPlayer
クラスは以下のようになります。
using Fusion;
public class Player : NetworkBehaviour
{
private NetworkCharacterController _cc;
private void Awake()
{
_cc = GetComponent<NetworkCharacterController>();
}
public override void FixedUpdateNetwork()
{
if (GetInput(out NetworkInputData data))
{
data.direction.Normalize();
_cc.Move(5*data.direction*Runner.DeltaTime);
}
}
}
チートを防ぐために、提供されたインプットはノーマライズされる点にご注意ください。
テストする
さて、残るは本当に動かせるアバターがゲーム内にあるのかを検証するのみとなりました。ただしその前に、スポーンしたオブジェクトがビューからこぼれ落ちないように、シーンにはフロアが必要です。
Cubeオブジェクトを作成し、(0,-1,0)
の場所に設置します。そしてXとZで100ごとに大きさを調整します。気を付けて新しいマテリアルを作成し割り当てて、全てが同じ色にならないようにしましょう。
アプリケーションをビルドして複数のインスタンスを起動します(または、Unity内から直接1つのインスタンスを実行します)。Host
ボタンを押すクライアントは1人で、後のクライアントはJoin
を押すようにしてください。
Unity内から実行する際に気を付けること:エディタでゲームを実行する場合、フレームレートが非常に不安定になる可能性があります。これは単にエディタそのものが偶発低なレンダリングを必要としているためです。これはFusionの機能に作用し、タイミングを正確に予測できるようになり、ジッタが少量になり、ビルドされた後のアプリケーションには表れない程度になります。不安がある場合は、2つのスタンドアロンビルドをお試しください。