Click here to Skip to main content
Click here to Skip to main content

Sprite Editor 2.0

, 30 Mar 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
build, edit and animate your own sprites in .Net

source code

sprites

Sprite Editor 2.0 : what's new?

Many of you may have already read the first Sprite Editor article which i published about a year ago. Since then i've been working on it on and off while plugging away at other projects and both the class and its editor have improved quite a bit. The sprites themselves still work much in the same way as they always have and I'll refer you to the first article for the details about the inner workings of the sprites but will summarize here as follows : a sprite is an animatable figure consisting of images called limbs linked together by articulating joints called hinges.

A typical sprite may consist of a torso with several slave limbs such as arms, legs and a head linked to it via shoulder, hip and neck hinges. There is no limit to the number of limbs a sprite can have neither is there any limit to the depth of slave limbs so that you can have a complete humanoid sprite with arms attached to a torso, forearms attached to the arms, hands attached to the forearms and fingers attached to the hands.

Here are some of the new improvements I'll be talking about :

  1. load time reduced to microseconds
  2. variable number of images per rotation
  3. RAM & file caching system for performance enhancement
  4. single-load sprite sharing - reduced memory requirements for multiple instances of the same sprite
  5. sprite-composite - independent sprites dynamically linked together at run-time
  6. stationary limb
  7. form-sprite
  8. new Editor features for a more user-friendly development environment

Building your first sprite

First let's go through the process of building our first sprite. To follow along with this example you'll need to download the source code as well as the sprite_Smurf .zip file which contains all the smurf-images along with an existing Smurf-sprite if you don't quite feel up to doing any of the fun stuff.

What you need to do now is launch the Editor and click the File-New Sprite menu option which will bring up the form seen below. This is where you get to decide how many images your sprite will have per quarter rotation. Because the Bitmap functions native to the .Net languages allow you to easily rotate an image by turns of 90° the sprite class only needs rotate an image through its first quarter rotations and then quickly generate the rest of the images on the fly. Sixteen images per quarter turn means that there'll be 64 angles around the circle giving you a new angle about every 5.5° which you'll probably find adequate but can change whenever you like using the Limb-set num images per rotation menu option.

Type in the name of your new sprite and press enter.

building_a_new_sprite_01.PNG

Next you'll want to add a few limbs. Remember that whenever you add a new limb it will be attached to whatever limb you've currently got selected but since we're only getting started the first limb will be attached to a limb you can't see called the Master-Limb, its sort of like the Wizard of Oz behind the curtain. Click the Limb-Create New menu option and the following form will appear. and now you know you're not in Kansas anymore!

building_a_new_sprite_02.PNG

At this point you'll want to type in the name of the new limb. The first limb you attach to your master-limb will be the one off of which everything else branches so in most humanoid type characters I'd suggest you make this the torso. This isn't a rule but if you make one of the feet the first limb then all other limbs will branch away from the foot, this may be what you want but it will start to look peculiar when the body follows the foot in a toe-tapping jig that sends the head and all the rest contorting in every direction later.

Start with the torso.

building_a_new_sprite_03.PNG

Type in the name of the new limb, select a new limb image then enter the name of its hinge. It'll make things easier for you later if you stick to a few simple limb-naming guide-lines and you stay consistent. In the case of many profile sprites you'll find it less confusing to talk about the far-arm and the near-arm rather than the left-arm and the right-arm because these will be backwards when the sprite is flipped in a mirror. Press the 'Enter' key in the Limb-Name textbox after typing a partial name and the editor will try to locate the file you mean, if it doesn't get it right or doesn't try at all you'll have to do it manually by clicking the image and finding the file yourself but when you press the Enter-key in the hinge-name textbox you'll be proceeding as if you had clicked the ok button at the bottom of this form.

Now that we've got the torso on the screen you'll have to consider renaming your new sprite's first configuration. Briefly, a configuration is a series of positions in which your sprite has been configured. You can position the limbs of your sprite at any angle you want and add steps to your configuration so that the creature you've built appears to move like an animated cartoon. You can have as many configurations as you want such as walk, dribble or dance. The textbox labelled Name in the Configurations groupbox is where you type in the name you want and when you press the enter-key that name will also appear in the combo-box near the top called Selected Configuration.

building_a_new_sprite_04.PNG

Now we're ready to add yet another limb. Let's give Grumpy his head back! First you'll have to select the limb onto which the head will be attached. Since we've only got the torso limb on the screen, and that would likely be the obvious choice anyway, the torso is the limb you're lookin' for. You can select a limb by either clicking it on the screen when your mouse goes over it, picking it out of the combo box near the top of the screen or by selecting it from the listbox on the left. Once you've got the torso selected go to the limb-create new menu option again.

This time you'll see that the torso limb appears in the image below the new-hinge-name text box. If the master limb you wanted does not appear here its because you're not attaching your new limb to the right master and will have to abort this form and try again. You'll notice that there's a new combo-box that didn't appear here before labelled "use same image as:". Because the limb images need to be rotated 16x, 45x or ??x its sometimes easier to rotate one limb and then reuse the same rotated images for subsequent limbs that use the exact same image. You can do this by selecting the original already rotated image out of this combo box. building_a_new_sprite_05.PNG

