22 Nov 2008

Month of Shooters

I'm currently playing through the Gears 2 single player campaign, and I don't know why exactly, but I'm not having very much fun. There are a few easy to identify points which I clearly don't like:
  • Vehicle sequences: I don't know why FPS designers still torture us with vehicle sequences. The mini-tank in Gears 2 controls like shit (a bit like the Mass Effect Mako, but worse), and there's too much driving through boring tunnels with nothing else happening.
  • Stupid story: So far I'm not seeing the better story that was promised. Instead I'm just seeing Dom bringing up his wife all the time (just found her yesterday). What is this emotional bullshit doing in my Gears... really. If you want to do an emotional story, do it right. The Gears main character are simply not built for stuff like this. It's like asking Schwarzenegger to play Shakespeare. Some things simply don't work, no matter how hard one tries.
  • Dialog: alright, Marcus isn't exactly a second Cicero when it comes to conversational skills, but those stupid one-liners get old pretty fast.
  • Too much brown: I know it's cliche, but Gears could just as well be played on an old black-and-white monitor and not a lot of information would be lost. I'm sick of monochromatic shooters.
Except from the vehicle sequences (which are inexcusable) I can live with the other points. I like my bad story and cheesy dialogs, if there's a good game at the core.

But somehow, the core gameplay isn't very satisfying to me in Gears2. I've been playing into the COD4 single-player-campaign again for comparison, and holy shit this an entirely different level, even the "quiet moments" in COD4 are packed with action.

Gears2 brings up unbelievable stuff like mile-long worms, giant fish and whole cities sinking into the ground, but it doesn't grab me. Maybe it is because the scale is too big, or maybe it is because the actual fire fights aren't very exciting. I think maybe the main reason why the game leaves me cold is that there are too long pauses between fire fights, and fights are too predictable. Coming into an area with a lot of conveniently placed barricades? Sure as hell a few seconds later a door will shut behind you and Locust will start attacking.

Good thing Dead Space came along, otherwise I might have lost my faith in shooters ;)

I'm very impressed with the new Xbox dashboard, especially with the fast and painless update process (took maybe 2 minutes through my 16Mbit DSL line at home). I was prepared for the worst (something like 2 weeks without Xbox Live like last Christmas), but apart from a few glitches on the Marketplace which were fixed the next day everything went perfect. I love how responsive, fast and colorful everything is now, and the hard drive installation is a god-send because it turns off the jet-engine noise from the DVD drive.

I even like the Avatar stuff more then I should. I've been trying to create a Ron Jeremy avatar, but that's where the avatar creator really comes to its limits. Here's my wishlist for the next update:
  • the current fat setting needs to be a lot fatter, the current maximum isn't even enough to build a realistic Elvis in his later years
  • more 70's porn star accessories and clothing please
  • more real hair styles(!), there's plenty of 90's neo-hippie shit, but no hair which nearly does Ron Jeremy justice
  • hair styles should include chest and armpit options
Since I didn't find the right hair and clothes, I went for a Ron Jeremy/Princess Leia hybrid:

8 Nov 2008

CoreGamer

It feels like this is the most packed holiday season of all time. It is a shame that publishers don't spread their releases a bit more over the year. There was hardly a single good game coming out every quarter for the 360, and now suddenly since end of October it feels like there's a block buster released every day. Unfortunately, a lot of great games will be buried under the heavy weights like Gears or Fallout3, but well who am I to complain. It's a great time for hardcore gamers across all platforms, and that's all that counts :)

I think I'll have to work on the oncoming backlog until at least February next year, and careful scheduling is necessary to manage the avalanche of games indeed. Fortunately some of the games didn't turn out quite as good as pre-release-hype made me belief, so I can push a few games into next year. One of those is FarCry2. It's not a bad game by any means, I was prepared for the open-world-ness (which is the exact opposite of the original FarCry) and shitty to non-existent story (which is exactly in line with the original FarCry). But still I was disappointed. I must confess that I didn't give the game a real chance (only played for one evening, about 2 to 3 hours). Graphics are great, but game-play wise it is somewhere between Just Cause and Mercenaries2, and I already had my share of sandbox games this year I guess. However I will definitely come back to FC2 next year when the storm has settled a bit, but it looks like this will be the Assassin's Creed of 2008 (spectacular presentation, shallow gameplay).

Next up was Fable2. Great game (especially with the downloadable English audio track), but I just haven't the time to appreciate the game as a whole. The game requires a lot of time investment, but rewards the player for the time spent with a great sense of immersion. But at the moment I just don't have the attention span required for a game like this. One thing I found surprising was, that one of my old favorites, Overlord, in places looks better or at least very similar to Fable2 (not a surprise, since the Overlord designers definitely drew a lot of inspiration from the original Fable), but considering that Overlord is a 2007 title, Fable2 should have been a prettier game. I was about half way through Fable2, when along came my personal surprise hit of 2008 (so far at least):

