Tanknarok
概要
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パッケージがプロジェクトに含まれていることを確認してください。
Window > Package Manager
に移動Packages: Unity Registry
を選択- "Post Processing"を探し
- パッケージをインストールしてください。
スクリーンショット
ダウンロード
バージョン | リリース日 | ダウンロード | ||
---|---|---|---|---|
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
クラスと、トップレベルのマネージャーであるGameManager
、LevelManager
、PlayerManager
があります。
Quick Fusion プライマー
Fusionでは、ネットワーク状態をNetworkObject
コンポーネントで識別します。ネットワーク状態を持つゲームオブジェクトには、必ずNetworkObject
を付けなければなりません。ネットワーク状態を持つゲームオブジェクトには、必ずNetworkObject
があります。NetworkObject
自体は、ネットワーク全体のアイデンティティをゲームオブジェクトに割り当てるだけで、実際のネットワーク状態はNetworkBehaviour
から派生したコンポーネントに格納されます。Fusionにはいくつかのデフォルトの動作があります。例えば、Unity Transformを同期させるNetworkTransform
などがあります。
物理ビヘイビアが FixedUpdate()
で物理状態を変換するのと同様に、NetworkBehaviour
は FixedUpdateNetwork()
メソッドでネットワーク状態を変換します。これは、レンダリングフレームレートとは無関係に、ネットワークからの更新とは無関係に、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)
{
...
}
レベルの遷移
ロビーからレベルへの移行、またはその逆の移行は、LevelManager
がTransitionSequence()
コルーチンで処理します。移行自体はローカル時間で実行されますが、(リモートプロシージャコールのRPC_ScoreAndLoad
によって)トリガーされたときと、(playState
をLEVEL
に設定することで)終了したときには、クライアント間で同期します。
ゲームの終了時には、レベル移行がロビーに戻り、同じ方法で勝者を表示し、ループを終了してゲームをReady Up状態に戻します。
入力の処理
Fusionは、Unityの標準的な入力処理メカニズムを使ってプレイヤーの入力を取得し、それをネットワーク経由で送信できるようにデータ構造に格納し、FixedUpdateNetwork()
メソッドでこのデータ構造を利用します。この例では、これらすべてを InputController
クラスが実装していますが、実際の状態変化は Player
クラスに渡します。
シューティング
この例の戦車は、4種類の武器を持ち、2種類の異なるヒット判定を持っています。
- インスタントヒット
- 発射物
それぞれ、HitScan
クラスとBullet
クラスで実装されています。どちらもオブジェクトプーリング、予測スポーン、ラグ補正という Fusion の重要な機能を使用しています。
オブジェクトプーリング
新しいオブジェクトを生成する際のフレーム落ちを避けるためには、新しいオブジェクトを破壊してインスタンス化するのではなく、古いオブジェクトを再利用することが望ましいです。これは、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
(両方が必要な場合)を配置することをお勧めします。