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
にアタッチした後、ビルドしてボールのテストを実行しましょう。