Creating semi-random character appearances – Part 2October 22nd
The continuation of last month's
Part 1 is long overdue, but here it finally is. In Part 1, I described the process for creating the many subsprites that go into creating the full character sprites. In this blog post, I will focus on how the multiple subsprites are actually drawn to the screen to correctly form the complete character.
From:

To:

Two things need to happen for these subsprites to be drawn correctly. 1) The program needs to know where to
bind the subsprite from, and 2) it needs to know where to
blit it on the screen.
All these subsprites are pretty small in size and storing them all as individual images would not only result in a mess of thousands of image files, it would also present a problem for the game engine. The engine uses OpenGL for drawing stuff to the screen and OpenGL prefers its
textures (the images to draw) to be power of 2 in size, meaning that the height and width of the image must be 16, 32, 64, 128, 256, 512 or 1024 pixels. Extensions exist for OpenGL that allow the usage of any size, but not all video cards support this extension, and there's actually really no reason for using non-power of 2 textures. Why? Well, because even though the subsprites are all very small, they can be packed together in a so-called texture atlas, and this texture atlas can just be power of 2 sized:

This mean that the extension for non-power of 2 textures isn't needed, but there's also another benefit of packing subsprites together in one large image file. Whenever OpenGL has to bind (prepare for drawing to the screen) a texture, it takes time as the texture data must be moved to the GPU. If all the subsprites were in separate image files, I'd have to constantly bind new files, but when related subsprites are packed together on a texture atlas, I only have to bind this texture atlas once and can then just tell OpenGL which part of the texture atlas to blit to the screen.
Telling the program where to draw fromAnd that's where we get to the first requirement: Telling the program on which texture atlas each subsprite can be found and what the individual subsprite's coordinates are on this texture atlas. Thankfully, I don't have to manually create a texture atlas and paste the subsprites onto it. A lot of free programs exist for doing this automatically. You just set the size of the texture atlas and select which files should be packed in it, and the program does it all for you - packing the subsprites together in the most efficient manner. Furthermore, these programs can then output the coordinates of each subsprite on the texture atlas in various formats. It's then just a matter of getting this coordinate information into the program and creating a procedure for grabbing the correct information for each subsprite.
In code terms, I've created a procedure that gets called with the subsprite's name and then returns the number of the texture atlas and the coordinates where the subsprite can be found. Here's a snippet of the Pascal code that returns the subsprite positions of all the sprites that go into making the ten frames of a male character walking south:
PROCEDURE GetWalk1SpritePositionValues(SpriteName : String);
BEGIN
IF SpriteName = 'walk1-1malehair-1' THEN SetSpritePositionValues(walkmale1, 334, 257, 12, 11)
ELSE IF SpriteName = 'walk1-1malehair-2' THEN SetSpritePositionValues(walkmale1, 442, 257, 12, 11)
ELSE IF SpriteName = 'walk1-1malehat-1' THEN SetSpritePositionValues(walkmale1, 394, 257, 12, 11)
ELSE IF SpriteName = 'walk1-1malehat-2' THEN SetSpritePositionValues(walkmale1, 409, 209, 12, 16)
ELSE IF SpriteName = 'walk1-1malehat-3' THEN SetSpritePositionValues(walkmale1, 406, 232, 12, 14)
ELSE IF SpriteName = 'walk1-1malehat-4' THEN SetSpritePositionValues(walkmale1, 337, 209, 12, 16)
ELSE IF SpriteName = 'walk1-1malehat-5' THEN SetSpritePositionValues(walkmale1, 167, 392, 22, 17)
ELSE IF SpriteName = 'walk1-1malehat-6' THEN SetSpritePositionValues(walkmale1, 345, 162, 18, 15)
ELSE IF SpriteName = 'walk1-1malehead-1' THEN SetSpritePositionValues(walkmale1, 497, 162, 12, 17)
ELSE IF SpriteName = 'walk1-1maleleftarm-1' THEN SetSpritePositionValues(walkmale1, 327, 270, 8, 10)
ELSE IF SpriteName = 'walk1-1maleleftarm-2' THEN SetSpritePositionValues(walkmale1, 232, 488, 9, 24)
ELSE IF SpriteName = 'walk1-1maleleftarm-3' THEN SetSpritePositionValues(walkmale1, 286, 232, 9, 24)
*snip*
ELSE IF SpriteName = 'walk1-10malerightfoot' THEN SetSpritePositionValues(walkmale1, 351, 270, 7, 10)
ELSE IF SpriteName = 'walk1-10malerighthand' THEN SetSpritePositionValues(walkmale1, 202, 245, 9, 36)
ELSE IF SpriteName = 'walk1-10malerightshoe-1' THEN SetSpritePositionValues(walkmale1, 202, 499, 9, 13)
ELSE IF SpriteName = 'walk1-10malerightshoe-2' THEN SetSpritePositionValues(walkmale1, 494, 179, 11, 27)
ELSE IF SpriteName = 'walk1-10malerightshoe-3' THEN SetSpritePositionValues(walkmale1, 259, 484, 9, 13)
ELSE IF SpriteName = 'walk1-10maletorso-1' THEN SetSpritePositionValues(walkmale1, 410, 0, 35, 37)
ELSE IF SpriteName = 'walk1-10maletorso-2' THEN SetSpritePositionValues(walkmale1, 48, 409, 35, 36)
ELSE IF SpriteName = 'walk1-10maletorso-3' THEN SetSpritePositionValues(walkmale1, 460, 59, 35, 33)
ELSE IF SpriteName = 'walk1-10maletorso-4' THEN SetSpritePositionValues(walkmale1, 425, 59, 35, 33)
ELSE IF SpriteName = 'walk1-10maletorso-5' THEN SetSpritePositionValues(walkmale1, 390, 59, 35, 33);
END;
The SetSpritePositionValues procedure that gets called is just a simple procedure for passing on the values to a set of global variables that I then use when calling the OpenGL blitting procedure:
PROCEDURE SetSpritePositionValues(TextureMap, X, Y, Width, Height : Word);
BEGIN
GetSpriteMap := TextureReference[TextureMap];
GetSpriteXPosition := X;
GetSpriteYPosition := Y;
GetSpriteWidth := Width;
GetSpriteHeight := Height;
END;
So the first value that gets passed is the number of the texture atlas (in this case, it is a constant value called 'walkmale1'), then the X and Y coordinates, and then the width and height.
All these procedures fill up a lot of lines of code, but they are quickly made since it's just a matter of formatting the coordinate information given from the image packing program. This is the quick and easy part. The program now knows where to draw from. It's the second requirement that takes a lot of time.
Telling the program where to draw toFor everything related to drawing and placing of characters, it all starts with the character template sprite. This is just the basic character frame with no clothes on:
This sprite is actually never used in the game, since the character sprites are all drawn by using the subsprites to form a complete character sprite. But the height and width of this template sprite is what's used for drawing the subsprites to the screen. The upper left corner of this template sprite has the coordinates (0,0). When I for example need to figure out what the coordinates of the character's pants are in relation to this, I just go into GIMP, paste the pants onto this template sprite, move them to the upper left corner to 'reset' the coordinates, and then drag the pants to the correct position on the sprite and read how far the pants have been dragged along the X and Y axis:

This gives me the pants' relative coordinates (relative to the template sprite's (0,0) coordinates): (6,43). When the character is drawn to the screen, I already know what the character's overall coordinates are. Let's say the character's coordinates are (201,750). To figure out where the pants subsprite should be drawn, I just add the pants' relative coordinates to the character's overall coordinates:
(201,750) + (6,43) = (207,793)
So I just tell OpenGL to blit from the coordinates of the texture atlas previously found and to the screen coordinates (207,793). This will draw the pants in the correct spot. It's quick and easy when everything's programmed correctly. What takes time is manually determining what each subsprites' coordinates are relative to the template sprite. That takes a lot of patience and use of GIMP. So, again, I get to listen to a lot of podcasts while doing this stuff!
There's still one thing left to do before the character sprite is ready to be drawn. As it consists of multiple subsprites, these all have to be drawn in the correct order or you risk having a character with his legs outside his pants, or his head over his hat. How this is managed will be the subject of Part 3.