This document is about: FUSION 1
SWITCH TO

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

2 - シーンのセットアップ

概要

Fusion 102では基本的なシーンのセットアップ方法を説明します。

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

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

NetworkInputの詳細はこちらのマニュアルをお読みください

Fusionを開始する

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

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

  1. 新しい空のGameObjectを作成する
  2. 新しいスクリプトを上記に追加する
  3. 上記のスクリプトの名前をBasicSpawnerとする
  4. スクリプトを開き、BasicSpawnerクラスにINetworkRunnerCallbacksを実装すると同時に、各メソッドのスタブを記述する

C#

public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks
{
  public void OnPlayerJoined(NetworkRunner runner, PlayerRef player) { }
  public void OnPlayerLeft(NetworkRunner runner, PlayerRef player) { }
  public void OnInput(NetworkRunner runner, NetworkInput input) { }
  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 OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken) { }
  public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ArraySegment<byte> data) { }
  public void OnSceneLoadDone(NetworkRunner runner) { }
  public void OnSceneLoadStart(NetworkRunner runner) { }
}

BasicSpawnerクラスにINetworkRunnerCallbacksを実装することで、FusionのNetworkRunnerと連携できるようになります。NetworkRunnerはFusionの最も大事なコアの部分で、実際のネットワークシミュレーションを実行します。

StartGameメソッド内でNetworkRunnerを同一のゲームオブジェクトに追加すると、NetworkRunnerBasicSpawnerINetworkRunnerCallbacksを実装したことを自動的に検出し、そのメソッドを呼び出すようになります。

C#

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メソッドは、まずNetworkRunnerを作成して、クライアントが入力を提供することを伝えます。その後、ハードコードされた名前と特定のゲームモードを指定した新しいセッションを開始します(ゲームモードについては後述)。現在のシーンのインデックスが渡されていますが、これはホストにのみ関係があり、クライアントはホストが指定したシーンの使用を強制されます。最後に、デフォルトのSceneManagerを追加で指定しています。SceneManagerは、シーン上に直接置かれたネットワークオブジェクトのインスタンス化処理を行いますが、厳密にいえば、この例ではこのようなオブジェクトが存在しないため、必須ではありません。

Fusionはいくつかのネットワークトポロジーに対応していますが、このイントロダクションではHosted Mode(ホストモード)に絞って紹介します。ホストモードでは、一つのネットワーク上のピアがサーバーとクライアントを兼ねて、ネットワークセッションを作成します。残りのピアは単なるクライアントとして、存在するセッションに参加します。

これを行うには、ユーザーがゲームをホストするか、既存のセッションに参加するかを選択する方法が必要です。簡単のため、以降の例ではUnityのIMGUIを使用します。以下のメソッドをBasicSpawner.csに追加してください。

C#

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コンポーネントを追加する

これによってゲームオブジェクトにはネットワークIDが与えられ、全てのピアがそのオブジェクトを参照できるようになります。また、ユーザーはこのアバターを操作することになるので、NetworkCharacterControllerPrototypeも必要になります。これは必須ではありませんが、プレイヤーが操作するオブジェクトを作成する良い出発点となるため、やってみることをお勧めします。NetworkCharacterControllerPrototypeはUnityのCharacterControllerコンポーネントを利用しているので、コンポーネントが自動的に追加されます。

一般的な推奨として、ネットワークオブジェクトとビジュアル表現は分けましょう。ネットワークオブジェクトをネットワーク上の状態にスナップさせながら、ビジュアル表現はスムーズに補間されるようにします。

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

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

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

player avatar configuration
Player Avatar Configuration

プロジェクトをSave(保存)し、Fusionが新しいネットワークオブジェクトをベイクするようにします。その後、プロジェクトのフォルダへとドラッグしてアバターのプレハブを作成し、シーンからオブジェクトを消去します。

アバターをスポーンする

