13. 推測航法機能

13.1. 推測航法の概要

推測航法機能(Dead Reckoning)とは、他のプレイヤーの動き(位置や向きなど)を自然に見えるように補間する機能です。 アプリケーションは、補間方法や更新頻度などを指定するだけで、システムが自動的に必要な送受信処理、補間処理を行います。 推測航法機能による補間は、DDLに extrapolation_filter が設定がされたデータセットに対して行われます。

NetZ では、カスタマイズされたアルゴリズムを実装して、容易にデータセット値を推定できます。 アルゴリズムの使用は簡単で、時間の経過による変数値の変化に関する事前知識は必要ありません。 推測航法は、レイテンシをマスクする優れた方法で、この章では推測航法ベースのレイテンシマスク技術をいくつか説明します。 また、推測航法は帯域幅の使用を大幅に削減します。 使用可能な帯域幅を効率的に使用するため、推定の精度は一定であるか、 この章で後述するオブジェクトとユーザー定義オブザーバー間の距離を基にするかのどちらかになります。 また、NetZ で用意されている推測航法スキームが要件に合わない場合、 18.5. カスタム推測航法 に説明されているものと同じ名前の拡張を使用して独自のカスタム推測航法スキームを実装できます。

各ステーションのセッションクロックが同期されていないと、予期しない推測航法動作になる可能性が高くなります。 そのため、実装の微調整を開始する前に、セッションクロックが同期されていることを確認し(画面のセッションクロックを表示すると簡単です)、 同期されていない場合は 7.3. セッションクロック に説明されている適切なセッションクロックパラメータを調整します。

推測航法は拡張として実装されるので、次のように PHBDR.h ファイルをインクルードする必要があります。

Code 13.1 PHBDR.h のインクルード
#include<NetZ/PHBDR.h>

13.1.1. レイテンシマスクが実装されていない場合

推測航法やその他のレイテンシマスク技術が実装されていない場合、更新にはステーション間を移動するためにある程度の時間がかかるため、 その時間内の任意の地点ではデュプリカがその DO マスターと同期されません。 これは、デュプリカがイベントを受信する際、その時点でイベントが有効であると仮定して処理するのに対し、実際にはイベントは送信された時点で有効であったためです。 そのため、 Figure 13.1 に示すように、 デュプリカは正確に示されていますが時間差があり、 DO マスターとデュプリカは同期されていません。 ゲームに低精度の同期への耐性がある場合、これは問題ではありません。 ただし、そうでない場合は、この章で説明する推測航法ベースのレイテンシマスク技術のいずれかを適用する必要があります。

../_images/Fig_Dead_Reckoning_EventTimeline_Without_LatencyMask.png

Figure 13.1 レイテンシのマスクが行われていない場合のイベントのタイムライン

13.1.2. レイテンシマスクの紹介

NetZ では、レイテンシのマスクのためにさまざまな対策を講じることができます。 デュプリカ側では、イベントのタイムスタンプに応じてイベントを処理することが選択できます。 DO マスター側では、先読みとループバックのメカニズムを適用できます。これらの技術は、ゲームに合う方法で複合したり、組み合わせたりでき、実行時に調整することも可能です。 また、 DO マスターとデュプリカ間の完全な時刻同期は必要ない場合が多く、プレイヤーに矛盾を感じさせない程度のレイテンシのマスクには一部同期でも十分な場合がほとんどです。 例えば、300 ms ほどのレイテンシが通常あるとすると、300 ms、場合によっては 200 ms にデュプリカのデータセットを推定すると、予測が非常に低下します。 イベントを良好に同期するには、100 ms の DO マスターに先読みを適用し、デュプリカ推測航法には 100 ms の推定遅延を適用できます。 予測の実行時間が短くなると、ステーションが 300 ms も同期から外れることなく、100 ms 以内で同期されるようになるため、より正確に予測され、 ほとんどのゲームではプレイヤーは非同期に気が付きません。

デュプリカがイベントの更新を受信する際、メッセージのタイムスタンプがデュプリカのメッセージの処理に影響します。 タイムスタンプはメッセージデータが有効な時刻を示します。 これが現在の時刻である場合、デュプリカはイベントを受信次第処理します。 タイムスタンプが過去または将来である場合、デュプリカはデータの処理方法を選択できます。 この章で後述するとおり、先読みが適用されている場合、将来のタイムスタンプが有効になる場合があります。 Figure 13.2 a に示すとおり、メッセージのタイムスタンプがイベントが過去に発生していることを示す場合、 デュプリカは (a) タイムスタンプを無視してデータが現時点において有効であると仮定するか、(b) イベントが過去の特定の時刻に発生したことから、 現時点での動作を予測することができます。 Figure 13.2 b に示すとおり、 タイムスタンプがイベントが将来に発生することを示す場合、デュプリカは (a) 将来に起こる動作を基に、 現時点でのデュプリカの動作を内挿するか、(b) タイムスタンプに示されている時間までイベントの処理を待機することができます。

../_images/Fig_Dead_Reckoning_EventTimeline_KeepTo_Timestamp.png

Figure 13.2 タイムスタンプに従ってデュプリカがイベントを処理する方法のオプション

データの推定または内挿が実行される状況は、一般に推測航法と呼ばれます。 推測航法が適用されると、デュプリカの動作は、通常は過去に発生したこと、または将来に発生することを把握しているデータを基に現在の状況を予測しますが、そうではない場合もあります。 デュプリカのメッセージのタイムスタンプが DO マスターのタイムスタンプと同じ場合、推測航法を使用すると、 DO マスターとそのデュプリカ間の時間差を回避できますが、この同期はデュプリカの精度を犠牲にします。 デュプリカの精度を向上させるには、タイムスタンプを変更して、データを推定する間隔を短くします。 ただしこれを行うと、同期への悪影響が生じます。

メッセージの元のタイムスタンプは DO セッションマスターから指定されるものですが、デュプリカステーションはこれを過去、将来、現在のいずれかに変更する場合があります。 タイムスタンプが変更される場合、これは通常遅延を生じさせ、デュプリカの推定の精度を上げるために推測航法を適用する場合に起こります。 この場合、デュプリカはメッセージの元のタイムスタンプを使用しないで、タイムスタンプに一定の時間(推定遅延)を追加します。 そのため、データがより短い間隔で推定されるために推定はより正確になりますが、デュプリカと DO マスターはその遅延分だけずれます。 推定遅延については 13.4.2. を参照してください。 タイムスタンプを変更するかどうかの決定は、当然適用されるデータと、精度の程度と必要な同期によって異なります。 通常、衝突などのオブジェクトの対話性は正確に同期されるようにする必要がありますが、プレイヤーは背景のアバターが同期されていないことにはほとんど気が付きません。 デュプリカが特定のタイムスタンプに処理できないデータを受信すると、イベントが受信時に有効であることを基にイベントを処理するか、 パケットを破棄してイベントを処理しないかのいずれかが選択できます。 当然、これはゲームに合わせて選択します。

13.1.3. 帯域幅の削減について

レイテンシのマスクを援助するだけでなく、推測航法は帯域幅の使用を削減できます。 推測航法を実装すると、NetZ で自動的に実行され、デュプリカのデータセットを更新するときが決定されます。 頻繁に変更するデータセットには、 DO マスターのデータセットが更新されるたびにデュプリカのデータセットを更新すると、貴重な帯域幅を使い果たしてしまいます。 そのため、推定フィルタを使用してデュプリカのデータセットを更新し、 DO マスターのデータセット値とそのデュプリカ間の最大エラーを設定することを推奨します。 このエラーは推測航法の推定エラーのことで、エラー許容を超えると、デュプリカのデータセットが DO マスターから更新されます。

