So as mentioned, my laptop's hard drive remains broken and I haven't been able to get a new one yet. Fortunately, all the code for ProjectFPS remained perfectly intact, and I was able to get it off and continue where I left off.
My animation system has been put on hold for now. There are things I'd like to accomplish (such as more efficient resource memory usage, separation of skeletons and the meshes that skin them, and per-object callbacks) that just don't jive with the D3DX animation system, nor any X file exporters. In fact, the version of Blender I have doesn't even have an X file exporter, and when I threw in one written for an older version of Blender, meshes would not export correctly.
So I looked into the COLLADA format. I knew Blender had an exporter for it, and I had heard that 3D modeling programs like Maya and 3DSMax commonly used it to pass data from one program to another. I set out trying to use the OpenCOLLADA library to write a converter from COLLADA to a proprietary skeleton format and mesh format. It took me 2-3 days just to get the OpenCOLLADA part of the converter to compile and link, thanks to cryptic and nonsensical errors. Of course, when I tried using Visual Studio 2008 instead of 2010, these errors magically went away.
In my infinite wisdom, however, I had failed to do research on Blender's COLLADA exporter to see if it actually worked okay. Turns out it doesn't. My vertices had no weights on them at all (which you can't do skeletal animation without), and from what I've read on the web, animation export is kinda funky too. I could write my own Blender exporter, or fix the COLLADA one, or get a student version of 3DS Max. All these I either can't do right now or they require significant effort, and I felt that it just wasn't worth it yet, considering I still don't have much in terms of a game that people can play with each other.
So I've decided to work on something else: networking. You can have a multiplayer game without animation, but you can't have a multiplayer game without multiplayer. So far, I've got a server that multiple players can connect to. They can see each other and shoot the walls. As usual, a topic for another post. Now, back to game objects.
So as mentioned in the last post, the hierarchical model has its fair share of problems, mainly because of implication. If an object is able to do X, it is implied that it can also do Y and Z, which is often not the case, especially when you're defining new objects that weren't in the original design. What we need, then, is a way to precisely specify what objects can and can't do where the sheer variety of them defies hierarchical classification.
The key to doing this lies in converting what is called "is-a" relationships to "has-a" relationships. What this means is instead of saying Player is an AnimatedObject, you say he has a AnimationComponent. Thus, one could define a player like this instead:
class Player : public GameObject
{
void Update();
void Draw();
AnimationComponent m_Animation;
PhysicsComponent m_Physics;
NetworkReplicationComponent m_NetworkReplication;
// Other components, variables
};
Note that we inherit directly from GameObject and nothing else. That's because all the functionality we need has been put into these components, which act like plug-ins. We stick only necessary components into the object, and that gives it the functionality it needs, nothing more. So if we need a StatuePlayer class or something that can't animate, we do this:
class StatuePlayer : public GameObject
{
void Update();
void Draw();
PhysicsComponent m_Physics;
NetworkReplicationComponent m_NetworkReplication;
// Other components, variables
};
Nothing's changed, we're still inheriting from GameObject. We just omit the AnimationComponent. This system offers us a lot of convenience. Even two completely unrelated objects can have the same components, and thus do the same things. Building new objects is also easy, and thanks to the plug-in nature of components, code duplication becomes mostly unnecessary.
An object's updating might involve updating its components, changing their settings, etc., then acting off the results. So it would, for example, tell the animation component to update its current animation in Update(). Then, in Draw(), the object would grab the matrices generated by the component and use that to draw itself.
This doesn't mean that inheritance completely goes away, however. Like before, if we wanted to update everything in the scene, since they all still inherit from GameObject, we can have a list of GameObject pointers that we call Update() and Draw() on. And if you had, say, a WeaponInventory component, it could store a collection of Weapons, which all weapons would inherit from. Note, however, that this kind of inheritance is different - we're more working off what objects are, rather than what they can do. This kind of inheritance is typically very shallow.
Then there's the problem of inter-object relationships. Don't you hate that? The issue is that since implication is now gone, unless we have the object's specific type, we can't tell exactly what components it has. So you can't iterate over a collection of GameObjects and modify their physics, because not all of them will have it. There are multiple ways to tackle this problem, and some of them could be done at the same time.
One simple way is to put a query function in GameObject that all derived classes must overload. You give it a component name, and it either gives you back a component pointer or NULL. Simple, but slow, since the requested type must go through a switch statement or something to that effect every time. So to modify the physics of all relevant GameObjects, you might do this:
for (int i = 0; i < numObjects; i++)
{
PhysicsComponent* pPhysics = gameObjects[i].GetComponent(COMP_PHYSICS);
if (pPhysics) pPhysics->ChangeSomeSetting();
}
A more efficient but complex approach is to create lists of components for the more important component types. When a PhysicsComponent is created, then, it would register itself with the appropriate list. The code then becomes:
for (int i = 0; i < numPhysicsComps; i++)
{
physicsComps[i]->ChangeSomeSetting();
}
In my project, I use both approaches, depending on how much benefit I would get from the latter vs. the convenience of the former. Inter-component communication (e.g. between Physics and Transform) is a very similar problem, only now you have to be careful not to hurt the re-usability of the component in other kinds of objects.
And that's the basis of my game object design. Hopefully I didn't forget to say anything important. =P My next topic will probably be networking, since it has been the sole thing occupying my mind for weeks now. It also plays a big part in how you design your objects.
No comments:
Post a Comment