This document is about: FUSION 2
SWITCH TO

Tanknarok

Level 4

概要

Fusion Tanknarokのサンプルは、小規模なマルチプレイヤーのアリーナスタイルのタンクゲームを構築する方法を示しています。このゲームはホストモードまたは共有モードのいずれかで実行されます。ホストモードでは、独自のサーバー(スタンドアロンアプリケーションまたはサーバーとクライアントの両方を実行する単一のアプリケーション)がゲームを管理します。サーバーは、ゲームオブジェクトとその動作を制御します。共有モードでは各クライアントが自分自身のオブジェクトに権限を持ち、1つのクライアントが「共有」オブジェクトを制御します。つまり、各クライアントは自分自身のオブジェクトに対して権威を持ちますが、すべてのプレーヤー間で共有されるオブジェクトを管理するクライアントが1つあります。

Polyblock Studiosが提供したゲームプレイのロジック、オーディオ、素晴らしいグラフィックスが使用されているため、このサンプルに類似したゲームをSteamで見かけるかもしれません。このサンプルはPhoton Fusionに移植された実際のゲームのほんの一部です。元のゲームはPhoton Boltを使用して作成されました。

Fusion Tanknarokサンプルは、予測ネットワークシステムと非決定論的な物理エンジンであるPhysXの組み合わせによる複雑さを避けながら、物理的な効果をどのように実現するかを示しています。Fusionは、必要に応じてUnityのrigidbodyの同期を完全にサポートしています。

始める前に

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

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

  1. Window > Package Managerを開きます
  2. Packages: Unity Registryを選択します
  3. "Post Processing"を検索して、
  4. パッケージをインストールします。

スクリーンショット

ダウンロード

バージョン リリース日 ダウンロード
2.0.0 Jan 18, 2024 Fusion Tanknarok 2.0.0 Build 392

ハイライト

  • 共有モードとホストモードに対応
  • ラグ補償されたレイキャスト
  • 予測スポーン
  • オブジェクトプーリング
  • 完全なゲームループ

プロジェクト

デモを実行する前に、Photon CloudのFusionのAppIDを作成しPhotonAppSettingsアセットにコピーアンドペーストする必要があります。AppIDは Photonダッシュボードから作成できます。RealtimeIDだけでなく__Fusion__ AppIDを作成してください。

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

生成されたAppIDをApp Id Fusionフィールドにペーストします。

フォルダ構造

