4 - 物理
概要
Fusion 104では、サーバー主導のゲームでFusionがどのようにPhysXに作用するのかを検証します。
このセクションの最後には、プロジェクトでプレイヤーが物理的な挙動をするボールをスポーンして相互作用できるようになります。
セットアップ
デフォルトでは、Fusionはホスト上で物理シミュレーションを実行し、クライアントはそれに従います。しかし、ローカルで予測しているオブジェクトと異なる時間におかれた物理オブジェクトが配置されて、それらのオブジェクトが衝突すると物理シミュレーションに問題が発生します。
「時間差」の解決は複雑なトピックです。うまくごまかしたり、一般的なアプリケーションでは負荷が高すぎる処理であったり、プレイヤーへの即時のフィードバックを諦めたり、様々な方法で取り扱われます。残念ながら、あらゆるケースで有効なシンプルな解決方法は存在しません。
このチュートリアルでは、物理オブジェクトをローカルで予測するFusionの機能を使って、物理的な挙動をするオブジェクトとプレイヤーが操作するアバターを同じ時間におきます。これは実用的で堅牢なソリューションですが、PhysXで予測と再シミュレーション(ティックごとに複数回)を実行するのは負荷が高いことを覚えておいてください。
この機能を有効にするには、Fusionのメニューに進み、Network Project Config
を選択してください。するとインスペクターでFusionのコンフィグファイルが開きます。そこでServer Physics Mode
のClient Prediction
を選択してください。
物理オブジェクト
PhysXで動作するネットワークオブジェクトは、通常のUnityのオブジェクトと同じRigidbody
を使用しますが、NetworkRigidoby
と呼ばれるオブジェクトを同期するFusionのコンポーネントも持ちます。NetworkRigidbody
はNetworkTransform
を代替します。
- Unity Editorで空の新しいGameObjectを作成する
GameObject
の名前をPhysxBall
にする- 新しい
NetworkRigidbody
コンポーネントを追加する - Fusionが
NetworkObject
コンポーネントが足りないという警告を表示するので、続けてAdd Network Object
を押す。Fusionは自動的にPhysXのシミュレーションで必要なRigidBody
も追加する Interpolation Data Source
をPredicted
へ変更しWorld Space
に設定する- Sphereの子を
PhysxBall
に追加する - 子のスケールを0.2にサイズダウンする
- 子オブジェクトを親オブジェクトの
NetworkTransform
コンポーネントのInterpolationTarget
内にドラッグする - Sphereからコライダーを削除する
- 親オブジェクトで新しいSphereコライダーを、子オブジェクトを完全に覆うように半径0.1で作成する
- ゲームオブジェクトに新しいスクリプトを追加し、
PhysxBall.cs
と名前を付ける - 全てのオブジェクトをプロジェクトフォルダにドラッグしプレハブを作成する
- シーンを保存してネットワークオブジェクトをベイクし、シーンからプレハブインスタンスを削除する
PhysxBall スクリプト
ボールはPhysXによって動かされ、NetworkRigidbody
がネットワーク上のデータの面倒を見てくれるので、Kinematicなボールよりも記述する必要があるコード量は少なくなります。PhysxBall.cs
に追加する必要があるのは、数秒後にボールをデスポーンするタイマー(Kinematicなボールと全く同じもの)と、初速度を設定するメソッドだけです。
これらは両方とも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);
}
}
インプット
ボールをスポーンするには、Kinematicなボールの場合と同じ3ステップに従って、コードを記述する必要がありますが、かわりにマウス右ボタンを使用するように変更しています。以下で見ていきましょう。
1. NetworkInputData
NetworkInputData.cs
で新しいボタンフラグを追加する。
C#
using Fusion;
using UnityEngine;
public struct NetworkInputData : INetworkInput
{
public const byte MOUSEBUTTON1 = 0x01;
public const byte MOUSEBUTTON2 = 0x02;
public byte 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();
...
if (_mouseButton1)
data.buttons |= NetworkInputData.MOUSEBUTTON2;
_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 (delay.ExpiredOrNotRunning(Runner))
{
if ((data.buttons & NetworkInputData.MOUSEBUTTON1) != 0)
{
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 & NetworkInputData.MOUSEBUTTON2) != 0)
{
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 );
});
}
}
}
}
最後に、新しいボールのテストのため、作成したプレハブをPlayer
プレハブのPrefab Physx Ball
フィールドに割り当て、ビルドして実行します。