Sunday 30 August 2015

Rendering sprites #1 (part 8)

As I mentioned in my previous post, life has been a bit busy but I finally found a few hours this weekend to continue onto the next subject. This one will take a few parts to get through and I'm afraid my schedule is only going to keep me as occupied as before but I'll try my best to follow this one up in due time.

Sprites have been the corner stone for 2D games since before I was born and in fact our tile map approach for our background is a basic sprite implementation. Once you start using sprites for the characters and other things under the gamers (or computer AIs) control it is only the way they are applied that differ. Also with modern computers most of the limitations that plagued earlier games have gone. At the core however a sprite is nothing more then a fancy name for a bitmap.

Most games have dozens if not hundreds of these individual bitmaps that make up all the game graphics and these more often then not are all combined into a single image that is loaded in one go. Often the sprites all had fixed sizes just like in our tile map so that it was easy to address them by an index within the larger image instead of keeping coordinates for each sprite not to mention that old hardware often had build in support for blitting (copying to screen) such fixed layout images but we'll see that the example I'm going for requires a bit more flexibility then was possible in those days.

Initially I was thinking about making a simple side scrolling shooter, something like a toned down R-type or something. The nice thing here is that the ship requires only a handful of sprites often without much animation in them just showing a different image depending on how the ship is moving. But I wanted to do something a little more fun but something as it turned out, a little harder to find graphics for (I'm not a graphics artist and thus have to rely on what can be found on the web for these types of examples).

As a kid I loved playing platformers like Prince of Persia, Another World and Flashback. I do believe Prince of Persia used sprites but Another World used 2D vector graphics because the amount of storage required for all the images was challenging for computers of the day to say the least.
Flashback as the spiritual successor of Another World seemed to be using the same technique but did use fully drawn backgrounds. I was surprised to find a full spritesheet for the main character Conrad on the internet. I'm not sure if this was due to someone screen capturing this or whether they are indeed using sprites for drawing Conrad in the actual game foregoing 2D vector graphics (by then computers had a lot more memory to play with).

Anyway I chose to use this sprite sheet because it is one of the few ones I could find that has the range of animations I wanted to use in this example.
Needless to say the Conrad character is owned by Delphine and is their copyright so I hope I'm not overstepping any boundaries here as I'm not actually trying to build a commercial game but showcasing a technique I hope I'm in the clear.

House keeping

So first off I've done a little house keeping. I'm not sure how much of this ended up in previous check-ins as it was a while ago but I've stripped out some of the keyboard commands as we'll be taking this into a different direction and moved all the tilemap logic into a single tilemap.h include file using the single file implementation approach we've been using for other parts. I've also reverted the tilemap logic back to its original implementation.

With the tilemap logic the initialization and render code has mostly moved into the tilemap.h file but I've left loading the texture maps themselves in the main engine code.
The shader code itself still resides in separate shader files that are loaded into memory. As it will be reused in other parts I've added callback hooks for a simple file loader function.

All our data for a tile map is neatly gathered up into a single struct.

Our sprite sheet implementation

For our spritesheet logic I'm following the single file implementation approach from the start and this code can all be found in our spritesheet.h file. Together with this we have our spritesheet.vs and spritesheet.fs shader files. I won't list the full shader code here, they explain themselves well.
I'll also not discuss our full spritesheet.h implementation as it mostly follows along the same lines as we've discussed for our tilemap but I'll highlite a few parts of the source code.

First off are our two structs.

The first one, simply called sprite, is a straight forward left/top, width/height structure. This identifies the location of each sprite in our master bitmap, you'll see when looking at our conrad.png file that our sprites aren't uniformly sized so we need to identify the location of each sprite manually.
I'm storing the width and height instead of the right and bottom values as this makes it easier to write the shader and it allows us to use the width and height to determine the size of the bitmap we draw on screen.
Also note that normally you would use the center of the bitmap to position the bitmap on screen but we use the center bottom of the bitmap, this will become important later on.

The second structure is called spritesheet and defines all the information we need to render sprites in our spritesheet. Most of its contents is set after calling spLoad (kind of like a constructor) which loads the shader files and retrieves the uniform IDs. Our sprite texture (conrad.png) however is loaded within engine.c itself.
Last but not least we have a couple of variables which maintain an array of sprite structures which can be filled using the spAddSprite and spAddSprites functions and again, those are called from engine.c.

This allows us to use our spritesheet implementation for multiple sprite sheets without making any assumptions of the contents of the image we're using.

Next we have our forward defines of all our functions followed by the actual implementation (don't overlook the #define SPRITE_IMPLEMENTATION added to our main.c).

The main function which are important are:

spLoad: this loads our shaders and initializes our structure, think of this as a constructor for our structure.
spUnLoad: unloads our shader and frees up any memory we've allocated, think of this as a destructor.
spAddSprite and spAddSprites: which adds sprite definitions to our structure so we know which sprites exist within our image.
spRender: which renders a single sprite to screen.

Using our sprite sheet

When we look at engine.c we see our spritesheet in use. For now we're just cycling through all the different sprites.

Note that there are a few changes in this file after we've moved the tilemap logic into its own source file and followed the same path for our spritesheet implementation. At the start we now invoke two macros that setup global variables for our tilemap structure and our spritesheet structure.

Most of the other globals are still in tact and our mapdata is included but after our mapdata we find a new lookup table for all our sprite coordinates. Note that at this point I've only added about 2 and a half lines from our image so the list is incomplete but we'll be adding on to this as we go along.

Next our error callback function has been enhanced to also set the error callback for our tilemap and spritesheet implementations. We also see our font setup and text file loading functions below that.

The load_shaders and unload_shaders functions are the first with major changes. Now that all the code for the tilemap has moved into our tilemap implementation file we simply call that.
The same applies to our spritesheet implementation. For all these functions we parse through a pointer to our related structs.

Our load_objects and unload_objects have similarly been changed but more code is retained here for now. Also note that besides loading the maps for our tilemap we now also load our Conrad.png file.
Our array is copied into our sprite sheet using the spAddSprites function making the initialization of our sprite sheet complete. This may seem a little overkill but just like with our mapdata the idea in the long run is that this data will be loaded from a file on disk instead of hard coded into our source file.

Finally if we scroll all the way down to our render code we'll yet again see that our tile map logic is now called within our implementation and we see a call to our render method for our sprite sheet. Here we simply cycle through all the sprites based on what frame we're rendering. This obviously is only temporary.

Download the source code here

So where from here?

This is only the start. I may add some code snippets up above when I have a chance to reread all the information but I didn't see much point at this time to duplicate what is already on github.

Our next step is to further put structure to the animations for our character so we can identify all the individual animations that we have such as walking, rolling, jumping, etc.

Then we'll start getting some basic control so we can actually control our character.

By this time I'll likely add in a more appropriate tile map and we'll start looking at how we can use the information in our tile map (or if needed augment it) to determine where our character can walk.

To be continued...

No comments:

Post a Comment