キャラクターの移動コンポーネント

Character Movement コンポーネントについて

Windows
MacOS
Linux

CharacterMovementComponent を利用するキャラクターには、自動的にネットワークの構築が組み込まれます。ネットワークゲームで CharacterMovementComponent を使用してフレームごとにプレイヤーの移動予測、 レプリケーション、修正を行う方法は以下の通りです。

  • TickComponent 関数を呼び出す

  • フレームの加速度と回転の変更を計算する

  • PerformMovement (ローカルでコントロールしているキャラクターの場合) または ReplicateMoveToServer (ネットワーククライアントの場合) のいずれかを呼び出す。 ReplicateMoveToServer は移動を (PendingMove リストへ) 保存し、PerformMovement を呼び出してから、レプリケート関数 ServerMove を呼び出すことで移動をサーバーへレプリケートします。

ServerMove はクライアントの合成位置、タイムスタンプを含む移動パラメータを受け取ります。移動パラメータがデコードされ、 適切な移動を発生させるサーバーで実行されます。次に最終位置を見て、 最後の応答から時間が経ちすぎている場合、または位置エラーが注目に値する場合は、サーバーはレプリケート関数 ClientAdjustPosition を呼び出します。 この関数はクライアントにレプリケートして、修正した位置を渡します。

TickComponent がクライアントに再び呼び出されると、サーバーからの修正を受け取っている場合は、 クライアントは PerformMovement を呼ぶ前に ClientUpdatePosition を呼び出します。この処理により、サーバーが調整していた移動のタイムスタンプを記録した後に作成された保留移動リストの移動がすべてリプレイされます。

キャラクター移動とシミュレーションしたプロキシ

これまでに説明した CharacterMovementComponent によるネットワークは構築は、権限のあるサーバーと接続した 1 つのクライアントの詳細のみで処理しました。AI 制御のキャラクターとリモート コンピュータ上のプレイヤーはシミュレーションしたプロキシとして扱われ、 若干異なるコードパスを移動します。

リモート制御されたキャラクターの移動は、通常は標準の PerformMovement コードを使用してサーバー (この例では権限者) で更新します。位置、回転、ベロシティに加えて他に選択したキャラクター固有の情報 (ジャンプなど) などのアクタ ステート データは、 通常のレプリケーション メカニズムを使用して別のマシンへレプリケートされます。 つまり、ローカル制御されたキャラクターのようにフレーム単位で更新を受け取るわけではありませんこれらのキャラクターをリモート クライアントによりスムーズに表示するために、クライアント マシーンはシミュレーションしたプロキシに対して、 新規データがサーバーから届くまでフレームごとにシミュレーション更新を実行します。通常、AI 制御のキャラクターはサーバー上で直接実行されますが、 リモートプレイヤーは自身のローカル更新をサーバーに送信し、サーバーがプレイヤーのフル移動モードを更新し、定期的にこのデータを他のすべてのプレイヤーにレプリケートします。

この更新は次の更新までの「間を埋める」ために、レプリケートしたステートを元に、移動の推定結果をシミュレートすることを意図しています。別の更新が届くと、ローカル シミュレーションを効率よくリセットして、 新しいシミュレーションを開始します。