Tanknarokから派生するサンプルのコードは/Scriptsフォルダにあります。一般的なユーティリティのための Utilityサブフォルダと、この例に特化しないFusionユーティリティのためのFusionHelpers`フォルダがあります。

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

  • Audio - サウンドエフェクトと音楽
  • Camera - カメラの配置コード
  • Level - すべてのレベルロジック、挙動、パワーアップ、およびその他の非プレイヤーアイテム
  • Player - すべてのタンクコントロール、タンクのビジュアルとエフェクトのほか武器と弾丸のロジック
  • UI - ユーザーインターフェースコンポーネント

メインフォルダでのメインエントリーポイントはGameLauncherクラスで、トップレベルマネージャーはGameManagerLevelManagerおよびPlayerManagerです。

tank game
タンクゲームのメニュー

Quick Fusion Primer

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

物理敵な挙動が物理状態をFixedUpdate()で変化させるのと同様に、NetworkBehaviourはネットワークの状態をFixedUpdateNetwork()メソッドで変化させます。これはレンダリングのフレームレートやネットワークからの更新とは別に、tickと呼ばれる固定の時間ステップによって発生します。各アップデートでは前のtickの状態を元に作業が行われます。特定のtickの状態がネットワークによって確認されるとFusionはオブジェクトの状態をそのtickに戻し、そのtickから現在のtickまでのすべてのFixedUpdateNetwork()の中間呼び出しを再び適用します。

現在のローカルのtickは常に最後に確認されたtickよりも進んでいるため、そのアップデートは「予測」と呼ばれ、確認された状態の適用およびその後のFixedUpdateNetworkメソッドの再実行は「ロールバック」、「再シミュレーション」と呼ばれます。

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

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

GameLauncher

タンクゲームのメインUIはGameLauncherクラスで処理されます。

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

  • GameManager (ホストモードでホストによって、または共有モードでマスタークライアントによってスポーンされます)
  • Player (ホストモードでホストによって、または共有モードで各クライアントによってスポーンされます)

Ready Up

プレイヤーのタンクはプレイヤーが接続するとすぐにスポーンし、プレイヤーは他のタンクがスポーンするのを待機する間に操作可能な「ロビー」モードでプレイ可能です。

ゲーム自体はすべての接続されたプレイヤーが準備ができたことを示すまで開始しません。このロジックはすべてのクライアントで実行されますが、GameManagerインスタンスのStateAuthorityを持つクライアントのみがレベルをロードできます。

注: この簡略化されたサンプルではレベルは「ロード」されて「有効化」されるのではなく、最初から初期シーンに両方のレベルが含まれています。

ロードはリモートプロシージャコール(RMC)でおこなわれます。呼び出し元はランダムなレベルインデックスを生成し、それをすべてのクライアントに渡すことで全員が同じレベルをロードすることが保証されます。

C#

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

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

C#

    [Rpc(sources: RpcSources.StateAuthority, targets: RpcTargets.All, InvokeLocal = true, Channel = RpcChannel.Reliable)]
    private void RPC_ScoreAndLoad(int winningPlayerIndex, byte winningPlayerScore, int nextLevelIndex)
    {
        ...
    }
tank game
プレイヤーの準備ができるのを待機中。

レベル移行

ロビーからレベルへの移行、およびその逆は TransitionSequence()コルーチンのLevelManagerで処理されます。移行そのものは完全にローカル時間で実行されますが、トリガーされた場合(RPC_ScoreAndLoadリモートプロシージャコールによって)と終了した場合(GameManagerのステート権限によってのみ設定できるネットワークプロパティであるplayStateをLEVELに設定することによって)、クライアント間で同期されます。

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

tank game

入力処理

FusionはUnityの標準的な入力処理メカニズムを使用してプレーヤーの入力を取得し、ネットワークを介して送信可能なデータ構造に保存した後にこのデータ構造をFixedUpdateNetwork()メソッドで処理します。この例では、実際の状態の変更はInputControllerクラスによって実装されていますが、実際の状態の変更はPlayer クラスに委譲されます。

シューティング

このサンプルの戦車には4つの武器があり、2種の当たり判定があります:

  • 即時ヒット
  • 飛翔体

これらはそれぞれ、HitScanクラスとBulletクラスによって実装されます。これらはともにオブジェクトプーリング、予測スポーン、ラグ補償といったFusionの3つの重要な機能を使用します。

weapons
爆撃開始。

オブジェクトプーリング

新しいオブジェクトを生成する際にフレームのドロップを回避するために、常にオブジェクトを破棄してインスタンス化するのではなく古いオブジェクトを再利用することをお勧めします。これは、すべてのゲームにおいて特にUnityやFusionにおいても真です。

これを実現するために、Fusionではアプリケーションが再利用可能なゲームオブジェクトを提供および収集するためのフックを指定することができます。

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

予測スポーン

予測スポーンは、クライアントが新しいネットワークオブジェクトの作成を予測し、ステート権限が作成を確認するまで一時的なローカルプレースホルダーを作成します。Fusionは、確認後にプレースホルダーを実際のネットワークオブジェクトに自動的に昇格させます。

手動処理が必要なのは予測が失敗した場合です。これはプレースホルダーを破棄するだけであるかもしれません(プレースホルダーは単なるUnityオブジェクトです)。アプリケーションは、フェードアウトや失敗のビジュアライゼーションなどさまざまな形式の処理を実装することができます。

また、プレースホルダーには状態がないため予測段階での移動をネットワークプロパティにアクセスせずに処理する必要があります。

ラグ補償

ローカルでは、各プレーヤーは自分自身(Input Authority)のオブジェクトの予測された未来バージョンと、他のクライアントのオブジェクトの補完または外挿されたバージョンを見ることができます。どちらもサーバーが見ているものと完全に一致しているわけではありません。したがって、高速移動するオブジェクト(たとえば弾丸)は各マシン上で異なるものに当たる可能性が非常に高いです。弾丸を発射した人が何かがおかしいと気づく可能性が最も高いです。しかし、これと同時にサーバーは当たり判定に権限を持っているため、単に成功したヒットを決定するだけの誤ったクライアントを防ぐ必要があります。

この問題を解決するため、Fusionはラグ補償されたレイキャストをサポートしています。これは、サーバーで実行されている場合でもショットが撃たれた時点でクライアントが見たものを尊重するレイキャストです。この機能を実現するため、シーンの裏側で多くのスナップショット補間の処理が行われていますが、開発者にとってはラグ補償されたレイキャストの実装と使用は通常のUnityのレイキャストと同じくらい簡単におこなえます。

唯一の留意点は、Fusionには独自のコライダーオブジェクトであるHitBoxが存在するということです。HitBoxはオブジェクト階層で完全に包括的なHitBoxRootノードの兄弟または子ノードである必要があります。これにより、Fusionは子ノードのより高価なチェックを行う前にルートを迅速に除外できます。

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

ラグ補償はPhysXコライダーのような動的な(つまり移動する)コライダーには適用されないということです。さらに、Unityは静的なコライダーと動的なコライダーの区別をおこなうレイキャストクエリを提供していません。したがって、PhysXコライダーをフィルタリングして静的な結果のみを得るには、HitBox/HitBoxRootとPhysXのCollider(両方が必要な場合)を動的なオブジェクトの異なるレイヤーに配置することをおすすめします。

weapons
勝つために。
Back to top