11

私は少しピクルスになっています。単純な2Dのゼルダのようなゲームを作っているとしましょう。2つのオブジェクトが衝突すると、それぞれにアクションが発生するはずです。ただし、主人公が何かに衝突した場合の反応は、衝突したオブジェクトの種類にのみ依存します。それがモンスターの場合、彼は跳ね返る必要があります、それが壁の場合、何も起こらないはずです、それがリボン付きの魔法の青い箱の場合、彼は癒す必要があります、など(これらは単なる例です)。

また、両方が衝突の一部であることに注意する必要があります。つまり、衝突イベントは、どちらか一方だけでなく、キャラクターとモンスターの両方で発生する必要があります。

このようなコードをどのように記述しますか?たとえば、グローバルWorldObjectクラスに仮想関数を配置して、属性を識別する、非常に洗練されていない方法をいくつか考えることができます。たとえば、GetObjectType()関数(int、char * s、オブジェクトをモンスターとして識別するものを返します) 、Box、またはWall)の場合、Monsterなどのより多くの属性を持つクラスでは、GetSpecies()などのより多くの仮想関数が存在する可能性があります。

ただし、これは維持するのが面倒になり、衝突ハンドラーに大きなカスケードスイッチ(またはIf)ステートメントが発生します。

MainCharacter::Handler(Object& obj)
{
   switch(obj.GetType())
   {
      case MONSTER:
         switch((*(Monster*)&obj)->GetSpecies())
         {
            case EVILSCARYDOG:
            ...
            ...
         }
      ...
   }

}

ファイルを使用するオプションもあり、ファイルには次のようなものがあります。

Object=Monster
Species=EvilScaryDog
Subspecies=Boss

そして、コードは、仮想関数がすべてを乱雑にすることなく、属性を取得できます。ただし、これはカスケードIfの問題を解決しません。

そして、それぞれの場合に関数を持つオプションがあります。たとえば、CollideWall()、CollideMonster()、CollideHealingThingy()などです。これは個人的には私の最も嫌いなものです(ただし、それらはすべて好感が持てるとは言えませんが)、維持するのが最も面倒なようです。

誰かがこの問題のよりエレガントな解決策についていくつかの洞察を与えてもらえますか?助けてくれてありがとう!

4

5 に答える 5

11

逆もまた同様です。キャラクターがオブジェクトと衝突すると、オブジェクトもキャラクターと衝突するからです。したがって、次のような基本クラスObjectを持つことができます。

class Object  {
  virtual void collideWithCharacter(MainCharacter&) = 0;
};

class Monster : public Object  {
  virtual void collideWithCharacter(MainCharacter&) { /* Monster collision handler */ }
};

// etc. for each object

一般に、OOP設計では、仮想関数が次のような場合の唯一の「正しい」ソリューションです。

switch (obj.getType())  {
  case A: /* ... */ break;
  case B: /* ... */ break;
}

編集
明確化した後、上記を少し調整する必要があります。はMainCharacter、衝突する可能性のあるオブジェクトごとにオーバーロードされたメソッドを持っている必要があります。

class MainCharacter  {
  void collideWith(Monster&) { /* ... */ }
  void collideWith(EvilScaryDog&)  { /* ... */ }
  void collideWith(Boss&)  { /* ... */ }
  /* etc. for each object */
};

class Object  {
  virtual void collideWithCharacter(MainCharacter&) = 0;
};

class Monster : public Object  {
  virtual void collideWithCharacter(MainCharacter& c)
  {
    c.collideWith(*this);  // Tell the main character it collided with us
    /* ... */
  }
};

/* So on for each object */

このようにして、衝突について主人公に通知し、適切なアクションを実行できます。また、衝突について主人公に通知してはならないオブジェクトが必要な場合は、その特定のクラスの通知呼び出しを削除するだけです。

このアプローチは、ダブルディスパッチと呼ばれます。

また、MainCharacter自体を、Objectに移動し、オーバーロードをに移動して、の代わりにObject使用することも検討します。collideWithcollideWithCharacter

于 2010-09-04T21:38:37.237 に答える
2