※推測航法を使用する場合に、エラー許容が定義されない場合、エラー許容にはデフォルト値の 0 が使用されます。

推定モデルとエラー演算は、 DO マスターのあるステーションで推定フィルタを使用するオブジェクトの各デュプリカに実行されます。 そのため、 DO マスターのステーションは複数の推定モデルを管理し、複数のエラー演算を実行する必要があり、推測航法によって得た帯域幅使用の削減の代わりに CPU とメモリの使用量が増加します。 それでも、これらの演算は比較的効率的なので、パフォーマンスにはあまり影響はありません。

13.1.4. 推測航法を適用できるデータセットについて

推測航法は、連続性の切断が時折含まれることもある連続的なデータセットのみに使用できます。

連続性の切断についてはこの章で後述します。 推測航法を実装するには、次の 3 つの基本操作を行う必要があります。

  • 15.1. に説明されているとおり、DDL でのデータセットの定義では、extrapolation_filter が使用されていることを示す必要があります。
  • この章で後述するとおり、推定の精度は、ErrorToleranceFunction::SetParameters() メソッド、または適切なラッパーのいずれかを使用して定義する必要があります。
  • 後述のとおり、データセット変数は、 DO マスター上で更新され、すべての関連デュプリカ上でリフレッシュされる必要があります。

13.1.5. データセットの伝達と反映について

DO マスターのデータセットの内容をそのデュプリカに伝達するには DuplicatedObject::Update() メソッドを使用し、 バッファされているデータセットのデュプリカのデータセット値をリフレッシュするには DuplicatedObject::Refresh() を使用します。 これらのメソッドは、すべてのゲームで定期的に呼び出される必要があります。 例えば、これらのメソッドは、次のように DoPhysics メソッドと Render メソッドを使用して呼び出すことができます。

Code 13.2 データセットのデュプリカへの伝達とデュプリカにあるデータ値のリフレッシュ
void Sphere :: DoPhysics()
{
    // compute the physics and then update the duplication
    // master’s position
    Update(m_dsPos);
}

void Sphere :: Render(unsigned long ulDummy)
{
    if (IsADuplica())
    {
        // Refresh the position
        Refresh(m_dsPos);
    }
    // render the object at the correct position
}

このコードが実行されると、DuplicatedObject::Update() メソッドは、現在の推定エラーがエラー許容関数で定義されているものを超えるかどうかを確認します。 エラーが許容を超えると、推定モデルを修正するためにメッセージが送信されます。 デュプリカ側では、NetZ で生成されたデータセットの推定モデルが使用され、必要に応じてデータセット変数を更新します。 DuplicatedObject::Update() メソッドと DuplicatedObject::Refresh() メソッドは同期する必要はありません。 デュプリカが DO マスターより早く実行される場合、NetZ はデータセットの値を自動的に内挿します。

13.1.6. 更新遅延のカスタマイズ

推定されているデータセットが信頼性のない通信を使用すると、メッセージの損失によってデータセットの値が大きく変化する場合があります。 そのため、NetZ では、DataSet::SetMaximumUpdateDelay() メソッドを使用して最大更新遅延を設定できるようにしています。 これにより、ローカルの予測モデルが正しくても、メッセージはリモートステーションに定期的に送信されます。 信頼性のある通信が使用される場合、最大更新遅延には効果がありません。 最大更新遅延は、データセットの更新間に許可されている最長時間です。 データセットが最大遅延時間内に更新されないと、前回の更新以降データセットの値が変更されていなくても、 データセットの更新がリモートステーションに自動的に送信されます。 最大更新遅延のデフォルト値は DATASET_UPDATE_DELAY::DEFAULT_MAXIMUM_UPDATE_DELAY(2000) ms です。 DataSet::GetMaximumUpdateDelay() は現在の最大更新遅延を返します。 単一のデータセットクラスのすべてのインスタンスは、同じ最大更新遅延を共有しますが、異なるクラスのデータセットは異なる最大更新遅延を持つ場合があります。

反対に、推定データセットに定義されたエラー許容が低い場合、データセットは多量の帯域幅を使用して過度に頻繁に更新されることがあります。 そのため、NetZ では、DataSet::SetMinimumUpdateDelay() を使用して最小更新遅延を設定できるようにしています。 これにより、開発者はデータセットが更新される最大頻度を制御できます。 最小更新遅延は、データセットの更新間に許可されている最短時間です。 データセットは、最小更新遅延が過ぎるまでは更新されません。 DuplicatedObject::Update() が最小更新遅延より頻繁に呼び出される場合、データセットの更新が停止されます。

ただし、DataSet::IndicateContinuityBreak() で連続性の切断が指定されている場合、 最小更新遅延は次の数回の更新では無視され、NetZ でデータセット値を正確に推定できるようにします。 DEFAULT_MINIMUM_UPDATE_DELAY は、1 秒当たり約 6 回の更新に相当する 150 ms の最小更新遅延の初期値を指定します。 DataSet::GetMinimumUpdateDelay() は現在の最小更新遅延を返します。 単一データセットクラスのすべてのインスタンスは、同じ最小更新遅延を共有しますが、異なるクラスのデータセットは異なる最小更新遅延を持つ場合があります。

13.1.7. データセットが使用するチャネルの信頼性

推定フィルタを使用してデータセットを更新する場合、更新は DDL のデータセットの更新ポリシーに従って、 信頼性のあるチャンネルまたは信頼性のないチャンネルに送信されます。 デフォルトでは、更新は信頼性のあるチャンネルに送信されますが、unreliable DDL プロパティが指定されている場合は、 常に信頼性のないチャンネルを使用するのではなく、NetZ が必要に応じて信頼性のないチャンネルでのメッセージ送信と 信頼性のあるチャンネルでのメッセージ送信間を自動的に切り替えます。 これにより、メッセージはデュプリカの推定モデルが正しい場合には送信されないので、 NetZ は DO マスターとそのデュプリカ間で送信されるメッセージ数を削減することができます。 DO マスターの位置が変わり、推定エラーを超えると、更新が信頼性のないチャンネルで送信されます。 ただし、 DO マスターが停止した場合や、直線上を移動した場合など、推定エラーを超えない場合は、更新は信頼性のあるチャンネルに送信されます。 デュプリカが信頼性のある更新を 3 回連続で受信すると、推定エラーが次回許容を超えるまでは、それ以上の更新は DO マスターからそのデュプリカへは送信されません。 この間、デュプリカは、その位置の更新のために信頼性のあるチャンネルで送信された 3 つのポイントから計算された推定モデルを使用します。

13.2. 一定推定エラー

推測航法に一定推定エラーが選択されると、推定エラーはオブザーバーとオブジェクト間の距離に関係なく変化しません。 そのため、オブザーバーとオブジェクトの相対位置に関係なく、エラーが許容を超えると、オブジェクトに送信される位置更新の数と使用する帯域幅の量は同じに維持されます。

推測航法を実装するには、推定フィルタを使用してデータセットの値を更新する必要があります。 これは、 15.1. に説明するとおり、DDL で定義する必要があります。

13.2.1. 現行推定エラー

現行推定エラーは、データセットの現在値と推定されている値の違いとして計算されます。 エラーは次の方程式で計算され、xi 値は同じデータセットに含まれる異なる変数値で、プライム符号は変数値が推定されていることを示します。

../_images/Fig_Dead_Reckoning_Eq_Extrapolation_Error.png

推定エラーの計算では、エラーの最終値は、データセットの各変数に平等に依存します。 そのため、1 つの変数が推定エラーに支配的な影響を持つのを回避するため、データセットが一定であることを保証し、データセットの各変数が同じ範囲の値を持つようにします。