Get Grumpy's head off the hard-drive, type in the name of this new limb as well as its hinge name and click the ok button.

building_a_new_sprite_07.PNG

When the new-limb form disappears you'll see the image below. You can't see the torso anymore because Grumpy-smurf's head is so big it covers the torso completely, must be the genes. What we have to do is position the neck hinge somwhere on the torso and then position the head somewhere relative to the neck because right now the sprite-editor has the head-neck-torso all caught up together right in the middle of the screen.

First off, click the Mouse Set button circled below.

building_a_new_sprite_08.PNG

The following form should appear. This is an image of the head's master-limb. We've still got the head-limb selected and the button you clicked allows you to set the head's master-hinge's master limb position : that's just a confusing way of saying 'where to stick the neck'. The neck-hinge needs to be positioned somewhere relative to the torso so click on the torso's picture where the neck belongs and this form will disappear.

building_a_new_sprite_09.PNG

That's better, but obviously we're not done yet. Now click the next Mouse Set button in the second groupbox shown here circled again and you'll be setting the head's master hinge's slave limb position : 'where the head's at!'

you positioned the neck relative to the torso and now you need to position the head relative to the neck. This way the neck follows the torso and the head follows the neck. Master and servant, where the master goes all the servants follow.

building_a_new_sprite_10.PNG

The form below will appear, click on the image of Grumpy smurf's head where the neck belongs and this form will disappear.

building_a_new_sprite_11.PNG

Still not right, eh?!? You now have to turn Grumpy's head around so that its on straight. He doesn't always like that but don't worry he's grumpy all the time.

To do this just click the set Angle button here circled for you and spin the mouse-wheel to rotate the selected limb, in this case Grumpy's head. Alternately, if you right-click your mouse a context menu will appear with a few options including set Angle and set Angle Control. The set Angle Control options allow you to either use the mouse-wheel (default setting) or resort to the original Mouse-Pos setting which lets you move the mouse to any position you like, sometimes this is faster but the mouse-wheel allows better precision.

building_a_new_sprite_12.PNG

here we are, just a little more...

building_a_new_sprite_13.PNG

once Grumpy's head is on his shoulders we can start adding the other limbs. Let's start by adding the left arm and calling it far-arm. When you've gone through all the steps it might look something like this and you'll ask yourself "why is Grumpy's arm sticking out of his chest?" and the answer is simple "He got beat-up by the Teenaged Mutant Ninja Turtles" of course!

building_a_new_sprite_14.PNG.

so now you know...

This is where we talk about the draw-sequence and the order in which limbs need to be drawn. You can see from the image below that if we draw the torso first, the head second and the far-arm last then this "far-arm" will appear at the front. To correct this we need to re-order the sequence in which these limbs get drawn. Typically you'll want to draw a sprite's limbs in this order

  1. far arm
  2. far leg
  3. torso
  4. head
  5. near leg
  6. near arm

and to change the order in which the limbs are drawn we turn our attention to that big white list box on the left side of the screen. This list-box allows you to control the draw-sequence of your sprite, or the order in which the limbs are drawn. To make the changes you'll have to select the limb you want to move up/down the list and spin the mouse-wheel to get it into position.

when you've moved Grumpy's far-arm into place you'll instantly see the difference.

building_a_new_sprite_15.PNG

now that you know what you're doing see if you can put ol'grumpy here back together again!

assemble_smurf.png

Configurations

Configurations should probably more aptly be called "Animations" but when I first came up with the idea i was more conscious of the inner-workings of the thing than any marketable naming ideas and configuring all the hinge angles for each step of the animation was really all i was thinking about and so now we're both stuck with the word. Just get used to it : configurations are really just animations. That's it. Each configuration consists of steps, and each step consists of values for all the hinges in your sprite. When you go from one configuration step to the next the hinge angle values are set to the values which you programmed in your configuration. To do this you position the sprite's limbs at whatever angle you want them for the step you're working on and when you're ready you just click the add configuration step circled below and the next step will be ready for you to edit. When you've got all your steps configured you can watch your animation by pressing the animation button.

You can do all this conveniently using the right-mouse click context menu.

building_a_new_sprite_16.PNG

Using the Sprites in your project

I'll talk about the sprite-composites in a little while but for now we'll get you started with what you've got. To instantiate a sprite into your project you'll first have to include the following three files along with your intended sprite .sp3 file(s).

  1. classSprite.cs
  2. classMath.cs
  3. PerPixelAlphaForm.cs

and don't forget ol'Grumpy!

Then, in your code, you create yourself a sprite-maker and use it to load your sprite.

classSpriteMaker cSpriteMaker = new classSpriteMaker();
classSprite cSpriteSmurf = cSpriteMaker.loadSprite(strFileDirectory + "smurf.sp3");

when you're ready to get an image of your sprite you call the sprite class's handy image getter function shown below

public classBmpAndPtTLInfo getImageOnBitmap
           (int intConfigurationIndex, 
            int intStep, 
            double Angle, 
            double dblDisplaySize, 
            enuMirror eMirror, 
            ref classSpriteLimb limStationary, 
            bool bolForceRebuild)
