I am creating a relatively simple game engine in C++ and Qt. I am using Irrlicht for graphics, and as of now, I will not be using any physics or audio libraries. For time's sake, I am using Irrlicht's built-in bounding-box for collision detection and neglecting audio.
Here is what I would like:
GameObject: An update-able container that has guaranteed components. Note that an update-able object simply means there exists a virtual function, update, that allows the object to update with respect to the time from the last rendered frame. Updateable is an Abstract Class.
- TransformComponent: Encapsulates the responsibility of storing position, rotation and size. Whenever a property is changed, TransformComponent emits a signal, informing its listeners that the property has changed by a given delta-value. GameObject's TransformComponent keeps its other components aligned. For example, changing the position of a GameObject's TransformComponent will change the position position of the GameObject's graphical representation and its physics-related information. This information is relayed via signals and slots that GameObject handles at instantiation-time. Note that the components know nothing of each other -- GameObject simply handles the following task: signals from a given component need to talk to slots of another component.
- GraphicsComponent: Encapsulates the responsibility of rendering and playing animations. GraphicsComponent has a TransformComponent, which stores the graphical object's (Irrlicht-functionality) position, size and rotation. Note that updating a GameObject's TransformComponent will update the GameObject's GraphicsComponent's TransformComponent. However, these *TransformComponent*s are loosely coupled in the sense that it is possible to update the position of GraphicsComponent's TransformComponent without updating the position of the GameObject's TransformComponent. Again, this is all done via event-listeners.
- PhysicsComponent: Encapsulates the responsibility of determining when it has collided with another GameObject. This is where I am having trouble.
In order to do this, GameObject must know about PhysicsComponent, and PhysicsComponent must know about GameObject. Not only is this is a circular reference, which denotes strong coupling, but one of my Components must know about its aggregate class, which contradicts the entire purpose of black-box reuse.
For the curious, here is some example code for how my GameObject works:
class Player : public GameObject
{
Q_OBJECT
public:
Player();
~Player();
protected slots:
void onUpdate(float deltaTime); // virtual function from GameObject
void onEnteredCollision(GameObject* gameObject) // An imaginary function that I would like to have.
};
Player::Player() : GameObject() {
// Initialize player-specific member variables here.
// Here is where I would setup some specific signals and slots.
// Below I show how I would connect Player to PhysicsComponent's onEnteredCollision signal.
connect(getPhysicsComponent(), SIGNAL(onEnteredCollision(GameObject*), this, SLOT(onEnteredCollision(GameObject*))); // Now our method, onEnteredCollision(GameObject* gameObject) is called whenever a GameObject collides with Player.
}
void Player::onUpdate(float deltaTime) {
// An example of how to move the character:
getTransform()->translate(0, 10 * deltaTime, 0); // Moves Player 10 units per second in the positive y-direction.
// An example of how to move the character's graphical object:
getGraphics()->getTransform()->translate(10 * deltaTime, 0, 0); // Moves Player's graphical object 10 units per second in the positive x-direction.
}
void Player::onCollisionEntered(GameObject* gameObject) {
// Collision-code goes here.
}
(Note that I could continue using component-based paradigms to encapsulate the idea of capturing Player's collision into a PlayerCollisionComponent. However, the above code more than exemplifies what I would like to do, and I left that further abstraction out for simplicity.)
The issue with how this works is as aforementioned. PhysicsComponent must have some logic that calculates when its physics object (Collider, for example) has collided with another GameObject's Collider, and then inform (through signals) which GameObject is being collided with. This makes PhysicsComponent strongly coupled with GameObject and also creates an ugly C++ problem with circular dependencies. In a way, it makes sense that the two are coupled, but I am convinced there must be a better alternative.
Does anyone have any feedback or experience?
Thanks in advance,