This document is about: FUSION 1
SWITCH TO

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

ネットワーク入力

はじめに

入力の定義、ポーリング、消費は、Fusionの中核をなすものです。

Input Struct Definition

入力構造体の定義

入力構造体には、必要に応じて単純なデータや複雑なデータを格納することができます。Fusionは実際に変化するデータのみを送信します。そのため、データはコンパクトにしておく方が有利ですが(例:ボタンにフラグを使う)、あまり使われないフィールドを含めても問題ありません。例えば、ローカル プレイヤーが基本1人しかいない場合でも、複数のローカルプレイヤーから入力を収集する場合です。これらのプレイヤーが実際に入力を行っていない場合は、入力構造体の帯域幅の使用には影響しませんが、ローカルメモリには影響します。

入力構造体には、次のような制約があります。

  • INetworkInput を継承していなければなりません。
  • プリミティブな型と構造体のみを含むことができます。
  • 入力構造体とそれを保持する構造体は、トップレベルの構造体でなければならない(つまり、クラスの中にネストすることはできない)。
  • C# では、プラットフォーム間で bool のサイズを統一していないため、1 ビットとして適切に直列化するために、NetworkBool を使用します。

Fusion は構造体の型をインテリジェントにマッピングします。これにより、プレイモードやゲームの各部分に応じて異なる構造体を使用することができます。入力をアンラップする際、Fusionは正しい型の入力のみを返します。

C#

public struct MyInput : INetworkedInput {
    public Vector3 aimDirection;
}

ボタン

特別な NetworkButtons 型があります。これは、ボタンを押したことを INetworkInput 構造体に保存するための、便利なラッパーです。

入力構造体にボタンを追加するには、次のようにします。

  1. ボタン用の列挙型を作成します( 重要: それは明示的に定義され、0から始まる必要があります)。
  2. INetworkedInputNetworkButtons 変数を追加します。

C#

enum MyButtons {
    Forward = 0,
    Backward = 1,
    Left = 2,
    Right = 3,
}

public struct MyInput : INetworkInput {
    public NetworkButtons buttons;
    public Vector3 aimDirection;
}

変数 NetworkButtons に直接値を代入したり読み込んだりするために利用できる API は以下のとおりです。

  • void Set(int button, bool state): ボタンの enum 値とその状態 (pressed = true, not pressed = false) を受け取ります。
  • bool IsSet(int button): ボタンの enum 値を受け取り、その状態をブール値で返します。

NetworkButtons 型はステートレスなので、ボタンの以前の状態に関するメタデータを保持しません。NetworkButtons が提供する次のメソッド群を使用できるようにするには、ボタンの以前の状態を追跡する必要があります。これは、各プレイヤーの以前の状態の [Networked] バージョンを作成することで簡単に行うことができます。

C#

public class PlayerInputConsumerExample : NetworkBehaviour {
    [Networked] public NetworkButtons ButtonsPrevious { get; set; }

    // Full snippet in the GetInput() section further down.
}
For the full snippet, see the __GetInput__ section further down.

これにより、ボタンの現在の状態と以前の状態を比較し、ボタンが押されたばかりか、離されたばかりかを評価することができるようになります。

  • NetworkButtons GetPressed(NetworkButtons previous): 押されたばかりのすべてのボタンの値の集合を返します。
  • NetworkButtons GetReleased(NetworkButtons previous): たった今離されたすべてのボタンの値のセットを返します。
  • (NetworkButtons, NetworkButtons) GetPressedOrReleased(NetworkButtons previous): 押されただけのボタンと放されただけのボタンの値のタプルを返します。

重要: ボタンの値を割り当てるには Input.GetKey() のみを使用します。Fusionのティックと同期していないため、見逃してしまう可能性があります。

ポーリング入力

Fusionはローカルクライアントをポーリングし、事前に定義された入力構造体に入力することで入力を収集します。Fusion Runnerは1つの入力構造体しか追跡しないので、予期せぬ動作を避けるために、入力ポーリングは一箇所で実装することを強く推奨します。

Fusion Runner は INetworkRunnerCallbacks.OnInput() メソッドを呼び出すことで入力をポーリングします。 OnInput() の実装は、INetworkInput を継承した構造体に選択したデータを入力することができます。入力された構造体は、提供された NetworkInput に対して Set() を呼び出すことでFusionに返されます。

