• 11
• 9
• 10
• 9
• 10
• entries
686
1952
• views
386917

# So...wait...ok...

107 views

I've been pondering over a problem in my code for awhile:

How to make enemies interact with each other.

This may seem like it should be easy, but the way I wrote the code makes it nearly impossible for enemy objects to directly interact with each other.

Why? Well first, I need to explain how the enemy objects work.

First, there is an abstract enemy base class. This holds all the properties common to every enemy. All enemies are derived from that class.

Than all enemies that are currently spawned into the map are handled by the "Enemy Manager", which updates and draws them. The enemy manager boils down to one thing in the end, this:

std::vector enemies;

Any type of enemy class can be added to this vector, but once they are, they essentialy lose their identity. Ofcourse, I suppose that I could find out the type of an enemy by:

typeid(*enemies[0])

This still doesn't fix my problem. The problem really lies in the fact that, by default, an enemy class doesn't "know" that there will be more enemies. If it doesn't know about other enemies, how can it directly interact with them?

The C++ Cookbook supplies a possible solution. One of the "recipes" in the book regards creating classes that automatically store themselves in a container:

class Enemy{private:   static std::vector enemies;protected:   // Member variablespublic:   Enemy()   {      // Automatically insert self into container upon creation      // Will this work?      enemies.push_back(this);   }      // Methods};

Usually when I think of class recursion like this, my brain starts to hurt, and I have to lie down.

Ofcourse, I won't be using this method, because I don't believe it will work.

So with the problem still not solved, I kept thinking. The only solution I can think of is to have enemies send signals. Since enemy objects are not able to interact directly, I figure that they can act indirectly through messages sent out from the enemies themselves.

To give an example, I recieved a suggestion awhile ago that BombBots should be able to damage other enemies when they explode. I didn't think I could do this at the time, but now I think I should change my enemy code so this is possible. For instance, when a BombBot dies, it will send out a signal saying that there is an explosion at it's current location. Then, if another enemy walks into the explosion area, they will get that signal and the object will know that it should take damage.

This would also allow the level 2 battleships to parachute in new enemies without having to allocate memory. Instead of having to create new enemies to parachute, using a signal would allow dead enemies to be reused.

Much like the homing launcher, this is still theory and not yet tried, so I don't even know if it will work.

Quote:
 Ofcourse, I won't be using this method, because I don't believe it will work.

Did you even try it? To me it looks like it would work. I was planning on using something like that except by giving each enemy a pointer to a global vector, but a static vector should do the same thing(and probably look cleaner).

Is storing the type as an int or something out of the question. Mine is set up like so:
class Enemy
{
const int m_nType;
public:
Enemy(int nType) : m_nType(nType){}

int GetType(){...}
};

class Goblin : public Enemy
{
public:
Goblin() : Enemy(MONSTER_GOBLIN){}
};

class Gnoll : public Enemy
{
public:
Gnoll() : Enemy(MONSTER_GNOLL){}
};

etc


Quote:
 Did you even try it? To me it looks like it would work. I was planning on using something like that except by giving each enemy a pointer to a global vector, but a static vector should do the same thing(and probably look cleaner).

Good point, I shouldn't say that before I actually try it.

However, one thing I predict is that doing this kind of method will bring up a bigger problem with regard to creating and destroying the enemies between levels.

Still, I suppose I should see where the idea could take me before denouncing it.

About the int flags, that's another thing that I need to put in. However, as I think I mentioned above, knowing the type of the enemy won't necessarily fix the problem, but it might certainly make it easier.

A message/event system should work fine. Here's how I would do it:

- Create a separate CharacterEventTracker class

- Create a separate CharacterEvent base class with a single virtual method Run(); have Run() return true to keep the event around for another frame, or false to remove the event from the CharacterEventTracker. Run() should get passed a const reference to the EnemyManager so that events can "see" all the enemies in the game. You might also consider having the CharacterEvent constructor take a reference to the Enemy that triggered the event, but that may not be all that useful depending on the types of events you create.

- Derive a class from CharacterEvent for each event type. For example, you could have a CharacterExplodingEvent for the BombBots when they die.

- Have an instance of CharacterEventTracker take care of gathering CharacterEvent objects and processing them each frame (i.e. call Run() on each one). The tracker only has to know that the objects it holds are CharacterEvents; the compiler will take care of calling Run() in the right event class since it is virtual.

- Now, when a BombBot dies, create a new CharacterExplodingEvent() and add it to the CharacterEventTracker. The next frame, the tracker will call Run() on the event, and give it the list of all enemies. The event class can then look at each enemy, see if it is in range of the explosion, and damage them if so.

That should do it, and accomplish what you need with a minimum of code changes.

That sounds like a good way to make it, thank you. It is indeed more ambitious than the system I was thinking of, but would allow for more possibilities.

In addition, I might have to do some memory cleanup during runtime, because I don't want "dead" events lingering around.

I know what I'll be working on next now...

Excellent suggestions ApochPiQ.

For completeness, you could also use a signal-slot framework (see Boost.Signals) to handle the interactions.

Wow, this is complicated stuff.

I have one preliminary question: All of this enemy state memory management and all of that, aren't we talking like mere kilobytes at most of memory, the kind of quantities that are hardly worth worrying about? I might get yelled at for that, lol, but in the age of having GBs of physical memory...

Following that (and following your mental note to not trust any programming advice I have ever again! :p), I'll offer my minor suggestions about enemy communication...

I think the 'GetEnemyType()' (storing type as int) is the best method. That's because it's the one I use! Nah, it's what I was thinking, though, more or less.

Now, here's how I'd do enemy communication. There are basically two things you have to know:

1) Is an event happening where inter-enemy communication is necessary? (e.g., exploding bomb-bot)

2) Should / will any other enemies be affected? (e.g., blast radius)

Question 1 is easy to answer. For your bomb bot, at some point you'll detect that it's been hit by a player's bullet and should explode. At this point, Question #1 is YES. Now, you need to look at Question #2. Here, you could just do a quick loop through all of the enemies.

For the bomb-bot explosion, you'd do a quick distance calculation; if any enemy is within 50 pixels (for instance) of the explosion, they are going to be affected, so you lower their health, too. And maybe, the enemy in question is ALSO a bomb-bot, and the explosion also causes it to explode. Then you'll have a nested Question #1, and you'll have to do a loop inside the loop to check all the enemies. I'm thinking something along the lines of a Explode() function for the Enemy class, or your special bomb-bot inherited class (if that's what you call it?).

In that case Question #2's YES/NO would be decided based on distance. Maybe in other cases you'd decide it based on enemy type. There could be a sonar explosion when you destroy a spaceship that goes across the entire level and scrambles the circuits of infantry robots, causing them to attack each other for 5 seconds or something. Then, instead of a 50-pixel distance test, you'd just check to see if each enemy object is type == INFANTRY, and adjust accordingly.