13.2.2. エラー許容

推測航法エラー許容関数は、次の方程式で計算されます。

\(エラー許容 = dConstantFactor + dLinearFactor * z + dQuadraticFactor * z2\)

ここでは、dConstantFactor、dLinearFactor、dQuadraticFactor は、ErrorToleranceFunction::SetParameters() で定義される、エラー許容関数のパラメータです。 z はオブザーバーとオブジェクト間の距離です。

一定推定エラーを使用する場合、一次係数(dLinearFactor)と二次係数(dQuadraticFactor)は 0 に設定する必要があります。 そのため、エラー許容は距離には依存しないので、距離を定義する必要はありません。

ErrorToleranceFunction::SetParameters() メソッドの構文は次のとおりです。

Code 13.3 SetParameters メソッドの構文
ErrorToleranceFunction::SetParameters(
                                qDouble dConstantFactor,
                                qDouble dLinearFactor,
                                qDouble dQuadraticFactor,
                                qDouble dMaximumError
                                )

ErrorToleranceFunction::SetParameters() メソッドを使用しないで一定エラーを簡単に設定する方法は、 次のように ErrorToleranceFunction::SetConstantError() メソッドを呼び出す方法です。

Code 13.4 SetConstantError メソッドを呼び出す方法
ErrorToleranceFunction::SetConstantError(qDouble dError)

ErrorToleranceFunction::SetConstantError() メソッドは、ErrorToleranceFunction::SetParameters() メソッドを呼び出し、次になります。

Code 13.5 SetConstantError メソッドを呼び出したときの SetParameters メソッド
ErrorToleranceFunction::SetParameters(qDouble dError, 0, 0)

エラー許容の設定の目安としてオブジェクトのサイズの約 5 ~ 10% の値から始め、推測航法の動作が最適化されるまで値を微調整します。

13.3. 距離ベースの推定エラー

距離ベースの推測航法を使用する場合、許容されるエラーはオブザーバーとオブジェクト間の距離によって変化します。 推定の精度はオブザーバーとオブジェクト間の距離に応じて動的に調整され、帯域幅を有効活用することができます。

推定エラーは、オブジェクトの現在のデータセット値( DO マスター)と推定されたデータセット値(デュプリカ)間の相違として定義されます。

オブザーバーは Station::RegisterObserver() を使用して、任意の複製オブジェクトを登録することができます。 ここで登録した複製オブジェクトを距離計算で使用することができます。 オブザーバーは必要に応じて再定義でき、オブザーバーが定義されていない場合、NetZ はデフォルトの距離を使用してエラー許容を計算します。

距離ベースの推測航法を利用する場合、エラー許容はオブザーバーとオブジェクト間の距離の定数または関数によって異なります。 それぞれの特徴は以下のとおりです。

  • オブザーバから距離が近いオブジェクト
    • 許容されるエラーが小さくなる。
    • データセットの推定が正確になる。
    • 帯域幅の大部分がオブザーバーに近いオブジェクトに使用される(パケットが多い)。
  • オブザーバから距離が遠いオブジェクト
    • 許容されるエラーが大きくなる。
    • データセットの推定精度が低くなる。
    • 使用帯域幅は少なくなる(パケットが少ない)。

NetZ オブザーバからのオブジェクトの距離を自動では計算しません。 距離ベースの推測航法を利用する場合には、この章で後述する DuplicatedObject::ComputeDistance() システムコールバックを開発者が実装する必要があります。 このコールバックの実装については、この章で後述します。

距離ベースの推測航法がデータセットに使用される場合、データセットを持つ DO マスターで DuplicatedObject::Update() メソッドが呼び出されるたびに、 NetZ でエラー許容が計算され、データセットの推定エラーが許容範囲内かどうかを確認します。 アルゴリズムの一般コンセプトは、現在の推定エラーとエラー許容の 2 つの値を比較することです。 現在の推定エラーがエラー許容より大きい場合、DuplicatedObject::Update() メソッドがすべての DO データセットの内容をそのデュプリカにリフレッシュします。 推定エラーのほうが小さい場合は、データセットは更新されません。

NetZ はこの演算を効率よく実行するために最適化されています。 各ステーションのオブザーバーとオブジェクト間の距離は異なる場合があるため、データセットの更新は、 異なる頻度でさまざまなリモートステーションに送信される場合があります。

13.3.1. 現行推定エラー

13.2.1. に説明するとおり、現行推定エラーは、データセットの現在値と推定されている値の違いとして計算されます。

13.3.2. エラー許容

エラー許容は、オブザーバーとオブジェクト間の距離を使って計算されます。 そのため、 ErrorToleranceFunction::SetParameters() で設定したパラメータと、 オブザーバーとオブジェクト間の距離に応じて許容されるエラーが変化します。

例えば、エラー許容関数は、オブザーバーから遠く離れて表示されるオブジェクト、 つまりシーンの背景にあるオブジェクトにはより大きなエラーが許容され、オブザーバーに近いオブジェクト、 つまり前景にあるオブジェクトにはより小さいエラーが許容されるように設定できます。

エラー許容は、2 つの手順で計算されます。

最初に、複製オブジェクトが更新されるとき、その複製オブジェクトとステーションに登録された複製オブジェクト(オブザーバー)との距離が計算されます。 距離計算では、 DuplicatedObject::ComputeDistance() システムコールバックが呼ばれます。

補足

DuplicatedObject::ComputeDistance() システムコールバックは、 オブザーバーとして使用される可能性のあるアプリケーション側で定義した各 DO クラスでオーバーライドする必要があります。 このメソッドによって返された値が UNKNOWN_DISTANCE の場合、デフォルトの距離が使用されます。

このデフォルトの距離は ErrorToleranceFunction::SetDefaultDistance() にて設定できます。 NetZ は2 つの DO 間の距離が不明の場合、またはオブザーバーが更新されるステーションで定義されていない場合に UNKNOWN_DISTANCE を使用します。 デフォルトの距離が定義されていない場合、NetZ は 0 の値を使用するので、エラー許容は一定になります。

次に、 複製オブジェクト間の距離を利用して、次の方程式でエラー許容が計算されます。

\(エラー許容 = dConstantFactor + dLinearFactor * z + dQuadraticFactor * z2\)

ここでは、 \(dConstantFactor\)\(dLinearFactor\)\(dQuadraticFactor\) は、 ErrorToleranceFunction::SetParameters() メソッドを使用して定義される、エラー許容関数のパラメータです。 \(z\) は更新されるオブジェクトとオブザーバー間の距離です。

エラー許容は ErrorToleranceFunction::SetParameters() メソッドを使用して設定できますが、より簡単に設定するために以下のメソッドも用意されています。

許容エラーの最大値は、 ErrorToleranceFunction::SetLinearError() 、 ErrorToleranceFunction::SetQuadraticError() では必ず指定してください。 無限の最大エラーを指定するには、 ErrorToleranceFunction::SetParameters() メソッドを使用してエラー許容を記述する必要があります。 同様に、距離に一次従属と二次従属の両方を示すエラー許容を定義するには、 ErrorToleranceFunction::SetParameters() メソッドを使用して dLinearFactor 変数と dQuadraticFactor 変数の両方に値を定義する必要があります。

補足

ErrorToleranceFunction 変数の設定の目安として、最大推定エラーをオブジェクトとほぼ同じサイズにします。 最大値に到達する際の距離は、オブジェクト間の距離を計算する測定基準によって異なります。 通常、水平線の近くに表示されるオブジェクトとの距離です。 この値を、推測航法の動作に満足するまで微調整します。

