Posts Tagged animation

Animation markers and triggers

One of the first things I did this week was implement support for scripted animation markers. Markers let me attach names to various parts of a sprite so that I can use them in animation and collision detection – for example, lining up a weapon with a character’s hand, or doing hit detection against a character’s hand instead of his entire hitbox.

Once I had some simple markers for the player’s feet added to his animation script, to test them out more thoroughly, I implemented support for attaching simple script triggers to a combination of a marker and a given animation frame, like this:

    <SpriteMarker typeId="2">
        <Name>Left Foot</Name>
        <Groups>
            ...
            <Group name="run">
                <Frames>
                    <Frame anchor="tl" x="80"  y="170" index="0" />
                    <Frame anchor="tl" x="61"  y="169" index="1" />
                    ...
                    <Frame anchor="tl" x="132" y="143" index="6" />
                    <Frame anchor="tl" x="99"  y="173" index="7" trigger="Footstep" />
                </Frames>
            </Group>
        </Groups>
    </SpriteMarker>

Using the combination of markers and triggers, I was able to write a simple bit of C# to go with the animation script and spawn small puffs of smoke every time the player’s feet land on the ground during his run animation:

    public void Footstep (string foot) {
        if (Jumping && !JumpLanded)
            return;

        var pos = ResolveMarker(foot);
        if (!pos.HasValue)
            return;
        Game.SpawnSmokePuff(Position + pos.Value - GetDrawOrigin(Animator.Frame), 10, 0.5f);
    }

The real motivation behind the marker system, however, was syncing animations with the game world – I wanted the player’s grappling hook to extend from his hand, have his melee attacks actually sync up with his animation frames, and have his collision detection change realistically while crouched.

Essentially, where before I used hard-coded coordinates and percentages, now I ‘resolve’ one of the player’s named markers. Resolving a marker either returns coordinates (relative to the top left of the frame) or null, which allows me to determine whether a given frame has a particular marker attached to it. Once I have coordinates for a marker, I compute the player’s origin (used for positioning his animation frames) and use that to compute the on-screen or in-world coordinates for the marker, based on the frame-relative coordinate I already have. Given that, I can sync up things like a grappling hook or a potion with the player’s hand:

    public Vector2 GrappleFrom {
        get {
            var handPos = ResolveMarker("Right Hand");
            return Position + handPos.GetValueOrDefault(
                new Vector2(0, Obstruction.Y * -0.5f)
            ) - GetDrawOrigin(Animator.Frame);
        }
    }

After using this technique to make the player’s bounding box change while crouching, I realized that by uncrouching, the player could become stuck in a ceiling. The solution to this ended up being a bit of a hack – before increasing the size of the player’s bounding box, do a quick collision check to see if the player’s current bounding box is able to move upward by that same amount. If the check fails, I now know exactly how far the player’s current bounding box would have been able to move, and I can constrain the growth of the bounding box to avoid making the player become stuck.

Once the player is out of a tight space, I can grow the bounding box to its full height, restoring sanity. It’s not perfect (and looks kind of stupid since it allows the player’s head to stick through ceilings), but it was relatively simple to implement. I suspect I’ll end up with a different solution in the final game – perhaps forcing the player to stay crouched if he can’t stand up in his current position.

During the process of rigging up the player’s animations, I spent a lot of time fine-tuning things and working out issues. To make things move quicker, I ended up adding a couple new features to my engine:

First, I added support for reloading sprites and tiles at runtime. My existing game code already was designed in such a way that this wasn’t too difficult, but I still had to do some work in order to get the rest of the way. One of the biggest changes was that I had to change entities to store a sprite’s Name instead of storing the sprite itself, so that I could replace a given sprite instance at runtime after reloading it from disk. As a result, where before I had:

    this.Sprite.Animations["Walk"].Run(this.SpriteContext);

Now, I have something like this:

    Game.Sprites[this.SpriteName].Animations["Walk"].Run(this.SpriteContext);

A bit more verbose, but easy to abstract out using properties and helper methods – such that actual game code can just use convenience methods, like so:

    this.PlayAnimation("Walk");

And everything happens behind the scenes.

