Pre-built Components
はじめに
Fusionでは、すぐに使えるように様々なNetworkBehaviourが用意されています。
NetworkRunner
NetworkRunnerは、Fusionの中核をなすものです。NetworkRunnerは、ネットワークへの接続を管理し、入力の収集からスナップショットのマージ、コールバックやSimulationBehaviourのライフサイクルメソッドの呼び出しまで、シミュレーションを制御します。 シーン内のNetworkRunnerは、クライアントとサーバーのそれぞれに1つだけ存在します。
これらのタスクを実行するために、NetworkRunnerはすべてのNetworkObject、NetworkBehaviour、SimulationBehaviourを追跡します。
プロパティ
すべての NetworkBehaviour は Runner プロパティを介して現在の NetworkRunner にアクセスでき、Runner 自体もいくつかの重要なシステムプロパティを公開しています。
- IsServer: このランナーが権威あるサーバーを表している場合はtrue。
- Stage: シミュレーションの現在のステージです。シミュレーションが次の状態を予測している場合には- Forward、サーバーのアップデートに合わせて古い状態を再シミュレーションしている場合には- Resimulateになります。なお、- Resimulateはサーバー上や、シミュレーションが常に- ForwardであるClient-Authorityモードでは発生しません。
GameMode
GameModeは、ローカルのピアがどのように動作するかを定義します。GameModeは、NetworkRunner.StartGame()メソッドのパラメータとして渡されます。
- client: クライアントとして動作し、ローカルプレイヤーを作成して、クライアントホスト(クラウド共有)または専用ホスト(サーバーベース)に接続します。
- host: サーバーとして動作し、ローカルプレイヤーを作成します (サーバー + クライアント)
- server: 専用のサーバーとして動作し、プレイヤーはいません。
- shared: クライアントとして動作し、ローカルプレイヤーを作成してFusion Plugin Serverに接続します。
- single: "サーバー "として動作し、ローカルプレイヤーを作成します。
NetworkTransform
NetworkTransformは、オブジェクトの実際のローカルなTransformと、そのシミュレーションされた状態を補間することで、単純な運動オブジェクトを管理します。
NetworkRigidbody
リジッドボディの場合、NetworkRigidbodyは単に古い状態を補間するだけでなく、物理オブジェクトの予測可能な性質を利用して、より正確なローカル位置を推定します。
NetworkMecanimAnimator
NetworkMecanimAnimator は、関連する Unity mechanim Animator コンポーネントが持つパラメータの状態や値を同期させます。
Unity の Animator コンポーネントは巻き戻しや再シミュレーションができないため(前方にのみ動作するように設計されています)、ティックの状態を正確に設定することができないため、ティック精度のアニメーションに依存できないことに注意する必要があります。
この前方にのみ動作する制限のため、NetworkMecanimAnimator は再シミュレーションを試みず、NetworkMecanimAnimator は Animator コンポーネントを State Authority から Proxies に同期させるだけです。また、入力機関はForward tickでAnimatorに変更を適用する必要があります。
C#
void FixedUpdateNetwork()
{
  // Only apply changes to the Animator if input is available
  // (which is true for StateAuthority and InputAuthority),
  // and only on Forward ticks (resimulation should be ignored).
if (GetInput(out var input) && Runner.IsForward)
  {
     // Apply inputs to Animator
  }
}
SetTrigger()
パススルーの NetworkMecanimAnimator.SetTrigger() メソッドは Animator.SetTrigger() コールの代わりに使用すべきです。トリガーは一時的で、 NetworkMecanimAnimator が Animator コンポーネントの値を取得する前にバックブールを false にリセットすることが可能なためです。また、便利なことに NetworkMecanimAnimator.SetTrigger() には passThroughOnInputAuthority オプションがあり、入力権限ですぐに Animator.SetTrigger() にパススルーすることができます。
NetworkCharacterControllerPrototype
このコンポーネントはプロトタイピングのサンプルで、Unity の CharacterController を NetworkObject に同期させる方法を示しています。Unity の CharacterController は本質的に予測や再シミュレーションと互換性がないので、このプロトタイプは適切なタイミングで CC を有効にしたり無効にしたりしてその制限を回避する方法を示します。このコンポーネントは基本クラスとして使用し、必要に応じて拡張することができます。また、独自のカスタム実装の出発点としてコピーして名前を変更することも可能です。
NetworkCharacterController (廃止予定)
最も不安定なオブジェクトは、プレイヤーが直接操作するもので、一般的にCharacter Controllerと呼ばれています。Fusionには、このような特殊なケースに対応するために NetworkCharacterController があります。
ビルド済みの NetworkCharacterController は、一般的に望まれる動作を備えているので、素早くプロトタイプを作成することができます。しかし、キャラクタコントローラの実装は非常にゲームに依存するため、万能なキャラクタコントローラは存在しません。
そのため、NetworkCharacterController が使用する2つのコアメソッド Move() と ComputeRawMovement() のソースコードに目を通し、ゲーム制作のための代替カスタムキャラクタコントローラを作るためのインスピレーションを得ておくことをお勧めします。
ComputeRawSteer()
ComputeRawSteer()は内部メソッドで、キャラクターが現在行っている動きの種類に基づいて、動きの計算の大部分を行います。ビルド済みの NetworkCharacterController では、Move() が ComputeRawMovement() から movementPack の値を要求する際に、構造体への参照を渡して値を埋めています。
C#
void ComputeRawSteer(ref Movement movementPack, float dt) {
  Grounded = movementPack.Grounded;
  float minYSpeed = -100;
  float maxYSpeed = 100;
  var current = Velocity;
  switch (movementPack.Type) {
    case MovementType.FreeFall:
      current.y -= Config._gravityStrength * dt;
      if (!Config.AirControl || movementPack.Tangent == default(Vector3)) {
        current.x = Mathf.Lerp(current.x, 0, dt * Config.Braking);
        current.z = Mathf.Lerp(current.z, 0, dt * Config.Braking);
      } else {
        current += movementPack.Tangent * Config.Acceleration * dt;
      }
      break;
    case MovementType.Horizontal:
      // apply tangent velocity
      current += movementPack.Tangent * Config.Acceleration * dt;
      var tangentSpeed = Vector3.Dot(current, movementPack.Tangent);
      // lerp current velocity to tangent
      var tangentVel = tangentSpeed * movementPack.Tangent;
      var lerp       = Config.Braking * dt;
      current.x = Mathf.Lerp(current.x, tangentVel.x, lerp);
      current.z = Mathf.Lerp(current.z, tangentVel.z, lerp);
      // we only lerp the vertical velocity if the character is not jumping in this exact frame,
      // otherwise it will jump with a lower impulse
      if (Jumped == false) {
        current.y = Mathf.Lerp(current.y, tangentVel.y, lerp);
      }
      // clamp tangent velocity with max speed
      if (tangentSpeed > MaxSpeed) {
        current -= movementPack.Tangent * (tangentSpeed - MaxSpeed);
      }
      break;
    case MovementType.SlopeFall:
      current   += movementPack.SlopeTangent * Config.Acceleration * dt;
      minYSpeed =  -Config.MaxSlopeSpeed;
      break;
    case MovementType.None:
      var lerpFactor = dt * Config.Braking;
      if (current.x != 0) {
        current.x = Mathf.Lerp(current.x, default, lerpFactor);
        if (Mathf.Abs(current.x) < float.Epsilon) {
          current.x = 0;
        }
      }
      if (current.z != 0) {
        current.z = Mathf.Lerp(current.z, default, lerpFactor);
        if (Mathf.Abs(current.z) < float.Epsilon) {
          current.z = 0;
        }
      }
      // we only lerp the vertical velocity back to 0 if the character is not jumping in this exact frame,
      // otherwise it will jump with a lower impulse
      if (current.y != 0 && Jumped == false) {
        current.y = Mathf.Lerp(current.y, default, lerpFactor);
        if (Mathf.Abs(current.y) < float.Epsilon) {
          current.y = 0;
        }
      }
      minYSpeed = 0;
      break;
  }
  // horizontal is clamped elsewhere
  if (movementPack.Type != MovementType.Horizontal) {
    Vector2 h = new Vector2(current.x, current.z);
    if (h.sqrMagnitude > MaxSpeed * MaxSpeed) {
      h = h.normalized * MaxSpeed;
    }
    current.x = h.x;
    current.y = Mathf.Clamp(current.y, minYSpeed, maxYSpeed);
    current.z = h.y;
  }
  Velocity = current;
  // set jump state
  Jumped = false;
}
Move()
これは、完全な Move() 関数の基本的な実装です。動作のクエリを実行し、その結果を使って新しいVelocityを計算し、トランスフォームポジションに進入の補正とVelocityの統合を適用します。Rotationは変更しません。
- direction: 意図された移動方向で、移動クエリと加速の対象となります。
- callback: 任意のカスタムコールバックオブジェクトです。
- layerMask: 任意のレイヤーマスクです。渡さない場合は、コンフィグのデフォルトが使用されます。
C#
public void Move(Vector3 direction, ICallbacks callback = null, LayerMask? layerMask = null) {
    var dt           = Runner.DeltaTime;
    var movementPack = ComputeRawMovement(direction, callback, layerMask);
    ComputeRawSteer(ref movementPack, dt);
    var movement = Velocity * dt;
    if (movementPack.Penetration > float.Epsilon) {
      if (movementPack.Penetration > Config.AllowedPenetration) {
        movement += movementPack.Correction;
      } else {
        movement += movementPack.Correction * Config.PenetrationCorrection;
      }
    }
    _transform.position += movement;
    #if DEBUG
    LastMovement = movementPack;
    #endif
}
NetworkMechanimAnimator
NetworkMechanimAnimatorは、関連するUnityのメカニムAnimatorが持つパラメータの値を同期させます。
注意: アニメーション自体を同期させるわけではありません。
NetworkEvents
NetworkEventsは、NetworkRunner.AddCallbacks()の代わりに使用して、Unityインスペクタで直接イベントハンドラを配線することができます。コンポーネントを NetworkObject に追加し、ドラッグ&ドロップで個々のイベントハンドラを登録するだけです。
これは、ネットワークイベントをUnityシーンから設定する必要があるまれなケースの便宜のために含まれています。
Back to top