13.3.2.1. エラー許容と距離に1次関係を設定する簡単な方法

ErrorToleranceFunction::SetLinearError() メソッドはエラー許容と距離のリニアな一次関係を設定します。 次に構文を示します。

Code 13.6 SetLinearError メソッドの構文
qBool ErrorToleranceFunction::SetLinearError(
                                    qDouble dMinimumValue,  // 最小許容エラー
                                    qDouble dMaximumValue,  // 最大許容エラー
                                    qDouble dDistance       // 最大許容エラーに到達する際の距離
                                    )

距離とエラー許容間の関係の一次係数を次式に示します。

../_images/Fig_Dead_Reckoning_Eq_Linear_Coefficient.png

ErrorToleranceFunction::SetLinearError() メソッドは内部で以下のように ErrorToleranceFunction::SetParameters() メソッドを呼び出します。

Code 13.7 SetLinearError メソッドを呼び出したときの SetParameters メソッド
ErrorToleranceFunction::SetParameters(
                                    qDouble dMinimumValue,
                                    (dMaximumValue-dMinimumValue)/dDistance,
                                    0,
                                    qDouble dMaximumValue
                                    )

13.3.2.2. エラー許容と距離に2次関係を設定する簡単な方法

ErrorToleranceFunction::SetQuadraticError() メソッドは、一次従属のない、エラー許容と距離間の二次関係を設定します。 次に構文を示します。

qBool ErrorToleranceFunction::SetQuadraticError(
                                        qDouble dMinimumValue,  // 最小許容エラー
                                        qDouble dMaximumValue,  // 最大許容エラー
                                        qDouble dDistance       // 最大許容エラーに到達する際の距離
                                        )

距離とエラー許容間の関係の二次係数を次式に示します。

../_images/Fig_Dead_Reckoning_Eq_Coefficient.png

ErrorToleranceFunction::SetQuadraticError() メソッドは内部で以下のように ErrorToleranceFunction::SetParameters() メソッドを呼び出します。

Code 13.8 SetQuadraticError メソッドを呼び出したときの SetParameters メソッド
ErrorToleranceFunction::SetParameters(
                                dMinimumValue,
                                0,
                                (dMaximumValue-dMinimumValue)/(dDistance*dDistance),
                                dMaximumValue
                                )

13.3.2.3. SetLinearError() と SetQuadraticError() の比較

以下の図の通り、 ErrorToleranceFunction::SetLinearError() はエラー許容と距離が1次関係の真っ直ぐな線となり、 ErrorToleranceFunction::SetQuadraticError() はエラー許容と距離が2次関係の曲線になります。

../_images/Fig_Dead_Reckoning_SetError_InfluenceTo_ErrorTorelance.png

Figure 13.3 メソッド (a) SetLinearError と (b) SetQuadraticError のエラー許容への影響

13.3.3. 距離の計算

距離ベースの推測航法が使用される場合、 開発者は各ステーションのオブザーバーを Station::RegisterObserver(DOHandle idObserver) メソッドを使用して登録する必要があります。

すでにオブザーバーが登録される場合でも、別のオブザーバーを登録してオブザーバーを変更することができます。 オブザーバーは Station::UnregisterObserver() を使用して登録解除します。

すでに設定されているオブザーバーを取得するには以下のメソッドを利用してください。

また、オブジェクト間の距離を計算するシステムコールバックは、 DuplicatedObject::ComputeDistance(DuplicatedObject* pObservedDuplicatedObject) をアプリケーション側の複製オブジェクトでオーバーライドして実装する必要があります。

補足

デフォルトでは、DuplicatedObject::ComputeDistance() は UNKNOWN_DISTANCE を返します。 この場合、距離のデフォルトとして0が自動的に使用されます。 このデフォルトの距離は ErrorToleranceFunction::SetDefaultDistance() にて設定できます。

通常の距離計算は次のように実装します。 2 つのオブジェクトのデータセットに同じ変数が含まれると仮定すると、 オブジェクト間の距離 \(z\) は次の方程式で計算されます。 \(xi\) 値は同じデータセットに含まれる異なる変数値で、下付き文字の DO とオブザーバーは異なるオブジェクトの変数値を示します。 ※推定エラーと同様の計算式となります。

../_images/Fig_Dead_Reckoning_Eq_Distance_Of_Objects.png

DuplicatedObject::ComputeDistance() を使った距離計算は、プレイヤーの視野には依存していません。 そのため、プレイヤーにオブジェクトが見えるかどうかにかかわらず、オブジェクトがオブザーバーに近寄ると、 オブジェクトのデータセットがより頻繁に更新され、その結果より多くの帯域幅を使用します。 視野にないオブジェクトがプレイヤーには重要ではない場合、頻繁に更新するのは帯域幅の無駄です。 例えば、カーレースゲームでは、ドライバーであるオブザーバーの視野は通常前方です。 この場合、開発者は必要ないオブジェクトが頻繁に更新されないようにして帯域幅の使用を削減できるように、 後方にあるオブジェクトの距離を実際の距離の数倍にするといった、 視野を考慮に入れた DuplicatedObject::ComputeDistance() を実装すると良いでしょう。

13.3.4. 複数オブザーバー

分割画面モードなど、ステーションに複数のオブザーバーが必要な場合、上述の手順に次の 2 つの変更を加えて実行します。

  1. 単一のアバターオブジェクトをオブザーバーとして登録するのではなく、オブザーバーアバターのリストを含むオブジェクトを登録する。
  2. DuplicatedObject::ComputeDistance() を実装する際に特定のオブジェクトと各オブザーバーの距離を計算し、最小値を返す。

例えばゲームに分割画面モードがあるとします。 分割画面モードであるプレイヤーのアバターをオブザーバーとして登録するのではなく、 オブザーバーとみなされるプレイヤーのアバターのリストである「Observers」データセットを含む「SplitScreen」オブジェクトを登録します。 そして、SplitScreen オブジェクトの ComputeDistance コールバックで特定のオブジェクトと各オブザーバー間の距離を計算し、最小値を返します。 これにより、オブザーバーのいずれかが、オブジェクトの更新が必要になる距離内に入ると、更新が送信されるようにできます。

13.3.5. 距離ベースの推測航法の実装例

距離ベースの推測航法の実装を SphereZ サンプルプログラムの例を使用して説明します。 この例では、 P2P セッションに参加するすべてのプロセスが 2 つの球体インスタンスを作成すると仮定します。 1 つはユーザーによってコントロールされる球体で、もう 1 つは AI (コンピュータ)によってコントロールされる球体です。 各ステーションのユーザーコントロールの球体はオブザーバーとして定義されます。 そのため、更新が発生するステーションの更新が適用されるオブジェクトとユーザーコントロールの球体間の距離によって、推定に必要な精度が決まります。

各ステーションでは、カメラのフォーカスのある球体は、次のメソッドを使用してそのステーションのオブザーバーとして定義されます。

Code 13.9 ステーションのオブザーバーとして定義
if (m_bHasFocus)
{
    if (!Station::RegisterObserver(GetHandle()))
    {
        // Error handling
    }
}

そして、Sphere クラスの球体と別のオブジェクト間の距離を次の構文を使用して計算します。 この例では、Sphere クラスの 2 つのオブジェクト間の距離が、その位置間の距離として計算されます。 別のクラスの Sphere とオブジェクト間の距離は不明です。