重要:

  1. 複数のポーリングサイトがあると、最後の入力構造体のバージョン以外はすべて上書きされます。
  2. 入力はローカルにしかポーリングされません(すべてのモード)。

SimulationBehaviour / NetworkBehaviour

SimulationBehaviour または NetworkBehaviour コンポーネントで OnInput() を使用するには、INetworkRunnerCallbacks インターフェースを実装して NetworkRunner.AddCallbacks() を呼び出してローカルランナーにコールバック機能を登録します。

C#

public class InputProvider : SimulationBehaviour, INetworkRunnerCallbacks {

  public void OnEnable(){
    if(Runner != null){
       Runner.AddCallbacks( this );
    }
  }

  public void OnInput(NetworkRunner runner, NetworkInput input) {
    var myInput = new MyInput();

    myInput.Buttons.Set(MyButtons.Forward, Input.GetKey(KeyCode.W));
    myInput.Buttons.Set(MyButtons.Backward, Input.GetKey(KeyCode.S));
    myInput.Buttons.Set(MyButtons.Left, Input.GetKey(KeyCode.A));
    myInput.Buttons.Set(MyButtons.Right, Input.GetKey(KeyCode.D));
    myInput.Buttons.Set(MyButtons.Jump, Input.GetKey(KeyCode.Space));

    input.Set(myInput);
  }

  public void OnDisable(){
    if(Runner != null){
        Runner.RemoveCallbacks( this );
    }
  }
}

MonoBehaviourとPure CSharp

SimultionBehaviourを継承していないスクリプトからの入力をポーリングするには、以下の手順を実行します。

  1. InetworkRunnerCallbacksOnInput() を実装する。
  2. スクリプトの AddCallbacks() を呼び出して、スクリプトを NetworkRunner に登録する。

C#


public class InputProvider : Monobehaviour, INetworkRunnerCallbacks {

  public void OnEnable(){
    var myNetworkRunner = FindObjectOfType<NetworkRunner>();
    myNetworkRunner.AddCallbacks( this );
  }

  public void OnInput(NetworkRunner runner, NetworkInput input) {
    // Same as in the snippet for SimulationBehaviour and NetworkBehaviour.
  }

  public void OnDisable(){
    var myNetworkRunner = FindObjectOfType<NetworkRunner>();
    myNetworkRunner.RemoveCallbacks( this );
  }
}

Unity新入力システム

Unity新入力システムを使用するには、プロセスは同じですが、作成された入力アクションから来る入力を収集する必要があります。

入力アクションを作成し、目的のボタンを定義したら、C#のクラスを生成し、コード内にそのインスタンスを作成します。OnInput()で消費される入力がローカルキャッシュに保存されている限り、PlayerInputクラスで来るイベントも使用することが可能です。

目標は、OnInput() のボタンの状態を、古い入力システムではなく、新しい入力システムから来るものを集めることなので、システムの設定部分を除けば、他の部分は基本的に同じです。

C#

public class InputProvider : SimulationBehaviour, INetworkRunnerCallbacks {

  // creating a instance of the Input Action created
  private PlayerActionMap _playerActionMap = new PlayerActionMap();

  public void OnEnable(){
    if(Runner != null){
        // enabling the input map
        _playerActionMap.Player.Enable();

        Runner.AddCallbacks(this);
    }
  }

  public void OnInput(NetworkRunner runner, NetworkInput input)
  {
    var myInput = new MyInput();
    var playerActions = _playerActionMap.Player;

    myInput.buttons.Set(MyButtons.Jump, playerActions.Jump.IsPressed());

    input.Set(myInput);
  }

  public void OnDisable(){
    if(Runner != null){
        // disabling the input map
        _playerActionMap.Player.Disable();

        Runner.RemoveCallbacks( this );
    }
  }
}

低いティックレートの入力をポーリングする

低ティックレートで入力を収集するには、Unity の Update 関数を使用して、記録された入力を構造体に蓄積し、後で消費できるようにする必要があります。

OnInputでは、この構造体が読み込まれ、input.Set()コールによってFusionに正しく転送され、次のtickの入力のために蓄積を開始するためにリセットされます。

C#

public class InputProvider : SimulationBehaviour, INetworkRunnerCallbacks {

