このエンティティシステムをネットワーク化する方法は?


33

FPSのエンティティシステムを設計しました。基本的には次のように機能します。

GameWorldと呼ばれる「世界」オブジェクトがあります。これは、GameManagerの配列とComponentManagerの配列を保持します。

GameObjectはComponentの配列を保持しています。また、非常にシンプルなイベントメカニズムも提供します。コンポーネント自体は、すべてのコンポーネントにブロードキャストされるイベントをエンティティに送信できます。

コンポーネントは基本的にGameObjectに特定のプロパティを与えるものであり、GameObjectは実際にはそれらの単なるコンテナであるため、ゲームオブジェクトに関係するすべてはコンポーネント内で発生します。例には、ViewComponent、PhysicsComponent、およびLogicComponentが含まれます。それらの間の通信が必要な場合は、イベントを使用して行うことができます。

ComponentManagerは、Componentと同様の単なるインターフェイスであり、各Componentクラスには、通常1つのComponentManagerクラスが必要です。これらのコンポーネントマネージャは、コンポーネントを作成し、XMLファイルなどから読み取ったプロパティでコンポーネントを初期化する役割を果たします。

ComponentManagerは、外部ライブラリを使用するPhysicsComponentのように、コンポーネントの大量更新も処理します(世界中のすべてを一度に実行します)。

構成可能性のために、XMLファイルまたはスクリプトのいずれかを読み取るエンティティのファクトリーを使用し、ファイルで指定されたコンポーネントを作成します(また、一括更新のために適切なコンポーネントマネージャーに参照を追加します)。次に、それらをGameObjectオブジェクトに注入します。

次に問題が発生します。これをマルチプレイヤーゲームに使用しようとしています。私はこれにどのようにアプローチするのか分かりません。

まず、最初からクライアントはどのエンティティを持っているべきですか?まず、シングルプレイヤーエンジンがどのエンティティを作成するかを決定する方法を説明する必要があります。

レベルエディタでは、「ブラシ」と「エンティティ」を作成できます。ブラシは、壁、床、天井など、基本的に単純な形状のものです。エンティティは、先ほどお伝えしたGameObjectです。レベルエディタでエンティティを作成するとき、各コンポーネントのプロパティを指定できます。これらのプロパティは、エンティティのスクリプト内のコンストラクターのようなものに直接渡されます。

ロードするエンジンのレベルを保存すると、エンティティとそれに関連付けられたプロパティのリストに分解されます。ブラシは「ワールドスポーン」エンティティに変換されます。

そのレベルをロードすると、すべてのエンティティがインスタンス化されます。簡単ですね。

さて、エンティティをネットワーク化するために、多くの問題に遭遇しました。最初に、最初からどのエンティティがクライアントに存在する必要がありますか?サーバーとクライアントの両方にレベルファイルがあると仮定すると、クライアントは、サーバー上のゲームルールのためだけに存在する場合でも、レベル内のすべてのエンティティをインスタンス化できます。

もう1つの可能性は、サーバーがその情報を送信するとすぐにクライアントがエンティティをインスタンス化することです。つまり、クライアントは必要なエンティティのみを持つことになります。

別の問題は、情報の送信方法です。サーバーはデルタ圧縮を使用できると思います。つまり、フレームごとにクライアントにスナップショットを送信するのではなく、何かが変更されたときにのみ新しい情報を送信します。ただし、サーバーは各クライアントが現時点で知っていることを追跡する必要があります。

最後に、ネットワークをエンジンにどのように注入する必要がありますか?ネットワーク化されることになっているすべてのエンティティに注入されるNetworkComponentというコンポーネントを考えています。しかし、どのようにネットワークコンポーネントの知っておくべきどのようなネットワークへの変数は、とどのようにそれらにアクセスするために、そして最終的にクライアントに対応するネットワークコンポーネントは、ネットワーク変数を変更する方法を知っている必要がありますか?

私はこれに近づくのに大きな問題を抱えています。あなたが途中で私を助けてくれたら本当に感謝しています。コンポーネントシステムの設計を改善するためのヒントも受け付けていますので、提案することを恐れないでください。

回答:


13

これは、質問の神々しい(恩赦)獣であり、多くの詳細+1があります。それにつまずく人々を助けるのに十分に間違いなく。

物理データを送信しないことについて2セントを追加したかっただけです!正直言って、これでは十分強調できません。たとえそれが最適化されていても、実際には微小衝突で跳ね返る40個の球体を送信でき、フレームレートを低下させない揺れ部屋で全速力で移動できます。私はあなたが議論したデータ差分としても知られているあなたの「デルタ圧縮/エンコーディング」を実行することに言及しています。それは私が育てようとしていたものと非常に似ています。

推測航法とデータ差分:これらは十分に異なり、実際には同じメソッドを占有しません。つまり、両方を実装して最適化をさらに強化することができます。注:両方を一緒に使用したことはありませんが、両方で使用しました。

デルタエンコードまたはデータ差分:サーバーは、クライアントが知っているデータを保持し、古いデータとクライアントに変更する必要があるデータとの差分のみを送信します。例:pseudo->データがすでに「310 435 210 4000 40」である場合、データ「315 435 222 3546 33」を送信できます。一部はわずかに変更され、1つはまったく変更されません。それよりも、かなり短い「5 0 12 -454 -7」を(デルタで)送信します。

より良い例は、それよりもはるかに大きく変化するものかもしれません。たとえば、現在45個のリンクオブジェクトを含むリンクリストがあるとします。私はそれらの30を殺したいので、私はそれをしてから、新しいパケットデータが何であるかを全員に送信します。これは、このようなことをするためにまだ構築されていない場合、サーバーたとえば、自分自身を修正します。デルタエンコーディングでは、(pseudo) "list.kill 30 at 5"を置くだけで、5番目のオブジェクトの後、サーバーからではなく各クライアントで、30個のオブジェクトをリストから削除し、データを認証します。

