Lacklore Dev Blog: Pixies

Pixies are like sprites, except they’re composed of vector paths that have various possible Pixie material types associated with them. Okay, maybe they’re not like sprites at all!

A Pixie allows me to take a vector graphic, like the Iron Tower creep here, and apply various cool things to it at runtime:

For example, the left Iron Tower creep has a glass material, and less obviously, the right one has a cork material. Not exactly visible are entries in a color palette. A path can be assigned a color, such as “iron” (like the Iron Tower here). The default palette would have iron being that pastel brown color, while a “rusty metal” palette would make iron orange. Thus, a rusty Iron Tower would be orange! There are some special palette entries, default and variants, that are replaced at runtime based on various attributes.

Every Thing in Lacklore (objects, items, NPCs) have ThingMaterials associated with them. For example, a potion bottle would have a base ThingMaterial of glass, and a secondary of cork. The base material is replaced by the description given to the Thing, if applicable, while the other materials are unaffected.

To visually represent this behavior, I create material vector graphic that can be tiled, like glass:

I then assign a TexturedPixieMaterial (not to be confused with ThingMaterial; the former is a material in a rendering sense and the latter is a material in a gameplay sense) to specific paths I want to inherit the material fill. A TexturedPixieMaterial has properties like rotation, scale, and offset that allow more flexibility in how the material fills the path.

The rendering is simple:

  1. Enable texture wrapping.
  2. Set the stencil buffer to increment.
  3. Draw the path.
  4. Set the stencil buffer test to not equal zero.
  5. Draw a rectangle the same bounds of the path (to cover it completely), rotated by the TexturePixieMaterial’s rotation, using a source rectangle scaled and translated by the other properties. (The actual scaling, translation, and repeat will be texture wrap mode).

And voila!

Here’s the step-by-step process:

(Path drawn into framebuffer. This is the view of the stencil buffer).

(Tile drawn into framebuffer. This is the view of the color buffer.)

Note: There’s a small problem with multisampling causing seams. That’ll require some thinking to solve…

To map ThingMaterials to Pixies, I created a ThingModelRenderer. A model is a resource that describes the graphical parts of a Thing; in other words, it’s like an unrigged model in a 3D game (hence the name). The textures (another resource class; exactly analogous to textures in the previous example) of the ThingMaterials assigned to the Thing instance are then assigned to slots in the Pixie (0 being the base material, positive numbers being secondary materials, and negative numbers for “filler” materials, like potion bottle contents). The ThingModelRenderer then renders the models with the corresponding ThingMaterial textures, including adjusting the color palette and whatever other graphical attributes the materials provide. The final object Pixie can then be used by other rendering systems, like the ThingSkeletonManager.

Here’s an example of the Pixie system when spawning some knights:

Notice the changes in color and texture when spawning a glass knight, a green cork knight, and an undead knight. This is all thanks to the Pixie system!

Pixie Builder

As an aside, I had to create a tool to assign Pixie materials (not to be confused with ThingMaterials) to an input vector graphic (stored as SVGs):

The material data is serialized into an attribute of the path element (lacklore-pixie-material) as a Base64-encoded blob. This means I can edit the shapes in InkScape without having to reassign existing materials later! Nifty.