AniSprite tips

Skip to main content (skip navigation menu)






AniSprite tips

 

This page contains several tips that exploit specific AniSprite features or tricks that show some creative use of the AniSprite toolkit. What you will find here is a hodgepodge of mostly unrelated snippets, explained in an informal style, for example:

Articles that are either too long for a "tip" or that are not uniquely specific to AniSprite are covered at separate pages. See for example:

 

 

Splitting a sprite and its shadow mask into two different sprites

In AniSprite, a sprite can have a shadow. Or more accurately, a sprite may have a luma mask that says not only what parts of the sprite are transparent and what parts are opaque, but also what parts of the image below the sprite must be darkened by a certain degree. In the implementation of AniSprite, every sprite has a Z-order value that tells which sprite is on top of which other sprite. The shadow of a sprite with a high Z-order value would not only fall over the shadow of a lower sprite (lower in terms of the Z-order), but also over the "face image(s)" of any lower sprite(s).

With careful classification of Z-orders, you can often achieve an interface that looks right. Assuming that the virtual light point is in the upper left corner of the window (so that the shadows drop to the ground and to the right of any object), the general procedure is to start at the upper left corner of the window and give the sprite in that corner the lowest Z-order value. Then you work rightwards and downwards and increment the Z-orders of the sprites progressively.

The technique outlined above works fairly well when the interface is fairly static, but it becomes a pain to maintain the Z-orders if sprites are added and removed or when the sprites change in Z-level frequently. In those cases, a more advanced solution is required.

For the moment, let us assume that all sprites in an animation are logically standing side-to-side. All shadows, then, should fall behind all faces of the sprites. The Z-order of the face images does not matter, since the buttons do not overlap and, as a result, the shadows do not overlap either. AniSprite does not support sprites with both a Z-order for the face and one for the mask, but it is fairly easy to generate two sprites with different Z-orders from a single image:

  1. The first step is to load the image for the button and create a luma mask for it.
  2. You then create a second luma mask from the same image. With function as_ReduceMask(), you subsequently convert the additional luma mask to a cutout mask.
  3. The "face sprite" is made from the face image and the cutout mask.
  4. The "shadow sprite" contains only the luma mask (it has no image).
  5. When you link the shadow sprite to the face sprite, the shadow sprite automatically follows the face sprite when the face sprite's position changes. Through the master/link connection, your program can manipulate the sprite duo mostly as if it were a single sprite (with two Z-orders).

text divider

 

Using luma masks to simulate alpha blending with a single fixed colour

A new feature of AniSprite 2.0 is that a luma mask can blend towards any palette index. This makes luma blending applicable to other areas as only shadows. The new "Sisters" demo uses luma blending (towards white) to make the halos around the narrative text that slides in and out of the top of the screen. The function as_SmoothMask() creates the softened edges of the sprites. Since the "Sisters" animation uses luma levels both for shadows and for a limited (but effective) form of alpha-blending, the luma levels that as_SmoothMask() makes must be relocated to the correct starting point. This is the purpose of as_MapMultiLevels().

text divider

 

Group operations

NOTE: This tip refers to AniSprite version 2.1. Starting with version 3.0, AniSprite supports circular links, which makes the "master cycling" procedure below redundant.

Links between sprites let the linked sprite move along with the master sprite when that master sprite changes position or in shape. Linked sprites are convenient when building an animated object ("animob") from several layers, for example, the master sprite of the hero has a white spot where his face should be and his eyes and mouth are in separate sprites that are linked to the origin of the master sprite. A good position for the origin of the master sprite would then be the centre of his face.

Master/link relations between sprites are not always "hierarchical" like in the preceding example. Sometimes you will want to group a series of sprites without denoting any particular sprite as the "master". AniSprite does not have this concept, though. An approximation of "groups without master" that has worked for me is to make the sprite that I am going to move temporarily the master of the group. Now, whatever sprite I move, all other members of the group will follow it, because that sprite has just become the master.

Below is the code snippet that I use to change the master sprite of a group.

Cycle the master of a group of sprites
void CycleMaster(ASPRITE NewMaster)
{
  ASPRITE Sprite, OldMaster;

  /* unlink the new master (which is a "link" of the
   * current master) from the current master
   */
  OldMaster = as_GetData(NewMaster, AS_DATA_MASTER, 0);
  as_Link(NewMaster, NULL);

  /* move the links of the old master to the new master */
  while ((Sprite = as_GetData(OldMaster,AS_DATA_LINK,0)) != NULL)
    as_Link(Sprite, NewMaster);

  /* the old master becomes a link of the new master */
  as_Link(OldMaster, NewMaster);
}

