ゲームメッセージングシステムの設計


8

私は簡単なゲームを作っており、メッセージングシステムの実装を試みることにしました。

システムは基本的に次のようになります。

エンティティはメッセージを生成します->メッセージはグローバルメッセージキューにポストされます-> messageManagerはonMessageReceived(Message msg)を介してすべてのオブジェクトに新しいメッセージを通知します->オブジェクトが必要な場合、メッセージに作用します。

メッセージオブジェクトを作成する方法は次のとおりです。

//base message class, never actually instantiated
abstract class Message{
  Entity sender;
}

PlayerDiedMessage extends Message{
  int livesLeft;
}

これで、SoundManagerEntityは、onMessageReceived()メソッドでこのようなことを実行できます。

public void messageReceived(Message msg){
  if(msg instanceof PlayerDiedMessage){
     PlayerDiedMessage diedMessage = (PlayerDiedMessage) msg;
     if(diedMessage.livesLeft == 0)
       playSound(SOUND_DEATH);
  }
}

このアプローチの利点:

  1. 非常にシンプルで実装が簡単
  2. 必要な情報を持つ新しいMessageサブクラスを作成するだけでよいので、メッセージには必要なだけの情報を含めることができます。

短所:

  1. メッセージのサブクラスごとに異なるプールがない限り、メッセージオブジェクトをオブジェクトプールにリサイクルする方法を理解できません。そのため、時間の経過とともに、オブジェクトの作成やメモリの割り当てが非常に多くなっています。
  2. 特定の受信者にメッセージを送信できませんが、ゲームではまだ必要ないので、あまり気にしません。

ここで何が欠けていますか?より良い実装または私が見逃しているいくつかのアイデアがあるはずです。


1)それらが何であれ、なぜそれらのオブジェクトプールを利用する必要があるのですか?(余談ですが、それらは何で、どのような目的に役立ちますか?)何が機能するかを超えて設計する必要はありません。そして、あなたが現在持っているものは、それに問題がないようです。2)特定の受信者にメッセージを送信する必要がある場合は、メッセージを見つけてそのonMessageReceivedを呼び出します。特別なシステムは必要ありません。
snake5 2012年

さて、私は現在、オブジェクトを汎用オブジェクトプールにリサイクルすることはできません。したがって、必要なメモリ割り当てを減らしたい場合は、できません。今のところ、私のゲームは非常にシンプルなので、問題なく実行できます。
you786

これはJavaだと思いますか?イベントシステムを開発しようとしているようです。
ジャスティンスカイズ

@JustinSkiles正解、これはJavaです。私はあなたが正しいと思う、それは本当にイベントシステムとして機能するために使用されます。メッセージングシステムを使用するには、他にどのような方法がありますか?
you786

回答:


11

簡単な答えは、メッセージをリサイクルしたくないということです。1つは、パーティクルのようにフレームごとに数千個作成または削除されたオブジェクト、または非常に重いオブジェクト(数十のプロパティ、長い初期化プロセス)をリサイクルします。

いくつかのビットマップ、速度、関連付けられた物理属性を持つスノーパーティクルがビューの下に落ちた場合、新しいパーティクルを作成してビットマップを初期化し、属性を再度ランダム化する代わりに、雪粒子をリサイクルすることができます。この例では、Messageを維持するのに役立つものは何もありません。新しいMessageオブジェクトを作成するよりも、インスタンス固有のプロパティを削除するために多くのリソースを浪費します。


3
いい答えです。私はメッセージにプールを使用しており、それらが有用かどうかを実際に調査したことはありません。(過去-)​​プロファイルする時間!
ラプトルミット

うーん。フレームごとに出力されるメッセージがある場合はどうなりますか?答えは、その種のオブジェクト用の特定のプールを持つことだと思います。これは理にかなっています。
you786

@ you786 Actionscript 3で簡単なダーティテストを行いました。遅いデバッグ環境では、配列を作成して1000メッセージを入力します。これで問題が解決することを願っています。
Markus von Broady、2012年

@ you786彼は正解です。最初にこれをボトルネックとして特定します。インディーズゲームの開発者になりたい場合は、物事を高速かつ効果的に行う方法を学ぶ必要があります。コードの微調整に時間をかけることは、そのコードが現在ゲームの速度を低下させ、これらの変更を行うとパフォーマンスが大幅に向上する場合にのみ価値があります。たとえば、AS3でSprtieインスタンスを作成すると、非常に時間がかかります。それらをリサイクルする方がはるかに優れています。Pointオブジェクトの作成はそうではありません。それらをリサイクルすると、コーディング時間と可読性の無駄になります。
AturSams

まあ、もっと明確にすべきだったのですが、メモリの割り当てについては心配していません。ゲームの途中でJavaのガベージコレクターが実行され、速度が低下するのではないかと心配しています。しかし、時期尚早の最適化についてのあなたの主張は依然として有効です。
you786

7

私のJavaベースのゲームでは、メッセージ処理コードが次のようになることを除いて、非常によく似たソリューションを思い付きました。

@EventHandler
public void messageReceived(PlayerDiedMessage msg){
  if(diedMessage.livesLeft == 0)
     playSound(SOUND_DEATH);
}

