The first thing I needed to do was extend my Portal class to allow for more than one child. Portals are used in the engine to represent a connection between two Containers (e.g. rooms, areas, etc). At some point, I'm going to add some Portal rendering code that actually allows you to see through them - but for now, they're purely acting as a logical connection allowing you to walk from one room to another. Any object that is part of the Portal itself (e.g. a doorframe, a door, a shimmering stargate, etc) needs to be a single object (so that opening the door on one side, opens it on the other side too), but also needs to show up on both sides. The Portal/Container architecture handles this magic, allowing a single object to be in two places at once. While I thought change would be a rather straight forward exercise of changing a Node reference member into an array, it actually turned up a rather large problem. Unbeknownst to me, all the doorways in my level were actually the same object, just getting shuffled around all over the place. When the doorway was static, this was no problem, but the new animated door needed per-instance state (to remember whether the door is open or closed, which animation is playing, etc). Fixing this turned up a rather nasty serialisation problem which had me stumped for a while. All is good now though - I can now drag and drop doors onto doorways, they show up correctly from both of the connecting rooms, and load/save just fine.
The next challenge was working out how the door would operate. I had a door "open" animation, and a door "close" animation - both of which worked splendidly in isolation. But what happens when you tell a half open door to close? Even with animation blending, the door would fly fully open (as this is the first frame in the "close" animation - potentially allowing things to pass through) before sliding shut. Given I'm hoping to put in lots of tricksy traps and logic puzzles for opening doors, I wanted to get this right. I looked into some intelligent animation blending that would actually scan the animation for the closest "pose" as a starting point, but this starts to put too much knowledge of what the bones of a skeleton mean into the animation code - so in the end I just added support for playing animations in reverse (which might come in handy in other places too). You can now just give an animation sampler a negative "speed", and it will play the animation backwards, starting from the current point in the animation if it's already being played. In scripting land, open and close now become:
Door.play( Open, 1.0) # open
Door.play( Open, -1.0) # close
And voila! My little door slides open and shut, stopping my character or letting him through. It's quite satisfying to see something interactive in the world after many months of ostensibly static geometry. Mind you, there's currently nothing in the world that can control the door (e.g. proximity sensor, switch, etc), so my poor Cow needs a little help from the console window to get around the level right now.
Still, "Nice neat job!" as a guy on a show my Dad always watched would say.
Which brings me to my next big design conundrum ... how does the game[play]'s logical object model relate to the 3D scene graph? You see, at this point, this model has stopped being just a Skeleton with some Shapes and animation Controllers, and start to assume meaning in the game: this is a door, which might be locked by a key or hooked up to a proximity detector, and characters in the game will want to recognise it and decide to walk through it, open it, close it, etc.
While there would obviously be a strong relationship between the two object models - it almost feels like there ought to be a separate network of objects that represents the gameplay elements/concepts in the world: this is a key, it opens this refrigerator, this is the angry dwarf, he's hungry so he'll want to find that key and make his way back to the kitchen. This seems to be the point where something like OGRE stops being a rendering engine and starts being a game. Donning my Model/View/Controller hat, it should even be possible to use exactly the same data to play the game as a text adventure if done right --- not that I'm considering doing this, just that it would be a good indication I've got a good decoupling. I wonder how this problem is usually solved? Neither object models seems to be dominant, the logical model relies on the scene graph for positional information, and the scene graph relies on the logical model to provide meaning to the objects in the world. Should each scene Node hold an optional reference to a logical object and vice-versa? Is a logical object just another type of Controller that can be attached to a Node? Perhaps after my little one is asleep tonight I might pour over some Game Programming Gems and David Eberly and see what they've got to say for themselves. I wonder how Unreal Engine and Renderware handle this? Has anyone out there tackled this beast - perhaps someone using OGRE?
The broad categories of use-cases seem to be:
- Support logic model queries (e.g. Which key opens this door?)
- Support scene -> logic queries (e.g. What is this? Are there any enemies nearby? Which set of doors do I need to traverse to get over there?)
- Support logic -> scene queries (e.g. Where is this object? How big is this object?
- Allow the logic model to control it's scene manifestation (e.g. play the "Open" animation. Walk over there)
- Allow the logic model to handle scene events (e.g. Someone just bumped into you. You just saw someone. The player just ate you)
- The logical model must be extensible both in C++ and script
Yet another "big problem" that will bubble away on my backburner for a while methinks - but in the mean time, I welcome your insightful suggestions!