Another typical operation on groups of sprites is to get the bounding box of the entire group. This is a relatively simple routine that walks through the group and updates the minimum and maximum extents in horizontal and vertical coordinates with the bounding box of each sprite.

A code snippet the does this:

Get the bounding box of a group of sprites
void GetGroupBounds(ASPRITE Master, LPRECT rc)
{
  int x1, y1, x2, y2;
  int i;
  ASPRITE Sprite;
  LPRECT rect;

  rect = (LPRECT)as_GetData(Master,AS_DATA_BOX,0);
  x1 = rect->left;
  y1 = rect->top;
  x2 = rect->right;
  y2 = rect->bottom;
  // get bounding box around the group
  for (i=0; (Sprite=as_GetData(Master,AS_DATA_LINK,i))!=NULL; i++) {
    rect = (LPRECT)as_GetData(Sprite,AS_DATA_BOX,0);
    x1 = min(x1, rect->left);
    y1 = min(y1, rect->top);
    x2 = max(x2, rect->right);
    y2 = max(y2, rect->bottom);
  } /* for */
  SetRect(rc, x1, y1, x2, y2);
}

text divider

 

Storing pictures in resources

AniSprite supports loading image from a resource. To this end, you must pass to function as_LoadDIB the instance handle (HINSTANCE) and the resource type. For the resource type, AniSprite supports RT_BITMAP, RT_RCDATA and user defined types like "picture".

The resource compiler treats the RT_BITMAP differently from RT_RCDATA or custom types: it strips off the file header of the DIB. In addition, at least two resource compilers that we tested did not handle RLE compressed DIBs correctly. I do not know whether this is a problem common to all resource compilers. In any case, when you store RLE compressed DIBs in a resource, I advise you to use the RT_RCDATA type or a custom type.

text divider

 

Using a "filmstrip" approach for internal animation

Changing the shape of a sprite while moving it over the screen makes the animation more interesting and, more importantly perhaps, it increases the perceived fluency of the animation.

How can internal animation improve fluency? Sprite animation is a simple act of moving a sprite to a new position at a fixed time interval. If the time interval is too big (the frame rate is too low), we don't see fluent motion but a kind of stroboscope effect: discrete positions at discrete time intervals. It is my experience that the changing of the sprites appearance distracts our visual system from noting the discrete positions of the sprite at discrete time intervals. It is as if, when there is little change from one frame to the next, we subconsciously focus on that change and, when there is a lot of change, our visual system gives up and blends (or blurs) the sequence of frames in "motion".

When you have a lot of different shapes/frames for a sprite, I encourage you to combine AniSprite with EGI. This gives you compressed storage of the frames in memory, efficient decoding of a frame and its associated mask and a few other goodies like automatic cycling from the last frame back to the first frame. This is the technique used in the Sisters demo.

For fewer frames, there is an alternative that may be a bit more work, but one that does not require a second tool. The "filmstrip" approach is one where all frames of the sprite are stored in one large bitmap. I usually create a vertical strip: if I have a sprite that is 50 × 50 pixels and 4 frames, my bitmap is 50 × 200 pixels. When displaying the sprite, you create a clip rectangle that has the width and height of the sprite and the (x,y) offset to the desired frame. Referring to the example sprite of 50 × 50, the clip rectangle would also be 50 × 50, the x offset is always 0 and the y offset is the frame number × 50 (for frames 0 to 3). Then, the filmstrip bitmap and the clip rectangle are all passed to as_Animate().

If you move a sprite to some location, what AniSprite does is to set the sprite's origin to that location. The origin of a sprite, by default, is the upper left corner of the sprite's image (or, in our case, the filmstrip)). We will want to always have the sprite's origin to be inside the clip rectangle, for example: the upper left corner of the clip rectangle. To that end, we call as_SetOrigin() after as_Animate().

There is an unintended side effect with as_Animated() in the procedure described so far: if you move the clip rectangle over the filmstrip, to select a new frame, the position of the sprite on the display changes as well. Note that the sprite does not move relative to the board: the board stays fixed, the sprite's position relative to the board stays fixed, but the clip rectangle (which is a kind of viewport through which one sees the visible part of the sprite) moves over the sprite. So what you will want to do is to move the sprite's image with the inverse distance as what the clip rectangle is moved with. Now, here is a trick: when you query the position of the sprite, using as_GetPos(), you get the coordinates of the sprite's origin relative to the board. When you set a position of a sprite, with as_SetPos(), you specify the new location of the origin of the sprite. In the switch from one frame of the filmstrip to another, the location of the origin of the sprite relative to the board does not change; what changes is the position of the sprite's origin relative to the filmstrip image. So, if you query the sprite's position before changing the sprite's origin and set sprite to exactly that location after changing the origin, AniSprite makes the new clip rectangle align with the old sprite position.