Enemy Manager and Ambiguity.
|
For this week, my primary focus was on rehauling how we manage our enemy objects in the game. Previously, we have in our gamestate stored all our managers in an vector, and performing all the actions that should be done manually. This includes checking collisions against all colliding objects (the player, owlets that are following the player, projectiles, and other hazards like thunderclouds), as well as putting them away in an separate pool when too far away from the player, and retrieving them when we want to spawn a new one. This both affected the readability of the code, and made it quite hard to introduce new enemies, requiring you to create a new type of vector to store the new type of enemy, and a lot of unnessecary work in making sure everything is updated, checked for collisions against everything colliding etc. So, we decided it was time to address this issue. The result we’re going for, is that whenever we want do something related to enemies, we simply tell the class managing the enemies to do it. Whatever we want to do, nothing more than making one function call should be nessecary. And so it began. In order to keep the code readable, and not have a bazillion different containers, one for each type of enemy, I made a base class so that we can just have one container for all enemies that should be interacted with. This also makes it much less work when implementing new types of enemies, since as long as we make it inherit from the base class, we do not need to change any functions at all. One problem came up when optimizing our manager, but first some background of what we were doing: (Pooling objects is a good practice, this lets you recycle objects, which saves the processor the work of constantly creating and destroying objects constantly, read more about it HERE: http://gameprogrammingpatterns.com/object-pool.html), This is more efficient than the solution of using a flag within the object that indicates if it is active or not, since now when we run our code we do not need to look in objects and check if we need to skip it or if we want to run it. Every object referenced in the vector should be updated, or drawn, or whatever it is we’re doing. These are both not too hard to implement, and together its still not very complicated. However when we started mixing in that we wanted to have enemies that are fleeing, that also were called inactive at the time, caused a bunch of headache and confusion in how we should solve the problem. If we put an enemy that has been hit in the inactive pool when it is hit, we stop drawing it (since we dont want to draw things that do not need to be drawn), and we want to see the enemy flee instead of popping out of existence. This is when we realised we could, instead of calling it active or inactive, we can create inside each enemy a flag that states if it collides or not, and when we check our collisions, we skip the object if it has a flag that says it doesn’t collide. Although this is the very thing we worked against when we were optimizing previously, creating an additional two pools would probably be even more efficient than using this method. The fleeing behavior was simply handled as another state the enemy can be in, with its own movement pattern, since we no longer need to inactivate it when it is hit. In the end it was a really simple problem, but due to calling different things by the same name it ended up taking much more time than nessecary. Be careful with the language you use when first describing a problem, and try to avoid ambiguity, as that is really setting yourself up for trouble.
|
