This document is about: FUSION 1
SWITCH TO

Tanknarok

Level 4

概要

Fusion Tanknarok サンプルでは、権威あるサーバー(スタンドアロンアプリケーション、またはサーバーとクライアントの両方を実行する単一のアプリケーション)を使用する「ホストモード」、または各クライアントが独自のオブジェクトに対する権限を持ち、1つのクライアントが「共有」オブジェクトを制御する「シェアードモード」のいずれかで動作する、多人数アリーナスタイルの小規模な戦車ゲームを構築する方法を説明しています。

この例のゲームはSteamでお馴染みのもので、ゲームプレイロジック、オーディオ、グラフィックはPolyblock Studiosが提供してくださっています。このサンプルは、実際にPhoton Fusionに移植されたゲームのほんの一部で、オリジナルのゲームはPhoton Boltを使って作られています。

Fusion Tanknarokのサンプルでは、PhysXのような不確定なリジッドボディ物理エンジンに予測ネットワークシステムを混ぜることで生じる複雑さを伴わずに、物理的な効果を実現します。必要であれば、FusionはUnityリジッドボディの同期を完全にサポートします。

始める前に

3Dテンプレートを使って新しいUnityプロジェクトを作成し、Project Settings > Player > Other Settings > Color SpaceでColor Spaceを「Linear」に設定してください。

サンプルをダウンロードしてインポートする前に、Unity Post Processingパッケージがプロジェクトに含まれていることを確認してください。

  1. Window > Package Managerに移動
  2. Packages: Unity Registryを選択
  3. "Post Processing"を探し
  4. パッケージをインストールしてください。

スクリーンショット

ダウンロード

バージョン リリース日 ダウンロード
1.1.7 Jun 28, 2023 Fusion Tanknarok 1.1.7 Build 202

ハイライト

  • 共有モードとホストモードのサポート
  • ラグ補正レイキャスト
  • 予測スポーン
  • オブジェクトプーリング
    • 完全なゲームループ

プロジェクト

デモを実行する前に、Photon Cloud用のFusion App IDを作成して、PhotonAppSettingsアセットにコピーペーストする必要があります。App IDはPhoton Dashboardから作成できます。Realtime Idではなく、Fusion App Idを作成するようにしてください。

Photonのアプリ設定アセットは、FusionメニューのFusion > Realtime Settingsから選択できます。

生成されたApp IdをApp Id Fusionフィールドに貼り付けます。

フォルダ構成

Tanknarok 派生サンプルのコードは /Scripts フォルダに入っていて、汎用ユーティリティのための Utility サブフォルダと、このサンプルに特有ではない Fusion ユーティリティのための FusionHelpers フォルダがあります。

残りのTanknarokフォルダには実際のゲームコードが入っていて、以下のサブフォルダに分かれています。

  • Audio - サウンドエフェクトと音楽
  • Camera - カメラの配置コード
  • Level - すべてのレベルロジックと動作、パワーアップ、その他のノンプレイヤーアイテム
  • Player - すべての戦車コントロール、武器、弾のロジック、戦車のビジュアライゼーションとエフェクト。
  • UI - ユーザーインターフェイスコンポーネント

メインフォルダには、メインのエントリーポイントとして、GameLauncherクラスと、トップレベルのマネージャーであるGameManagerLevelManagerPlayerManagerがあります。

Tank Game
戦車ゲームのメニュー。

Quick Fusion プライマー

Fusionでは、ネットワーク状態をNetworkObjectコンポーネントで識別します。ネットワーク状態を持つゲームオブジェクトには、必ずNetworkObjectを付けなければなりません。ネットワーク状態を持つゲームオブジェクトには、必ずNetworkObjectがあります。NetworkObject自体は、ネットワーク全体のアイデンティティをゲームオブジェクトに割り当てるだけで、実際のネットワーク状態はNetworkBehaviourから派生したコンポーネントに格納されます。Fusionにはいくつかのデフォルトの動作があります。例えば、Unity Transformを同期させるNetworkTransformなどがあります。