Code 13.10 オブジェクト間距離の計算
// Sphere複製オブジェクトで ComputeDistance をオーバーライドします。
qReal Sphere::ComputeDistance(DuplicatedObject * pDO)
{
    if ( pDO->IsA(DOCLASSID(Sphere)) ) {
        return sqrt(
                sqr(m_Pos.x-( (Sphere*)pDO )->m_Pos.x) +
                sqr(m_Pos.y-( (Sphere*)pDO )->m_Pos.y) +
                sqr(m_Pos.z-( (Sphere*)pDO )->m_Pos.z)
                );
    } else {
        return UNKNOWN_DISTANCE ;
    }
}

エラー許容は、次のメソッドを呼び出して、0.05 と 1 を限度とする距離の 2 乗で変化し、最大エラーに 30 の距離で達するように設定されます。

Code 13.11 エラー許容の設定
Position::GetErrorToleranceFunction()->SetQuadraticError(0.05, 1, 30);

P2P セッションがステーション A によって作成され、ステーション B によって参加される場合、合計 4 個の DO が各ステーションにあります。 これは、ユーザーがコントロールする球体 2 個と AI がコントロールする球体 2 個です。 4 つの DO のうち、2 つはステーションによって作成された DO マスターで、2 つはセッションへの参加時に検出されたデュプリカです。 例えば、ステーション A の DO マスターは、そのステーションが作成したユーザーコントロールの球体と、AI コントロールの球体で、 デュプリカはステーション B が作成した 2 個の球体です。 この概要を Figure 13.4 に示します。 ここでは、ステーション A と B がコントロールする球体は、それぞれ赤と緑で示します。 DO マスターが、DuplicatedObject::Update() を実行してステーションで更新されると、次の操作が行われます。 示された例は、メソッド Update がステーション A の AI のコントロールする球体上で呼び出された場合です。

  1. 推定エラーが計算されます。NetZ が、ステーション A の AI のコントロールする球体の実際の(更新された)位置 (x, y, z) と推定されたステーション B のデュプリカの位置 (x´, y´, z´) の差を計算します。この値が推定エラーで、次の方程式で計算されます。

    ../_images/Fig_Dead_Reckoning_Eq_Estimated_Error.png
  2. 距離 z が計算されます。ステーション A では、この例ではユーザーがコントロールするステーション B の球体である、ステーション B のオブザーバーのデュプリカが検索されます。上述のとおり、B のオブザーバーのデュプリカ ( x_{User-cont(B)}, y_{User-cont(B)}, z_{User-cont(B)} ) と、ステーション A で AI のコントロールする球体の DO マスター ( x_{AI-cont(A)}, y_{AI-cont(A)}, z_{AI-cont(A)} ) の差が、Sphere::ComputeDistance() を呼び出して NetZ によって計算されます。ステーションで更新が発生すると、距離が計算される始点のオブジェクトは、リモートステーションに設定されていたオブザーバーによって決定されます。

    ../_images/Fig_Dead_Reckoning_Eq_Distance_Of_Spheres.png
  3. その後 NetZ はエラー許容関数を使用して、上述のようにエラー許容関数に設定されているパラメータに基づいてエラー許容を計算します。この例では、SetQuadraticError( 0.05, 1, 30) を呼び出します。そのため、エラー許容は エラー許容= 0.05 + [(1-0.05)/900] * z2 の式で求められます。エラー許容は距離の二乗で増加し、最大値と最小値はそれぞれ 0.05 と 1 です。最小エラー許容の 0.05 は x=0 の場合に発生します。これは、A の AI でコントロールされる球体が B のユーザーにコントロールされる球体に非常に近寄ったときです。距離が増加し、エラーが 30 の距離まで上昇すると、エラー許容はその最大値の 1 に達し、その後は一定となります。

  4. 最後に、推定エラーとエラー許容が比較されます。推定エラーがエラー許容より大きい場合、メッセージがステーション A からステーション B に送信され、ステーション A のAI でコントロールされる球体デュプリカ(AI コントロールl (A´))の位置が B 上で更新します。

../_images/Fig_Dead_Reckoning_UsingDistanceBased_Extrapolate.png

Figure 13.4 距離ベースの推測航法を使用する球体 AI コントロール (A) の位置の更新

13.4. 推測航法の微調整

NetZ で使用する推測航法アルゴリズムはさまざまな方法で微調整できます。 ほとんどの場合、NetZ で使用されるデフォルト値は十分な推測航法パフォーマンスを提供しますが、 ゲームのパフォーマンスを最適化するためにデータセット更新が NetZ で処理される方法を変更することもできます。 さまざまな微調整メカニズムは、データセットの型に合わせて選択しますが、データセットは推定フィルタを使用する必要があります。 この章の最後には、ゲームの微調整方法のステップ別のワークフローを提示しています。

13.4.1. データセットの更新

リモートステーションが DO マスターからデータセット更新を受信すると、トラッキングと収束の 2 つの手順でデータセットモデルを更新します。

  • トラッキングアルゴリズム

    データセットの推定を行い、以前の更新からのデータセット値を使用して、次の更新を受信するまでデータセット値を予測します。

  • 収束アルゴリズム

    収束手順では、データセットのローカル値を調整して、値がトラッキングアルゴリズムによる予測値にスムーズに収束するようにします。

データセットに推定フィルタが使用されると、データセットの各変数を予測するために線形トラッキングアルゴリズムまたは非線形トラッキングアルゴリズムのいずれかが使用されます。 線形アルゴリズムが採用される場合、最後の 2 回の更新が変数の予測に使用され、非線形、つまり放物線アルゴリズムは、最後の 3 回の更新を使用します。 アルゴリズムは、更新間での変数の変化程度に合わせて選択します。 これは、包囲角θによって定義されます。 Figure 13.5 に示すように、時間の経過による変数値の変更が比較的小さい場合、 包囲角は大きくなり、非線形トラッキングが使用されます。 ただし、時間の経過により変数が突然変化する場合、角度は小さくなり、線形トラッキングが使用されます。

../_images/Fig_Dead_Reckoning_TrackingAngle_UniDataset.png

Figure 13.5 単一のデータセット変数の (a) 線形トラッキングアルゴリズムおよび (b) 非線形トラッキングアルゴリズムのトラッキング角度

トラッキング角度のしきい値は、線形または非線形のどちらの予測アルゴリズムが使用されているかを調べる際に使用されます。 ラジアンまたは角度で指定されるしきい値は、PHBDRParameters::SetTrackingAngleThreshold() メソッドを使用して各データセットに設定されます。 角度のデフォルトの単位はラジアンです。 包囲角がトラッキングしきい値角度より小さい場合、線形トラッキングアルゴリズムが使用されます。 包囲角がこれより大きい場合は、非線形トラッキングアルゴリズムが使用されます。 PHBDRParameters::ForceLinearTracking() メソッドおよび PHBDRParameters::ForceNonLinearTracking() メソッドを使用して線形または 非線形のトラッキングアルゴリズムのいずれかの使用を強制することができます。 異なるステーションのオブジェクトのデータセット間での一貫性を保証するため、 トラッキング角度のしきい値はネットワーク全体の各ステーションで同一に設定する必要があります。

データセット更新後に行う 2 つめの手順は、リモートステーションのデータセットの各変数を DO マスターからの更新によって指定された値に収束することです。 収束が実装されていない場合、デュプリカが更新メッセージを受信すると、データセットの値が更新の値に直接変更します。 そのため、データセットが配置されている場合、オブジェクトはある位置から別の位置にジャンプするように見えます。 これに対し、収束アルゴリズムが使用されている場合、データセットの値はトラッキングアルゴリズムによって予測されたパスにスムーズに収束します。 デフォルトでは、NetZ は収束手順を実装します。 これを使用しない場合は、PHBDRParameters::ApplyConvergence() が偽を返すように設定します。

