このページは編集中です。更新が保留になっている可能性があります。

Fusion 102 - シーンをセットアップする

概要

Fusion 102は基本のネットワークセットアップ方法を説明します。

このセクションの終了時には、プロジェクトには以下が含まれています。

  • マッチを開始したりマッチに参加するための基本のFusion Runner
  • 操作することができるキャラクター

ネットワークインプットの詳細の説明に関するマニュアルをお読みください

トップに戻る

Fusionを開始する

Fusionを開始するには、Fusion NetworkRunnerStartGameメソッドを呼び出す必要があります。そのため、アプリケーションではシーン内にこのコンポーネントがあるか、コードから追加しておく必要があります。多くのネットワーキングロジックでは、どちらにせよ大量のコードを記述しなければならないので、このチュートリアルはコード中心で進めていきます。

UnityデフォルトのシーンでUnityを開きます。

  1. 新しい空白のGameObjectを作成する
  2. 新しいスクリプトコンポーネントを上記に追加する
  3. 上記のスクリプトの名前をBasicSpawnerとする
  4. スクリプトを開き、 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のNetworkRunnerBasicSpawnerクラスと連携できるようになります。NetworkRunnerはFusionの最も大事なコアの部分で、実際のネットワークシミュレーションを実行します。

NetworkRunnerは、BasicSpawnerINetworkRunnerCallbacksを実装したことを自動的に検出し、そのメソッドを呼び出します。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,
    SceneObjectProvider = gameObject.AddComponent<NetworkSceneManagerBase>()
  });}

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のエディターで以下を行います。

  1. 空白のGameObjectを作成し、PlayerPrefabと名前を付ける。
  2. 上記にNetworkObjectコンポーネントを追加する。

これによりネットワークアイデンティティが与えられ、全てのピアがそのネットワークアイデンティティを参照できるようになります。ユーザーが子のアバターをコントロールしているため、NetworkCharacterControllerも必要になります。これは要件ではありませんが、多くのプレイヤーがコントロールするオブジェクトの良い入り口となるため、やってみることをお勧めします。

一般的に、ネットワークオブジェクトとビジュアル表現を分けて、ネットワークオブジェクトがネットワークステートをスナップしているときでも、ビジュアル表現がスムーズに内挿されるようにしておくことを推奨します。

これを実現するには、以下を行います。

  1. 標準のUnity Cubeを子オブジェクトとしてPlayerPrefabに追加する。
  2. 上記の名前をBodyに変更する。
  3. Colliderを削除する。
  4. Bodyゲームオブジェクトを親のNetworkCharacterControllerInterpolation Targetプロパティにドラッグする。

キャラクターコントローラはコライダーを必要とするため、最後に以下を行います。

  1. 追加のCube子オブジェクトを追加する。
  2. 上記の名前をColliderとする。
  3. 上記からMeshRendererコンポーネントとMeshFilterコンポーネントを削除する。
  4. 上記をNetworkCharacterControllerColliderプロパティにドラッグする。

アバター構成は以下の様に表示されているはずです。

Player Avatar Configuration
Player Avatar Configuration

プロジェクトを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)
{
  // 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エディタに戻り、作成したプレハブアバターをBasicSpawnerPlayer 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にパスしており、ホストやこのクライアントがインプットオーソリティを持つオブジェクトがそれを使用できるようにしています。

トップに戻る

インプットを適用する

最終ステップは、収集したインプットデータのプレイヤーアバターへの適用です。

  1. PlayerPrefabを選択する。
  2. 上記にPlayerという新しいスクリプトコンポーネントを追加する。
  3. 新しいスクリプトを開き、MonoBehaviourをNetworkBehaviourで置き換える
  4. 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つのスタンドアロンビルドをお試しください。

ドキュメントのトップへ戻る