プレイヤー
概要
Player(Playerスクリプト、Playerプレハブ)は、ゲームに接続しているプレイヤー(ピア)を表し、ビジュアルはありません。Playerは、ユーザーID、ニックネーム、選択したキャラクター、そしてAgentのリスポーン後も引き継がれるべきその他のデータといった共通のメタデータにアクセスするためのインターフェースを提供します。
Agent(Agentスクリプト、AgentBaseプレハブ、およびそのバリアント)は、ゲーム内でプレイヤーが操作するキャラクターを表します。GameplayModeによってスポーンされ、HealthやWeaponsなどのコンポーネントを持ちます。このキャラクターは必要に応じてスポーンおよびデスポーンされます。
以下の図は、コンポーネントの階層と、それがどのように処理の流れ(ウォーターフォール)を示しているかを表したものです。
入力処理
以下の図は、入力の処理とアクションの実行の流れを示しています。
注意点:
Frame:Unityのフレーム番号(UnityEngine.Time.frameCount)。読みやすさを優先し、フレーム101と102は途中までの状態になっています。- 各フレームの開始時に、端末からの入力が
ProcessFrameInput()内で収集され、Render Inputというデータ構造に書き込まれます(入力の権限を持つクライアントのみ)。 - 同じ入力は、
Accumulated Inputにも蓄積されます(例:視点回転のデルタなど)。 - 蓄積されたデルタ時間が次の固定ティック(フレーム103)をシミュレーションするのに十分な場合:
Accumulated InputはNetworkEvents.OnInput()のコールバックを通じてポーリング/消費される- プレイヤーの入力はFusionの
BeforeTick()で読み取られ、Fixed Inputに保存され(入力と状態の権限を持つプレイヤーのみ処理) AgentInput.FixedUpdateNetwork()の呼び出しはAgent.EarlyFixedUpdateNetwork()に転送され、そこではFixed Inputを使って移動を処理。これにより、シミュレーションの現在のティックで誰よりも先にすべてのエージェントが移動を完了します。Agent.FixedUpdateNetwork()はFixed Inputを読み取り射撃を処理AgentInput.Render()の呼び出しはAgent.EarlyRender()へ転送され、ここではRender Input(またはAccumulated Inputも使用可能)を使ってレンダー上で予測した動きや視点回転を処理Agent.Render()はレンダリングの予測射撃には処理を行わない(このサンプルの範囲外)。射撃は固定シミュレーション内だけで行われる。
- 蓄積されたデルタ時間が次の固定ティック(フレーム101、102)をシミュレーションするのに十分でない場合:
AgentInput.Render()はAgent.EarlyRender()に転送され(上記の図には記載されていませんが、見やすさのため)、Render Input(またはAccumulated Inputも使用可能)を使ってレンダー上の予測動きや視点回転を処理。
この図は簡略化されたものであり、すべてのエッジケースをカバーしているわけではありません。詳細はドキュメント化されたコードを参照してください。
視点回転の平滑化
BR200には、どのような条件でも滑らかな視点回転を実現するためのカスタムソリューションが搭載されています。
以下のログは、一般的なハードウェア(125Hzでポーリングされるマウス)と高いレンダリング/出力リフレッシュレート(200Hz以上)を使用した場合に生じるエイリアシングの問題を示しています。たとえCPUやGPUが非常に優れていても、レンダリング結果は常にギザギザした感じを感じるでしょう。
この問題を解決するために、入力値はタイムスタンプとともに記録され、その後、現在のフレームの値は一定の時間範囲(BR200ではデフォルトで25msのウィンドウ)内の値の平均を計算して求めます。これにより、非常に滑らかで自然な動きの感覚(特に高リフレッシュレートのモニターでは顕著に感じられる)が得られますが、少しだけ入力遅延が発生します。より高いサンプリングレートのハードウェアを使用することで、スムージングウィンドウを最小限に抑えることが可能です。
以下のグラフは、生のマウスのデルタ(下の線)とキャラクターの視点回転(上の線)を時間軸で表したものです。
以下のグラフは、平滑化されたマウスのデルタ(下の線)とキャラクターの視点回転(上の線)を時間軸で示したものです。
これにより、サンプリングエラー(机の表面の影響)や、不規則な手やマウスの動き(机の摩擦や筋肉の動きによる微細な揺れ)によるマイクロジッターの軽減にも役立ちます。
キャラクターアニメーション
このプロジェクトは、Playables APIをベースにしたカスタムのアニメーションコントローラーを実装しています。これにより、ティックごとの正確なアニメーション評価とダイナミックなパフォーマンススケーリングをサポートします。
以下の図は、Mecanimに類似したアーキテクチャを示しています。
エージェントのオブジェクト階層におけるアニメーションレイヤーとステートの設定:
アニメーションレイヤー:
Locomotion:移動のための基本的なフルボディレイヤーFullBody:フルボディのアクション用のオーバーライドレイヤーで、LocomotionとブレンドされるLowerBody:下半身のターン動作用のオーバーライドレイヤーUpperBody:上半身のアクション用のオーバーライドレイヤーで、通常はLocomotionとブレンドされるShoot:手のアクション(射撃)用のオーバーライドレイヤーで、通常はLocomotionとブレンドされるLook:上下を見るためのアディティブ上半身レイヤー
アニメーションコントローラーの複雑さによっては、200人のプレイヤーの評価はサーバーのボトルネックになりやすいです。パフォーマンス向上のために、サーバーは接続されているプレイヤー人数に基づいて、一定のフレームごとに間引き評価(インターレース評価)を行うことができます。すべての重要なプロパティ(レイヤーやステートのウェイトなど)は、引き続き毎フレーム計算されます。以下の表は、インターレース評価のルールを示しています。
| プレイヤー接続済み | PlayableGraph評価 |
|---|---|
| > 150 | 6フレームごと |
| > 100 | 4フレームごと |
| > 50 | 2フレームごと |
| Otherwise | 毎フレーム |
キャラクターコントローラー
このサンプルでは、Fusion KCC(高度な運動学的キャラクターコントローラーアドオン)を使用して移動処理を行います。これは、パフォーマンス、ゲームプレイのインタラクション、カスタマイズ性に重点を置いた汎用的なローレベルのキャラクターコントローラーです。
Fusion KCCの特徴:
- 位置と視点回転(ピッチ+ヨー)の制御
- Capsuleコライダーによる形状定義
- ローカルプレイヤーの予測レンダリング動作
- ダイナミック(物理的な)とキネマティック(非現実的な)速度ベースの動きの組み合わせ
- 爆発や移動プラットフォームなどの外力の適用
- 移動の加速度と摩擦
- 高度なKCC処理パイプライン(速度や方向の上書き、ブロックなどのカスタマイズ)
- 標準プロパティ(半径、高さ、質量など)のネットワーク同期と、必要に応じて他のプロパティの同期も可能
- コライダーのカスタムフィルターと無視リスト設定
- CCD(連続衝突判定)
- 衝突コールバック機能
- 地面へのスナップや段差の対応
- ローカルモード(ネットワーク通信なし)のサポート
- ネットワーク・パフォーマンス最適化
- プラットフォームに依存しない、モバイル対応
- フレームごとのデバッグサポート(エディター上の描画やログ出力)
プレイヤーの体力が一定以下になり、かつ現在戦闘状態でない場合、自動回復機能が作動し、プレイヤーの体力を回復します。
ジェットパック
ジェットパックは、飛行能力とレベル内での迅速な移動を可能にしながら、燃料を消費します。燃料はアイテムボックス内にある燃料缶を拾うことで補充できます。
ジェットパックの状態はJetpackスクリプトによって管理され、燃料の消費、プロペラ音、サウンド、オン/オフの状態を制御します。実際の空中移動はJetpackKCCProcessorによって行われます。このスクリプトはKCCの速度を上書きし、デフォルトの動作を抑制します。
観戦モード
プレイヤーがエリミネートされたり、遅れてゲームに参加した場合、観戦モードに入ります。観戦モードでは、他のプレイヤーの視点からゲームを観察することができます。コード上では、これを非常にシンプルに処理しています。カメラとUIは、SceneContextに割り当てられたObservedAgentに基づいて動作します。ObservedAgentは、ローカルプレイヤーのエージェントまたは観戦しているプレイヤーのエージェントのいずれかです。