トラッキングについては、線形または非線形(放物線)アルゴリズムを使用して、収束パスを決定します。 どちらを使用するかは、包囲角によって異なります。 Figure 13.6 に示すように、大きな包囲角の場合は変数の値がほぼ一定であることを示し、 線形収束が使用されます。反対に、小さな包囲角の場合は、変数が大きく変化するため、非線形の収束アルゴリズムが使用されます。 変数がトラッキングアルゴリズムで予測されたパスに収束されると、次の更新を受信するまでこのパスをたどり、更新受信後はプロセスが繰り返されます。

../_images/Fig_Dead_Reckoning_ConvergencePath_UniDataset.png

Figure 13.6 単一のデータセット変数の (a) 非線形収束アルゴリズムおよび (b) 線形収束アルゴリズムのパス

古い値から予測パスの値への変数の収束にかかる時間は、収束遅延、CPDelay と呼ばれます。 NetZ はヒューリスティックなアプローチで収束遅延を演算し、収束の最小遅延および最大遅延は PHBDParameters::SetConvergenceDelay() メソッドを使用して定義できます。 収束遅延は、次の更新を受信するまで変数が予測パスに収束するように設定する必要があります。 このように設定されない場合、推定が不規則になります。

収束角度のしきい値は、線形または非線形のどちらの収束アルゴリズムが使用されているかを調べる際に使用されます。 ラジアンまたは角度で指定できるしきい値は、PHBDRParameters::SetConvergenceAngleThreshold() メソッドを使用して各データセットに設定されます。 角度のデフォルトの単位はラジアンです。 包囲角が収束しきい値角度より小さい場合、非線形トラッキングアルゴリズムが使用されます。 包囲角がこれより大きい場合は、線形トラッキングアルゴリズムが使用されます。 トラッキングアルゴリズムには、PHBDRParameters::ForceLinearConvergence() メソッドおよび PHBDRParameters::ForceNonLinearConvergence() メソッドを使用して線形または非線形の収束アルゴリズムのいずれかの使用を強制することができます。 収束アルゴリズムはローカルステーションのみに実装されるので、ネットワークの各ステーションは異なる収束角度しきい値を設定できます。

トラッキング角度しきい値と収束角度しきい値、および収束遅延に適切な値は、特定のデータセットの力関係に左右されます。 パラメータ選択の一般的な条件を Table 13.1 にまとめました。 例えば、オブジェクトの位置に関するデータセットがあるとします。 ボールなど、オブジェクトがその動きにより頻繁に変更される場合、トラッキング角度しきい値を高く設定して、 線形トラッキングが通常使用されるようにし、収束遅延を低い値に設定して、オブジェクトが予測パスにすばやく収束するようにする必要があります。 高い収束遅延が設定されている場合、次の更新はオブジェクトが予測パスに収束される前に受信する可能性が高くなり、 その結果オブジェクトが不規則な軌道になります。 ただし、飛行機などオブジェクトが比較的スムーズな動きをする場合、トラッキング角度しきい値を低く設定して、 非線形トラッキングが使用されるようにし、収束遅延を高い値に設定して、オブジェクトの動きがスムーズに保たれるようにする必要があります。 収束角度しきい値は、ローカルステーションのデュプリカの動きをどれほどスムーズにするかによって大きく異なります。 収束角度しきい値が高い場合、非線形収束が更新により頻繁に使用され、オブジェクトのパスがスムーズになります。 低い収束角度しきい値が使用されている場合、線形収束が使用され、オブジェクトの軌道が不規則になる可能性が高くなります。

Table 13.1 トラッキングおよび収束のパラメータ選択の一般条件
パラメータ 特徴 設定
Tracking Angle Threshold データセット値が頻繁に変更
  データセット値は比較的一定
Convergence Angle Threshold スムーズな軌道が必要
  軌道のスムーズさは不必要
Convergence Delay データセット値がスムーズに変更
  データセット値が突然変化する可能性あり

ほとんどのデータセットで、NetZ によって提供されているデフォルトのトラッキング角度しきい値と収束角度しきい値、 および収束遅延のデフォルト値は適切で、この値を調整する必要はありません。 データセットの更新を微調整するには、表 9 1 に示されている条件を目安にして、個別にデータセットを試してみて最適化します。 推測航法を微調整するには、(収束は不良トラッキングを隠すので)トラッキングを最初に微調整し、その後収束アルゴリズムを微調整する必要があります。 微調整を開始するには、PHBDRParameters::ApplyConvergence() を使用して収束アルゴリズムをオフにし、 オブジェクトの軌道が比較的正確で、角張らなくなるまでトラッキングアルゴリズムを微調整します。 その後、収束角度しきい値と収束遅延の値を、オブジェクトの軌道が満足いくものになるまで微調整します。 トラッキング角度と収束角度のデフォルト値は、タイムスケールが変数のスケールより大幅に大きいものと仮定しているため、大きくなっています。

13.4.2. 推定遅延

2 つのステーション間のレイテンシは、更新が DO マスターによって送信された時点とデュプリカが更新を受信した時点の間での遅延を生じさせます。 そのため、 DO マスターのデータセットが変更されると、デュプリカが更新される前に時差が生じます。 この遅延が長い場合、デュプリカのデータセットの推定が不正確になることがあります。 データセットの値が大きく変化する場合にはこれが顕著になります。 この状況に対処するため、NetZ ではメッセージの受信時にメッセージのタイムスタンプを変更して、 デュプリカで実行される推定を遅延させることができます。 例えば、 \(t\) が DO マスターから受信するメッセージの初期タイムスタンプの場合、 推定遅延を使用すると、デュプリカが使用する値は \(t + tiDelay\) となります。 このため、推定がより短い期間で実行され、 DO マスターとそのデュプリカの表示間に時差があるため、 NetZ はデュプリカのデータセットをより正確に予測できます。デュプリカの推定を遅延すると、 遅延されたデータセット値ではなく、デュプリカの表示がさらに正確になりますが、 DO マスターと同期される時間の値はより不正確になります。 通常、推定遅延はループバックが適用されている場合に設定されます。

PHBDRParameters::SetExtrapolationDelay() は次のように、時間がミリ秒で指定される推定遅延を実装するのに使用されます。 設定値は PHBDRParameters::GetExtrapolationDelay() メソッドで返されます。

Code 13.12 SetExtrapolationDelay メソッドの構文
PHBDRParameters::SetExtrapolationDelay(TimeInterval tiDelay);

例えば、次のコード行はデュプリカの推定を 100 ms 遅延します。

Code 13.13 デュプリカの推定を 100 ms 遅延させるコード
PHBDRParameters::SetExtrapolationDelay(100);

DO マスターからのデータセット更新のみが遅延され、RMC、アクション、障害回復操作などの他のメッセージは遅延されません。 そのため、場合によってはデュプリカの表示が不正確になったり、ランタイムエラーが発生したりすることがあります。

13.4.3. 連続性の切断

連続性の切断は、オブジェクトがテレポートしたり、壁に跳ね返ったりする場合など、データセット値が非連続的に変化する場合に起こります。 例えば、アバターがある位置から別の位置にテレポートする場合、2 つの位置間を内挿する必要はありません。 連続性の切断を示すと、NetZ は推定エラーを無視することで、スムーズな推測航法を実行できます。 連続性の切断が発生すると、データセットは推定エラーに関係なく連続性の切断の種類によって特定の回数更新され、 デュプリカに変更に関する情報を提供します。 この更新は、データセットの更新が通常送信されるチャンネルに関係なく、信頼性のあるチャンネルから送信されます。 13.1. で示したとおり、 推定エラーはローカルステーションの DO マスターのデータセット値とリモートステーションのデュプリカの推定データセット値の間の差です。

