<-- Return to Comic Engine -->
Better than Sprites, they're Sprouts |
When creating a comic, a surprisingly time consuming aspect is selecting the right character pose and redrawing the face to match the action. Part of the conception of an app for building comics was built around simplifying this process to basically one line in a script. I wanted to be able to write:
Maxim says (pointing, angry) "Don't point! It's impolite!"
It turns out that it isn't that difficult at all. The most obvious first step was to break the character poses down into individual parts, which I could swap out. For instance, the difference between "pointing" and "shrugging" is which body is used - the head would otherwise be untouched. Similarly, "angry" entirely concerns the eyes and does not concern itself with what the body is doing.
So the requirements became two fold.
- I need a way to swap out body parts at will.
- I need a way to specify multiple body parts being changed in a single term.
The end result is a relatively simple process of Sprout and Sprout Templates. It should be noted that this was written after the comic builder prototype was created, and I've refined some of the terminology since then. Where it differs from the code, I'll try to point it out.
A sprite is a ubiquitous pattern in game development. It's simply a rectangular image, with transparency, that is drawn somewhere. What makes a sprite slightly different is that sprites have an anchor point which they are centered around. For instance, characters tend to have their anchor point at their feet, in the center of mass. Then if you animate a walking animation and the sprites have different heights and widths, they'll all still be centered at this one point.
A Sprout is a Sprite replacement that is literally just a Sprite with a little bit extra. That extra is a list of links to other Sprouts. For instance, a body might have a link to a head, while the head has a link to hair, eyes, and mouth. When you draw the body, the body sees a link for a head and draws it (the head is anchored to the link position). The head Sprout sees links for hair, eyes, and mouth (in that order) and then draws them. In that way, as single body and sprout new parts, which in turn sprouts additional parts. The body only needs to know the location of the head, and doesn't care that the hair, eyes, and mouth exist at all. It should be pointed out that having a Sprout link to itself will create an infinite loop.
How does a Sprout know which head to draw? A Sprout's draw routine is also passed a sort of Sprout Server - something it can query for the link names it finds. So the body Sprout will see a link for "HEAD" and then ask the Sprout Server, "Give me a sprout for the name 'HEAD' please" and the server returns the appropriate Sprout ("Ok, use 'MAXIM_HEAD' buddy"). The role of Sprout Server will be played by the SproutKey, which I'll take about in a moment.
One other noteworthy thing is that Sprouts do not actually require a sprite to draw. You can create theoretical Sprouts which exist only as links. In the Comic app, I use a root Sprout which links to the drop shadow and then to a body. If you wanted to make a sparkle effect, you could create a different root Sprout which links to the original (shadow + body) with a second link to a sparkle effect Sprout, itself just a series of links to SPARKLE Sprouts.
Sprouts are loaded from a JSON structure and stored, by name, in a global database. In this way, Sprouts can be referred to by a name string rather than referencing the object directly. As such, each Sprout name must be globally unique.
In the code, this is referred to as a SproutKeyEntry. Simply put, Sprouts are intended to be an immutable object. They are loaded once and referenced by many others. To keep track of information about how it is drawn, you need a second mutable object which has stuff like the transparency level, position, whether it is visible or flipped, and so on.
This render context is kind of the building block of the comic system. Even though Sprouts know how to draw each Sprout at a link, I wanted more control over how they were drawn. For instance, I might want to flip just the head for a character looking back. Or if the head was flipped around, it might not immediately look good without being shifted over a pixel or two. Or shadows needed to be drawn at 30% transparency. As such, the Sprout Server doesn't return a Sprout directly, but a Sprout Render Context which draw said Sprout after modifying its render capabilities.
You can apply one context onto another. One of the cool things about JavaScript is for variables to be undefined, so you can apply only the defined elements and ignore the others. For instance, if I wanted to turn the head, I could have a render context which consisted solely of "flipped:true" that, when applied to another context, would only change the flipped member.
That is the basics of how something like "POINTING" works. It just changes the render context for "BODY" to point to "MAXIM_POINTING", and touches nothing else. "LOOKING_BACK" is a render context which changes the flipped variable to true of another context.
Finally, render contexts can be cloned. The premise of the comic building app is that each frame contains a copy of the scene, which is then changed and copied again for the next frame. Since these render contexts make up the information on how to draw a character, they are also copied.
Called a SproutKey in the code, though referenced as templates elsewhere, a Sprout Template is basically just a table of render contexts associated with key names like "BODY" or "HEAD". These serve the purpose of a Sprout Server combined with the render context. Indeed, the entirety of an Actor's existence is just a Sprout Template. Similar to render contexts, a template can be applied to another template. In this way, "SHOUTING_ANGRILY" could change the attributes of "BODY", "MOUTH", and "EYES". And like the render contexts, it can be cloned for use in the next frame. Templates are kept in a global DB just like Sprouts.
I created a basic sort of class system for templates using a basic prefix system. When you declare an actor, you declare which template it uses ("MAXIM"). It then sets up the initial template by copying the template with the name + "_DEFAULT" (such as "MAXIM_DEFAULT"). Any time you want to apply a template, it will first search for a template with the name prefixed. For instance, for "SHOUTING" it will first look for a template named "MAXIM_SHOUTING" and then "SHOUTING" if the first one returned nothing. This allows me to have a character specific version of common actions.
If you look in the data_test.js file, you'll find the definitions for the sprouts and templates. The MAXIM_DEFAULT template sets up all the basic pieces of the Maxim actor type, with MAXIM_POINTING, ANGRY, LOOKING_BACK, and MAXIM_HOVERING providing state changes that modify what the actor is doing. Looking at the comic definition file in data_testscene.js, you'll see these templates being used with the "actorApplyState" commands, such as:
["actorApplyState", "MAXIM2", "POINTING", "ANGRY", "LOOKING_BACK", "HOVERING"]
All that is doing is applying those named templates (POINTING becomes MAXIM_POINTING because it is the MAXIM class) to the actor's own template representing how to draw itself.
|