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 Fusion;
using UnityEngine;
public class PhysxBall : NetworkBehaviour
{
private const float LifetimeSeconds = 5f;
[Networked]
private TickTimer _life { get; set; }
private Rigidbody _rigidbody;
private void Awake()
{
_rigidbody = GetComponent<Rigidbody>();
}
public void Init(Vector3 forward)
{
_life = TickTimer.CreateFromSeconds(Runner, LifetimeSeconds);
_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);
}
void INetworkRunnerCallbacks.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#
private const float BallSpeed = 10f;
...
public override void FixedUpdateNetwork()
{
if (GetInput(out NetworkInputData data))
{
data.Direction.Normalize();
_cc.Move(MoveSpeed * 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, ShootDelay);
Runner.Spawn(
_prefabBall,
transform.position + _forward,
Quaternion.LookRotation(_forward),
Object.InputAuthority,
(runner, o) => { o.GetComponent<Ball>().Init(); }
);
}
else if (data.Buttons.IsSet(NetworkInputData.MouseButton1))
{
_delay = TickTimer.CreateFromSeconds(Runner, ShootDelay);
Runner.Spawn(
_prefabPhysxBall,
transform.position + _forward,
Quaternion.LookRotation(_forward),
Object.InputAuthority,
(runner, o) => { o.GetComponent<PhysxBall>().Init(BallSpeed * _forward); }
);
}
}
}
}
物理演算をネットワークに対応させるには、NetworkRunnerオブジェクトにRunnerSimulatePhysics3Dコンポーネントが必要です。BasicSpawner.csのStartGameメソッド内でNetworkRunnerコンポーネントを追加した後に、以下のようなコードを追加してください。
C#
var runnerSimulatePhysics3D = gameObject.AddComponent<RunnerSimulatePhysics3D>();
runnerSimulatePhysics3D.ClientPhysicsSimulation = ClientPhysicsSimulation.SimulateAlways;
最後に、ボールのプレハブをPlayerプレハブの_prefabPhysxBallにアタッチした後、ビルドしてボールのテストを実行しましょう。