  // Local variable to store the input polled.
  MyInput myInput = new MyInput();

  public void OnEnable() {
    if(Runner != null) {
        Runner.AddCallbacks( this );
    }
  }

  public void Update()
  {
    if (Input.GetMouseButtonDown(0)) {
      myInput.Buttons.Set(MyButtons.Attack, true);
    }

    if (Input.GetKeyDown(KeyCode.Space)) {
      myInput.Buttons.Set(MyButtons.Jump, true);
    }
  }

  public void OnInput(NetworkRunner runner, NetworkInput input) {

    input.Set(myInput);

    // Reset the input struct to start with a clean slate
    // when polling for the next tick
    myInput = default;
  }
}

UIによる入力のポーリング

UI による入力のポーリングは、上記と同じロジックになります。UI経由で呼び出されるメソッドから NetworkButton を設定し、 OnInput で読み込みとリセットを行います。

入力の読み込み

シミュレーションで入力を読み込むと、以前にポーリングされた入力に基づいて、既存のネットワークの状態を現在の状態から新しい状態に変更することができます。Fusionは、入力構造体をネットワーク上で同期させ、入力権限を持つクライアントと状態権限を持つもの(ホスト)でシミュレーション中に利用できるようにします。

ポーリング入力とは逆に、入力の読み込みは必要な数だけ異なる場所で行うことができます。

注.: プレイヤー入力は、Input AuthoriyとState Authorityを持つクライアントでのみ利用可能です。ホストモードとサーバモードでは、プレイヤークライアントとホスト/サーバを指しますが、シェアードモードでは1つのクライアントを指します。

あるクライアントの入力を別のクライアントで読み取ることはできません。したがって、入力に依存する変更は、他のクライアントで複製するために [Networked] 状態として保存される必要があります。

GetInput()

入力構造体を取得するには、対象のオブジェクトに対して入力権限を持つ任意の NetworkBehaviourFixedUpdateNetwork() 内で GetInput(out T input) を呼び出します(例えば、プレイヤーの動きを制御するコンポーネント)。GetInput()の呼び出しは、以前に OnInput() で入力されたものと同じ入力構造体を提供します。

以下の場合、GetInput() の呼び出しは false を返します。

  • クライアントが状態権限または入力権限を持っていない。
  • 要求された入力の種類がシミュレーションに存在しない場合。

GameMode 固有の情報です。

  • HostModeServerModeでは、与えられたtickの入力はプレーヤーとホスト/サーバーのシミュレーションでのみ利用可能です。入力はプレイヤー間では__共有されません__。
  • SharedMode では OnInput()GetInput() のパターンを維持することは良いことですが、中央機関がないため、ローカルシミュレーションのみがローカルプレイヤーの入力にアクセスすることができます。入力はプレイヤー間で__共有されません__。

C#

using Fusion;
using UnityEngine;

public class PlayerInputConsumerExample : NetworkBehaviour {

  [Networked] public NetworkButtons ButtonsPrevious { get; set; }

  public override void FixedUpdateNetwork() {

    if (GetInput<MyInput>(out var input) == false) return;

    // compute pressed/released state
    var pressed = input.Buttons.GetPressed(ButtonsPrevious);
    var released = input.Buttons.GetReleased(ButtonsPrevious);

    // store latest input as 'previous' state we had
    ButtonsPrevious = input.Buttons;

    // movement (check for down)
    var vector = default(Vector3);

    if (input.Buttons.IsSet(MyButtons.Forward)) { vector.z += 1; }
    if (input.Buttons.IsSet(MyButtons.Backward)) { vector.z -= 1; }

    if (input.Buttons.IsSet(MyButtons.Left)) { vector.x  -= 1; }
    if (input.Buttons.IsSet(MyButtons.Right)) { vector.x += 1; }

    DoMove(vector);

    // jump (check for pressed)
    if (pressed.IsSet(MyButtons.Jump)) {
      DoJump();
    }
  }

  void DoMove(Vector3 vector) {
    // dummy method with no logic in it
  }

  void DoJump() {
    // dummy method with no logic in it
  }
}

Runner.TryGetInputForPlayer()

