This is the third article in a series that starts with Sprite Editor 2017.
Introduction
In this article, I'll show you how you can draw the same sprite using a single instance of the classSprite
so that each sprite on your screen looks different from the one before by dynamically changing the images of your sprite's limbs during runtime. Here's a screenshot of what I mean.
We've already seen how to put a sprite on the screen in the Bugs article. So today, we'll look at how to change the sprite's appearance using another class designed specifically for that purpose called classUniform
.
How It's Done
I'm going to keep this article short because there are other projects I want to get to. Essentially, what this app does is it takes images from its Resources directory that are divided into four categories:
- Hair - their hair color images
- Leotards - the leotards the gymnasts are wearing can be of various colors
- Skintone - there are three skin tones: White, Black & Light skinned
- General - these are the images that do not fit into any of the previous categories
The hair & general categories are relatively simple. The randomly selected hair color images are loaded from file and used as is while the general images such as tongue, eyes & flower bouquet are all the same for all the girls. The complications come in when we get to the Leotards & Skintones because some of these have to be randomly selected just like the hair, but they have to be pasted together dynamically to generate the images that are a combination of both. The TorsoBottom
image & most of the SkinTone
images are fine by themselves but the TorsoTop
, ArmLeft
& ArmRight
limbs need to be generated at runtime to satisfy the randomly selected combination of skintone/leotard color.
The original source images are first rotated while the app is loading. They're very small images and don't take very long to rotate, so that can be done at runtime while the default gymnast does her animations on the screen to keep the user distracted. The advantage here is that you can add more images, as many as you want, of different hair colors, styles, different skintones, and leotards. You could even make a boy gymnast if you wanted to, or a policeman or any hominid with images that are the same size. You just have to stick to the standard source file image name convention this app expects. I'll get to that later. The classUniform
makes a list of all the source filenames and of all the rotated image filenames, compares the two lists, then loads and rotates any new source images that are not in the list of filenames of rotated images. This business is a tedious process and involves collecting the images by their name tags to gather all the skintones with the skintones, the colors with the colors. It then separates the hair from the leotards and manages lists of leotard parts that are sorted by color. Each list has a complete set of whatever skintone/hair color/leotard type they are. Mind you, at this point, the only images that the App has actually loaded are the ones it has had to rotate and save to file. These lists are just sorted lists of filenames. Once this process is completed, the source data has been sorted and is ready to be used to generate random combinations of them and make 'uniforms' for our little gymnasts. Only then does each instance of the formGymnast
load the images appropriate for its 'uniform'.
So far, I've made two failed attempts to include a way to swap the sprite's images quick and easy. The issue with that is that currently the classAnimation_Frame_LimbData
holds a pointer to the limb along with the variables needed to position the limb for that frame but what I'll have to do is rewrite that whole scheme and have the HingeContacts
in the same class as the LimbImages
and then keep a collection of these in the same order as the limbs for every uniform. So one sprite's hinge contacts no longer have to match the next version of the same's sprite's limbImages
. The limb images of each version of the same sprite carry with them the HingeContact
information needed to draw the sprite. It's do-able. I'm just eager to publish this project as an act of catharcism. I'll get around to doing that and update you with a fourth article when I've completed that task. Whenever that happens.
How to Dress Your Sprite
There really isn't much to once you've got your limb images ready. All the code you need to swap the appearance of your sprite is found in the first few lines of the formGymnast
's Animate()
function. Let's have a look:
classAnimation_Frame cFrame
= cData.cAnimationData.cAnimation.lstFrames[cData.cAnimationData.intFrameIndex];
setSpriteUniform(ref cFrame);
bmpImage = cData.cAnimationData.cSprite.draw_Animation_Frame(ref cData);
SetBitmap(bmpImage, (byte)255);
Point ptRelLimb
= (Point)cData.cDrawData_current.cAT_LimbPositions.Search(cFrame.Limb_DrawRelativeTo.Name);
Location = cMath3.SubTwoPoints(cData.pt, ptRelLimb);
First, we load the frame we want to draw into a variable we can use as a pointer for the call we make to the function:
setSpriteUniform(ref cFrame);
This is where the images get swapped.
public bool bol_I_Am_the_Loading_Sprite = false;
void setSpriteUniform(ref classAnimation_Frame cFrame)
{
for (int intLimbCounter = 0; intLimbCounter < cFrame.lstDrawSequence.Count; intLimbCounter++)
{
classSprite_Limb cLimb = cFrame.lstDrawSequence[intLimbCounter];
if (cLimb == null) return;
List<classsprite_limb.classsprite_limb_images> lstImages =
(List<classsprite_limb.classsprite_limb_images>)
cUniform.cAT_LimbImages_ByLimb.Search(cLimb.Name);
if (lstImages != null)
cLimb.lstImages = lstImages;
}
}
Since all the girls have their own 'uniforms', each gymnast has to copy her own images over those that are already in the classSprite
and the setSpriteUniform()
function is where it all happens. This is essentially all you have to do to change the sprite's appearance. Load the limb, search the uniform's LimbImage alphaTree
for that limb's images and convert the result to a list. Then, when you know all is right with your list of LimbImages
, you just have the limb's lstImages
point to this new set of images for that limb. Simple.
When the app leaves this function, it goes back to the Animate()
function, gets an output image from the classSprite
then places it on the screen in the same way the formCreepyCrawler
did in the bugs app of the previous article.
Finite State Machine
Although the FSM is not what this article is about, I'll just put a few words in. Essentially, it keeps track of what the gymnast is doing. Mostly, it just loops through the same animation a randomly predetermined amount of times. When it completes that animation's last frame, it goes into a Switch/Case to decide what to do. If its current FSM state is Frolick
, then it will randomly pick another animation and repeat it a random number of times. However, if it was told to Wrap_It_Up
, it will have her FSM state switched to Waving
. When it completes that animation, it goes through the Switch/Case again but now she's in the FSM state Waving
, so this time her FSM state is set to Exiting
and she starts to walk off the screen. When she reaches the same Switch/Case again, she'll ask herself if she's off the screen yet. If not, she'll just keep on walking for another loop of her animation and she'll eventually reach a point off the screen at the end of her Exiting
animation and her FSM state will be set to quit. At which time, the parent calling form will detect it and dispose of that instance of the formGymnast.
All this work is done in the Switch/Cases found in the FSM property and the Animate()
function. It's a pretty basic Finite State Machine.
Adding Images to the Gymnasts
You are, of course, invited to create and add your own images. You must keep in mind the file naming convention which the Gymnasts app expects.
- There are three types of groups of images. In order for your images to be included into the app, the group in which it belongs must be complete. All the images in the set must be present for any of those images to be included into the app.
- Listed above are the base filenames of the images included in each of the three sets.
- All the files you are adding to the App must have a unique identifying name-tag added to their base name.(e.g.
Hair_Pink
& Ponytail_Pink
make a complete set) - skintones must end with the word "
Skinned
" in order to differentiate them from the Leotard
images where ArmLeft_
, ArmRIght_
& TorsoTop_
appear in both the Leotard
and Skintone
lists. A closer look at the list of files already in the Resources directory may help you in naming your images. (e.g. ArmLeft_BlackSkinned
is a skintone
and ArmLeft_Pink
is a leotard
, when random chance combines the two together, you will have a black-skinned gymnast in pink leotards.) - You are not limited to the hair styles represented in the original source material. It's easiest to use a copy of an image already included and alter the color. Altering the shape of your image is more difficult because the app will position it relative to the center of the image. The Hair_MohawkBlack.bmp image is a sample you can use, where the image was stretched vertically upwards to fit the Mohawk hairstyle and had therefore to be stretched vertically downwards by an equal amount in order for the hair to appear on the sprite properly.
In order to generate the images you want and be certain that you get all the images you need to complete a set, it's easiest to make a separate directory, make copies of the images you want to imitate and make sure you have a complete set placed aside in your working directory. Then, go over each image and tinker with their colors and styles. Make a Bob Marley leotard if you like. Whatever goes. When you're happy with your new images, copy them into the resources directory with new names that are appropriate for their appearance and follow the rules listed above. When you run the App again, your images should be included and you'll have new Hair-styles and leotards to enjoy.
Remember that the images are stored in .rot files once they've been included into the project, so merely changing the .bmp of an existing file will not be recognized by the App
until you delete the equivalently named file with the .rot extension.
e.g. If you edit the Hair_Pink.bmp file that already exists in the Resources directory and save it with the same name. Your changes will not be loaded into the App
until you delete the Hair_Pink.rot file.
What's Next
During the process of going over the SpriteEditor, Bugs and Gymnasts apps while writing these articles, I realized there were a few things I could do that could improve performance:
- Review the need to test the
LimbImage
names when caching images - Draw a single full size image of every frame and resize only the output image and limb position variables rather than resizing every individual limb.
- Rethink the way the Frames position the limbs so that there is a new class that holds both the Limb Images and the Limb Hinge Contacts. Keeping a list of this class that is in the same order as the list of limbs, the entire list of
limbImages
& Contact Hinges could be loaded together with a single pointer variable like a changeable cartridge which will allow the creation of sprites that describe general types of creatures like Hominids or 6-legged insects that all share the same animations but load their own 'cartridge' of images and their accompanying hinge contacts greatly improving the flexibility and versatility of the entire class.
For now, however, I'm glad to put this aside and go back to my LatinProject.
Gratias do et, si vis, fortasse iterum id agemus. Vale!