Dead Space!

What a f*cking great game. It's a bit of Alien (the movie), a bit of System Shock, and a bit of Quake2, merged into a wonderfully old-school survival-horror-corridor-shooter. The really surprising bit is: this is an EA game! With the exception of Fight Night 3 and Skate, I wasn't interested in a single EA game for the entire life-time of the 360, and now all of the sudden, EA actually starts to produce great games in a row. This year alone I bought Battlefield BC, Mercenaries2 and Dead Space from EA, and will probably get Mirrors Edge soon. And considering that Bioware is now EA as well... oh dear. What has the world come to, EA making great games... The end must be near indeed hehe.

The PS2 is now at 77 Euro in Germany, just a tad more expensive then a typical 360 or PS3 game. That's the latest model: slim, with integrated power supply, one dualshock, composite cables, looking sexy as hell (no memory card though). Since my last PS2 went MIA, this was a very good reason to impulse-buy a new one. Better investment then my PS3 to be honest. Just playing a few minutes into MGS3 again was worth it.

CoreAnimation

Rewriting the animation system of Nebula2 was one of the big items on my personal to-do list for the last couple of months. Too many small features had been stacked on top of each other over time by too many programmers without that the underlying old code really supported the new stuff. The result was that the N2 animation system became a fragile and difficult to work with mess. It mostly does what it needs to do for Drakensang (which isn't trivial with its finely tuned animation queueing during comat), but it isn't elegant any more, and it is not really a joy to work with.

I have only been working very sporadically on the new N3 animation code during the past few months, restarting from scratch several times when I felt I was heading into a wrong direction. In the past couple of weeks I could finally work nearly full-time on the new animation and character subsystems, and not too early, because only then I really was satisfied with the overall design. The new animation system should fix all the little problems we encountered during the development of Drakensang and offer a few new features which we wished we had earlier. And it resides under a much cleaner and more intuitive interface then before (this was the main reason why I started over several times, finding class interfaces which encapsulate the new functionality, but are still simple to work with).

One of the earliest design decisions was to split the animation code into two separate subsystems. CoreAnimation is the low level system which offers high-performance, simple building blocks for a more complex higher level animation system. The high-level Animation subsystem sits on top of CoreAnimation and provides services like mapping abstract animation names to actual clip names, and an animation sequencer which allows easy control over complex animation blending scenarios.

The main focus of CoreAnimation is high performance for basic operations like sampling and mixing of animation data. CoreAnimation may contain platform-specific optimizations (although none of this has been implemented so far, everything in CoreAnimation currently works with the Nebula3 math library classes). CoreAnimation also assumes that motion-capturing is the primary source of animation data. Like sampled audio vs. MIDI, motion capture data consists of a large amount of animation keys placed at even intervals instead of a few manually placed keys at random positions in the time-line. The advantage is that working with that kind of animation data can be reduced to a few very simple stream operations, which are well suited for SSE, GPUs or Cell SPUs. The disadvantage to a spline-based animation system is of course: a lot more data.

Spline animation will be used in other parts of Nebula3, but support for this will likely go into a few simple Math lib classes, and not into its own subsystem.

Although not limited to, CoreAnimation assumes that skinned characters are the primary animation targets. This does not in any way limit the use of CoreAnimation for animating other types of target objects, but the overall design and optimizations "favours" the structure and size of animation data of a typical character (i.e. hundreds of clips, hundreds of animation curves per clip, and a few hundred to a few thousand animation keys per clip).

Another design limitation was, that the new animation system needs to work with existing data. The animation export code in the Maya plugin, and the further optimization and bundling during batch processing isn't exactly trivial, and although much of the code on the tools side would benefit from a cleanup as well (as is usually the case for most of the tools code in a production environment), I didn't feel like rewriting this stuff as well, especially since there's much more work in the tools-side of the animation system compared to the runtime-side.

So without further ado I present: the classes of CoreAnimation :)

  • AnimResource: The AnimResource class holds all the animation data which belongs to one target object (for instance, all the animation data for a character), that is, an array of AnimClip objects, and an AnimKeyBuffer. AnimResources are normal Nebula3 resource objects, and thus can be shared by ResourceId and can be loaded asynchronously.
  • StreamAnimationLoader: The StreamAnimationLoader is a standard stream loader subclass which initializes an AnimResource object from a data stream containing the animation data. Currently, only Nebula2 binary .nax files are accepted.
  • AnimKeyBuffer: This is where all the animation keys live for a single AnimResource. This is just a single memory block of float4 keys, no information exists in the key buffer how the keys relate to animation clips and curves. However, the animation exporter tools make sure that keys are arranged in a cache-friendly manner (keys are interleaved in memory, so that the keys required for a single sampling operation are close to each other in memory).
  • AnimClip: An AnimClip groups a set of AnimCurves under a common name (i.e. "walk", "run", "idle", etc...). Clip names are usually the lowest level component a Nebula3 application needs to care about when working with the animation subsystem. Clips have a number of properties and restrictions:
    • a human-readable name, this is stored and handed around as a StringAtom, so no copying of actual string data happens
    • a clip contains a number AnimCurves (for instance, a typical character animation clip has 3 curves per skeleton-joint, one for translation, rotation and scaling of each joint)
    • all anim curves in a clip must have the same key duration and number of keys
    • a clip has a duration (keyDuration * numKeys)
    • a pre-infinity-type and post-infinity-type defines how a clip is sampled when the sample time is outside of the clip's time range (clamp or cycle).
  • AnimCurve: An AnimCurve groups all the keys which describe the change of a 4D-value over a range of time. For instance, the animated translation of a single joint of a character skeleton in one clip is described by one animation curve in the clip. AnimCurves don't actually hold the animation keys, instead they just describe where the keys are located in the AnimKeyBuffer of the parent AnimResource. AnimCurves have the following properties:
    • Active/Inactive: an inactive AnimCurve is a curve which doesn't contribute to the final result, for instance, if an AnimClip only animates a part of a character skeleton (like the upper body), some curves in the clip are set to inactive. Inactive curves don't have any keys in the key buffer.
    • Static/Dynamic: an AnimCurve whose value doesn't change over time is marked as static by the exporter tool, and doesn't take up any space in the anim key buffer.
    • CurveType: this is a hint for the higher level animation code what type of data is contained in the animation curve, for instance, if an AnimCurve describes a rotation, the keys must be interpreted as quaternions, and sampling and mixing must use spherical operations.
  • Animation keys: There isn't any "AnimKey" class in the CoreAnimation system, instead, the atomic key data type is float4, which may be interpreted as a point, vector, quaternion or color in the higher level parts of the animation system. There is no support for scalar keys, since most animated data in a 3d engine is vector data, and vector processing hardware likes its data in 128 bit chunks anyway.
  • AnimEvent: Animation events are triggered when the "play cursor" passes over them. The same concept has been called "HotSpots" in Nebula2. AnimEvents haven't been implemented yet actually, but nethertheless they are essential for synchronizing all types of stuff with an animation (for instance, a walking animation should trigger events when a foot touches the ground, so that footstep sounds and dust particles can be created at the right time and position, and finally events are useful for synchronizing the start of a new animation with a currently playing animation (for instance, start the "turn left" animation clip when the current animation has the left foot on the ground, etc...).
  • AnimSampler: The AnimSampler class only has one static method called Sample(). It samples the animation data from a single AnimClip at a specific sampling time into a target AnimSampleBuffer. This is one of the 2 "front-end-features" provided by the CoreAnimation system (sampling and mixing). The AnimSampler is used by the higher level Animation subsystem.
  • AnimMixer: Like the AnimSampler class, the AnimMixer class only provides one simple static Method called Mix(). The method takes 2 AnimSampleBuffers and a lerp value (usually between 0 and 1) and mixes the samples from the 2 input buffers into the output buffer (k = k0 + l * (k1 - k0)). The AnimMixer is used for priority-blending of animation clips higher up in the Animation subsystem.
  • AnimSampleBuffer: The AnimSampleBuffer holds the resulting samples of the AnimSampler::Sample() method, and is used as input and output for the AnimMixer::Mix() method. An important difference from the AnimKeyBuffer is that the AnimSampleBuffer also has a separate "SampleCounts" array. This array keeps track of the number of the sampling operations which have accumulated for every sample while sampling and mixing animation clips into a final result. This is necessary for mixing partial clips correctly (clips which only influence a part of a character skeleton). The AnimSampler::Sample() method will set the sample count to 1 for each key which was sampled from an active animation curve, and to 0 for each inactive sample curve (which means, the actual sample value is invalid). Later when mixing 2 sample buffers the AnimMixer::Mix() method will look at the input sample counts, and will only perform a mixing operation if both input samples are valid. If one input sample is invalid, no mixing will take place, instead the other (valid) sample will be written directly to the result. If both input samples are invalid, the output sample will be invalid as well. Finally, the AnimMixer::Mix() method will set the output sample counts to the sum of the input sample counts, thus propagating the previous sample counts to the next mixing operation. Thus, if at the end of a complex sampling and mixing operation the sample count of a specific sample is zero, this means that no single animation clip contributed to that sample (which probably should be considered a bug).

That's it so far for the CoreAnimation subsystem, next up is the Animation subsystem which builds on top of CoreAnimation, and after that the new Character subsystem will be described, which in turn is built on top of the Animation system.