NetworkRunner.TryGetInputForPlayer<T>(PlayerRef playerRef, out var input)を呼び出すことで、NetworkBehaviour の外側から入力を読み取ることができます。INetworkInput 型に加え、入力を取得するプレイヤーを指定する必要があります。つまり、入力権限を持つクライアントか、サーバー/ホストで、指定されたプレーヤーの入力を取得することができます。

C#

var myNetworkRunner = FindObjectOfType<NetworkRunner>();

// Example for local player if script runs only on the client
if(myNetworkRunner.TryGetInputForPlayer<MyInput>(myNetworkRunner.LocalPlayer, out var input)){
    // do logic
}

権限に関する注意

シミュレーションの完全な権限を保証するためには、入力構造体を生成する際に OnInput() で入力値のみを収集することが重要です。入力に基づいて実行されるロジックは、完全に GetInput() の中で行う必要があります。

例えば、弾丸を発射する場合は以下のような分岐になります。

  • OnInput(): プレイヤーの発射ボタンの値を保存します。
  • GetInput(): 発射ボタンが押されたかどうかをチェックし、押されていれば弾丸を発射する。

1つのピアに複数のプレーヤー

「カウチ」、「画面分割」、「ローカル」マルチプレイヤーと呼ばれることがあります。複数の人が一つのピア(例えば、複数のコントローラを持つゲーム機)に入力を行い、同時にオンラインマルチプレイヤーゲームに参加することが可能です。Fusionは、一つのピアにいる全てのプレイヤーを一つのPlayerRefとして扱い(PlayerRefは個々のプレイヤーではなく、ネットワークピアを識別します)、プレイヤー間の区別は行いません。プレイヤー とは何か、どのような入力を提供するのかを決めるのは、あなたです。

このユースケースを扱う方法の一例として、各プレイヤーに対して INetworkStruct をネストした INetworkInput 構造体を定義する方法があります。

C#

public struct PlayerInputs : INetworkStruct
{
  // All player specific inputs go here
  public Vector2 dir;
}

public struct CombinedPlayerInputs : INetworkInput
{
  // For this example we assume 4 players max on one peer
  public PlayerInputs PlayerA;
  public PlayerInputs PlayerB;
  public PlayerInputs PlayerC;
  public PlayerInputs PlayerD;

  // Example indexer for easier access to nested player structs
  public PlayerInputs this[int i]
  {
    get {
      switch (i) {
        case 0:  return PlayerA;
        case 1:  return PlayerB;
        case 2:  return PlayerC;
        case 3:  return PlayerD;
        default: return default;
      }
    }

    set {
      switch (i) {
        case 0:  PlayerA = value; return;
        case 1:  PlayerB = value; return;
        case 2:  PlayerC = value; return;
        case 3:  PlayerD = value; return;
        default: return;
      }
    }
  }
}

複数のプレーヤーの入力を収集する。

C#

public class CouchCoopInput : MonoBehaviour, INetworkRunnerCallbacks
{
  public void OnInput(NetworkRunner runner, NetworkInput input)
  {
    // For this example each player (4 total) has one Joystick.
    var myInput = new CombinedPlayerInputs();
    myInput[0] = new PlayerInputs() { dir = new Vector2( Input.GetAxis("Joy1_X"), Input.GetAxis("Joy1_Y")) };
    myInput[1] = new PlayerInputs() { dir = new Vector2( Input.GetAxis("Joy2_X"), Input.GetAxis("Joy2_Y")) };
    myInput[2] = new PlayerInputs() { dir = new Vector2( Input.GetAxis("Joy3_X"), Input.GetAxis("Joy3_Y")) };
    myInput[3] = new PlayerInputs() { dir = new Vector2( Input.GetAxis("Joy4_X"), Input.GetAxis("Joy4_Y")) };
    input.Set(myInput);
  }

  // (removed unused INetworkRunnerCallbacks)
}

シミュレーションのための入力の取得

C#

public class CouchCoopController : NetworkBehaviour
{
  // Player index 0-3, indicating which of the 4 players
  // on the associated peer controls this object.
  private int _playerIndex;

  public override void FixedUpdateNetwork()
  {
    if (GetInput<CombinedPlayerInputs>(out var input))
    {
      var dir = input[_playerIndex].dir;
      // Convert joystick direction into player heading
      float heading = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
      transform.rotation = Quaternion.Euler(0f, heading - 90, 0f);
    }
  }
}
Back to top