there really isn't much to it. I may not have mentioned before but whenever you load the same sprite multiple times inside your project you're really only creating multiple pointers to the same sprite because the sprite-maker keeps a static array of sprites and whenever you try to load the same sprite a second or third time then all you get is a pointer to the first one. This way you don't need to use so much memory to get the same images on the screen. The down-side to this is that you can't have two projects running at the same time trying to access the same file, if you try to do that you'll get a file-sharing issue but all you'd have to do is copy the file to a separate directory and it won't be an issue any more. What's important to note about this is that with the possibility of multiple characters all using the same sprite there's no real point in keeping information about the character(e.g. configuration, step, angle, size, etc.) in the sprite class since each character may be doing its own thing. Therefore, this information is no longer retained by the sprite itself, it is however kept in the sprite-composites which I'll bring up in a little bit. The sprites have a file-stream caching system and the load time is reduced to near zero because the only information that needs to be loaded from the start is the configuration values and it can do that in no time at all. Then the limb images are loaded on request and as needed. You have the option of using Ram-Cache but even if you don't the Hard-drive cache file is pretty quick and you won't have to worry too much about memory issues. the cache-file uses the sprite's name, as oppoosed to the sprite's filename, and creates a binary file-stream such as "smurf_cache.fs". When the sprite is first loaded and it generates its first configuration image, that image is cached only after the cache file is created. Since you don't know which configuration, step, angle and mirror combination is going to be called first and you don't want to have to spent twenty minutes generating them all, the cache file uses an indexing system and only the index portion of the file needs to be initialized.

We know there's a fixed number of directions the sprite can be facing in since there are only so many images per rotation, this value is fixed for all limbs within a single sprite but can vary from sprite to sprite. We also know the number of configurations a sprite has, the number of steps each configuration has and there are only four possible mirrors. Armed with these values rebuilding the cache file is simply a matter of storing a -1 in every possible image's index location.

Given that all of these values are whole numbers the class uses the function

long getImageIndex(int intConfig, int intStep, enuMirror eMirror, int dir, int intLimbIndex)
{
return (long)(lngConfigIndexStart[intConfig]
                + lngSizeIndex * (intStep * intNumDir * intNumMirror * intNumLimbs
                                    + (int)eMirror * intNumDir * intNumLimbs
                                    + dir * intNumLimbs
                                    + intLimbIndex));
}

to uniquely map out where each image's index will be for this particular sprite. This way whenever it wants to cache a new image it looks at the size of the file and records that value in the appropriate index location for this particular image and then appends the file with the new image. Though the images are generated randomly and in any order as required the index portion of the file gets filled out as the images come in and when testing to see if the image we're looking for is in the cache we test the index for a value greater than the -1 we initialized it with then come back with a freshly generated image if its not there or go and fetch it off the file if it is.

sprite bitmap information

the function getImageOnBitmap() returns a variable of the class shown below :

public class classBmpAndPtTLInfo
{
    public Bitmap bmp;
    public classMath.classDoubleCartesian dptTL = new classMath.classDoubleCartesian();
    public classConfigStepLimbPosition[] limbPositions;
    public classConfigStepHingePosition[] hingePositions;
    public classCacheTreeNodeKeys cKey;
    public bool bolRamCached = false;
}

This information is what will help you put the image on the screen exactly where you want it. You may have already picked out the fact that I didn't mention the image size with too bold a font. Well, I'm about to. Everything else about the "getImage" call can be described with whole numbers but the image size is a value ranging around 1.0. Anything bigger than one will result in an output image bigger than the original, anything smaller than a one will result in an output image smaller than the original but the variations have an infinite range and therefore do not lend themselves easily to index mapping like the other parameters do. Therefore, the 1.0 image is the only image that is stored onto the hard-drive's cache and then when it is retrieved this image's bitmap is resized and the values in the limbPositions[] and the hingePositions[] are also adjusted before going to the screen. This may sound like a lot of work but its much easier to resize an image and multiply a bunch of cartesian variables by some factor than to generate the entire sprite all over again.

The limb and hinge positions are relative to the stationary limb. and the stationary limb is the limb about which every thing else moves. By default the stationary limb is always the first limb you added to your sprite, or more exactly the limb to which that is attached that infamous and never seen -Master-Limb- mentioned earlier. but an interesting feature about the new sprite class is the ability to position the sprite relative to any limb. you may make a game with a sprite of Angelina Jolie jumping onto a wall and grabbing a ledge then pulling herself up but if you try to animate that by locating her position relative to her torso then her hand may not necessarily always be on that ledge from which she's hanging. but if you make her jump and then lock the hand as the stationary limb while she swings from the ledge then you know where her hand goes and the rest just follows.

how far did you get in Tomb Raider on your PC? i got stuck at some point where she was running around and there wasn't anywhere to go, then i got bored and played Wolfenstein.

moving right along...

so, the bitmap information that you get from calling the sprite's get-image function tells you the most important thing you need to know : where is your stationary limb relative to the top left of this bitmap. If Laura Croft's hand is supposed to be on the ledge at pixel (100,100) on your screen and you've got the hand set as the stationary-limb then the bitmap you get back needs to tell you how far up and to the left of (100,100) you need to draw it for the hand to be exactly where you want it to be. luckily, your friendly bitmap-info sales rep included it in the price : ptTL. ptTL stands for "point-top-left" and is the distance from the top-left of the bitmap it just handed you to the location of your sprite's stationary limb.

