Projectiles Essentials
概要
Fusion Projectilesは、ネットワーク化された発射体を実装するための複数の方法を示しています。よりシンプルな概要と学習のために、実装例はProjectiles EssentialsとProjectiles Advancedの2つのプロジェクトに分割されています。
このプロジェクト「Projectiles Essentials」は、このテーマへの入門編として機能します。これは、すべての一般的なアプローチの簡単な例を説明し、与えることを目的としています。すべての例はコメント付きで、独立した構造になっており、有用なコードスニペットを取得し、複雑な関係なしにコアコンセプトを理解することができます。
Projectiles Advanced は、シューティングゲームを作るときによくあるユースケースを解決し、異なる発射体タイプ(跳ねるもの、帰巣するもの、爆発するものなど)のソリューションを提示することによって深く入り込むゲームサンプルとして機能します。
このドキュメントは、Projectiles Essentialsのみを対象としています。Projectiles Advancedについては、別のプロジェクトページを参照してください。
HostMode
トポロジーを使用しています。特徴
- 投射の概念を理解するのに役立つ、コメント付きコードによる5つの例題
- 例1-NetworkRigidbody/NetworkTransformを用いたネットワークオブジェクト
- 例2-発射データを持つネットワークオブジェクト
- [例3-発射回数プロパティ](#example_3__projectile_count_property)
- 例4-発射体データバッファ-ヒットスキャン
- 例5-発射体データバッファ-キネマティック
- 移動にKCCアドオンを使用したシンプルなFPSプレイヤー
- 物理法則に基づき、箱を撃つことができる環境
ダウンロード
バージョン | リリース日 | ダウンロード | ||
---|---|---|---|---|
1.1.6 | Apr 14, 2023 | Fusion Projectiles Essentials 1.1.6 Build 183 |
必要条件
- Unity 2021.3
- Fusion AppId: サンプルを実行するには、まずPhotonEngine DashboardでFusion AppIdを作成し、Real Time Settings(Fusionメニューからアクセス可能)の
App Id Fusion
フィールドにペーストしてください。ゲームの開始セクションの指示に従います。
正しいアプローチを選択する
プロジェクトに入る前に、Fusionで発射体をネットワーク化するための様々なオプションについて説明しましょう。
A)NetworkTransform/NetworkRigidbody を持つ NetworkObject
発射体を NetworkObject
としてスポーンし、NetworkTransform
または NetworkRigidbody
を使用して位置を同期させます。
- ✅ 予測されるスポーンが含まれない場合のシンプルなソリューション
- ✅ インタレストマネジメントは、すぐに機能する
- ✅ 適切な物理演算(PhysX)を使用できる
- ❌
NetworkObject
のインスタンス化のオーバーヘッド - ❌
NetworkTransform
/NetworkRigidbody
は、すべてのトランスフォームの更新をネットワーク経由で送信することを意味します。 - ❌ 必要に応じて予測されるスポーンへの対応
注: 特定の状況において、少数の投射物にのみ適しています。例えば、複雑な物理学の投射物が必要な場合です(例:転がるボール)。それ以外の場合は、このアプローチの使用は避けるべきです。
このアプローチは、例1で紹介されています。
B) 発射データとNetworkObject
発射体をNetworkObject
としてスポーンし、スポーンしたオブジェクトのNetworkBehaviour
でネットワーク上のカスタムデータ(通常は発射ティック、発射位置、発射方向)を使用して、サーバーとクライアントで発射体の全軌跡を計算します。
- ✅ ポジションの更新はバンド幅を必要としない
- ❌
NetworkObject
のインスタンス生成のオーバーヘッド - ❌ 必要に応じて予測されるスポーンへの対応
- ❌ 手動によるインタレストマネージメントの処理
注: 特定の状況下で、少数の発射体にのみ適しています。例えば、射手の寿命が非常に長い発射体(例:Unreal Tournamentの核ロケット)。このアプローチは、非物理的な発射体に対しては通常Aよりも優れていますが、それでも重大な欠点があるため、言及した使用例を除いては、さらなる解決策を優先して避けるべきです。
この方法は、Example 2 で紹介されており、Projectiles Advancedでは、長寿命の発射体に対してこの方法を使用しています(Standalone Projectile 参照)。
C) 発射数カウントのプロパティ
任意の NetworkBehaviour
に格納されている、発射された発射体のカウントのみを同期させます。発射体は単なるビジュアルで、標準的なUnityオブジェクトとしてスポーンされます。Input AuthorityとState Authorityでは正確に発射されますが、プロキシでは補間された位置と回転に基づき発射されます。発射体が低ケイデンスである場合、最後の発射体に関する情報(例:衝突位置)を保存することが可能であり、このソリューションの精度の問題を軽減することができます。
- ✅ シンプルなソリューション
- ✅ ネットワーク経由で転送されるデータ量が最も少ない
- ✅ サーバー上でビジュアルを無視することができる
- ✅ 操作対象(プレイヤー、武器)の一部としてのインタレスト管理
- ✅ 予測スポーンの必要なし
- ❌ ヒットスキャンプロジェクターにのみ適している
- ❌ プロキシについては不正確。プロキシはスナップショットで補間された位置にあるため、発射位置や方向は大きく異なる可能性があります。(この点は、発射速度が速いために最後の発射に関するデータを保存できない場合にのみ適用されます)。
- ❌ 発射体のターゲット位置(命中位置)に関するデータがないため、プロキシでは正確な発射体の経路は不明です。このことは、弾道が不明であるため、いくつかの弾道ビジュアルが壁や他のオブジェクトを通過する可能性があることを意味します。(この点は、発射速度が速いために最後の発射に関するデータを保存できない場合にのみ適用されます)。
注: プロキシの精度やビジュアルをあまり気にしないシンプルなユースケース、例えばシンプルな発射体と短い発射体のビジュアル(軌道)を持つトップダウンシューター、または最後の発射体に対してのみ追加のネットワーク情報を持つことで十分である場合、最大武器ケイデンスは非常に低く、一度に複数の発射体(ショットガン・スタイル)を撃つ必要がない場合に適したソリューションです。通常、発射体データバッファを使用するソリューションの方が、少し複雑さが増しますが、より良い選択肢となります。例4参照。
この方法は、例3で紹介されています。
D) 発射データバッファ
各発射体のカスタムデータを同期させる - NetworkBehaviour
のリングバッファの形でネットワーク配列に格納されます。発射体は、標準的なUnityオブジェクトとしてスポーンされる単なるビジュアルです。最適なバンド幅を使用するためには、発射体のデータセットはスパースであるべきです。例えば、発射体の軌道は発射位置、発射方向、まその他のデータから計算されますが、いくつかの特別な使用例では、常に位置を更新することも可能です。
- ✅ バンド幅の消費が良い
- ✅ ほとんどの発射体の使用例をカバー
- ✅ サーバー上でビジュアルのスポーンを無視することができる
- ✅ 操作するオブジェクト(プレイヤー、武器)の一部としてのインタレスト管理
- ✅ 予測スポーンが必要ない
- ❌ 発射体は、制御するオブジェクトがなければ存在できない=長生きはできない
- ❌ バウンドなどの発射体物理は手動で計算するのみ
注: 非常に長く生きている、または重要な発射体(所有者よりも長生きできる発射体、例えば、所有者がデスポーンまたは切断された場合でもレベル内を移動する必要があるUnreal TournamentのRedeemer発射体)または非常に長く移動する(インタレスト管理のため)を除くほとんどの状況に対して非常に良いソリューションです。
このアプローチは、[例4]で紹介されています。
(#example_4___projectile_data_buffer___hitscan) と 例5 であり、Projectiles Advancedサンプルのコア部分でもあります。
プロジェクト組織
プロジェクトは、5つの小さな独立した例のセットとして構成されています。
/01_NetworkObject | 例1:NetworkRigidbody/NetworkTransformを使用したネットワークオブジェクト |
/02_NetworkObjectFireData | 例2:発射データを持つネットワークオブジェクト |
/03_ProjectileCountProperty | 例3 - 発射回数プロパティ |
/04_ProjectileDataBuffer_Hitscan | 例4 - 発射データバッファ - Hitscan |
/05_ProjectileDataBuffer_Kinematic | 例5 - 発射体データバッファ - Kinematic |
/Common | 全例に共通するプレハブ、スクリプト、マテリアル |
/ThirdParty | サードパーティーのアセット(モデル、エフェクト) |
ゲームの開始
それぞれのサンプルは、独自のシーンファイルを持っており、それを開いて再生することができます。また、すべてのサンプルは、スタートシーン(/Common/Start
)から起動することもできます。
サンプルシーンを開始すると、Fusion標準のNetworkDebugStart
ウィンドウが表示され、ゲームを開始するモードを選択することができます。
マルチピア
Start Host + 1 Client
または Start Host + 2 Clients
を選択すると、マルチピアモードでゲームが開始されます。この方法は、プロキシ上でのプロジェクタイルの挙動をテストするために非常に推奨されます。
ピアを切り替えるには、テンキーで 0
、1
、2
、3
キーを押します。
また、Runner Visibility Controls
ウィンドウ(トップメニュー Fusion/Windows/Runner Visibility Controls
)を使用してピアを切り替えることも可能です。
プロキシ上での撮影の挙動を確認するには、クライアントAのみを可視化し、クライアントBのみを入力プロバイダとして有効にします。これで、クライアントBからの撮影は、クライアントAの視点から観察できるようになりました。
操作方
移動には W
、S
、A
、D
を、射撃には Mouse1
を使用します。
ENTER
キーでカーソルを固定したり離したりすることができます。
例
Projectile Essentialsでは、Choosing The Right Approach セクションで紹介したすべてのアプローチについて、サンプルを提供しています。すべてのスクリプトにはコメントが付けられているので、特定のコードを調査するために自由に探索することができます。
すべての例では、プレイヤーがダミーの箱を撃つことができる同じ物理プレイグラウンドを使用しています。発射時の物理予測をよりよく見せるために、Physics Predictionをオンにしています(NetworkProjectConfig asset / Server Physics Mode set to ClientPrediction )。しかし、物理の予測はパフォーマンスが高いので、物理を中心としたゲームでない限り、この設定はオフにしておくべきです。
例 1 と例 2 は、ネットワークオブジェクトのスポーニングを使用しています。これは、初心者にとって最もわかりやすいアプローチですが、このようなアプローチは、上記のような多くの理由から推奨さ れません(特にスポーン予測による複雑さ)。発射体へのシンプルなアプローチについては、例3を参照してください。
例1:NetworkRigidbody/NetworkTransformを使用したネットワークオブジェクト
例1では、環境内で跳ねたり転がったりする物理的な発射体を示しています。発射体は、独自の動作を持つスポーンされたネットワーク・オブジェクトです。発射体の位置と回転(およびその他の物理プロパティ)は、NetworkRigidbody
によって同期されます。
このような発射体は、NetworkRunner
でスポーンして、カスタム発射体スクリプトでFire
を呼び出すだけで、簡単に発射することができます:
C#
var projectile = Runner.Spawn(projectilePrefab, fireTransform.position, fireTransform.rotation, Object.InputAuthority);
projectile.Fire(fireTransform.forward * 10f);
Fire
メソッドは、物理的な投射物にインパルスを加えることができます:ss
C#
public void Fire(Vector3 impulse)
{
_rigidbody.AddForce(impulse, ForceMode.Impulse);
}
しかし、この単純なアプローチには欠点があります。発射体のスポーンは予測されないので、ネットワークの状態が理想的でない場合、発射入力後に発射体が実際に発射されるまでにかなりの間が空くことになります。この問題を軽減する通常の方法は、 スポーン予測 を使用することです。
スポーン予測は例2で紹介されています。しかし、この例のような物理的な発射体の場合、予測的にスポーンされたオブジェクト(例えば発射体のダミー)とシーンにすでに登録されているネットワークオブジェクトとの間の複雑な物理的相互作用のために、スポーン予測は推奨されません。最もシンプルな解決策は、事前にいくつかの発射体を生成してバッファに保存し、発射入力後にバッファから既に完全に生成された発射体を使用することです。このようなアプローチは、例1の一部で紹介されており、Predicted
サブフォルダをチェックしてください。この解決策はより高度なものなので、他の例を見てからこの方法に戻ることをお勧めします。
例2:Fireデータを持つネットワークオブジェクト
例2は、時間的に移動するキネマティック発射体を示します。発射体はまだスポーンされたネットワークオブジェクトですが、その位置と回転は、ネットワーク上で毎ティック位置/回転を同期させるのではなく、いくつかの初期発射データに基づいて各クライアント上で計算されます。
*注:ヒットスキャン発射体とキネマティック発射体の違いは、Projectiles Advancedサンプルの Projectiles Types セクションで説明しています。
発射は、前の例と同様です:
C#
var projectile = Runner.Spawn(projectilePrefab, fireTransform.position, fireTransform.rotation, Object.InputAuthority);
projectile.Fire(fireTransform.position, fireTransform.forward * 10f);
public void Fire(Vector3 position, Vector3 velocity)
{
// Save fire data
_fireTick = Runner.Tick;
_firePosition = position;
_fireVelocity = velocity;
}
しかし、発射体の動きは、初期パラメータから計算されます:
C#
[Networked]
private int _fireTick { get; set; }
[Networked]
private Vector3 _firePosition { get; set; }
[Networked]
private Vector3 _fireVelocity { get; set; }
// Same method can be used both for FUN and Render calls
private Vector3 GetMovePosition(float currentTick)
{
float time = (currentTick - _fireTick) * Runner.DeltaTime;
if (time <= 0f)
return _firePosition;
return _firePosition + _fireVelocity * time;
}
通常は、Render
呼び出しの際に、実際の発射体トランスフォームを移動させるだけで十分です。FixedUpdateNetwork
では、前と次の位置を計算して、レイキャストを発射し、潜在的な衝突を解決することができます。
C#
public override void FixedUpdateNetwork()
{
if (IsProxy == true)
return;
// Previous and next position is calculated based on the initial parameters.
// There is no point in actually moving the object in FUN.
var previousPosition = GetMovePosition(Runner.Tick - 1);
var nextPosition = GetMovePosition(Runner.Tick);
var direction = nextPosition - previousPosition;
if (Runner.LagCompensation.Raycast(previousPosition, direction, direction.magnitude, Object.InputAuthority,
out var hit, _hitMask, HitOptions.IncludePhysX | HitOptions.IgnoreInputAuthority))
{
// Resolve collision
}
}
ネットワークオブジェクトのスポーンに関わるため、スポーン予測を使用する必要があります。異なるネットワーク条件下で最高の射撃体験を得るためには、Spawn Prediction を使用する必要があります。スポーン予測を有効にするには、予測キーで Runner.Spawn
メソッドを呼び出します:
C#
var key = new NetworkObjectPredictionKey()
{
Byte0 = (byte)Runner.Tick, // Low number part is enough
Byte1 = (byte)Object.InputAuthority.RawEncoded,
};
Runner.Spawn(projectilePrefab, fireTransform.position, fireTransform.rotation,
Object.InputAuthority, predictionKey: key);
予測スポーンされたオブジェクトは、基本的に標準的な NetworkBehaviour
呼び出しを模倣した IPredictedSpawnBehaviour
インターフェイスを実装する必要があります。この例では、予測スポーンに同じオブジェクトを使用しています。つまり、ネットワークプロパティへのすべてのアクセスは、ローカルフィールドによってバックアップされる必要があります。オブジェクトがネットワーク状態でないときは、ネットワークプロパティにアクセスできないからです。この方法は、ネットワーク・プロパティが1つのネットワーク構造体の一部である場合、よりシンプルになります:
C#
public struct FireData : INetworkStruct
{
public int FireTick;
public Vector3 FirePosition;
public Vector3 FireVelocity;
}
Example 3 - 例3 - 発射カウントプロパティ
例3は、武器自体に直接発射体カウントだけを同期させるという非常に効率的なアプローチを示しています。このアプローチは、発射体のヒットスキャンを表しています。発射体のヒットは、発射時に即座に評価されます。これは、命中効果(ダメージや物理インパルスなど)がターゲットに即座に適用さ れますが、通常、多空中を短時間移動するダミー射的のビジュアルをスポーンするのが適切です。この例ではダミー発射体を使用しています(Common/Scripts/DummyFlyingProjectile.cs
を参照)。
C#
public class Weapon : NetworkBehaviour
{
[SerializeField]
private LayerMask _hitMask;
[SerializeField]
private Transform _fireTransform;
[Networked]
private int _fireCount { get; set; }
private int _visibleFireCount;
public void FireProjectile()
{
// Whole projectile path and effects are immediately processed (= hitscan projectile)
if (Runner.LagCompensation.Raycast(_fireTransform.position, _fireTransform.forward,
100f, Object.InputAuthority, out var hit, _hitMask))
{
// Resolve collision
}
_fireCount++;
}
public override void Spawned()
{
_visibleFireCount = _fireCount;
}
public override void Render()
{
if (_visibleFireCount < _fireCount)
{
// Show fire effect
}
_visibleFireCount = _fireCount;
}
}
このアプローチは非常にシンプルで効率的であり、予測もすぐに機能します(ネットワークオブジェクトのスポーンがないため、スポーン予測に対処する必要はありません)。Choosing the Right Approachの章で述べたように、このアプローチにはいくつかの欠点がありますが、発射体データバッファを使用することで簡単に軽減することができます(例4)。
例4 - 発射データバッファ - Hitscan
例4は、発射体データバッファの使用法を示しています。ヒットスキャンバージョンのバッファは、シンプルさを保ちながら、前の例の発射体数プロパティからステップアップしています。
Projectile Data Bufferアプローチでは、円形バッファとして機能する ProjectileData
構造体の固定配列が使用されます。これは、発射時にInput/Stateオーソリティが現在のバッファヘッド(fireCount
プロパティ)に基づいたデータでバッファを満たし、すべてのクライアントがローカルヘッド(visibleFireCount
)に基づいたRenderコールで視覚的に追い付くことを意味しています。ProjectileData
には、すべてのクライアントの発射物を再構築するために必要なすべてのデータが含まれています。
C#
public class Weapon : NetworkBehaviour
{
[SerializeField]
private LayerMask _hitMask;
[SerializeField]
private Transform _fireTransform;
[Networked]
private int _fireCount { get; set; }
[Networked, Capacity(32)]
private NetworkArray<ProjectileData> _projectileData { get; }
private int _visibleFireCount;
public void FireProjectile()
{
var hitPosition = Vector3.zero;
var hitOptions = HitOptions.IncludePhysX | HitOptions.IgnoreInputAuthority;
// Whole projectile path and effects are immediately processed (= hitscan projectile)
if (Runner.LagCompensation.Raycast(_fireTransform.position, _fireTransform.forward,
100f, Object.InputAuthority, out var hit, _hitMask))
{
// Resolve collision
hitPosition = hit.Point;
}
_projectileData.Set(_fireCount % _projectileData.Length, new ProjectileData()
{
HitPosition = hitPosition,
});
_fireCount++;
}
public override void Spawned()
{
_visibleFireCount = _fireCount;
}
public override void Render()
{
if (_visibleFireCount < _fireCount)
{
// Play fire effects (e.g. fire sound, muzzle particle)
}
for (int i = _visibleFireCount; i < _fireCount; i++)
{
var data = _projectileData[i % _projectileData.Length];
// Show projectile visuals (e.g. spawn dummy flying projectile or trail
// from fireTransform to data.HitPosition or spawn impact effect on data.HitPosition)
}
_visibleFireCount = _fireCount;
}
private struct ProjectileData : INetworkStruct
{
public Vector3 HitPosition;
}
}
大量の発射体を発射する場合でも良いバンド幅を得るために、ProjectileData
構造体をできるだけ小さくして、(可能であれば)時間的に変化しないデータを使用することをお勧めします。
この方法は非常にシンプルで効率的かつ柔軟性があり、多くのゲームに推奨されます。ゲーム内でキネマティック発射体が必要な場合は、もう少し複雑なバージョンのバッファを使用する必要があります - 例 5 に進んでください。
例5 - Projectile Data Buffer - Kinematic
ゲーム中の発射体は、何かにぶつかったり、消滅したりするまでの間、環境中を移動することが多いです。このような発射体をKinematic Projectiles、または単にProjectilesと呼びます。発射体データバッファソリューションは、Input/State権限で発射体の状態に基づいて発射体データを更新し、Renderですべてのクライアントの発射体の視覚表現のスポーンおよび更新を処理するように調整する必要があります。
C#
private struct ProjectileData : INetworkStruct
{
public int FireTick;
public int FinishTick;
public Vector3 FirePosition;
public Vector3 FireVelocity;
public Vector3 HitPosition;
}
また、ProjectileData
構造体はできるだけ小さくし、データをあまり変化させないようにすることが望ましいです。この例では、1つの発射体に対して2つの発射体データが設定されます。発射されたとき(FirePosition
、FireVelocity
、FireTick
)と、環境と衝突したとき(HitPosition
、FinishTick
)に設定されます。特殊なケースでは、より頻繁にデータを更新することももちろん可能です - Projectiles Advanced の Homing Projectiles を参照してください。
Projectiles Timingについて
*注:FusionのPredictionの仕組みを最初に理解するようにしてください。
Fusionでは、主に2つのタイムフレームを認識しています。ローカルタイムフレームは、ローカルプレイヤーとローカルオブジェクトの時間です。Remoteタイムフレームは、リモートプレイヤーやリモートオブジェクトの時間です。ホスト/サーバー上のすべてのオブジェクトは、常にLocalタイムフレームでシミュレーションおよびレンダリングされます。クライアントは両方の時間枠でオブジェクトをレンダリングします。通常、Localはローカルオブジェクトに、Remoteはリモートオブジェクトに使用されます。シミュレーションの場合、プロキシオブジェクト(他のプレイヤーの投射物)がLocal時間枠でレンダリングされ、オーバーシュートを避けるために衝突やその他の相互作用の可能性に関するデータが必要な場合を除き、クライアントは通常ローカルオブジェクトのみをシミュレーションします(= FixedUpdateNetwork
はプロキシには実行されません)。
実際は、隣り合った2人のプレイヤーA、Bが同じタイミングで発射することになります。プレイヤーAから見て発射体Aはすぐに発射されますが、プレイヤーAは発射体Bに関する情報をまだ持っていません。この情報はプレイヤーBからサーバーに送られ、処理されてプレイヤーAに送られます。この情報が届いたとき、プレイヤーAはすでに数ティック先(=最後に判明したサーバーの状態から未来を予測すること)、発射体Aのレンダリングも先になっています。例えば、発射体AはプレイヤーAからすでに3メートル離れているとします。発射体Bのレンダリング方法について、2つの選択肢があります。ローカル時間軸で、プレイヤーBから3メートルの位置にあるべき発射体を「正しく」レンダリングすることもできますが(プレイヤーBから3メートル)、これではプレイヤーBから3メートルの位置に発射体の映像が突然現れることになります。このような動作は通常好ましくないので、発射体BをRemote時間枠でレンダリングすることにします。つまり、発射体BはプレイヤーBの武器から直接発射されますが、発射体が同じティックで発射されたにもかかわらず、プレイヤーAの発射体から3メートル遅れてレンダリングされます。このため、本サンプルの関連するすべての例では、発射体のレンダリング時間が次のように計算されています:
C#
float renderTime = Object.IsProxy ? Runner.InterpolationRenderTime : Runner.SimulationRenderTime;
上級者用の発射タイミングノート
*複雑なタイミングについて興味がある方のみお読みください。ただ発射物を撃つだけなら、このトピックを詳しく理解する必要はありません。
レンダリング(というより Render
呼び出しから値を読み取ること)に関しては、技術的にいくつかの時間枠が存在します:
- ローカル
- = 直前のフォワードティックからの値
- レンダリング時の値: Input/State権限でネットワークプロパティを読み込む
- レンダリングの時間:
Runner.Tick * Runner.DeltaTime
またはRunner.SimulationTime
- ローカル補間
- = 直近の2つのフォワードティック間で補間された値
- レンダーの中の値: 入力/状態権限でInterpolatorの値を読み込む。
- レンダリング時間:
(Runner.Tick - 1 + Runner.StateAlpha) * Runner.DeltaTime
またはRunner.SimulationRenderTime
またはRunner.InterpolationRenderTime
(サーバー(状態権限)で呼び出された場合。
- ローカル外挿
- = 直近のフォワードティックから外挿された値
- レンダーの中の値: Input/State権限でネットワークプロパティを読み込み、レンダリング部分の値をローカルで計算する。
- レンダリングにかかる時間:
(Runner.Tick + Runner.StateAlpha) * Runner.DeltaTime
またはRunner.SimulationRenderTime + Runner.DeltaTime
- リモート
- = 最新のサーバーのティックからの値(=>最後の受信ティック)
- レンダリング時の値: プロキシでネットワークプロパティを読み込む(再シミュレーション中にプロキシでプロパティが変更されない場合)
- レンダリングの時間:
Runner.Simulation.LatestServerState.Tick * Runner.DeltaTime
またはRunner.Simulation.LatestServerState.Time
- リモート補間
- = シミュレーションの
InterpFrom
とInterpTo
の間で補間された値(最新のサーバーのティックが補間に使われる前に安全ウィンドウが必要であるため、InterpFrom
とInterpTo
は最新のサーバーティックよりもさらに過去になります) - レンダーの中の値: プロキシでInterpolatorの値を読む
- レンダリングの時間:
Runner.InterpolationRenderTime
または(Runner.Simulation.InterpFrom.Tick + (Runner.Simulation.InterpTo.Tick - Runner.Simulation.InterpFrom.Tick) * Runner.Simulation.InterpAlpha) * Runner.DeltaTime
- = シミュレーションの
このようなタイムフレームの定義を見ると、例 5 の発射体は、Input/State 権限では Local Interpolated タイムフレームで、Proxy では Remote Interpolated タイムフレームで表示されます。しかし、これを正確に行うには、fireCount
とprojectileData
プロパティをそれぞれの補間子から読み込む必要があります。 これは簡素化のために省略されています - 私たちはRemote Interpolated time but Remote values(およびLocal Interpolated time but Local values)を使用しています。そのため、プロキシでは射撃が技術的に必要な時間よりも若干早く行われます。この差は複雑さを正当化するものではありません。完全な補間例については、Projectiles Advanced を確認してください。