Monday, July 8, 2019

c++ - How should game objects be aware of each other?


I find it hard to find a way to organize game objects so that they are polymorphic but at the same time not polymorphic.


Here's an example: assuming that we want all our objects to update() and draw(). In order to do that we need to define a base class GameObject which have those two virtual pure methods and let polymorphism kicks in:


class World {
private:
std::vector objects;
public:
// ...
update() {
for (auto& o : objects) o->update();

for (auto& o : objects) o->draw(window);
}
};

The update method is supposed to take care of whatever state the specific class object needs to update. The fact is that each objects needs to know about the world around them. For example:



  • A mine needs to know if someone is colliding with it

  • A soldier should know if another team's soldier is in proximity

  • A zombie should know where the closest brain, within a radius, is



For passive interactions (like the first one) I was thinking that the collision detection could delegate what to do in specific cases of collisions to the object itself with a on_collide(GameObject*).


Most of the the other informations (like the other two examples) could just be queried by the game world passed to the update method. Now the world does not distinguish objects based on their type (it stores all object in a single polymorphic container), so what in fact it will return with an ideal world.entities_in(center, radius) is a container of GameObject*. But of course the soldier does not want to attack other soldiers from his team and a zombie doesn't case about other zombies. So we need to distinguish the behavior. A solution could be the following:


void TeamASoldier::update(const World& world) {
auto list = world.entities_in(position, eye_sight);
for (const auto& e : list)
if (auto enemy = dynamic_cast(e))
// shoot towards enemy
}

void Zombie::update(const World& world) {

auto list = world.entities_in(position, eye_sight);
for (const auto& e : list)
if (auto enemy = dynamic_cast(e))
// go and eat brain
}

but of course the number of dynamic_cast<> per frame could be horribly high, and we all know how slow dynamic_cast can be. The same problem also applies to the on_collide(GameObject*) delegate that we discussed earlier.


So what it the ideal way to organize the code so that objects can be aware of other objects and be able to ignore them or take actions based on their type?



Answer



Instead of implementing the decision-making of each entity in itself, you could alternatively go for the controller-pattern. You would have central controller classes which are aware of all objects (which matter to them) and control their behavior.



A MovementController would handle the movement of all objects which can move (do the route finding, update positions based on current movement vectors).


A MineBehaviorController would check all the mines and all the soldiers, and command a mine to explode when a soldier gets too close.


A ZombieBehaviorController would check all zombies and the soldiers in their vicinity, pick the best target for each zombie, and command it to move there and attack it (the move itself is handled by the MovementController).


A SoldierBehaviorController would analyze the whole situation and then come up with tactical instructions for all soldiers (you move there, you shoot this, you heal that guy...). The actual execution of these higher-level commands would also be handled by lower-level controllers. When you put some effort into it, you could make the AI capable of quite smart cooperative decisions.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...