物理ビヘイビアが FixedUpdate() で物理状態を変換するのと同様に、NetworkBehaviourFixedUpdateNetwork() メソッドでネットワーク状態を変換します。これは、レンダリングフレームレートとは無関係に、ネットワークからの更新とは無関係に、1ティックと呼ばれる固定の時間ステップで行われます。それぞれの更新は、前のティックの状態に基づいて行われます。あるティックの状態がネットワークによって確認されると、Fusionはそのティックのオブジェクトの状態をロールバックし、その時点から現在のティックまでの間に行われたFixedUpdateNetwork()のすべての呼び出しを再適用します。

現在のローカルティックは常に最後に確認されたティックよりも前にあるため、この更新は「予測」と呼ばれ、確認された状態の適用とそれに続くFixedUpdateNetworkメソッドの再実行は「ロールバック」と「再シミュレーション」と呼ばれます。

コンポーネントがネットワークの状態を持たずにシミュレーションの一部となることは可能ですが、オーバーヘッドを減らすために、NetworkBehaviourではなくSimulationBehaviourから派生させるべきです。再シミュレーションのため、FixedUpdateNetwork()メソッドがフレームごとに何度も呼ばれる可能性があることに注意してください。ネットワーク状態のみを扱う場合はリセットされるので問題ありませんが、非ネットワーク状態に差分の変更を適用する場合は注意が必要です

シミュレーション、予測、ネットワークオブジェクトの詳細については、「Fusion Manual」を参照してください。

GameLauncher

TankゲームのメインUIは、GameLauncherクラスが担当します。

ゲームモードが選択されると、GameLauncherはセッションを確立するために、FusionLauncher.Launch()を呼び出します。FusionLauncherはFusionの接続イベントに応答し、提供されたコールバックを呼び出して、初期のネットワークオブジェクトを生成します。

  • GameManager (ホストモードではホストが、共有モードではマスタークライアントが生成します)
  • Player(ホストモードではホストが、共有モードでは各クライアントが生成します)

準備

プレイヤーが接続するとすぐにプレイヤーの戦車が出現し、残りの戦車が出現するのを待っている間に遊ぶことができる「ロビー」モードで完全に制御できます。

ゲーム自体は、接続しているプレイヤー全員が準備完了を示すまで始まりません。このロジックはすべてのクライアントで実行されますが、GameManagerインスタンスのStateAuthorityを持つクライアントだけがレベルを読み込むことができます。

注意: このシンプルな例では、両方のレベルが最初から初期シーンにあるため、レベルは「読み込まれた」というよりも「有効になった」と言えます。

読み込みはリモートプロシージャコールで行われます。呼び出し側はランダムなレベルインデックスを生成し、それをすべてのクライアントに渡すことで、全員が同じレベルを読み込むことを保証します。

C#

    if (Object.HasStateAuthority)
      RPC_ScoreAndLoad(-1,0, _levelManager.GetRandomLevelIndex());

RPC自体は以下のように定義されています。

    [Rpc(sources: RpcSources.StateAuthority, targets: RpcTargets.All, InvokeLocal = true, Channel = RpcChannel.Reliable)]
    private void RPC_ScoreAndLoad(int winningPlayerIndex, byte winningPlayerScore, int nextLevelIndex)
    {
        ...
    }
Tank Game
プレイヤーの準備を待つ

レベルの遷移

ロビーからレベルへの移行、またはその逆の移行は、LevelManagerTransitionSequence()コルーチンで処理します。移行自体はローカル時間で実行されますが、(リモートプロシージャコールのRPC_ScoreAndLoadによって)トリガーされたときと、(playStateLEVELに設定することで)終了したときには、クライアント間で同期します。

ゲームの終了時には、レベル移行がロビーに戻り、同じ方法で勝者を表示し、ループを終了してゲームをReady Up状態に戻します。

Tank Game

入力の処理

