This document is about: FUSION 2
SWITCH TO

4 - 物理

概要

ここでは、サーバー主導(Server Authoritative)のゲームでFusionとPhysXを連携する方法を説明します。

この章では、物理挙動をするボールをスポーンしたり触れたりする方法を学びます。

セットアップ

Fusionのデフォルトでは、物理シミュレーションはホスト上で実行され、クライアントはそれに従います。しかし、ローカル上で予測して動作するオブジェクトと、ホストに従って動作する物理オブジェクトとで、異なる時間軸で動作するオブジェクト同士が衝突する時、物理シミュレーションには問題が発生します。

「時間差」の解決は複雑なトピックです。うまくハックして隠蔽したり、一般的なアプリケーションでは負荷が高すぎる処理が必要だったり、プレイヤーへの即時のフィードバックをあきらめたり、様々な方法があります。残念ながら、あらゆるケースで有効なシンプルな解決方法は存在しません。

このチュートリアルでは、FusionのPhysicsアドオンを使用して、物理オブジェクトをローカル上で予測し、物理オブジェクトとプレイヤーアバターを同じ時間軸で動作させます。これは実用的で堅牢なソリューションですが、PhysXで予測と再シミュレーションを(ティックごとに複数回)実行するのは負荷が高いことを覚えておいてください。

Physicsアドオンは、Downloadページからダウンロードして、プロジェクトにインポートしてください。

物理オブジェクト

PhysXで制御されるネットワークオブジェクトは、通常のUnityと同じRigidbodyとは別に、NetworkRigidbodyと呼ばれるFusionの同期コンポーネントも追加します。NetworkTransformNetworkRigidbodyに置き換えられます。

  1. 新規で空のゲームオブジェクトを作成する
  2. 上記の名前をPhysxBallにする
  3. 上記にNetworkRigidbody3Dコンポーネントを追加する(自動的にRigidbodyも追加される)
  4. NetworkObjectが不足している警告が表示されるので、Add Network Objectを押す
  5. PhysxBallの子オブジェクトとして、Sphereを追加する
  6. 上記のスケールを0.2にする
  7. 上記を親オブジェクトPhysxBallNetworkRigdbody3DInterpolationTargetにドラッグする
  8. 上記からColliderを削除する
  9. PhysxBall側にSphereColliderを追加し、半径を0.1にすることでSphereと見た目を合わせる
  10. PhysxBallに新規スクリプトを追加し、名前をPhysxBall.csにする
  11. 上記オブジェクトをプロジェクトフォルダにドラッグし、プレハブを作成する
  12. シーンを保存してネットワークオブジェクトをベイクし、シーンからPhysxBallを削除する
ballプレハブ
Ballプレハブ

物理オブジェクトは、オブジェクトとビジュアル表現を分けて、ビジュアル表現をInterpolationTargetに設定することを推奨されますが、必須ではありません。RigidbodyColliderは、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.csStartGameメソッド内でNetworkRunnerコンポーネントを追加した後に、以下のようなコードを追加してください。

C#

gameObject.AddComponent<RunnerSimulatePhysics3D>();

最後に、ボールのプレハブをPlayerプレハブの_prefabPhysxBallにアタッチした後、ビルドしてボールのテストを実行しましょう。

次は ホストモード入門 5 - プロパティの変更

Back to top