連続性の切断は DataSet::IndicateContinuityBreak() メソッドで示され、このメソッドは次のように宣言されます。 byBreak は連続性の切断の型を示します。

Code 13.14 IndicateContinuityBreak メソッドの構文
DataSet::IndicateContinuityBreak(qByte byBreak);

連続性の切断に使用できる型は、stop、sudden change、teleport です。 名前のとおり、これらはデータセットの値がある値のセットから別の値のセットに突然停止、変更、ジャンプする場合を示すのに使用されます。 連続性の切断を示すだけでなく、連続性の切断の発生時のデータセットの変更方法がわかれば、 連続性の切断更新モデルを使用して NetZ に連続性の切断の特定の型のデータセットへの影響に関する追加情報を提供できます。 示された連続性の切断がテレポートまたは突然の変更の場合、更新モデルが使用できます。 これにより、NetZ はデータセット値の突然の変更によりすばやく反応し、データセット値をより正確に予測できます。 例えば、サンプルとして使用している SphereZ ゲームの球体は定期的に壁にぶつかり、Position データセットの値は突然変更されます。 そのため、衝突時に連続性の切断を示すため次のコードを使用します。

Code 13.15 衝突時に連続性の切断を示すためのコード
m_dsPos.IndicateContinuityBreak(CONTINUITY_BREAK_SUDDEN_CHANGE |
                                               CONTINUITY_BREAK_UPDATED_MODEL);

この場合、連続性の切断に関する追加情報のある更新モデルが切断に関連付けられていることも示しています。 更新モデルは DataSet::SetModel メソッドを使用して設定し、MODEL マクロを使用してアクセスします。 例えば、球体の壁との衝突の更新モデルは次のようになります。 これは、球体の新しい位置、速度、加速を x、y、z 方向に定義します。

m_dsPos.SetModel(MODEL(m_dsPos,x), m_dsPos.x, m_Speed.sx/1000, 0);

m_dsPos.SetModel(MODEL(m_dsPos,y), m_dsPos.y, m_Speed.sy/1000, 0);

m_dsPos.SetModel(MODEL(m_dsPos,z), m_dsPos.z, m_Speed.sz/1000, -9.0/1000000);

変更が起こると、NetZ に推定データセットへの突然の変更に関する追加情報を知らせるため、SetModel メソッドが DO マスター上で呼び出されます。 そのため、デュプリカがデータセットを推定するために十分な情報を得るために 3 回の更新を待つ必要なく、 連続性の切断の直後のデータセット変更方法に関する情報が DO マスターからデュプリカに連続性の切断の発生直後に送信されます。 これで、デュプリカのデータセットは連続性の切断が発生した際により正確に示されます。

13.4.4. ローカル修正

ローカル修正はローカルステーションで実行され、後続の更新を DO マスターから受信する前に、 デュプリカのデータセットの不正確な推定を修正するのに使用されます。 例えば、各ステーションで球体のデュプリカは壁を貫通してはならないことがわかっています。 ただし、ネットワークレイテンシのため、球体デュプリカの位置の推定が、 球体が壁の中に入っている、または壁を貫通したように表示することがあります。 この場合、 DO マスターから更新を受信する前に、ローカルステーションのデュプリカ位置の不正確な推定を修正することができます。 デュプリカの位置は位置が更新されると DO マスターの位置に収束されるので、この修正は正確である必要はありません。 そのため、ローカル修正を実装すると、球体デュプリカの位置はより正確に示されます。 レイテンシが低い場合、ローカル修正を実装する効果があまり見られない場合があります。 ただし、レイテンシが大きくなるほど、その実装は正確な推定を保証するためにより重要になります。

デュプリカのローカル修正は、DataSet::SetLocalCorrection() メソッドを使用して設定し、デュプリカ上で呼び出されます。 このメソッドが実行されると、 DO マスターからの更新は、 DO マスターから 2 つのポイントが受信されるまで無視されます。 2 つのポイントが受信されると、NetZ には推定を再開するのに十分な情報があります。 ローカル修正を設定する際、デュプリカのデータセット値は、 DataSet::PredictValue()、DataSet::PredictRateOfChange() を使用して予測できます。 これらのメソッドは、現在値を基にデータセット値を予測します。例えば、すべての球体のデュプリカにローカル修正を設定して、 壁の中に表示されないことを保証できます。次のコードでは、球体が壁にぶつかると、 位置は同じ所にとどまり、速度と加速が変化するように指定しています。 x 方向と y 方向の速度は、DataSet::PredictRateOfChange() メソッドで予測された現在値の負の値です。 z 方向では、球体に重力を考慮するために軌道速度と負の加速が指定されています。

Code 13.16 デュプリカのローカル修正のコード例
if (ComputeXWallCollision())
{
    qDouble dCurrentSpeed=m_dsPos.PredictRateOfChange(MODEL(m_dsPos, x));
    m_dsPos.SetLocalCorrection(MODEL(m_dsPos, x), m_dsPos.x, -dCurrentSpeed,
                                                                            0);
    m_dsPos.SetLocalCorrection(MODEL(m_dsPos, z), m_dsPos.z, 10.0/1000,
                                                                 -9.0/1000000);
}
if (ComputeYWallCollision())
{
    qDouble dCurrentSpeed=m_dsPos.PredictRateOfChange(MODEL(m_dsPos, y));
    m_dsPos.SetLocalCorrection(MODEL(m_dsPos, y), m_dsPos.y, -dCurrentSpeed,
                                                                            0);
    m_dsPos.SetLocalCorrection(MODEL(m_dsPos, z), m_dsPos.z, 10.0/1000,
                                                                 -9.0/1000000);
}

最大ローカル修正遅延は、PHBDRParameters::SetMaximumLocalCorrectionDelay() メソッドと PHBDRParameters::GetMaximumLocalCorrectionDelay() メソッドを使用して、 設定し、返すことができます。 最大ローカル修正遅延は、ローカル修正がデュプリカに適用される最大時間です。 ローカル修正遅延が過ぎ、デュプリカが推定モデルに示された位置に収束されていない場合、オブジェクトはこの位置にテレポートされます。 これで、壁にぶつかるなど、ローカル修正がデュプリカに繰り返し実行され、デュプリカが推定モデルに収束できなくなるまれなケースにも、 短時間でデュプリカの位置が修正されるように保証されます。

13.4.5. 推奨するワークフロー

