4 - 物理
概要
ここでは、サーバー主導(Server Authoritative)のゲームでFusionとPhysXを連携する方法を説明します。
この章では、物理挙動をするボールをスポーンしたり触れたりする方法を学びます。
セットアップ
Fusionのデフォルトでは、物理シミュレーションはホスト上で実行され、クライアントはそれに従います。しかし、ローカル上で予測して動作するオブジェクトと、ホストに従って動作する物理オブジェクトとで、異なる時間軸で動作するオブジェクト同士が衝突する時、物理シミュレーションには問題が発生します。
「時間差」の解決は複雑なトピックです。うまくハックして隠蔽したり、一般的なアプリケーションでは負荷が高すぎる処理が必要だったり、プレイヤーへの即時のフィードバックをあきらめたり、様々な方法があります。残念ながら、あらゆるケースで有効なシンプルな解決方法は存在しません。
このチュートリアルでは、FusionのPhysicsアドオンを使用して、物理オブジェクトをローカル上で予測し、物理オブジェクトとプレイヤーアバターを同じ時間軸で動作させます。これは実用的で堅牢なソリューションですが、PhysXで予測と再シミュレーションを(ティックごとに複数回)実行するのは負荷が高いことを覚えておいてください。
Physicsアドオンは、Downloadページからダウンロードして、プロジェクトにインポートしてください。
物理オブジェクト
PhysXで制御されるネットワークオブジェクトは、通常のUnityと同じRigidbodyとは別に、NetworkRigidbodyと呼ばれるFusionの同期コンポーネントも追加します。NetworkTransformはNetworkRigidbodyに置き換えられます。
- 新規で空のゲームオブジェクトを作成する
- 上記の名前をPhysxBallにする
- 上記にNetworkRigidbody3Dコンポーネントを追加する(自動的にRigidbodyも追加される)
- NetworkObjectが不足している警告が表示されるので、- Add Network Objectを押す
- PhysxBallの子オブジェクトとして、- Sphereを追加する
- 上記のスケールを0.2にする
- 上記を親オブジェクトPhysxBallのNetworkRigdbody3DのInterpolationTargetにドラッグする
- 上記からColliderを削除する
- PhysxBall側に- SphereColliderを追加し、半径を- 0.1にすることで- Sphereと見た目を合わせる
- PhysxBallに新規スクリプトを追加し、名前を- PhysxBall.csにする
- 上記オブジェクトをプロジェクトフォルダにドラッグし、プレハブを作成する
- シーンを保存してネットワークオブジェクトをベイクし、シーンからPhysxBallを削除する
 
    物理オブジェクトは、オブジェクトとビジュアル表現を分けて、ビジュアル表現をInterpolationTargetに設定することを推奨されますが、必須ではありません。RigidbodyやColliderは、NetworkRigidbodyが付いている親オブジェクトに追加する必要があります。
PhysxBall スクリプト
ボールはPhysXで制御され、ネットワーク周りはNetworkRigidbodyが面倒を見てくれるため、キネマティックボールに比べて記述する必要があるコード量は少なくなります。PhysxBall.csに記述する必要があるのは、数秒後にボールをデスポーンするための(キネマティックボールと全く同様の)タイマーと、初速度を設定するメソッドだけです。
これらは両方ともInit()メソッドで、以下のように記述します。
C#
using UnityEngine;
using Fusion;
public class PhysxBall : NetworkBehaviour
{
  [Networked] private TickTimer life { get; set; }
  public void Init(Vector3 forward)
  {
    life = TickTimer.CreateFromSeconds(Runner, 5.0f);
    GetComponent<Rigidbody>().velocity = forward;
  }
  public override void FixedUpdateNetwork()
  {
    if(life.Expired(Runner))
      Runner.Despawn(Object);
  }
}
入力
ボールをスポーンするために、キネマティックボールと同様のステップに従ってコードを記述する必要があります。ただし、ボタンはマウス左ボタンのかわりにマウス右ボタンを使用します。
1. NetworkInputData
NetworkInputData.csで新しいボタンのフラグを追加します。
C#
using Fusion;
using UnityEngine;
public struct NetworkInputData : INetworkInput
{
  public const byte MOUSEBUTTON0 = 1;
  public const byte MOUSEBUTTON1 = 2;
  public NetworkButtons buttons;
  public Vector3 direction;
}
2. BasicSpawner
BasicSpawner.csでマウス右ボタンをポーリングして、以下のようにフラグを設定します。
C#
  private bool _mouseButton0;
  private bool _mouseButton1;
  private void Update()
  {
    _mouseButton0 = _mouseButton0 || Input.GetMouseButton(0);
    _mouseButton1 = _mouseButton1 || Input.GetMouseButton(1);
  }
  public void OnInput(NetworkRunner runner, NetworkInput input)
  {
    var data = new NetworkInputData();
    data.buttons.Set(NetworkInputData.MOUSEBUTTON0, _mouseButton0);
    _mouseButton0 = false;
    data.buttons.Set(NetworkInputData.MOUSEBUTTON1, _mouseButton1);
    _mouseButton1 = false;
    input.Set(data);
  }
3. Player
Player.csで実際にボールをスポーンするコードを記述するため、以下のようなプレハブの参照を追加します。
C#
[SerializeField] private PhysxBall _prefabPhysxBall;
Playerでは先ほど作成したInit()メソッドを使用して、ボールのスポーンと初速度の設定を行います。
C#
public override void FixedUpdateNetwork()
{
  if (GetInput(out NetworkInputData data))
  {
    data.direction.Normalize();
    _cc.Move(5*data.direction*Runner.DeltaTime);
    if (data.direction.sqrMagnitude > 0)
      _forward = data.direction;
    if (HasStateAuthority && delay.ExpiredOrNotRunning(Runner))
    {
      if (data.buttons.IsSet(NetworkInputData.MOUSEBUTTON0))
      {
        delay = TickTimer.CreateFromSeconds(Runner, 0.5f);
        Runner.Spawn(_prefabBall, 
          transform.position+_forward,
          Quaternion.LookRotation(_forward), 
          Object.InputAuthority, 
          (runner, o) =>
          {
            // Initialize the Ball before synchronizing it
            o.GetComponent<Ball>().Init();
          });
      }
      else if (data.buttons.IsSet(NetworkInputData.MOUSEBUTTON1))
      {
        delay = TickTimer.CreateFromSeconds(Runner, 0.5f);
        Runner.Spawn(_prefabPhysxBall, 
          transform.position+_forward, 
          Quaternion.LookRotation(_forward),           
          Object.InputAuthority, 
          (runner, o) =>
          {
            o.GetComponent<PhysxBall>().Init( 10*_forward );
          });
      }
    }
  }
}
物理演算をネットワークに対応させるには、NetworkRunnerオブジェクトにRunnerSimulatePhysics3Dコンポーネントが必要です。BasicSpawner.csのStartGameメソッド内でNetworkRunnerコンポーネントを追加した後に、以下のようなコードを追加してください。
C#
gameObject.AddComponent<RunnerSimulatePhysics3D>();
最後に、ボールのプレハブをPlayerプレハブの_prefabPhysxBallにアタッチした後、ビルドしてボールのテストを実行しましょう。