長所:(現時点ではそれぞれ1つしか考えられません)

  1. 速度:明らかに、最後の例で説明しました。前の例よりもはるかに大きな違いになります。一般的に私は経験から、どちらがより一般的であるかを正直に言うことはできません。

短所:

  1. システムを更新していて、デルタを介して編集する必要があるデータをさらに追加する場合、そのデータを変更するための新しい関数を作成する必要があります。(たとえば、以前の「list.kill 30 at 5」のように、クライアントに取り消しメソッドを追加する必要があります!「list.kill undo」)

推測航法:簡単に言えば、これは類推です。ある場所への行き方に関する地図を誰かに書いていますが、それは十分だからです(建物に立ち寄って左に曲がってください)。他の人の地図には通りの名前と左折する角度が含まれていますが、それはまったく必要ですか?(いいえ...)

推測航法では、各クライアントがクライアントごとに一定のアルゴリズムを持っています。どのデータを変更する必要があるか、どのように変更するかを言うことで、データはほとんど変更されます。クライアントは独自にデータを変更します。たとえば、プレイヤーではなく、他の人と一緒にプレイしているキャラクターがいる場合、フレームごとにデータを更新する必要はありません。多くのデータが一貫しているからです!

私のキャラクターをある方向に動かしているとしましょう。多くのサーバーは、プレイヤーがいる場所(ほぼフレームごと)で、(アニメーションの理由で)動いているというデータをクライアントに送信します。それは非常に多くの不必要なデータです!ユニットがどこにあり、どの方向を向いていて、移動しているのか、すべてのフレームを更新する必要があるのはなぜですか?簡単に言えば:しません。クライアントは、方向が変わるとき、動詞が変わるとき(isMoving = true?)、およびオブジェクトが何であるかだけを更新します!その後、各クライアントはそれに応じてオブジェクトを移動します。

個人的にこれは常識の戦術です。それは私がずっと前に思いついたのが賢いと思ったもので、いつも使われていることが判明しました。

回答

率直に言って、ジェームズの投稿を読み、データについて私が言ったことを読んでください。はい、間違いなくデルタエンコードを使用する必要がありますが、推測航法の使用も検討してください。

個人的には、サーバーからデータに関する情報を受け取ったときに、クライアント上でデータをインスタンス化します(あなたが提案したもの)。

変更できるオブジェクトのみを最初に編集可能として注意する必要がありますか?コンポーネントとエンティティシステムを介して、オブジェクトにネットワークデータを含めることを含めるというアイデアが気に入っています。それは賢く、うまく動作するはずです。ただし、ブラシ(または完全に一貫性のあるデータ)にネットワーク手法を一切使用しないでください。彼らはそれを変更することさえできないものだからです(クライアントからクライアントへ)。

それがドアのようなものである場合、ネットワークデータを提供しますが、それが開いているかどうかについてのブール値のみを提供します。クライアントは、それを変更する方法を知っている必要があります。たとえば、開いたり閉じたり、各クライアントはすべて閉じる必要があることを受け取るので、ブールデータを変更してから、閉じられるドアをアニメートします。

ネットワーク化する変数をどのように知るかについては、真にサブオブジェクトであるコンポーネントを用意し、ネットワーク化したいコンポーネントを提供することができます。別のアイデアは、持っているだけでなくAddComponent("whatever")AddNetComponent("and what have you")個人的にスマートに聞こえるという理由だけでもあります。


これはとてつもなく長い答えです!それについてすみません。私はわずかな知識だけを提供し、いくつかのことについては2セントを提供するつもりでした。そのため、その多くは注意する必要がないかもしれません。
ジョシュアヘッジス

3

コメントを書くつもりだったが、これは答えに十分な情報かもしれないと判断した。

まず、答えを判断するための詳細がたくさんあるこのようなきちんと書かれた質問に対して+1。

データをロードするには、クライアントにワールドファイルからワールドをロードさせます。エンティティにデータファイルからのIDが含まれている場合は、デフォルトでそれらをロードするため、ネットワークシステムはそれらを参照して、それが話しているオブジェクトを知ることができます。同じ初期データをロードする全員は、それらがすべてそれらのオブジェクトに対して同じIDを持っていることを意味する必要があります。

第二に、NetworkComponentコンポーネントを作成しないでください。これは、他の既存のコンポーネントにデータを複製するだけなので、物理コンポーネント、アニメーションなどは一般的に送信するものです。独自の命名を使用するには、NetworkComponentManagerを作成することができます。これは、他のコンポーネントとComponentManagerの関係からわずかに外れますが、ネットワーク化されたゲームを開始するときにインスタンス化され、ネットワークの側面を持つ任意のタイプのコンポーネントがマネージャーにデータを提供してパッケージ化できるようにすることができます送ってください。前述のように、データのパッケージ化にも使用できるシリアル化/逆シリアル化メカニズムがある場合、ここで保存/読み込み機能を使用できます。

あなたの質問と情報のレベルを考えると、私はこれ以上詳しく説明する必要はないと思いますが、何か明確でない場合はコメントを投稿してください。これに対処するために回答を更新します。

お役に立てれば。


だから、あなたが言っていることは、ネットワーク化されるべきコンポーネントは、このようなインターフェイスを実装する必要があるということですか?:void SetNetworkedVariable(string name、NetworkedVariable value); NetworkedVariable GetNetworkedVariable(string name); NetworkedVariableは、補間やその他のネットワーク関連の目的で使用されます。ただし、これを実装するコンポーネントを特定する方法はわかりません。ランタイム型の識別を使用できますが、それはいようです。
カーター
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.