1つの共通の抽象クラスからすべての衝突可能なオブジェクトを派生させるのはどうですか(衝突可能と呼びましょう)。そのクラスには、衝突と1つのHandleCollision関数によって変更できるすべてのプロパティを含めることができます。2つのオブジェクトが衝突する場合、引数として他のオブジェクトを使用して、各オブジェクトでHandleCollisionを呼び出すだけです。各オブジェクトは、衝突を処理するために他のオブジェクトを操作します。どちらのオブジェクトも、バウンスしたばかりの他のオブジェクトタイプを知る必要はなく、大きなswitchステートメントはありません。

于 2010-09-04T21:38:28.727 に答える
1

すべてのcollidableエンティティにcollideWith(Collidable)メソッドを使用したインターフェイス(「Collidable」など)を実装させます。次に、衝突検出アルゴリズムで、AがBと衝突することを検出した場合、次のように呼び出します。

A->collideWith((Collidable)B);
B->collideWith((Collidable)A);

AがメインキャラクターでBがモンスターであり、どちらもCollidableインターフェイスを実装していると仮定します。

A->collideWith(B);

次のように呼びます:

MainCharacter::collideWith(Collidable& obj)
{
   //switch(obj.GetType()){
   //  case MONSTER:
   //    ...
   //instead of this switch you were doing, dispatch it to another function
   obj->collideWith(this); //Note that "this", in this context is evaluated to the
   //something of type MainCharacter.
}

これにより、Monster :: collideWith(MainCharacter)メソッドが呼び出され、そこですべてのモンスターキャラクターの動作を実装できます。

Monster::CollideWith(MainCharacter mc){
  //take the life of character and make it bounce back
  mc->takeDamage(this.attackPower);
  mc->bounceBack(20/*e.g.*/);
}

詳細:シングルディスパッチ

それが役に立てば幸い。

于 2010-09-04T22:37:51.547 に答える
1

あなたが「迷惑なswitchステートメント」と呼ぶもの私は「素晴らしいゲーム」と呼ぶので、あなたは正しい軌道に乗っています。

すべてのインタラクション/ゲームルールに対応する機能を持つことは、まさに私が提案することです。これにより、新しい機能の検索、デバッグ、変更、および追加が簡単になります。

void PlayerCollidesWithWall(player, wall) { 
  player.velocity = 0;
}

void PlayerCollidesWithHPPotion(player, hpPoition) { 
  player.hp = player.maxHp;
  Destroy(hpPoition);
}

...

したがって、問題は、これらの各ケースをどのように検出するかということです。XとYの衝突を引き起こすある種の衝突検出があると仮定すると(N ^ 2オーバーラップテストのように単純です(プラントvsゾンビで機能し、多くのことが行われています!)、またはスイープとプルーンのように複雑です+ gjk)

void DoCollision(x, y) {
  if (x.IsPlayer() && y.IsWall()) {   // need reverse too, y.IsPlayer, x.IsWall
     PlayerCollidesWithWall(x, y);    // unless you have somehow sorted them...
     return;
  }

  if (x.IsPlayer() && y.IsPotion() { ... }

  ...

このスタイルは、冗長ですが

  • デバッグが簡単
  • ケースを追加するのは簡単
  • 論理的/設計上の不一致または脱落がある場合に表示されます。「「PosessWall」機能のためにXがプレーヤーと壁の両方である場合はどうなりますか?!?!」(そして、それらを処理するためにケースを追加するだけで済みます)

Sporeのセルステージはまさにこのスタイルを使用しており、約100のチェックがあり、約70の異なる結果が得られます(パラメーターの反転はカウントされません)。たった10分のゲームで、ステージ全体で6秒ごとに1つの新しいインタラクションがあります。これがゲームプレイの価値です。

于 2010-09-22T23:57:57.017 に答える
0

私があなたの問題を正しく受けているなら、私は

Class EventManager {
// some members/methods
handleCollisionEvent(ObjectType1 o1, ObjectType2 o2);
// and do overloading for every type of unique behavior with different type of objects.
// can have default behavior as well for unhandled object types
}
于 2010-09-04T22:25:14.640 に答える