Fusionは、Unityの標準的な入力処理メカニズムを使ってプレイヤーの入力を取得し、それをネットワーク経由で送信できるようにデータ構造に格納し、FixedUpdateNetwork()メソッドでこのデータ構造を利用します。この例では、これらすべてを InputController クラスが実装していますが、実際の状態変化は Player クラスに渡します。

シューティング

この例の戦車は、4種類の武器を持ち、2種類の異なるヒット判定を持っています。

  • インスタントヒット
  • 発射物

それぞれ、HitScanクラスとBulletクラスで実装されています。どちらもオブジェクトプーリング、予測スポーン、ラグ補正という Fusion の重要な機能を使用しています。

Weapons
Bombs Away.

オブジェクトプーリング

新しいオブジェクトを生成する際のフレーム落ちを避けるためには、新しいオブジェクトを破壊してインスタンス化するのではなく、古いオブジェクトを再利用することが望ましいです。これは、UnityやFusionを含むどんなゲームでも同じです。

これを実現するために、Fusionでは、リサイクルされたゲームオブジェクトを提供・回収するためのフックを、アプリケーションで指定することができます。

オブジェクトプールは、NetworkObjectPoolを実装する必要があります。基本的には、プレハブに基づいてプールからオブジェクトを取得するメソッドと、再利用のためにプールにオブジェクトを戻すメソッドがあります。

予測スポーン

Predictive Spawningは、クライアントが新しいネットワークオブジェクトの作成を予測し、その作成が状態権限によって確認されるまで、一時的なローカルプレースホルダーを作成することができます。Fusionは、確認されると自動的にプレースホルダーを実際のネットワークオブジェクトに昇格させます。

手動で処理する必要があるのは、failed prediction です。これは、プレースホルダーを破壊するだけで済みます(ただのUnityオブジェクトであることを忘れないでください)。アプリケーションは、さまざまな形式のフェードアウトまたは障害の視覚化を自由に実装できます。

また、プレースホルダーには状態がないので、アプリケーションは、ネットワークのプロパティにアクセスしない方法で、予測段階の動きを管理する必要があります。

ラグ補正

ローカルでは、各プレイヤーは、自分(Input Authority)のオブジェクトの予測された未来のバージョンと、他のクライアントのオブジェクトのインターポーレーションまたはエクストラポーレーションされたバージョンを見ます。そのため、例えば弾丸のような高速移動する物体は、それぞれのマシンで異なるものに当たる可能性が高いのです。何かがおかしいと気づくのは通常弾丸を発射した人です。しかし、同時に、不正なクライアントがヒットを成功と判断してしまうことを避けるために、サーバーはヒット検出の権限を持つべきです。

この問題を解決するために、Fusionは遅延補償付きのレイキャストに対応しています。これは、サーバー上で実行されている場合でも、発射した時点でクライアントが見ていたものを尊重するレイキャストです。この機能を実現するために、背景では多くのスナップショット補間マジックが行われています。開発者にとって、遅延補償付きレイキャストの実装と使用は、通常のUnityレイキャストを使用するのと同じくらい簡単です。

唯一知っておかなければならないことは、「HitBox」と呼ばれる独自のタイプのコライダーオブジェクトが付属していることです。このHitBoxは、オブジェクト階層の中で、完全に包括的なHitBoxRootノードの兄弟または子でなければなりません。これにより Fusion は、子ノードに対してより高価なチェックを行う前に、ルートを素早く削除することができます。

パフォーマンス上の理由から、静的なエンティティには HitBox を適用すべきではありません。しかし、静的な環境は、レイキャストをブロックするために必要です。遅延補償されたレイキャストは、オプションで Unity のコライダもチェックすることができます。

注意点としては、ラグ補正は動的なPhysXコリダーには機能しないことです(動きなど)。さらに、Unityは静的なコリダーと動的なコリダーを区別するレイキャストクエリを提供しません。そのため、PhysXコライダをフィルタリングして、静的な結果のみを得るためには、ダイナミックオブジェクトの異なるレイヤーに、HitBox/HitBoxRootとPhysXCollider(両方が必要な場合)を配置することをお勧めします。

Weapons
For the win.
Back to top