Many many thanks to anyone who chooses to take the time to read through this all and offer any advice.
I'm writing a game in which multiple different kinds of Objects move around a grid-based board structure and interact with each other in various ways. The objects are all derived from a relatively barebones base class
class Object{
int ID;
virtual void render() = 0;
virtual void update() = 0;
...
};
I'm also loosely using a component system. So for example there's a MovementComponent,
class MovementComponent{
direction currentMovementDir;
int movementTimer;
int speed;
};
Which Objects that can move contain a pointer to an instance of, which is instantiated upon creation of the Object. The Object base class has a virtual function for returning each kind of Component, as follows:
class Object{
...
virtual MovementComponent * getMovementComponent() {return NULL;}
virtual PathfindingComponent * getPathfindingComponent() {return NULL;}
//etc. for every kind of Component I introduce to the game code.
};
And then the derived classes just override these functions to provide pointers to their components. This is great, because I can sort of determine the kind of object I'm dealing with based on whether or not I get NULL when I request to see the object's various components. So as an example,
class Player : public Object{
MovementComponent * movementC_;
Player(){
movementC_ = new MovementComponent;
}
~Player(){
delete movementC_;
}
void update(){
//Perform input stuff here etc.
}
virtual MovementComponent * getMovementComponent(){return movementC_;}
}
and so on so forth.
My board structure is simply an array of pointers to Objects, and the movement of Objects on the board is handled by a whole different system. All of the Objects are in a linked list held by a wrapper class called ObjectManager which handles adding new objects of any kind to the game or removing them etc.
My question is that I'm having an increasingly hard time writing elegant code for the various kinds of object interactions that are meant to take place in the game. The controls are intended to be fairly simple, so for example the player just 'actions' the tile they are facing and the game logic handles how this should play out.
Lets say for example its an enemy. 'Action'ing the enemy should just attack the enemy's health based on our current weapon's strength (or something along those lines, this game isn't really about combat, but just for the sake of the example). Within the player class's update function, the part that processes input might look like
void Player::update(){
...
if(actionKeyPressed){
//Uses the map's list of pointers to get a pointer to whatever object is in the tile in front of the player.
Object * target = WorldMap.getObject(pos,facing);
if(target.getHealthComponent() && target.getIsEnemyComponent()){
target.getHealthComponent.attack(weaponstrength);
}
}
}
But you can see how this turns into very ugly code very fast. I can't not check for a healthComponent because if there isn't one, I'll throw an error when I try to invoke its 'attack()' member function. Is there an elegant way to avoid the massive if-else branch I'm going to be sitting on here? It starts to get even worse when we look at Object interactions that might function differently in different contexts with the same Object types. Maybe there's a FriendlyTroop Object that, when actioned by the player at < full health, heals the FriendlyTroop, but when at full health, instead sets a flag to true in the FriendlyTroop's AI that tells it to head out into battle. I guess in addition to any general advice anyone has to offer, the big questions I have are:
-Am I doing this right at all? Is there a better way to handle this overall type-checking dilemma?
-Does every element of that FriendlyTroop's AI need to be pushed out into a unique FriendlyTroopAIComponent type now so that the Player class can interact with it?
-What if I wanted to add an NPC that could perform the same actions as the Player? Would this 'action' if-else structure be better in a PlayerAction component that this NPC could then also have an instance of? Or should the NPC just be a derived class of the Player? Or vice versa???
No comments:
Post a Comment