推測航法のもっとも複雑な作業は、もっとも困難な状況でもデュプリカがそのマスターの動きに従うように関連パラメータを微調整することです。 すべての推測航法パラメータは相互に作用してデュプリカの最終的な推定を行うので、 推測航法の微調整時には各変数を隔離して一度に 1 つずつ変更することが非常に重要です。 そのための基本のワークフローを次に示します。

  1. 一定エラーまたは距離ベースのエラーのいずれを使用する場合も、推測航法の微調整は一定エラー許容の ErrorToleranceFunction::SetConstantError から開始し、 DO マスターの動きが突然変わる場合を除き、動きに満足するまでこのエラーを調整します。一定エラーは相互に作用する変数の数を少なくするだけでなく、最小許容エラーの目安を提示します。

  2. DataSet::IndicateContinuityBreak() を使用して、データセットが非連続的に変更される場合の連続性の切断を示します。

  3. DataSet::SetLocalCorrection() を使用して、デュプリカが壁や他の固体オブジェクトを貫通しないようにローカル修正を実装します。

  4. デュプリカの推定を遅延させるには、PHBDRParameters::SetExtrapolationDelay() を使用して推定遅延を設定します。通常これは、マスターのループバックと一緒に設定されます。

  5. トラッキングアルゴリズムを微調整します。

    1. 収束は不良トラッキングを隠すので、PHBDRParameters::ApplyConvergence() で収束アルゴリズムをオフにします。これは、収束を適用する前にトラッキングが妥当であることを保証する必要があるためです。
    2. トラッキング角度しきい値を設定して、線形または非線形の予測アルゴリズムが使用されるときを決定します。この変数は特定のデータセットに合わせて微調整が必要ですが、一般にオブジェクトが頻繁に動きを変える場合は、しきい値を高くして線形トラッキングが通常使用されるようにします。オブジェクトが比較的スムーズな軌道で動く場合(車、飛行機)しきい値を低く設定して非線形トラッキングが使用されるようにします。このパラメータの微調整後、デュプリカの動きは比較的正確になりますが、多少ぎこちなさが残ります。
  6. 収束アルゴリズムを微調整します。

    1. 収束角度しきい値を設定します。非常にスムーズな軌道が必要な場合、このしきい値を高くして、非線形収束が使用されるようにし、多少のぎこちなさが許容される場合は値を低くして線形収束を設定できます。
    2. 収束遅延を設定します。データセット値がスムーズに変化する場合、これを高く設定し、急に変化する場合はこれを低く設定してデュプリカが予測パスにすばやく収束できるようにします。新しい更新をマスターから受信する前に収束できるのが理想的です。
  7. 距離ベースの推定エラーを使用する場合は、ここで設定します。距離ベースの場合、オブジェクトがオブザーバーから離れるほど帯域幅の使用は削減されますが、精度が低くなります。

    1. Station::RegisterObserver() を使用して、 DO マスターを特定のステーションのオブザーバーとして登録します。
    2. DuplicatedObject::ComputeDistance() コールバックを実装して、オブザーバーとデュプリカ間の距離を計算します。
    3. ErrorToleranceFunction::SetLinearError() または ErrorToleranceFunction::SetQuadraticError() を使用して許容推定エラーを設定します。
    4. 必要に応じて手順 5 と手順 6 を繰り返します。

13.5. DO マスター側の実装

DO マスター側では、次のように先読みまたはループバックが適用できます。 通常、これらの技術はレイテンシをマスクするために、デュプリカへ行う推測航法と一緒に使用されます。

13.5.1. 先読み

先読みが適用されると、 DO マスターは将来の特定の時間のイベントを予測し、 その予測と関連タイムスタンプをデュプリカに送信します。 予測したイベントが DO セッションマスターに実際に発生すると、 イベントの結果が次の Figure 13.7 のように表示されます。 当然、この技術はある程度の精度で予測できるイベントとデータセットのみに使用し、使用する場合は正確な同期が得られます。 通常、位置、方向、衝突などに基づく、物理計算 になります。 武器の発射などの隔離されたイベントには、通常この技術はあまり適しません。

先読みの典型的な使用では、衝突など、デュプリカよりも DO マスターのほうが正確に予測できるイベントに使用されます。 そのため、標準的な推測航法で行われるようにオブジェクトのデュプリカへの 物理計算(衝突)を予測するのではなく、 マスターの 物理計算 を予測し、その予測モデルをデュプリカに送信することができます。 このアクションの DO マスターの予測が正しくない場合、予測を正しいものとして受け入れるか、 予測が正しくないものとして受け入れてそれにパッチを適用することができます。 イベントの種類によって、解決方法はプレイヤーに一定の矛盾を感じさせる場合と、感じさせない場合があります。

../_images/Fig_Dead_Reckoning_DuplicationMaster_PredictedEvent.png

Figure 13.7 先読みの DO マスターへの適用方法

先読みを実装するには、 DO マスター上で将来の特定時間のデータセット値を予測し、 DuplicatedObject::Update() を使用してパラメータに有効な更新時間を指定して予測値をデュプリカに送信します。 DO マスターでは、現時点で有効なデータセット値を使用してシーンをレンダリングし、 デュプリカでは DuplicatedObject::Refresh() を使用して、データセットをリフレッシュし、 受信する更新が有効な時間がわかっていることから現時点でのデータセット値を演算し、シーンをレンダリングします。

13.5.2. ループバック

DO マスターに適用できる 2 つめの技術は、ループバックです。 ループバックは、イベントが発生(トリガされる)してから、その結果が計算される(画面に表示される)までの間に生じさせられる遅延です。 この遅延はゲームの事実上の反応時間です。 100 ms 程度の反応時間は、超高速アクションゲームであっても、ほとんどのゲームでは感じられることはありません。 また、反応時間が一定であれば、プレイヤーはゲームプレイをその反応時間に合わせて調整します。 ループバックの利点は、 DO セッションマスターで発生された遅延によって、デュプリカにイベントに関するメッセージを送信する時間が与えられるため、 DO マスターとデュプリカステーション間での同期が非常に正確になることです。

../_images/Fig_Dead_Reckoning_DuplicationMaster_LoopbackDelay.png

Figure 13.8 ループバックの DO マスターへの適用方法

ループバックの遅延は、DataSet::SetLoopbackDelay() メソッドを使用して設定し、DataSet::GetLoopbackDelay() によって返されます。 ループバックが適用されると、遅延の間にわたって DO マスターにすべてのデータセット値を保管するのではなく、 値のサンプリングのみがキューに保管され、必要時にオブジェクトのデータを内挿するために使用されます。 保管される値のサンプリング間隔は、DataSet::SetLoopbackSamplingInterval() で設定され、DataSet::GetLoopbackSamplingInterval() で返されます。 デフォルト値は 50 ms で、1 秒当たり 20 ポイントのサンプリングに相当します。

DO マスターがループバックを使用すると、デュプリカの推定モデルを維持するだけでなく、 必要時に独自にループバックされるデータセットの値を予測するために使用される個別のループバックモデルも維持できます。 ループバックモデルを最新に保つため、モデルは標準の推測航法モデルと同様に更新され、リフレッシュされる必要があります。 そのため、データセットが変更されると、新しい値をループバックモデルに追加するために DataSet::UpdateLoopback() が呼び出され、 新しい値を使用してモデルを再計算するために DataSet::RefreshLoopback() が呼び出される必要があります。 ループバックモデルがリフレッシュされると、DataSet::PredictLoopbackValue() は予測されたループバックデータセット値または同様なメソッドのいずれかを取得できます。 例えば、Position データセットにループバックを使用する場合、一般的に呼び出される DO マスターは次のようになります。

Code 13.17 Position データセットにループバックを使用する場合
// Set the new values in the Position dataset
pObject->SetPosition(oNewPosition);

// Update the values. This will trigger a network message to the
// duplicas
pObject->Update();

// Update the values in the loopback model
pObject->m_dsPosition.UpdateLoopback();

// Recalculate the loopback model
pObject->m_dsPosition.RefreshLoopback();

// Return the new predicted loopbacked values, where
// GetLoopbackPosition uses the PredictLoopbackValue method
// to return the predicted values for the Position dataset.
Vector3D vLoopbackedPosition = pObject->GetLoopbackPosition();

DO マスターとそのデュプリカ間での同期を保証するため、ループバックは、 PHBDRParameters::SetExtrapolationDelay で設定する推定遅延を適用して、推測航法とともに使用する必要があります。 そうでない場合、同期の精度がレイテンシの変化に左右されます。 推定遅延を使用すると、ネットワークを通るのにメッセージにかかる時間にかかわらず、 DO マスターとデュプリカで結果が計算される時間が一定になります。 完全に同期するには、推定遅延とループバック遅延を同じ値に設定します。