bada-bing! bada-boom!

then, the locations of the rest of your limbs and hinges are provided for you in the bitmap-info array of limbPositions[] and hingePositions[] except these are not relative to the top-left of the screen. These values are relative to the stationary limb. and since everything is centered around this stationary limb then you should be knowing where that is which makes it a simpler and easier no step to add the where its at when the limb's got to get what it was you was looking for it. so you can see that with all these values recorded for a 1.0 sized image you just need to multiply them by the draw-size factor and when you've resized your image all is right with the world again.

Sprite Composites

I've been working on Mars Mission for a while now and have since developed this new feature for the sprites. For a while now I'd been wanting to find an easy way to stick multiple sprites together so that they could work as if they were a single sprite but with their own separate configurations. A simple example of this would be a space ship as one sprite and two other separate sprites to make up the landing-gear. The ship can be doing one thing while the landing-gear does something else and they all work independently yet getting an image of such a ship with its landing gear is as simple as a single "get-image" call you'd make with a sprite. But that still wouldn't be good enough because sometimes I might want to allow an astronaut to go on an EVA outside the ship and not have to worry about the location of that astronaut relative to some planet but only move the ship while that astronaut goes about his or her business scrubbing the ventral plating after some particularly nasty brawl with a desperate Nausicaan pirate ship that suddenly appeared off the port-bow.

or even something as mundane as a dude getting into his sports car and then grabbing his tool-box while still at the driver's seat. This may sound like an easy thing to do and maybe you have a simple solution which i hadn't thought of but here's what i did : i invented the sprite-composite! A sprite composite is a sprite inside a wrapper. This wrapper has an array of composites just like it as well as another array of sprite-composite-elements. The sprite composite elements are like composites but not quite, they're kind of like the wimps of the sprite world. These sprite-composite-elements steal the thunder from the sprites themselves in that they can sit inside a composite and be part of something bigger but they can't have other composite-elements of their own (nor composites neither) and latch onto them like an entourage may cling to Beyonce. Nope, Sprite-composite-elements are just wannabes. wimps, is all.