アノテーションを使用して、メソッドをイベントハンドラーとしてマークします。次に、ゲームの開始時に、リフレクションを使用してメッセージのマッピング(または「パイピング」)を計算します。特定のクラスのイベントが送信されたときに、どのオブジェクトでどのメソッドを呼び出すかを計算します。これはうまくいきます...

ミックスにインターフェースとサブクラスを追加したい場合、パイピングの計算は少し複雑になる可能性がありますが、それでもかなり簡単です。すべてのクラスをスキャンする必要がある場合、ゲームの読み込み中に若干の速度低下がありますが、それは1回だけ行われます(おそらく別のスレッドに移動できます)。代わりに得られるのは、メッセージの実際の送信の方が安いということです。コードベースのすべての "messageReceived"メソッドを呼び出して、イベントが適切なクラスかどうかを確認する必要はありません。


3
基本的に、ゲームの実行速度を遅くする方法を見つけましたか?:D
Markus von Broady 2012年

3
@MarkusvonBroady一度ロードした場合、それだけの価値があります。それを私の答えの怪物と比較してください。
michael.bartnett 2012年

1
すべての要素が揃っていれば、リフレクションによる@MarkusvonBroadyの呼び出しは通常の呼び出しよりもわずかに遅くなります(少なくとも、私のプロファイリングでは問題は示されていません)。確かに、発見にはコストがかかります。
Liosan 2012年

@ michael.bartnett自分ではなく、ユーザー用にコーディングします。実行を高速化するために、コードを大きくしておくことができます。しかし、それは私の主観的な発言でした。
Markus von Broady、2012年

+1、注釈パスは私が考えたことがないものです。だから明確にするために、基本的にはMessageReceivedと呼ばれるオーバーロードされたメソッドの束があり、Messageのさまざまなサブクラスがあります。次に、実行時にリフレクションを使用して、MessageSubclassを計算するある種のマップを作成します=> OverloadedMethod?
you786

5

編集 Liosanの答えはよりセクシーです。それに見る。


Markusが言ったように、メッセージはオブジェクトプールの一般的な候補ではありません。彼が述べた理由でそれを気にしないでください。ゲームが特定のタイプのメッセージを大量に送信する場合、おそらくそれは価値があります。しかし、その時点で、直接メソッド呼び出しに切り替えることをお勧めします。

そういえば、

特定の受信者にメッセージを送信できませんが、ゲームではまだ必要ないので、あまり気にしません。

送信したい特定の受信者がわかっている場合、そのオブジェクトへの参照を取得して直接メソッドを呼び出すのは簡単ではないでしょうか。システム間のアクセスを容易にするために、マネージャークラスの背後に特定のオブジェクトを隠すこともできます。

実装には1つの欠点があり、多くのオブジェクトが本当に気にしないメッセージを受け取る可能性があるということです。理想的には、クラスは実際に気にするメッセージのみを受信します。

を使用して、HashMap<Class, LinkedList<Messagable>>メッセージタイプを、そのタイプのメッセージを受信するオブジェクトのリストに関連付けることができます。

メッセージタイプをサブスクライブすると、次のようになります。

globalMessageQueue.subscribe(PlayerDiedMessage.getClass(), this);

あなたはsubscribeそのように実装することができます(私にいくつかの間違いを許してください、数年でJavaを書いていない):

private HashMap<Class, LinkedList<? extends Messagable>> subscriberMap;

void subscribe(Class messageType, Messagable receiver) {
    LinkedList<Messagable> subscriberList = subscriberMap.get(messageType);
    if (subscriberList == null) {
        subscriberList = new LinkedList<? extends Messagable>();
        subscriberMap.put(messageType, subscriberList);
    }

    subscriberList.add(receiver);
}

そして、次のようなメッセージをブロードキャストできます。

globalMessageQueue.broadcastMessage(new PlayerDiedMessage(42));

そしてbroadcastMessage、このように実装することができます:

void broadcastMessage(Message message) {
    Class messageType = message.getClass();
    LinkedList<? extends Messagable> subscribers = subscriberMap.get(messageType);

    for (Messagable receiver : subscribers) {
        receiver.onMessage(message);
    }
}

メッセージ処理をさまざまな匿名関数に分割できるようにメッセージをサブスクライブすることもできます。

void setupMessageSubscribers() {
    globalMessageQueue.subscribe(PlayerDiedMessage.getClass(), new Messagable() {
        public void onMessage(Message message) {
            PlayerDiedMessage msg = (PlayerDiedMessage)message;
            if (msg.livesLeft <= 0) {
                doSomething();
            }
        }
    });

    globalMessageQueue.subscriber(EnemySpawnedMessage.getClass(), new Messagable() {
        public void onMessage(Message message) {
            EnemySpawnedMessage msg = (EnemySpawnedMessage)message;
            if (msg.isBoss) {
                freakOut();
            }
        }
    });
}

少し冗長ですが、整理に役立ちます。すべてについて聞きたいのsubscribeAll個別のリストを保持する2番目の関数を実装することもできMessagableます。


1

。しないでください。

メッセージは低コストです。Markus von Broadyに同意します。GCはそれらを迅速に収集します。メッセージに多くの追加情報を添付しないようにしてください。それらは単なるメッセージです。のインスタンスの検索は、それ自体が情報です。それを使用します(例で行ったように)。

MessageDispatcherを使用してみてください。

最初のソリューションとは対照的に、メッセージを処理して目的のオブジェクトに渡す集中型メッセージディスパッチャーを使用できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.