シミュレートしたプロキシの移動更新のほとんどは、UCharacterMovementComponent::SimulateMovement で実行して、同様に MoveSmooth. MoveSmooth で実行します。 ほとんどの場合、`MoveSmooth はさまざまな移動モード (歩く、飛ぶなど) でのフル移動モード更新の最小バージョンです。この用途においては、実行コストが多少安く、複雑度も多少緩和します。

シミュレートしたプロキシのスムージング

キャラクターが単に前進している場合は、直線の移動は予測がとても簡単なため、 シミュレートした更新は次のレプリケート更新とほぼ一致します。ワールド内のスタティックなウォールへ向かって走る場合でさえ、偏差と後続の更新も高精度なシミュレーションとなります。

しかしながら、前のレプリケートステートスナップショットに基づいたローカルシミュレーションは、場合によっては実際の正確な位置と異なってしまいます。 あるベロシティで移動していたキャラクターのレプリケートステートを考えてみましょう。次の更新を待つ間、シミュレートしたプロキシはそのベロシティで移動し続けます。 ただし、リモート制御のキャラクターの場合は、ベロシティ更新を送信後にすぐに停止することが可能です。ローカル シミュレーションがこのことを認識する方法はないので、 次の更新が届くまで、間違った予測をします。

サーバー更新の受信時に、シミュレートしたプロキシの位置がビジュアル的に「飛び出さない」ように、 キャラクターの視覚表現の位置を UCharacterMovementComponent::SmoothClientPosition 関数を利用してスムーズにします。特定の時間内にターゲットの目的地へ到達するために、シンプルなスムージング関数をデフォルトで適用します
(クライアントのネットワークデータに "SmoothNetUpdateTime" で設定)。

CharacterMovementComponent ネットワーキングのデバッグ作業

キャラクター ネットワーキングのデバッグ作業と分析を行う際に便利なツールがいくつかあります。通常、まず最初に、 不規則な動作をしているクライアントのコンソールに "p.NetShowCorrections 1" を入力することから始めます (出荷ビルドを除いて機能します)。この機能をサーバー側でも有効にすると、便利かもしれません。この機能を使って、出力コンソールへロギングし、 かつ「正しい」位置 (緑) と「不正」な位置 (赤) にコリジョン形状を描画することで、 クライアント (またはサーバーへ送信) がネットワーク修正を受け取った時期が常にわかるようになります。クライアントにとっての「正しい」位置とはサーバーから修正として送信された位置になります。 一方で「不正」な位置とはサーバー側で誤差許容外と判断されたローカル位置になります。サーバー側も同様の考え方です。「正しい」サーバーの位置は緑で描画されながら、 「不正」な受信したクライアント位置は赤で描画されます。変数 "p.NetCorrectionLifetime" は、デバッグ ビジュアライゼーションが表示されなくなるまでワールド内に持続する時間 (秒) をコントロールします。

問題の診断に有用なその他の方法は、CharacterMovement networked movement 関数が送信したデータのロギングの一部をオンにします。コンソール コマンド "log LogNetPlayerMovement Verbose" は、 位置、回転、加速度を含めてキャラクター移動の送受信データをロギングします。サーバー側のロケーション変更がクライアントと同じ方法で行われなかったために、 クライアント側のみで位置が更新される場合など、なぜエラー修正が発生するのかこれで説明がつきます。

キャラクター移動速度のハッキングの防止 (ネットワーク ゲーム)

ネットワーク ゲームは、セキュリティ上の弱点を利用して不正に優位性を得ようとするユーザーの格好の標的です。偽のソフトウェアを使ってゲーム クライアント上での時間経過を速くするのが、 ゲームで頻繁に使われる抜け道です。修正されていない CharacterMovementComponent 機能を使って作成されたゲームは、 この抜け道をうまく利用することができるため、ズルをしているユーザーの移動をさらに速くしてしまいます。このような抜け道を防ぐために、時間不一致を検出および解決する機能を CharacterMovementComponent に追加しました。

検出

時間不一致は、サーバーがクライアントから報告された ServerMove RPCs (Remote Procedure Calls) の時間差を サーバー上に渡されている既知の (かつ信頼性のある) 時間の合計と比較することで検出することができます。この差が実行の合計時間として保持され、その合計時間がユーザーの設定できる閾値を超えると、 解決手段が講じられます。

解決策

そこで、クライアントから送られることになっている ServerMove RPC のタイムスタンプをオーバーライドしてサーバーの時間と一致させるという解決方法を採用しました。RPC 間のキャラクター移動データ (位置、回転、加速など) の差の一部を差し引いて、 クライアントが再びサーバーに同期するまでの 時間不一致を「修復」します。

コンフィギュレーション

時間不一致の検出と解決はデフォルトでは無効になっています。GameNetworkManager の以下の変数を設定することができます。 それらのデフォルト値は BaseGame.ini に入っています。これらの値はプロジェクト固有のゲーム コフィギュレーション ファイルでオーバーライドすることができます。

変数名

エフェクト

bMovementTimeDiscrepancyDetection

検出を有効にします。検出時に、警告ログがトリガーされます。解決が有効にされている場合は、それも適用されます。

bMovementTimeDiscrepancyResolution

解決を有効にします。十分な不一致が検出された場合、クライアントは修正して時間を「修復」することができます。

MovementTimeDiscrepancyMaxTimeMargin

検出 / 解決のトリガーを引き起こさずにサーバーが期待するゲーム時間よりクライアントが速く進むことができる最大時間です。

MovementTimeDiscrepancyMinTimeMargin

検出 / 解決がトリガーされずにサーバーが期待するゲーム時間よりクライアントが遅れることができる最大時間です。

MovementTimeDiscrepancyResolutionRate

解決中の時間修復レートです。デフォルトは 100% です。つまり、差が修復されるまでクライアントは移動することができません。

MovementTimeDiscrepancyDriftAllowance

容認できるクロック数のドリフトです。許容できる 1 秒当たりのパーセントとして指定します。これにより、誤検出のトリガーによる突発的なパケット ロスやパフォーマンスの処理落ちを防ぐことができます。

bMovementTimeDiscrepancyForceCorrectionsDuringResolution

解決中にクライアントに更新を強制するかどうかを設定します。移動エラー許容誤差が小さいプロジェクト、または ClientAuthorativePosition が有効にされたプロジェクトに必要です。

これらの設定は特定のプロジェクトに合うように微調整する必要があります。基本的なテスティングはクライアント上で slomo によるチートを使用して行うことができます。変数 "p.DebugTimeDiscrepancy" は サーバー上で time-discrepancy へのロギングをアクティベートします。

高度なトピック:新しいムーブ アビリティをキャラクター移動へ追加する

キャラクターへ新しいムーブ アビリティを追加する方法は多数あります。ネットワークゲームで動くキャラクターに "Teleport" アビリティを追加する手順を一通り試してみましょう。T キーを押すと、 目的地に障害物がない限りキャラクターは 10 メートル前方へテレポートすると想定しましょう。さらに、 このアビリティはネットワーク ゲームで機能する必要があることにしましょう。

手法 1:クライアントのみで実行する

障害物がないか確認し、目的地がはっきり見える場合はキャラクターを直接前方に 10 メートル移動させます。これはネットワーク環境を一切考慮せず、 クライアントのみで実行します。

結果:ネットワークゲームで失敗します。ローカル クライアントは前方へテレポートしますが、すぐに開始位置へワープして戻ります。

分析:コードを実行させるために確立した単なる基本部分です。ネットワーク ゲーム以外では機能する可能性があります。ただし、ネットワークの構築を考慮しないので、 ネットワーク ゲームでは失敗します。サーバーはアビリティがトリガーされたことを識別できないため、 移動をトリガーするためにクライアントのキャラクターの位置、回転、加速度を使って、クライアントがどこで終了するかを計算します。サーバーの視点からすれば、キャラクターの予想位置とクライアントによる位置の 10 メートルの差は クライアント側のエラーです。サーバーはそのエラーを修正し、クライアントはサーバーの修理をローカルに適用してテレポートをアンドゥします。

手法 2:サーバーだけに RPC を呼び出す

ネットワークゲームで機能する一番シンプルなアプローチは、アビリティをトリガーするために信頼できるネットワーク RPC を設定することです。そこで、UFUNCTION が クライアントからトリガーされるとテレポート コードを実行するReliableServer として識別されるように設定します。

結果:ネットワークゲームで機能しますが、いくつか問題点もあります。アビリティを使うとクライアント側で実行時に著しい遅延が発生したり、 瞬時の移動ではなく、滑らかに移動しているように見えます。

1 つめは、アビリティを使うとクライアント側で実行時に著しい遅延が発生します。 これは、クライアントからのコマンドはサーバーへ送られてサーバーで実行され、その後で送り返されて、初めてクライアントは実行されたことを知るためです。2 つめは、瞬時に位置が変わったと感じるべき時に、 スムージング コードがテレポートをスムーズな移動に感じさせてしまいます。3 つめは、プレイヤーのサーバーの移動は修正として認識されます。 ゲームのネットワーク プレイをデバッグしようとする時に本当の修正を隠してしまうため、この手法には適しません。

分析:動作はしますが理想には程遠いです。プレイヤーが T キー テレポートを押すと、コマンドがサーバーへ送られ、ローカルでは何も起こりません。プレイヤーは制御が反応していないように感じます。 また、テレポート コマンドが機能したかどうかもすぐには分かりません。サーバーはコマンドを受け取るとキャラクターを移動させますが、 クライアントには伝えません。クライアントが更新を送ると、サーバーはクライアントによる報告位置を 10 メートル離れて確認し、 修正を送ります。修正により、クライアントはサーバーが期待する位置に移動 (スムーズに) し、正しい位置に見えなくても効率的にテレポートを起します。"p.NetShowCorrections" を 1 に設定しておけば、 これがネットワークの修正であることが通知されます。さらに、パーティクルやサウンド エフェクトのようなものをアビリティに追加したい場合、 実際のアビリティはサーバーのみで実行されるため、これらのエフェクトは正しく表示されません。クライアントはアビリティの試み (そしてコマンドがサーバーに送られたこと) を知りますが、 成功 / 失敗の報告はなく、修正がその後すぐに表示されるのみです。

手法その 3:サーバー RPC とローカル トリガー

この方法では、クライアントはテレポートをローカルで実行し、その後でサーバーの Teleport RPC を呼び出します。

結果:ネットワーク ゲームで機能しますが、まれに深刻な問題を引き起こす可能性があります。

分析:このアプローチは、前回の試みで生じた障害を改善しようとします。 ここで言う障害とは、ローカル移動で生じる遅延と、アビリティではなく修正が原因で移動が発生することを指しています。アビリティはクライアントで実行されているので、サウンドやパーティクルエフェクトなど、Teleport のフル機能を活用できます。前の方法よりもかなりうまく機能しますが、 やはりいくつかの重要な注意事項があり、実際のネットワーク環境ではいまだに中断してしまいます。主な問題は、テレポートがトリガーされた時間よりも前の時間にクライアントの時間が戻されると、 通常の時間に戻った際に Teleport アビリティを再度トリガーしなくてはいけないことがわからなくなり、Teleport がクライアント側で紛失してしまったように見えることです。このため、 状況によって、特にネットワーク状態が悪い場合は、ゲームが反応していないに見えてしまいます。

手法その 4:CharacterMovementComponent Ability の実装

このアプローチは、Teleport アビリティの知識を CharacterMovementComponent に追加します。ネットワーク上で確実に行う方法です。

結果:ネットワークゲームで機能しますが、実装時にいくつか配慮が必要です。

分析:このアプローチはもう少しバックグラウンドが必要です。前述したように、CharacterMovementComponent を使用したキャラクターは "a saved move list" と呼ばれる入力の結果、待ち行列に入ります。保存したそれぞれの動作は、 フレーム用に動作の開始時に位置、回転、加速度 (通常はプレイヤー入力の結果として)、ジャンプなどのステートを記録します。移動がクライアントからサーバーへ送信されると、 移動はサーバーによって認識されるため、受信した移動を保持して前の移動は除去されます。修正の場合、時間内のどこで発生したかが分かっていて、 発生よりも後に起こったすべての移動を「再プレイ」することができます。修正時から時間内に再び前進しようと試みるため、 クライアントは修正があったことさえ気づかないので、これは最適です。これは修正が再適用された後に アビリティがトリガーされたという意味でもあります。UCharacterMovementComponent::DoJump 関数に見られるように、bReplayingMoves パラメータを確認することで、 このケースでは再プレイしないように特別なエフェクトを維持することができます。

Teleport アビリティそのものは CharacterMovementComponent と同じ要領で行います。このアビリティがトリガーされたことと、 サーバー側で正しく処理したことを示すだけです。クライアントの反応を早くするために、もちろんアビリティはローカルで実行します。クライアントとサーバー間のデータの送受信は既存ネットワーキングの一部として自動的に行います。 ユーザーはデータをパック/アンパックするだけです。UCharacterMovementComponent から子クラスを派生させたら、独自の FSavedMove_Character バージョンを作成するために AllocateNewMove をオーバーライドします。 これが保留移動リストに格納されます。独自の FSavedMove_Character にはオーバーライド方法が何通りかあります。 GetCompressedFlags は Teleport アビリティのトリガーを示ために新規フラグでオーバーライドされます。 UpdateFromCompressedFlags も新規の Teleport フラグをアンパックし、サーバー側にアビリティをトリガーするために更新されます。このメソッドは、作成されたムーブ アビリティをネットワーク ゲームで確実に動かすことができます。

Select Skin
Light
Dark

新しい Unreal Engine 4 ドキュメントサイトへようこそ!

あなたの声を私たちに伝えるフィードバックシステムを含め、様々な新機能について開発をおこなっています。まだ広く使える状態にはなっていないので、準備ができるまでは、ドキュメントフィードバックフォーラムで、このページについて、もしくは遭遇した問題について教えていただけると助かります。

新しいシステムが稼働した際にお知らせします。

フィードバックを送信