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 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.csStartGameメソッド内でNetworkRunnerコンポーネントを追加した後に、以下のようなコードを追加してください。

C#

var runnerSimulatePhysics3D = gameObject.AddComponent<RunnerSimulatePhysics3D>();
runnerSimulatePhysics3D.ClientPhysicsSimulation = ClientPhysicsSimulation.SimulateAlways;

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

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

Back to top