ゲームがホストモードで実行されているため、ホストのみが新しいオブジェクトをスポーンする権限を持っています。つまり、全てのプレイヤーアバターは、プレイヤーがセッションへ参加した時に、ホストがスポーンする必要があるということです。INetworkRunnerCallbacksインターフェイスのOnPlayerJoinedメソッドは、まさにこのために呼び出されます。

同様に、プレイヤーが切断するとOnPlayerLeftメソッドが呼び出されます。

空白のOnPlayerJoinedOnPlayerLeftスタブを以下のコードで置き換えます。

C#

[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エディタに戻って、作成したプレハブのアバターをBasicSpawnerPlayer Prefabフィールドにドラッグ&ドロップするのを忘れないようにしてください。

入力を収集する

入力権限を持っていても、クライアントはネットワーク上の状態を直接更新することは許可されません。かわりに、ホストがネットワーク上の状態を更新するための入力構造体を提供します。

クライアントは入力をローカルで適用して、ユーザーに即時のフィードバックを提供しますが、これはあくまでローカルの予測であり、ホストによって上書きされる可能性があります。

入力をユーザーから収集する前に、入力を保持するデータ構造を定義する必要があります。NetworkInputData.csと名付けた新しいファイルを作成し、以下の構造体を作成します。

C#

using Fusion;
using UnityEngine;

public struct NetworkInputData : INetworkInput
{
  public Vector3 direction;
}

簡単のため、この例では動きたい方向のベクトルを使用していますが、より帯域の消費量が少ない方法があることを覚えておきましょう。例えば、各方向1ビットのビットフィールドなどです。Fusionは入力を圧縮して実際に変更されたデータのみを送信するので、時期尚早な最適化に躍起にならないようにしましょう。

クライアントは、FusionからのポーリングでOnInputコールバックが呼び出された時に、ユーザーの入力を収集する必要があります。BasicSpawner.csに戻り、 OnInput()スタブを以下に置き換えてください。

C#

  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標準のInputから入力を収集して、ローカルクライアントの入力として前もって定義した構造体に入力を保存します。このメソッドの最終行で入力構造体をFusionに渡していて、これによって、ホストやこのクライアントが入力権限を持つオブジェクトの操作を可能にしています。

入力を適用する

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

  1. PlayerPrefabを選択する
  2. 上記にPlayerという新しいスクリプトを追加する
  3. 新しいスクリプトを開き、MonoBehaviourNetworkBehaviourで置き換える
  4. FixedUpdateNetwork()を実装して、処理をFusionのシミュレーションループに連動させる

C#

using Fusion;

public class Player : NetworkBehaviour
{
  public override void FixedUpdateNetwork(){}
}

FixedUpdateNetworkはティックごとに呼び出されます。Fusionは「過去の正しいネットワーク上の状態」を適用した後に、現在の(予測の)ローカルのティックまでの再シミュレーションを行うため、フレーム毎に複数回実行されることがあります。

ティック毎に適切な入力が適用されることを確認するためには、入力はFixedUpdateNetworkで適用する必要があります。FusionはGetInput()という名前のシンプルなメソッドを提供しており、ティックに対応した入力を取得することができます。入力を取得したら、NetworkCharacterControllerPrototypeのメソッドで実際の動きをアバターのTransformに反映させます。

Playerクラスの完全な実装は以下のようになります。

C#

using Fusion;

public class Player : NetworkBehaviour
{
  private NetworkCharacterControllerPrototype _cc;

  private void Awake()
  {
    _cc = GetComponent<NetworkCharacterControllerPrototype>();
  }

  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エディタから直接一つのインスタンスを実行します)。Hostボタンを押すクライアントは一人で、後のクライアントはJoinを押すようにしてください。

Unityエディタから実行する際に気を付けること:エディタでゲームを実行する場合、フレームレートが非常に高くなる可能性がありますが、それは単にエディタそのものが特別な描画を必要としているためです。これはFusionの機能に影響して、ビルドされたアプリケーションより予測タイミングが正確になりジッタが抑えられる結果になることがあります。もし不安がある場合は、二つのスタンドアロンビルドの実行でテストをお試しください。

Back to top