The other change I made was to overhaul my rendering model in order to make it easier to add debugging overlays, user interface graphics, and special effects without having to change the Game’s Draw function. First, I changed the signature of most of my objects’ Draw methods, such that they now take an extra parameter:

    public override void Draw (DrawFlags flags) {

Then, the game code can pass a combination of flags to an object to specify which types of rendering to perform:

    if (ShowHUD) {
        var flags = DrawFlags.HUD | (GlobalDrawFlags & DrawFlags.DebugHUD);
        RenderGeometry(flags);
        RenderEntities(flags);
    }

As a result, I have the ability to easily toggle off sections of an individual object’s painting code, without having to directly hook the logic into the Game object itself. This also nicely simplifies a lot of the code in my editor, since I can now do editor-related painting (like drawing bounding boxes, selection highlights, etc) in the Draw method of the object in question, instead of having to add a special function to the editor that draws those things manually, or having to add an ‘EditorDraw’ method to the objects (like I had before in a few cases, unfortunately).

With the addition of a couple extension methods to simplify things, it wasn’t too hard to wire things up inside my game objects. For example, this is what SpriteEntity.Draw looks like:

    public override void Draw (DrawFlags flags) {
        ...
        var drawPos = GetDrawPosition();
        var origin = GetDrawOrigin(Animator.Frame);

        if (flags.Check(DrawFlags.Entities))
            Draw(drawPos, origin, Scale, Color);

        if (flags.Check(DrawFlags.DebugEntities)) {
            Game.SetupRenderState(true);
            Game.DrawBox(this.Bounds, Color.White);
            Game.CleanupRenderState();
        }

It simply checks for each of the draw flags it knows how to handle, and processes them accordingly, in a given order. This gives me the ability to do multiple draw passes at once, or do them individually if I want finer-grained control over draw order. A good example of this is the HUD pass – I want HUD elements to cover all the objects in the game world, so I draw them in a separate pass. But debugging overlays like entities’ bounding boxes don’t need to cover everything, so I draw them in the same pass as the entities themselves.

For the curious, the extension methods I ended up writing look like this:

    public static bool Check (this DrawFlags drawFlags, DrawFlags flag) {
        return (drawFlags & flag) == flag;
    }

    public static DrawFlags Toggle (this DrawFlags drawFlags, DrawFlags flag) {
        return ((drawFlags ^ DrawFlags.All) & flag) | (drawFlags & (flag ^ DrawFlags.All));
    }

They’re fairly simple, but having them tremendously simplifies the process of working with the DrawFlags enumeration. This is one thing that I wish C# had built-in support for, but it’s at least fairly easy to add with extension methods. Unfortunately, generic types and enums don’t get along, otherwise you could have generic forms of these functions instead of having to write them for each Flags enumeration in your game.

Tags: , , , , , , ,

Scripted animations and saved games

I spent some time this week building a new animation system to replace my old hard-coded player animations. Right now the player doesn’t look any different, but the new system is set up such that I can drop the animation frames my artist is working on directly into my Content folder and see them in action with minor XML changes, instead of having to adjust large parts of the Player class.

One of the main goals I had for the Player class was to separate the presentation-related aspects of the animations (number of frames, size, etc) from the logic-related ones. Previously, both were handled in the same C# function – to handle punching, I had an Animation_Punch function in the Player class that looked something like this:

        protected Anim Animation_Punch () {
            _AttackShape = new DrawablePolygon(
                new Vector2[4]
            );

            using (Finally.Do(() => { ActiveAttack = Attack.None; _AttackShape = null; }))
            using (var a = Animation_OneShot(AnimId_Punch, 40 * Time.MillisecondInTicks))
            while (a.MoveNext() && (ActiveAttack == Attack.Punch)) {
                float x = ObstructionWidth / 2.0f * Facing;
                float y = -ObstructionHeight + ArmTop;
                float w = ((Sprite[Animator.Group][Animator.Frame].Width * ScaleFactor) - ObstructionWidth) / 2.0f * Facing;
                float h = ArmHeight;

                _AttackShape.SetVertex(0, new Vector2(x, y));
                _AttackShape.SetVertex(1, new Vector2(x + w, y));
                _AttackShape.SetVertex(2, new Vector2(x + w, y + h));
                _AttackShape.SetVertex(3, new Vector2(x, y + h));

                yield return a.Current;
            }

            yield return new SetAnimation {
                Animation = (Jumping && !JumpLanded) ? Animation_Jump_Fall() : Animation_Stand()
            };
        }

As you can see, the presentation - Animation_OneShot(AnimId_Punch, 40 * Time.MillisecondInTicks) – and logic are mixed directly together. Also, it has hard-coded coordinates and sizes in it, in order to keep the collision detection in sync with the animation. My goal is to eliminate both such that the Animation_Punch function becomes pure logic and all of the presentation-related data lives in the animation file. The use of an animation file also eliminates annoyances like hard-coded animation IDs, replacing them with filenames.

The equivalent in the new animation file looks like this:

            <SpriteAnimation typeId="1">
                <Name>Punch</Name>
                <Group name="punch" />
                <Frames delay="40" />
                <NativeWrapper name="Animation_Punch" />
            </SpriteAnimation>

As you can see, it expresses the same information, though in some cases it is able to omit things that had to be explicit before. The Animation_Punch function still exists, but has a more clearly defined purpose:

        protected Anim Animation_Punch (Anim inner) {
            using (Finally.Do(() => { ActiveAttack = Attack.None; _AttackShape = null; }))
            using (inner) {
                ActiveAttack = Attack.Punch;
                _AttackShape = new DrawablePolygon(
                    new Vector2[4]
                );

                while (inner.MoveNext()) {
                    var group = (SpriteGroup)Animator.Group;
                    var frame = group[Animator.Frame];
                    float x = ObstructionWidth / 2.0f * Facing;
                    float y = -ObstructionHeight + ArmTop;
                    float w = ((frame.Width * ScaleFactor) - ObstructionWidth) / 2.0f * Facing;
                    float h = ArmHeight;

                    _AttackShape.SetVertex(0, new Vector2(x, y));
                    _AttackShape.SetVertex(1, new Vector2(x + w, y));
                    _AttackShape.SetVertex(2, new Vector2(x + w, y + h));
                    _AttackShape.SetVertex(3, new Vector2(x, y + h));

                    yield return inner.Current;
                }

                if (Jumping && !JumpLanded)
                    PlayAnimation("Jump_Falling");
                else
                    PlayAnimation("Stand");
            }
        }

Oddly enough, the function actually got larger. Future refactorings will enable me to shrink it, but for now, it at least has less presentation mixed in – the actual animation itself is passed in via a parameter (inner), and the function is called as a result of being referenced by the animation file, instead of the other way around. The logic for pulling out the current frame is also abstracted out, which is another small but important improvement.

One of the other requirements for my animation system is the ability to set up simple triggers that cause one animation to switch to another. The way I previously implemented this was by checking the triggers every frame, using a convenience function in my C#:

        protected Anim Animation_Stand () {
            return Animation_PingPong(AnimId_Stand, 50 * Time.MillisecondInTicks)
                .SwitchIf(Animation_Stand_to_Walk, () => Acceleration != 0)
                .SwitchIf(Animation_Grapple, () => Grappling);
        }

Now, the entire animation lives in the file, more or less unchanged:

        <SpriteAnimation typeId="1">
            <Name>Stand</Name>
            <Group name="stand" />
            <Frames delay="50" loop="PingPong" />
            <Branches>
                <Branch name="Stand_to_Walk" if="Acceleration != 0" />
                <Branch name="Grapple" if="Grappling" />
            </Branches>
        </SpriteAnimation>

One detail of note here is that this means I’ve moved the conditions from compiled code (C#) to interpreted code (they’re now handled using the same expression evaluator I already use for configuring levels). This has a performance impact, but is worth it since it means I’m free to reload animations without having to recompile the game – particularly important if I’m trying to quickly fine-tune animations, since it cuts out the entire ‘Save, Exit, Compile, Start, Load’ cycle.

Thanks to the fact that my animations were already represented as composited iterators (take an iterator that represents an animation, and then layer another iterator atop it that will play a second animation when the first one completes – and so on), the core animation code didn’t have to change at all, which was a big time saver – when I switched from the old C# animations to the new file-based ones, the animations all looked the same, so I didn’t have to spend any time hunting down minor differences.

One other advantage to this approach is that it’s now a reasonable idea for me to let my artist make changes to the game’s animations, instead of doing it all myself. Even fairly complex animations are understandable:

        <SpriteAnimation typeId="1">
            <Name>Jump_Falling</Name>
            <Group name="jump_fall" />
            <Frames delay="70" first="-2" last="-1" loop="Repeat" />
            <Branches>
                <Branch name="Jump_to_Walk" if="(Running) and (!Jumping)" />
                <Branch name="Jump_to_Stand" if="(!Jumping)" />
                <Branch name="Grapple" if="Grappling" />
            </Branches>
        </SpriteAnimation>

As long as you understand XML, most of it is fairly easy to interpret. The equivalent C# would have been much harder to understand, and easier to break if my artist had to edit it to make changes.

The next thing I did was spend some time getting saved games working properly again. The addition of two-character support broke some parts of my saved game implementation, and it seemed like it was about time to fix them. The core problem ended up being larger than I expected – properly loading a saved game without a lot of hacks meant that I had to rethink the way I structured some of my game code.

Originally, objects like entities and geometry had both design-time and run-time representations. For entities, these were the LevelEntity and Entity objects. You constructed a LevelEntity with basic configuration information on the entity, and then added it to a Level. At runtime, the game would load the Level, and construct a RuntimeLevel object from it. Every LevelEntity would become a corresponding Entity in the RuntimeLevel. The core problem here was that constructing an Entity took a reference to the Game object, not to the Level or RuntimeLevel. So if I wanted to construct a new version of an entity (like a monster, or a player), I had no way to tell it which Level to use – it had to guess.

The first step I took was to rename Entity to RuntimeEntity, because it was getting pretty confusing. :) After that, I bit the bullet and changed everything such that constructing a RuntimeEntity required a RuntimeLevel instead of a Game. This meant I had to shuffle lots of things around, and update some code, but in the end, the payoff was worth it – now, I could load a new level from disk, construct all the entities and geometry, and swap it in for the current level, without any glitches. This was an important requirement for being able to undo actions like destroying walls, killing monsters, etc when the player loaded a saved game.

This change also helps kill off some technical debt I’d accrued in the design – lots of code in various places was jumping through ridiculous hoops, doing things like this.Player.Game.RuntimeLevel.Level.Entities[...]… in order to get at objects, so I clearly needed to rethink my dependencies.

One of my next goals is to add a way to place named ‘markers’ inside my animations, so that the game logic can reference locations like the player’s left hand without having to do arithmetic every frame. Getting that working will go a long way towards shrinking the size of functions like Animation_Punch and allow me to add more detailed animations for things like footsteps and attacks.

Tags: , , , , ,

Animation and combat

Over the past week I’ve spent most of my time working on animation and basic support for combat, along with a little time invested in performance optimizations to keep the game running well on the XBox.

My initial approach to animation for the player character was to use a single task that ran in the background, periodically updating the player’s animation frame and then going to sleep for a fraction of a second. It worked, but writing all of the player’s animation logic as a single task became cumbersome very quickly – I essentially was writing a very large, very complex state machine that also had to run concurrently with the rest of the game code. The idea of adding combat animations simply made this approach a non-option – it was too complicated.

As a result, I decided to toss the animation task I currently had and start fresh with a different approach. I ended up test-driving a very simple class designed for handling framerate-independent animation playback, called an Animator. The Animator‘s responsibility is to update the currently playing animation and handle transitions to new animations. Animations are written as generators, like the original animation task, but instead of using one giant generator for all the animations, or a generator for a set of animations, each individual animation is its own generator, only a few lines long.

Every frame, the Animator ‘steps’ the current animation’s generator repeatedly until it either suspends (returning an amount of time to suspend it for, so that the Animator knows when to wake it up next), or hands off control to a new animation. In the event that a new animation is selected, the Animator switches over to it and continues stepping it. This allows for complex animation logic to work correctly regardless of the current framerate, and makes it fairly simple to isolate the details of each animation from other animations. Having the Animator responsible for the current animation means that I can also easily interrupt an animation that’s in progress, even if the animation is suspended – something I couldn’t easily do when the entire animation system was a single task (mind you, it was possible with that approach – just a pain.)

The end result is that the player’s animations now look something like this:

        protected Anim Animation_Walk_to_Stand () {
            return Animation_OneShot(AnimId_Walk_to_Stand, 50 * Time.MillisecondInTicks)
                .Chain(Animation_Stand);
        }

        protected Anim Animation_Backdash () {
            var a = Animation_OneShot(AnimId_Backdash, 40 * Time.MillisecondInTicks)
                .Chain(Animation_Stand);

            using (Finally.Do(() => {
                Backdashing = false;
                Velocity.X = 0.0f;
            }))
            while (a.MoveNext())
                yield return a.Current;
        }

        protected Anim Animation_Walk_Turn (int direction) {
            return Animation_OneShot(AnimId_Walk_Reverse, 60 * Time.MillisecondInTicks)
                .SwitchIf(Animation_Walk, () => Facing != direction)
                .Chain(() => Animation_Wait()
                    .SwitchIf(Animation_Walk, () => Facing != direction)
                );
        }

While they’re still somewhat complicated, they individual animations are much more isolated from each other than they were previously, and it’s much easier to debug and fine-tune animations than it was before.

Functions like Animation_OneShot are predefined ‘animation templates’ that take a GroupID that specifies a set of frames to play, along with a rate at which to play them. The animation template is a generator that simply yields one frame at a time, followed by the specified delay, until the animation is complete. This has the nice advantage of being composable – you can add additional logic onto an existing animation by wrapping it with another generator that yields values from the inner generator after preprocessing them, like in the Animation_Backdash function above. This also gives you a way to express logic that should be executed at the beginning and end of an animation, even if the transition is abrupt (thanks to the IDisposable pattern).

The Chain and SwitchIf functions are extension methods that operate on animation generators by wrapping the generator with a new one and returning it.

Chain‘s behavior is fairly simple – at the end of the animation, control is transferred to a new one automatically, by evaluating a function passed in as a parameter. Passing the new animation in as a parameter allows dynamically choosing a new animation based on the current state of the game, or passing control off to an animation stored in a variable.

SwitchIf is slightly more complicated – it evaluates a condition (passed in as a function) on every step of the wrapped animation, and if the condition is true, it immediately switches to a new animation, without waiting for the current one to stop. This gives you an easy way to express immediate transitions between animations without having to manually detect state changes in your game code and explicitly force a transition. This is important for doing things like switching between a standing and walking animation based on the player’s velocity – you could explicitly call the Animator.SetAnimation method to switch to the walk animation, but doing that would require you to keep track of the player’s previous velocity and detect when he’s beginning to walk, which is a real hassle. Using SwitchIf allows you to express that logic like this instead:

        protected Anim Animation_Stand () {
            return Animation_PingPong(AnimId_Stand, 50 * Time.MillisecondInTicks)
                .SwitchIf(Animation_Stand_to_Walk, () => Acceleration != 0)
                .SwitchIf(Animation_Grapple, () => Grappling);
        }

The main advantage here is that the SwitchIf clause is only evaluated while the player is currently standing, so you don’t need to keep track of any additional state. The primary downside to this approach is that it’s possible for the player’s animation to get into a ‘dead-end’ state, where there are no active SwitchIf clauses. In this case, the only way to restore the player’s animation to normal is to explicitly call Animator.SetAnimation – kind of annoying, but not any worse than the old approach. Luckily almost all my animation logic so far is easily expressed using switch conditions, so I rarely run into the dead-end problem.

Once I had this working, I went ahead and implemented a basic punching attack for the player character, along with a castlevania-style backwards dash move. Those two additions combined with the basic movement and jumping already implemented gives the player enough control to have fairly interesting fights with enemies (assuming that the enemies actually put up a fight, at least…)

The next step was to put something in that’s worth fighting with. Towards that end, I spent a few hours making lots of refactorings to my game code – pulling logic for things like movement, animation, collision detection, and so on out of the Player class and into a new base class called Entity. This required generalizing some of my existing code, and I had to fix a couple bugs I hadn’t noticed along the way, but when I was done the player’s movement code ended up a lot simpler and I was able to create a monster entity with a few dozen lines of code.

I created a new Monster class derived from Entity and added some basic AI to it: the monster walks forward in a given direction until it encounters the player or encounters an obstacle. When it encounters the player, it stands still and attempts to hit the player with its sword until the player is gone. When it encounters an obstacle, it reverses direction. Not particularly challenging to fight, but enough to be worth testing out.

One of the main details I had to tackle was how to detect a collision between the monster’s sword and the player, or the player’s fist and the monster – what I ended up doing was adding an AttackShape field to the Entity class that can optionally contain a polygon. If the field contains a polygon, every frame it will automatically be checked against other entities in the game for collision, and if a collision occurs, the entity is added to a temporary list and an ‘attack landed’ event is fired. The temporary list allows me to avoid dealing damage to an entity multiple times with one attack, so that all you have to do is hit your target with any part of your attack animation. Using a separate shape for an entity’s attack turns out to be a good idea, because it makes it straightforward to create entities that don’t deal damage on contact, which gives the player a lot more freedom when fighting by allowing them to focus on dodging actual attacks instead of having to try and avoid the creature’s hitbox.

After I had the collision detection for attacks working, I had to spend a little time rigging up the attack shapes to roughly match the player and monster’s attack animations so things would look fairly convincing.

The end result is that the player can now fight with a sword-wielding skeleton in the test level. Both of them are able to take damage from attacks and deal damage with attacks of their own. The skeleton can traverse the level fairly effectively, walking up and down slopes and dropping off ledges as necessary, which is pretty decent for a ‘filler’ monster. More advanced monsters will need actual pathfinding, and logic for chasing after the player.

The player’s health also has some simple logic implemented now for regeneration – health passively regenerates at a fixed rate, but taking damage from any source will cause your regeneration to stop, and then slowly resume after a short time. This means that your health won’t regenerate in between strikes if you’re being attacked repeatedly by a monster, but if you’re near death and in combat, dodging attacks for long enough will allow you to begin to regenerate. Having fairly rapid health regeneration is important for achieving the kind of combat I’m after – fights with small numbers of extremely dangerous enemies that require caution and rhythmic timing of attacks and dodges, instead of fights that involve plowing through dozens of skeletons or zombies like in some of the Castlevania games.

After all this, I started to have some severe performance issues – the logic in the Player class for handling sloped surfaces and motion had always been fairly high in my profiler stats, and now that I was running all that logic twice (once for the player, once for the monster) it was accounting for nearly half my processor usage on the PC, and was bringing the XBox version of the game down to the point where it could no longer consistently hit 60 frames per second – not good at all.

I ended up taking two steps to address the problem:

The first step was to solve an issue I had been anticipating to begin with – I was storing all the level’s geometry in a single List container, which meant that every single collision detection operation had to scan the entire level, regardless of the location of the object being checked for collision. I solved this by creating a simple SpatialCollection type that could store a list of objects and group them by their general location, so that I could do efficient collision checks against only nearby objects, without having to write a bunch of code. It took me a couple hours to write a good set of unit tests for the container and get it working, and then another hour or so to integrate it into the game in place of the old List. Luckily I didn’t have to alter any serialization or level loading code – just the code that did the actual collision checks.

After that, I spent another hour or so running performance profiles of the game on my PC, and optimizing the obvious hotspots. There were a few obvious things, like calling the List.Count property getter hundreds of times in the body of a loop instead of once at the beginning of the loop, and then there were also less obvious things, like the fact that I was unnecessarily recomputing the bounding box of a polygon every time it changed position (stupid…). After I killed most of the obvious bottlenecks, I ended up with a profile that looked something like this:

One thing I had to learn the hard way was that the debugger and profiler both have a tendency to suppress JIT optimizations in .NET, like method inlining. This can cause some code (like the subtraction operator on vectors) to appear much higher in a profile than it would otherwise. Regardless, though, the top dozen or so items on a profile almost always contain one or two ‘low-hanging fruit’ that you can easily eliminate from the performance profile with some quick tuning and refactoring.

I even found that it was worthwhile to optimize out things like unnecessary calls to property getters, because they tended to account for a larger performance hit on the XBox than on the PC – probably due to the nature of the XBox’s JIT and processor (which differ greatly from a typical desktop environment). Taking a look at frequently accessed properties can also be helpful since they can occasionally contain code that performs actual computation – for example, the property getter for RuntimeGeometry.Anchor in the profile above accounts for more than 3% of total processor usage. As it happens, that property getter performs some floating point arithmetic to get the right value before returning it, and I was calling it repeatedly inside of a loop (assuming that it was a simple, inexpensive field access) – so optimizing out that call turned out to be a great decision.

Once I was done with my optimization work, I had dropped my average CPU usage on the PC from 25% to 8% – around 1/3 of what it was before, which brought my framerate back into much better territory, and got the game running well on the 360 again (currently at around 20% CPU usage). This leaves me plenty of room to add new gameplay logic and graphics code without having to optimize for a while.

Tags: , , ,