Okay, yeah doing everything with inheritance is really messy. However, I'm still not sure how to solve some issues with composition:
1) Who gets what data? My actor needs a position to display on the screen, but so do my CollisionObject and MoveLogic. Do I just give them each a position member and then update all of them whenever any of them change? If I modify them through the unit class that's holding all of them, it seems easy enough to pass the data along, but what happens when my CollisionObject detects a collision and wants to move? I now have to get that information back to my Unit object or? I'm not really sure.
EDIT: My most important question: How does my position get from my MoveLogic to my CollisionObject? My gameloop at the moment is something like this:
- Process input from user. Pass commands to actors and let them delegate the responsibility.
- Loop through all logic objects (MoveLogic, Collision), and let them do their logic.
- Loop through Actor objects and let them update themselves based on their logic objects (grab new position from MoveLogic, etc.)
- Get render data from Actors, draw to window
- repeat.
Have a data structure called PositionInfo, which is given to any object that needs a position in the game world. The MoveLogic modifies this data, the CollisionLogic modifies this data during the collision resolve step, the RenderLogic reads this data to draw the object at the right location.
Have a data structure called CollisionInfo, which is given to any object that can collide with other stuff in the game world. The CollisionLogic reads it and determines if it's colliding with any other CollisionInfo structure.
Have a data structure called RenderInfo, which contains any data required to draw the object (mesh, sprite, etc). The RenderLogic reads from this data, and draw its contents at the location indicated by the PositionInfo data.
Similar with data structures AnimationInfo, LocomotionInfo, InputInfo, HealthInfo, WeaponInfo, etc etc for any other unit data information.
Create a class called Tree, which composes the CollisionInfo, RenderInfo and PositionInfo. The RenderLogic requires PositionInfo and RenderInfo in order to do its job. When a Tree is created, the creation logic will register the Trees' PositionInfo and RenderInfo to the RenderLogic, so on each frame the RenderLogic will loop over all registered data, read from them and do its job. The CollisionInfo and PositionInfo are registered to the CollisionLogic so dynamic objects don't pass through the tree.
Create a class called PlayerCharacter, which composes PositionInfo, CollisionInfo, RenderInfo, AnimationInfo, InputInfo, HealthInfo, LocomotionInfo, ......., and on creation each of these data members are registered to any Logic that you want to operate on your PlayerCharacter. The Logic operations are key here, as they can only operate on objects that have certain combinations of data structures. For instance, you can't register the Tree to the MoveLogic because it doesn't have a LocomotionInfo, even tho it has a PositionInfo.
When you want to create a Tree, ask the TreeFactory to create one (which should also automatically register the Tree data structs to the neccesary Logic). The Tree still owns the data, but the Logic has pointers to this data (and ofcourse, different caching techniques are possible here, but this is a KISS method which should be preferred until you prove it's not good enough from profiling). When you determine the Tree should be destroyed, ask the TreeFactory to destroy it, which will also unregister the data from the Logic.
Your example of finding a collision and wanting to move should be handled by the CollisionLogic entirely. The collision resolve step doesn't modify the position of the object that much, only so it's not in a collision anymore. But your MoveLogic will modify the position with regards to the LocomotionInfo, because the object might have certain acceleration, velocity, forces, friction, dampers etc etc in effect. First find ALL collisions for all pairs of CollisionInfo+PositionInfo, and when you find them all, resolve them one at a time (again, look at Box2D or Bullet).
Your game loop is then modified:
- InputLogic()
- MoveLogic()
- CollisionLogic()
- AnimationLogic()
- HealthLogic()
- WeaponLogic()
- ...
- RenderLogic()
- repeat
2) How do I store them in containers? Without a common superclass, do I need a container for each type of object? With the actor superclass, it was easy:
for( auto actor: actorContainer )
{
doSomethingToActor( actor );
}
Do I just never actually talk to the Tank, Jeep, Chopper, etc classes directly? So I have a container of Actors, and container of MoveLogics, a container of CollisionObjects, etc. and I just iterate through those regardless of who actually owns them? In that case, again how do I synchronize data between the Actor, MoveLogic, CollisionObjects, and any other members that hold similar data?
EDIT:
As a potential solution to my question about containers, is it ever a good idea to implement some pure virtual classes to function like Java interfaces? Something like:
That way I could refer to the objects themselves in containers, while allowing myself to implement the actual logic behind-the-scenes using composition, which also allows me to keep a container of the CollisionModel, MoveLogic, etc. objects to loop through.
You don't need to sync data since all of the Logic operations have the same pointer the the one Info of a certain object they're all operating on.
Also, if you do a split of data vs logic (the Info vs Logic in my example above), you don't need the virtual interfaces. Logic operates on Info, to which it has a pointer to, and you don't have to cast them in any direction. Simply dereference, read/write and move on.