(update : despite all the praise about Sprite-composites that i apparently lavished on myself for this innovation, after implementing their use in my project I've found them to be needlessly slow and hamper the fluidity of my work. Although they were intended to facilitate the building of complex sprite generated animation characters the sprite-composite is less than adequate when dealing with more than just a few moving creatures because of their slow overall performance.)

let me start over. You have composites. Composites have arrays of composites. composites also have arrays of sprite-composite-elements. the getCompositeImage()takes all of these and puts them together into a single image and returns that image back to you with all the information you ever wanted to know about the where and hows of what its like to be a lowly composite-sprite-element, or even a worldly composite itself, stuck inside someone else's image.

come to think of it, sprite-composite-elements may only be a passing phase and may not make the cut for the next edition of "The Wide World of Sprites", not if Howard Cosell were here anyway. and so let me just say that if you want to get serious about the sport of sprites, get into the composites!

Four!

let's describe that dude with his sports-car again. first of all, since we know that our dude is going to want to sit "inside" the sports car and not behind it we need to draw the back-drop of the car, then the dude, then the front and the wheels. But this all has to be flexible enough to change dynamically so you can't just lock it all into place. You need a sports car in two parts, or two composites, one contained inside the other. Then you have your dude which may or may not be made up of one or more composites, let's say dude's body is one composite and then dude's Kung-Fu chop action figure arm is a second composite. Now, our player has decided it would be best if Ken left Barbie because he just can't take her whining anymore and Britney's got a plan that involves egging someone's car outside the CBS building. doesn't that sound like fun! So, Ken waltzes over to his Malibu Barbie Sports-Car and gets in. Then as he's driving away he realizes that he's forgotten his tool-kit, backs into Barbie's driveway, reaches out and goes away clutching his gear.

let's recap this play-by-play...

our two composite composite of a car has taken in two more composites in the form of Ken and his Kung-fu grip then Ken, already snugly sandwiched between two car-parts-composites reaches out from inside this already confusing little world and snatches yet another slave composite tool-kit for his slave-composite-arm attached to his slave-composite body that is now only a trifling part of the much more complex Barbie sports car.

then, when he gets to where he's going, still clutching on to his tool kit he hops out of the car, master once more leaving Barbie's over-heated engine purring contently parked between a bug and a buick. are you getting the idea?

for a while there there were three separate sprite composites. The car (made up of two composites), Ken (also made up of two composites) and the took-kit (potentially made up of a bunch but let's just say, one composite). Then Ken jumps into the car and becomes part of the car's composite sandwiching himself (there has to be a composite draw sequence much like the limb draw sequence of the individual sprite and this composite draw-sequence needs to be flexible during run-time!) and while he's inside this car-composite, himself a slave to the car, he grabs the tool-kit in his body's slave composite's hand. Every composite added to the base car-composite has to be connected to some point of the original and they all branch off from there. The back-chassis is positioned just so on the front chassis, Ken is neatly tucked between these two and his arm is positioned relative to his shoulder and the tool kit is positioned relative to the arm's hand. The arm is free to rotate & flex for all of Barbie's friends (and don't forget Britney!) and then though Ken grabbed the tool kit while sitting inside the car, the tool-kit is not a direct slave to the car but a slave to ken's hand so that when he steps out of the car, the tool-kit goes with him and the car just sits there and idles waiting for the impound truck because Barbie forgot to tell him that she's got a bunch of unpaid parking tickets. what a conundrum indeed!

sprite_composite.png

have a look at some animated gifs I put together while playing around with what I've got working so far in my Mars Mission project : moon-buggy, walk about and fork-lift . These appear totally sluggish on my computer so I'm not going to vouch for their quality but have a look anyway.

Using Composites

Using a composite is nowhere near as complicated as writing the classSpriteComposite was.

Here's some sample code taken from Mars Mission that shows you how to use the wimpy sprite-composite-elements. I'm cutting these down right now but they're actually pretty good. The only thing wrong with these is that they cannot be removed from the composite and wander off on their own like any other composite could. have a look ...

Sprite.classSprite cSprAstronaut = Sprite.classSprite.spriteMaker.loadSprite(classRotatedImage.strWorkingDirectory + "astronaut female.sp3");
cSprCom_Body = new Sprite.classSpriteComposite(ref cSprAstronaut);

Sprite.classSprite cSprArm = Sprite.classSprite.spriteMaker.loadSprite(classRotatedImage.strWorkingDirectory + "astronaut arm.sp3");

cSprCom_Body.cMasterSCE.addSlave(ref cSprArm, (int)enuAstronaut_female_Limbs.shouldernear, 1);
cSCE_Arm = cSprCom_Body.cMasterSCE.cSlaves[0];
cSCE_Arm.bolAngleTracksMaster = false;

the header for the addSlave() is shown here

public void addSlave(ref classSprite cSprite_NewSlave, int intIndexMasterLimbLink, double dblDrawSizeRelativeToMaster)

You can see here how both sprites are loaded using the same sprite-maker, the body is made into a composite and then this composite's master sprite-composite-element takes in the second sprite (arm) referencing the body's shoulder limb as the limb onto which it attaches itself and since both the sprite-body and the sprite-arm were generated to work in tandem the draw-size-relative-to-master is set to 1.0. There is, of course, an equivalent removeSlave() function but, as mentioned before, a composite-sprite-element without a master is like an empty sand-box. or actually, its more like a pile of sand without a box. but, just not a beach.

alright, enough of that.

Sprite Composites

scared yet? don't be. Composites are warm and friendly. They only eat dust and don't never never fuss. They like long walks, kittens and cuddling.

have a look at the getComposite() function in MarsMission's classResource where the Moon Buggy is built into a sprite-composite along with the fork-lift and backhoe. (yes, i said "Backhoe"! what of it?!?)

public static Sprite.classSpriteComposite getComposite(enuResources eResource, ref Sprite.classSprite cSprite)
{
if (cSprite == null) return null;

switch (eResource)
{
case enuResources.MoonBuggy:
    Sprite.classSpriteComposite cRetVal_MoonBuggy = new Sprite.classSpriteComposite(ref cSprite);
    Sprite.classSprite cSprMoonBuggy_BackChassis= Sprite.classSprite.spriteMaker.loadSprite(classResource.strWorkingDirectory + "MoonBuggy_BackChassis.sp3");
    Sprite.classSpriteComposite cSprCom_MoonBuggyBackChassis = new classSpriteComposite(ref cSprMoonBuggy_BackChassis);

    Sprite.classSpriteLimb limBackChassisPin = cSprite.getLimbByName("BackChassis_Pin");
    cRetVal_MoonBuggy.addSlaveComposite(ref cSprCom_MoonBuggyBackChassis, 0, limBackChassisPin.intMyLimbID);
    cSprCom_MoonBuggyBackChassis.myDrawSequenceIndex =0;

    return cRetVal_MoonBuggy;

case enuResources.ForkLift:
    Sprite.classSpriteComposite cRetVal_ForkLift = new Sprite.classSpriteComposite(ref cSprite);
    return cRetVal_ForkLift;

case enuResources.Backhoe:
    Sprite.classSpriteComposite cRetVal_Backhoe = new Sprite.classSpriteComposite(ref cSprite);

    // add crane

You can see it's not so hard. Just look at the MoonBuggy. The resource already has a sprite so its referenced in the classSpriteComposite instantiation then the back-chassis needs to be loaded as a separate sprite before its made into a composite of its own and then that composite is added to the first one with the addSlaveComposite() call only after we've located the limb called "backChassis_Pin" which the back-chassis sprite references when trying figure out its position inside the bigger picture.

Similarly, when "Ken" jumps into his moon-buggy he is added to the vehicle's composite into the driver's seat. Have a look at the code below :

void PerformAction_EnterVehicle()
{    
if (cVehicle != null)
{
// add astronaut to vehicle sprite
cVehicle.cObjCon.addAstronaut(ref cMyReference);
bool bolDriver = cVehicle.cObjCon.cAstronauts.Length == 1;

Sprite.classSpriteLimb limPin = bolDriver
              ? cVehicle.cSprComposite.cMasterSCE.cSprite.getLimbByName("SeatDriverPin")
              : cVehicle.cSprComposite.cMasterSCE.cSprite.getLimbByName("SeatPassengerPin");

cVehicle.cSprComposite.addSlaveComposite(ref cSprCom_Body, 0, limPin == null ? 0 : limPin.intMyLimbID);

double dblSizeRelativeToMaster = dblDrawSizeFactor / cVehicle.dblDrawSizeFactor;
cVehicle.cSprComposite.cComposites[cSprCom_Body.intMyCompositeIndex].cMasterSCE.dblSizeRelativeToMaster = dblSizeRelativeToMaster;
                
cSCE_Arm.dblSizeRelativeToMaster = dblSizeRelativeToMaster;
if (cHandTool != null)
cHandTool.cSprComposite.cMasterSCE.dblSizeRelativeToMaster = cHandTool.dblDrawSizeFactor / cVehicle.dblDrawSizeFactor;


switch (cVehicle.eResource)
{
    case enuResources.MoonBuggy:
        cSprCom_Body.cMasterSCE.intConfigurationIndex = (int)enuAstronaut___female_Configurations.Sitting_Vehicle_reclined;
        cVehicle.cSprComposite.cComposites[cSprCom_Body.intMyCompositeIndex].myDrawSequenceIndex = 1;
        break;

    case enuResources.Backhoe:
        cVehicle.cSprComposite.cComposites[cSprCom_Body.intMyCompositeIndex].myDrawSequenceIndex = 2;
        break;

    default:
    case enuResources.ForkLift:
        cSprCom_Body.cMasterSCE.intConfigurationIndex = (int)enuAstronaut___female_Configurations.Sitting_Vehicle_Up;
        cVehicle.cSprComposite.cComposites[cSprCom_Body.intMyCompositeIndex].myDrawSequenceIndex = 0;
        break;
}
cSCE_Arm.Angle = 0;
cSprCom_Body.cMasterSCE.intConfigurationStep = 0;
cCDO.cCDP.eMirror = cVehicle.cCDO.cCDP.eMirror;
cCDO.dptPos = cVehicle.cCDO.dptPos.Copy();
}
}

most of this is basically the same as putting the back-chassis onto the moonbuggy except that the moonbuggy's back-chassis drawsequenceindex was set to zero in the previous stretch of code above and in the case of Ken getting into his moonbuggy he has to take a composite-draw-sequence that puts him after the back-chassis but before the front chassis, sandwiched in the money. You can see this here in the Switch() where, in the case of the moon-buggy, Ken's composite has its MyDrawSequenceIndex set equal to 1 which forces the front-chassis of the buggy's composite to place itself at the #2 draw sequence (after 0 and 1). Then when Ken leaves the buggy his two composites need to be removed from the buggy before he can meet up with Britney.

Composite Bitmap Information

The composite bitmap information is considerably more complex yet still relatively painless to use. Since each composite can be made up of any number of composites branching out in every direction this makes it difficult when asking for the position of any one limb on any one composite sticking out who knows where. We could look at the individual sprite bitmap information instances that were used in the process of compiling all of these together but they relate only to their individual bitmaps and not the position of each limb relative to the vehicle or central figure upon which all the others are built. Instead of adding and subtracting and messing your head with another complexity you don't need to confuse yourself with what you need is a single sprite-bitmap-info-like thing that will answer all your questions without charging you an arm for it. To do this we first collate all the composite-sprite-image information at the Draw() stage. Then these are all inserted into a ternary tree using the composite index, spriteCompositeElement index, and limb index to uniquely map out which limb on which sprite we want and it then tells us what we need.

here's the code from the sprite composite's Draw() function that collates the point information for a single compositie and all of its sprite-composite-elements before doing the same with all its slaves composites and collating those into the one image called for.

cMyCompositeCbmp.dptTL = dptTL;
cMyCompositeCbmp.cCPIArray = new classCompositePointInfo[2 * intTotalLimbs];
int intIndex = 0;
for (int intSCECounter = 0; intSCECounter < cSpriteCompositeElementArray.Length; intSCECounter++)
{
    classSpriteCompositeElement cSCE = cSpriteCompositeElementArray[intSCECounter];
    for (int intLimbCounter = 0; intLimbCounter < cSCE.cBmp.limbPositions.Length; intLimbCounter++)
    {
        classMath.classDoubleCartesian dptLimbOnComposite = new classMath.classDoubleCartesian
            (
                recSCEDest[intSCECounter].Left + cSCE.cBmp.dptTL.X + cSCE.cBmp.limbPositions[intLimbCounter].dpt.X - dptTL.X,
                recSCEDest[intSCECounter].Top + cSCE.cBmp.dptTL.Y + cSCE.cBmp.limbPositions[intLimbCounter].dpt.Y - dptTL.Y
            );
        cMyCompositeCbmp.cCPIArray[intIndex++] = new classCompositePointInfo(dptLimbOnComposite, 0, intSCECounter, intLimbCounter, enuCompositePointType.Limb);

        classMath.classDoubleCartesian ptHingeOnComposite = new classMath.classDoubleCartesian
            (
                recSCEDest[intSCECounter].Left + cSCE.cBmp.dptTL.X + cSCE.cBmp.hingePositions[intLimbCounter].dpt.X - dptTL.X,
                recSCEDest[intSCECounter].Top + cSCE.cBmp.dptTL.Y + cSCE.cBmp.hingePositions[intLimbCounter].dpt.Y - dptTL.Y
            );
        cMyCompositeCbmp.cCPIArray[intIndex++] = new classCompositePointInfo(ptHingeOnComposite, 0, intSCECounter, intLimbCounter, enuCompositePointType.Hinge);
    }
}

Then, if this composite has slave composites as well we resort to the ternary tree.

for (int intCompositeCounter = 0; intCompositeCounter < cComposites.Length; intCompositeCounter++)
{
if (cComposites[intCompositeCounter].cMasterSCEComposite == this || cComposites[intCompositeCounter] == this)
{
    cDrawCompositeInfo[intCompositeCounter].drecDraw.X -= dptNewTL.X;
    cDrawCompositeInfo[intCompositeCounter].drecDraw.Y -= dptNewTL.Y;

    // build composite hinge/limb info
    for (int intCPICounter = 0; intCPICounter < cDrawCompositeInfo[intCompositeCounter].cCbmp.cCPIArray.Length; intCPICounter++)
    {
        classMath.classDoubleCartesian dptLimb = new classMath.classDoubleCartesian
                            (
                            (cDrawCompositeInfo[intCompositeCounter].drecDraw.Left + cDrawCompositeInfo[intCompositeCounter].dptTL.X + cDrawCompositeInfo[intCompositeCounter].cCbmp.cCPIArray[intCPICounter].dpt.X) - cCBmp.dptTL.X,
                            (cDrawCompositeInfo[intCompositeCounter].drecDraw.Top + cDrawCompositeInfo[intCompositeCounter].dptTL.Y + cDrawCompositeInfo[intCompositeCounter].cCbmp.cCPIArray[intCPICounter].dpt.Y) - cCBmp.dptTL.Y
                            );

        classCompositePointInfo cCPI = new classCompositePointInfo(dptLimb,
                                        cComposites[intCompositeCounter].intMyCompositeIndex,
                                        cDrawCompositeInfo[intCompositeCounter].cCbmp.cCPIArray[intCPICounter].intSpriteCompositeElementIndex,
                                        cDrawCompositeInfo[intCompositeCounter].cCbmp.cCPIArray[intCPICounter].intLimbIndex,
                                        cDrawCompositeInfo[intCompositeCounter].cCbmp.cCPIArray[intCPICounter].ePointType);
        if (cCBmp.InsertPointInfoIntoTernaryTree(ref cCPI))
        {
            cCBmp.cCPIArray[intIndex++] = cCPI;        
        }    


and so when we want to know where a limb is we need to get in the know with the composite-bitmap-info via the classCompositeBitmapInfo using the getCPI() function call. Since the composites may consist of any number of sprites each having a variety of steps and configurations the potential combinations are limitless and there is, therefore, no way to uniquely map out every instance such as we did in the case of the individual sprites which each had a definite and finite number of possibilities. Which means that caching the information becomes a bit more complicated. As it is the sprite composites allow you to cache a single instance in RAM and it will be returned and used only if the most recent call asked for the exact same set of configurations, steps, angles and mirrors for every sprite contained inside that composite. Aside from that everything is redrawn every time using cached sprite images.

here's an example of a call to the composite-sprite's draw :

Bitmap bmpAstronaut = cAstronaut.cSprCom_Body.drawComposite(cAstronaut.cCDO.dblAngle, cAstronaut.dblDrawSizeFactor / classGameState.dblZoom, cAstronaut.cSprCom_Body.cMasterSCE.Mirror).bmp;
Point ptScreen = classLandscape.getPointScreen(cAstronaut.cCDO.dptPos.Add(cAstronaut.cCDO.dptDrawShift), dptGameReference, ptScreenReference);
g.DrawImage(bmpAstronaut, new Point(ptScreen.X - cAstronaut.cSprCom_Body.dptTL.intX, ptScreen.Y - cAstronaut.cSprCom_Body.dptTL.intY));

putting the MarsMission's need to translate the game-world location to a real-world screen-location aside, all we're doing here is causing an image of the composite to be drawn onto the bitmap bmpAstronaut and then a position is calculated for the astronaut's torso(master limb) and the bitmap is drawn dptTL away from the position we want the torso to be. Alternately, instead of using just the ().bmp on the bitmap-info object we received from the drawComposite() call we get the ptTL info of it but since the body-sprite is the master in this case those two values would be the same.

Form Sprite

I didn't talk about the form-sprites but you can download an earlier article Form Sprite : I Love You Jessica! for a look though I'm not sure the sample sprite included in that article will work with the new editor. Essentially, the form-sprite uses the PerPixelAlphaForm.cs to generate a sprite that can walk apparently "form-free" on your desk-top by molding the form around it so as to fool the viewer into believing there is no form at all. have a look.

Editor Upgrade Features

there's a few new minor details to the editor which you'll probably notice on your own. The most useful one is probably the mouse-wheel option for setting the angle. That and the variable number of images per quarter rotation really make it simple to more precisely define your configurations. You can set the colors of the background, outline, hinge points, limb points. Choose to show or hide the center cross marker or the rotate sprite angle horizontal scroll bar.

Since the caching files were causing such a fuss in the editor I had to add a "Purge All Files" to the sprite-maker so that they stop causing all those problems. There's really no need for anyone to use the function aside from the Editor.

Graphics Editor & Force Color

The graphics editor and Force-Color projects are extra applications that you can use to help you manipulate a potential sprite's images. The one thing which the Graphics-Editor has which no other graphics editor available has is a neat kind of flood-fill which allows you to set the outline flood-limit of a region and then flooding an area until your color-flood reaches the border limits. To do this you go to the paint-bucket logo and switch the type of fill until the circle-square to the left of the mouse-cursor turns into a circle square inside a square (a border appears around the square) then you click the color you want to set as your flood limit (the perimeter of the square, by clicking the border of the square) and you click the circle to set the color of your flood(by clicking the circle in the icon) and then when you flood a color it will not only change the color that you click but all colors contained inside the colored-limit. E.g. make a red circle in a multi-color image and click inside the circle, your flood will be bounded by the red. Its great for cutting out images.

Force-Color is another one. It doesn't come with an instruction manual but the idea is you set a 'limit color', a 'force color' if you've you've not set 'keep color' and then whether you're rejecting "lighter" or "darker" any color "darker" or "lighter" than the limit is forced to the rejected color (note the button marks what will happen if you click it so "reject lighter" button says "click this and I'll reject lighter but if you don't I'll reject darker even if i look like i'm saying -reject lighter-", confusing, i know). neither is particularly user-friendly and i won't write an article about them but if you play with them they'll work for you, for what little they do do. at least they do for me.

Future upgrades

I'm still thinking about allowing the hinge radial coordinates to be modified during the editing of the configuration steps to make these variations part of the animation. Though its now possible to move objects along a straight path using a complicated set of swivels and hitches with the sprites as they are the added flexibility of keeping a hinge's angle intact and allowing the hinge's radial coordinate's radius to grow and shrink would facilitate some actions which are now only achieved with great difficulty.

I'm workin' on it...

but next I think I'll make tractor mower for Ken. that'll be fun!

Update

March 29, 2011 -

  • fixed bugs dealing with the Copy-Configuration feature
  • fixed bug in cached-file save which was re-saving and appending to the file needlessly
  • added rename sprite feature in the File menu

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Christ Kennedy
CEO unemployable
Canada Canada
Christ Kennedy, published his fourth novel "Cleats of the Counter Revolution" in the summer of 2010. He grew up in the suburbs of Montreal and is a bilingual Quebecois with a bachelor’s degree in computer engineering from McGill University and is currently walking across ontario plotting a new novel, far away from any computer.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberAbinash Bishoyi19-Mar-13 7:30 
GeneralMy vote of 5 PinmemberTJ Powell4-Jul-12 16:34 
GeneralMy vote of 5 PinmemberT_uRRiCA_N31-Mar-11 20:11 
GeneralRe: My vote of 5 PinmvpChrist Kennedy1-Apr-11 1:34 
GeneralMy vote of 5 PinmemberIFFI17-Mar-11 5:45 
GeneralRe: My vote of 5 PinmvpChrist Kennedy17-Mar-11 10:53 
GeneralMy vote of 5 PinmemberEspen Harlinn13-Mar-11 4:27 
GeneralRe: My vote of 5 PinmvpChrist Kennedy13-Mar-11 12:05 
GeneralMy vote of 5 PinmemberBryian Tan12-Mar-11 7:47 
GeneralRe: My vote of 5 PinmvpChrist Kennedy12-Mar-11 12:01 
GeneralMy vote of 5 PinmvpAbhinav S9-Mar-11 19:16 
GeneralRe: My vote of 5 PinmvpChrist Kennedy10-Mar-11 3:31 
Generalfind some errors... PinmemberSergeyAB9-Feb-11 10:31 
GeneralRe: find some errors... PinmvpChrist Kennedy9-Feb-11 13:46 
GeneralRe: find some errors... PinmemberSergeyAB9-Feb-11 21:33 
GeneralRe: find some errors... PinmvpChrist Kennedy10-Feb-11 2:09 
General5 and 10 PinmemberMeshack Musundi2-Feb-11 21:11 
GeneralRe: 5 and 10 PinmvpChrist Kennedy3-Feb-11 2:37 
Generalthanks for sharing - have 5 PinmemberPranay Rana1-Feb-11 7:23 
GeneralRe: thanks for sharing - have 5 PinmvpChrist Kennedy1-Feb-11 7:33 
GeneralMy vote of 5 Pinmemberring_01-Feb-11 6:48 
GeneralRe: My vote of 5 PinmvpChrist Kennedy1-Feb-11 7:32 
GeneralMy vote of 5 PinmemberSteve Maier1-Feb-11 6:19 
GeneralRe: My vote of 5 PinmvpChrist Kennedy1-Feb-11 6:24 
GeneralMy vote of 5 Pinmembersvigano1-Feb-11 4:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141216.1 | Last Updated 30 Mar 2011
Article Copyright 2011 by Christ Kennedy
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid