(This is a continuing series sharing design and implementation notes for a cross platform 2D game or graphical app engine I wrote in C# using Xamarin and Monogame.)
It was hard to choose next between covering the animation system, physics engine, or custom scripting language, as each is essential in its own way, and they’re also tightly integrated. But let’s start with animation.
Designing for Cats
When designing a graphical mobile experience, whether a game, educational app, utility, etc, you want to grab the user’s attention; and showing movement is an important way to do that. A person’s attention is naturally drawn to whatever is moving on the screen. And I guess people are like that in the real world, too. We’re kind of like cats in that respect.
It would be possible to implement your graphical app as just a series of static images arranged on the screen, sort of like a slide show or like clicking through a simple website. But you want your apps to stand out from the crowd and show nice numbers on App Annie. Right?
So with the above thoughts in mind, I set about designing what I thought a general purpose, cross platform animation framework should be. Sadly, a purely vector graphics approach like desktop Flash was out of the question, due to the constraints of graphics accelerated mobile apps (see earlier post explaining why). Whatever I put together would have to be able to take a single spritesheet of images and combine rendered subsets of it in different ways to display every scene.
Animation Hierarchy
Here is a somewhat simplified and abbreviated outline of the object hierarchy in my animation system. I’m not claiming it’s the best possible, but I’ve grown fond of it, and haven’t felt unduly constrained. Of course, it’s hard to feel constrained when you can simply write a new function on a whim in your own game engine. But to me, the following structure at least "feels like" the right number of levels of indirection to strike a balance between convenience and power. And as mentioned, whenever my visual animation designer doesn’t cover something, the embedded scripting functions make up the difference.
The class names imperfectly reflect the diversity of how these objects are actually used. For example, a "character" could be a single, visually recognizable entity whose name the end user knows, or it could encompass a collection of buttons or other UI elements and their related logic. The main intent is to promote good organization and resource re-use. Side note: the "A" class prefix is a common habit around here.)
In Action
There are two ways to create content within this animation system: using the visual designer or in script. My visual designer is (ahem) shall we say, a little minimalist. Don’t get me wrong: it lets me create and name new instances of the classes listed above, add instances to contained lists, create skeletal animations with separate curves for each animatable property, write scripting code as an alternative or complement to visual design, and preview all of the above interactively. So it’s really powerful. I guess what I’m saying is that the designer itself is rather uglier than the apps created with it. One of these days I’ll fix that.
Below is a screenshot of the designer when editing an animation curve (click for a larger version):
Below that is another screenshot showing the hierarchy of images in one animation.
Note that many attributes like scale and rotation apply recursively to child nodes, so you can modify a property on the root of something and affect the whole organic entity. This is what enables skeletal animation.
…and the resulting animation that was being edited in screen captures is seen above at the top of this post. (Please go easy on my artwork; I’m a programmer, okay?)
Also shown is the spritesheet that would result if this was the only animation included in a scene, so you can see how the parts are put together. The spritesheet is automatically generated by the designer, with images being exported at appropriate sizes as required from the original artwork (Inkscape SVG layers).
More on the Curves
The designer allows you to edit curves by placing points, which are interpolated/extrapolated using the Catmull-Rom method. Currently the only way to use other types of curves is from script. Wherever possible, I tried to use normalized units. The distance normalization is described below, but I also normalized rotation so that one unit of my rotation attribute represents Pi radians, and one unit of the rotation center x and center y offset represents a fraction of the image’s actual width or height. I think that making those decisions up front has kept a lot of things clean and easy since then.
Parsecs Per Hogshead
An alarming realization hit me while I was starting to implement my animation system: I didn’t know what units of measure to use instead of pixels for distance, given that I intended to publish to screens across a wide array of hardware profiles, from tiny smartphones to widescreen TVs. But on reflecting further, I saw that it didn’t really matter. All I had to do was totally make up a unit of measure and scale it consistently and intelligently, and everything would work out fine. As long as I was careful with the math in the logic that exports the spritesheets for each device/publishing profile, it would all turn out pixel perfect and awesome.
So I decided to choose the vertical screen height of a landscape screen as my fundamental device-dependent measurement, and "1/15 of a screenheight" as one unit in my abstract object coordinates. My intent in defining it this way was to avoid ever needing to vary what’s visible along the narrowest screen dimension. I was OK with leaving it as a future exercise for myself to ensure that the longer dimension didn’t have problems with widescreens — just needed to try to keep all the important stuff a bit central so it would remain visible for non-widescreens. You know, like the Apple iPad — that’s important to support, right? 😉
(Side note: it’s technically not 100% true that it doesn’t matter what unit of measure you use. My unit system was chosen to provide a reasonable default for the physics simulation layer; I guess it’s mildly useful to aim for your graphics abstractions and physics to be one-to-one.)
Platform to Build On
The animation system as described has worked out really well for my graphical app engine. I didn’t have time to describe additional features like physics integration, support for cuttable objects and related dynamic triangulation of the (still single!) spritesheet texture, scripting, or the many other unmentioned properties and methods of the scene, character, animation, and image instance objects. I’m sure that others have ideas about better ways to structure a system like this. This has worked well for me, but I am sure that a variety of approaches in graphical app engines and frameworks out there can effectively serve different needs.