• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
Servant of the Lord

SDL_tileset

0 posts in this topic

I spent a few hours today and sunday, tossing together a set of functions for SDL, allowing for easy use of tilesheets. To use it, there are two files you can include in your project: one header, and one source. The code is written clearly, and heavily commented, so if you want to see how it works, or want to change it, feel free. It includes functions for loading (SDL_LoadTileset()) and freeing(SDL_FreeTileset()) tilesets, supports spacing between the tiles, and borders around the tileset, has a function for easily drawing a tile from the tileset (SDL_DrawTile()), and a function for setting the colorkey of the entire tileset (SDL_SetColorkey()). You can also set the colorkey (or alpha!) of a tile individually, by getting the individual tile from the tileset using the provided function (SDL_GetTile). There is also a special function to load a spritesheet (SDL_LoadSpritesheet()), where the individual sprites in the sheet are non-uniform sizes, and not neatly in a row. The function will automaticly detect where the sprites begin and end, without you having to specify the dimensions or locations of each sprite. SDL_LoadTileset supports tilesets regardless of how they are stored, as long as they are the same size. For example, these are some tilesets I've tested it with: Horizontally: In a row Vertically: In a column Both: Vertically and horizontally As mentioned, you can specify gaps between the tiles and spacing along the edges of the tileset, as long as you know beforehand the size of the spacing and borders: gaps and borders If you don't know the size of the spacing or borders, or if the spacing isn't uniform, you can use SDL_LoadSpritesheet() instead, and the function will automaticly recognize the individual images and seperate them (It's slower though). Weird spacing, different sized images Here's a screenshot of the example code: Testing the functions Since the code is only two source files, I'll just embed it here. SDL_tilesets.h:
/*
	Created by: Servant of the Lord
	Created on: Sunday, May 3rd, 2009
	
	This is an example of loading a tileset in SDL, and keeping it in a form easy to manage.
	
	The code in these files are available for anyone to use, however they see fit, so long as
	they follow SDL's license. Redistribute it to your heart's desire.
	
	No credit is needed to be given; anyone can do whatever they want with this code. I retain
	no control or rights over it, do what you want with it. Use it and learn from it, if you
	desire to. If it benefits you, copy and paste it all you like.
	
	Warning: This is not officially part of the SDL library!
			 This code requires SDL and SDL_Image to run.
			 SDL can be found at http://www.libsdl.org/
*/


#include <iostream>
#include <vector>
#include <string>

#include "SDL\SDL.h"
#include "SDL\SDL_image.h"

#ifndef SDL_TILESET_H
#define SDL_TILESET_H

//Not a good idea (infact, it's a horrible idea), but available if someone wants them. =)
/*
	#define SDL_SpriteSheet SDL_Tileset
	#define SDL_FreeSpriteSheet SDL_FreeTileset
	#define SDL_DrawSprite SDL_DrawTile
	#define SDL_GetSprite SDL_GetTile
*/

//For ease of use, the vector that holds our images is being renamed to 'SDL_Tileset'
//Note that this means you can easily figure out the size of your tileset, by calling SDL_Tileset->size().
//Also, the first tile in the tileset, is the full tileset image, so your first tile starts at 1, not 0.
typedef std::vector<SDL_Surface*> SDL_Tileset;

//The first parameter is the name of the large tileset to load.
//The second and third parameters, are the sizes of each small tile. (For example, 32 pixels by 32 pixels)
//The third and fourth parameters allow the user to specify the size of a gap between each tile.
//The fourth and fifth parameters allow the user to the size of a border around the tileset.
SDL_Tileset *SDL_LoadTileset(std::string filename, int tileWidth, int tileHeight, int horizontalSpacing = 0, int verticalSpacing = 0, int horizontalBorder = 0, int verticalBorder = 0);

//The first parameter is the name of the spritesheet to load.
//The second parameter is the name of the spritesheet's background color.
//(Note: The background color must not be the same color as your colorkey. You should not use this color anywhere in your sprites)
//SDL_LoadSpritesheet does the same thing as SDL_LoadTileset, but tries to automaticly locate the sprites, by the background color around them.
//There must be at least 1 pixel of background color between every sprite.
//This function is slow! Luckily, you only need to call it once (per sheet). Still, it'd be
//better to just keep your sprites the same size, and load them with SDL_LoadTileset, when possible.
SDL_Tileset *SDL_LoadSpritesheet(std::string filename, SDL_Color backgroundColor);

//Frees the SDL_Tileset passed to it. The pointer will be set to NULL.
void SDL_FreeTileset(SDL_Tileset *tileset);

//Sets the colorkey on all the images in a SDL_Tileset.
void SDL_SetColorKey(SDL_Tileset *tileset, SDL_Color color);

//Gets a individual tile from the tileset, and returns it.
//Returns NULL if out of bounds.
SDL_Surface *SDL_GetTile(SDL_Tileset *tileset, int tile);

//Draws the tile number 'tile' in the tileset 'tileset' at the location 'x' and 'y' on the surface 'destination'.
void SDL_DrawTile(int x, int y, SDL_Tileset *tileset, int tile, SDL_Surface *destination);

#endif /* SDL_TILESET_H */

SDL_tilesets.cpp:
#include "SDL_tileset.h"

//The first parameter is the name of the large tileset to load.
//The second and third parameters, are the sizes of each small tile. (For example, 32 pixels by 32 pixels)
//The third and fourth parameters allow the user to specify the size of a gap between each tile.
//The fourth and fifth parameters allow the user to the size of a border around the tileset.
SDL_Tileset *SDL_LoadTileset(std::string filename, int tileWidth, int tileHeight, int horizontalSpacing, int verticalSpacing, int horizontalBorder, int verticalBorder)
{
	SDL_Tileset *tileset = new SDL_Tileset; //Our array where we'll stick the broken up surfaces.
	
	SDL_Surface *largeImage = IMG_Load(filename.c_str()); //Load the huge image.
	if(largeImage == NULL) 
	{
		//Oops, something went wrong. Print an error message.
		std::cout << "An error occured in 'LoadTileset()', and we couldn't load the tileset.\n"
				  << "\t" << IMG_GetError() << std::endl;
		
		return tileset; //Return the empty array.
	}
	
	tileset->push_back(largeImage); //The first element in the array is the full image.
	
	//Figure out the size of each tile, including spacing between the tiles.
	int fullTileWidth = tileWidth + horizontalSpacing;
	int fullTileHeight = tileHeight + verticalSpacing;
	
	//This is the width and height of the tileset.
	int tilesetWidth = largeImage->w;
	int tilesetHeight = largeImage->h;
	
	//We are giving the tileset back one of the spaces between the tiles, because we don't want to include
	//the space after the final tile. (We only want the spaces *between* the tiles)
	tilesetWidth += horizontalSpacing;
	tilesetHeight += verticalSpacing;
	
	//We subtract the offsets from the width and height of the tileset, so we don't include any extra
	//spacing the user may want to add to his tileset. (Such as, a border around the edges of the tileset)
	tilesetWidth -= (horizontalBorder / 2);
	tilesetHeight -= (verticalBorder / 2);
	
	//The number of tiles wide, and tiles high, the tileset is.
	int tilesHorizontally = (tilesetWidth / fullTileWidth);
	int tilesVertically = (tilesetHeight / fullTileHeight);
	
	//Loop through each tile horizontally and vertically, and create new images from the huge image.
	for(int y = 0; y < tilesVertically; y++)
	{
		for(int x = 0; x < tilesHorizontally; x++)
		{
			//Figure out where in the large image's pixels, will this tile's pixels be.
			int pixel_offset = ((y * fullTileHeight) + verticalBorder) * largeImage->pitch;
			pixel_offset += ((x * fullTileWidth) + horizontalBorder) * largeImage->format->BytesPerPixel;
			
			//Using the data from the large image, create the new tile.
			SDL_Surface *newTile = SDL_CreateRGBSurfaceFrom(((char*)largeImage->pixels + pixel_offset), tileWidth, tileHeight,
															largeImage->format->BitsPerPixel, largeImage->pitch, 
															largeImage->format->Rmask, largeImage->format->Gmask,
															largeImage->format->Bmask, largeImage->format->Amask);
			
			tileset->push_back(newTile); //Add the tile to the tileset.
		}
	}
	
	return tileset;
}

//The first parameter is the name of the spritesheet to load.
//The second parameter is the name of the spritesheet's background color.
//(Note: The background color must not be the same color as your colorkey. You should not use this color anywhere in your sprites)
//SDL_LoadSpritesheet does the same thing as SDL_LoadTileset, but tries to automaticly locate the sprites, by the background color around them.
//There must be at least 1 pixel of background color between every sprite.
//This function is slow! Luckily, you only need to call it once (per sheet). Still, it'd be
//better to just keep your sprites the same size, and load them with SDL_LoadTileset, when possible.
SDL_Tileset *SDL_LoadSpritesheet(std::string filename, SDL_Color backgroundColor)
{
	SDL_Tileset *spriteSheet = new SDL_Tileset; //Our array where we'll stick the broken up surfaces.
	
	SDL_Surface *largeImage = IMG_Load(filename.c_str()); //Load the huge image.
	if(largeImage == NULL) 
	{
		//Oops, something went wrong. Print an error message.
		std::cout << "An error occured in 'SDL_LoadSpriteSheet()', and we couldn't load the sprites.\n"
				  << "\t" << IMG_GetError() << std::endl;
		
		return spriteSheet; //Return the empty array.
	}
	
	spriteSheet->push_back(largeImage); //The first element in the array is the full image.
	
	//We create a copy of the surface before proceeding, because we're going to draw over the copy.
	SDL_Surface *copyImage = SDL_DisplayFormat(largeImage);
	
	if(SDL_MUSTLOCK(copyImage))
        SDL_LockSurface(copyImage);
	
	std::vector<SDL_Rect> spriteRectArray; //Where we'll store the position and size of the images, temporarily.
	SDL_Rect spriteRect;
	
	Uint32 background = SDL_MapRGB(copyImage->format, backgroundColor.r, backgroundColor.g, backgroundColor.b);
	
	Uint32 *pixels = (Uint32*)copyImage->pixels;
	
	int pixelPitch = (copyImage->pitch / copyImage->format->BytesPerPixel);
	
	//Loop through each pixel, looking for pixels that are not the background color.
	for(int y = 0; y < copyImage->h; y++)
	{
		for(int x = 0; x < copyImage->w; x++)
		{
			//Check if the pixel doesn't match the background color.
			if(pixels[(y * pixelPitch) + x] != background)
			{
				//If it doesn't match, we now have the x and y position of the sprite.
				spriteRect.x = x;
				spriteRect.y = y;
				
				int endY = y;
				
				//Check every pixel vertically, until we find a pixel that DOES match the background.
				while(pixels[(endY * pixelPitch) + x] != background)
				{
					endY++;
					if(endY == largeImage->h)
						break;
				}
				
				//The pixel found, is the end of the sprite vertically. That's the height of the sprite.
				spriteRect.h = endY - spriteRect.y;
				
				int endX = x;
				
				//Check every pixel vertically, until we find a pixel that matches the background.
				while(pixels[(y * pixelPitch) + endX] != background)
				{
					endX++;
					if(endX == largeImage->w)
						break;
				}
				
				//The pixel found, is the end of the sprite vertically. That's the height of the sprite.
				spriteRect.w = endX - spriteRect.x;
				
				//We now have the full rectangle, so push that onto the vector.
				spriteRectArray.push_back(spriteRect);
				
				//Since we know the width of the sprite, we can skip ahead by that amount.
				x += spriteRect.w;
				
				//Finally, draw a rectangle over the sprite we just found, filling it with the background color.
				//(This way, we don't try to get the same image twice)
				SDL_FillRect(copyImage, &spriteRect, background);
			}
		}
	}
	
	
	if(SDL_MUSTLOCK(copyImage))
        SDL_UnlockSurface(copyImage);
	
	//Free the copy of the pixels we made.
	SDL_FreeSurface(copyImage);
	
	
	//Loop through each SDL_Rect, and create new images from the huge image.
	for(int i = 0; i < (int)spriteRectArray.size(); i++)
	{
		//Figure out where in the large image's pixels, will this tile's pixels be.
		int pixel_offset = (spriteRectArray[i].y * largeImage->pitch);
		pixel_offset += (spriteRectArray[i].x * largeImage->format->BytesPerPixel);
		
		//Using the data from the large image, create the new sprite.
		SDL_Surface *newTile = SDL_CreateRGBSurfaceFrom(((char*)largeImage->pixels + pixel_offset), spriteRectArray[i].w, spriteRectArray[i].h,
														largeImage->format->BitsPerPixel, largeImage->pitch, 
														largeImage->format->Rmask, largeImage->format->Gmask,
														largeImage->format->Bmask, largeImage->format->Amask);
		
		spriteSheet->push_back(newTile); //Add the sprite to the sprite sheet.
	}
	
	return spriteSheet;
}

//Frees the SDL_Tileset passed to it. The pointer will be set to NULL.
void SDL_FreeTileset(SDL_Tileset *tileset)
{
	//Loop through each image in the tileset, starting with the
	//large image at the bottom, and free them.
	for(int i = 0; i < (int)tileset->size(); i++)
	{
		SDL_FreeSurface((*tileset)[i]);
	}
	
	//Delete the tileset itself.
	delete tileset;
	
	//Set the tileset to NULL, so we'll know if we accidentally use it in the future.
	tileset = NULL;
}

//Sets the colorkey on all the images in a SDL_Tileset.
void SDL_SetColorKey(SDL_Tileset *tileset, SDL_Color color)
{
	//Loop through each image in the tileset.
	for(int i = 0; i < (int)tileset->size(); i++)
	{
		//Get the individual tile from tileset.
		SDL_Surface *tile = (*tileset)[i];
		
		//Set the colorkey on that tile.
		Uint32 key = SDL_MapRGB(tile->format, color.r, color.g, color.b);
		SDL_SetColorKey(tile, SDL_SRCCOLORKEY, key);
	}
}

//Gets a individual tile from the tileset, and returns it.
SDL_Surface *SDL_GetTile(SDL_Tileset *tileset, int tile)
{
	if(tile < (int)tileset->size())
		return (*tileset)[tile];
	
	return NULL;
}

//Draws the tile number 'tile' in the tileset 'tileset' at the location 'x' and 'y' on the surface 'destination'.
void SDL_DrawTile(int x, int y, SDL_Tileset *tileset, int tile, SDL_Surface *destination)
{
	//Make sure the destination isn't NULL.
	if(destination == NULL)
	{
		std::cout << "An error occured in 'SDL_DrawTile()', and we couldn't draw the tile.\n"
				  << "\t" << "The destination surface we were trying to draw to was NULL." << std::endl;
		
		return;
	}
	
	//Make sure the tileset isn't NULL.
	if(tileset == NULL)
	{
		std::cout << "An error occured in 'SDL_DrawTile()', and we couldn't draw the tile.\n"
				  << "\t" << "The tileset we were trying to draw a tile from was NULL." << std::endl;
		
		return;
	}
	
	//Make sure we aren't going out of bounds.
	if(tile >= (int)tileset->size())
	{
		std::cout << "An error occured in 'SDL_DrawTile()', and we couldn't draw the tile.\n"
				  << "\t" << "Tried to draw tile '" << tile << "' when the tileset only has '" << tileset->size() << "' tiles" << std::endl;
		
		return;
	}
	
	//Fill the SDL_Rect with the position we wish to draw to.
	SDL_Rect drawRect;
	drawRect.x = x;
	drawRect.y = y;
	
	//Draw the tile, and check for errors.
	if(SDL_BlitSurface((*tileset)[tile], NULL, destination, &drawRect) == -1)
	{
		std::cout << "An error occured in 'SDL_DrawTile()', and we couldn't draw the tile.\n"
				  << "\t" << SDL_GetError() << std::endl;
		
		return;
	}
}

Here's the example program:
#include "SDL_tileset.h"

int main(int argc, char *argv[])
{
	SDL_Init(SDL_INIT_EVERYTHING);
	
	SDL_Surface *screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE);
	SDL_WM_SetCaption("SDL_Tileset example", NULL);
	
	//Load the tileset.
	SDL_Tileset *tileset = SDL_LoadTileset("tileset.png", 50, 50, 1, 3, 3, 2);
	
	SDL_Color backgroundColor;
	backgroundColor.r = 255; backgroundColor.g = 255; backgroundColor.b = 255;
	SDL_Tileset *spritesheet = SDL_LoadSpritesheet("spritesheet.png", backgroundColor);
	
	SDL_Color colorkey;
	colorkey.r = 0; colorkey.g = 255; colorkey.b = 255;
	SDL_SetColorKey(spritesheet, colorkey);
	
	SDL_ShowCursor(SDL_DISABLE);
	
	bool quit = false;
	
	while(!quit)
	{
		SDL_Event event;
		while(SDL_PollEvent(&event))
		{
			if(event.type == SDL_QUIT)
			{
				quit = true;
			}
		}
		
		//Erase the screen.
		SDL_FillRect(screen, NULL, 0x00);
		
		//Draw some tiles.
		SDL_DrawTile(0, 0, tileset, /* Tile # */ 1, screen);
		SDL_DrawTile(20, 20, tileset, /* Tile # */ 2, screen);
		SDL_DrawTile(40, 40, tileset, /* Tile # */ 3, screen);
		SDL_DrawTile(60, 60, tileset, /* Tile # */ 4, screen);
		SDL_DrawTile(80, 80, tileset, /* Tile # */ 5, screen);
		SDL_DrawTile(100, 100, tileset, /* Tile # */ 6, screen);
		
		//Draw some more tiles.
		SDL_DrawTile(400, 250, tileset, /* Tile # */ 7, screen);
		SDL_DrawTile(400, 300, tileset, /* Tile # */ 8, screen);
		SDL_DrawTile(450, 250, tileset, /* Tile # */ 8, screen);
		SDL_DrawTile(450, 300, tileset, /* Tile # */ 7, screen);
		
		//Draw some sprites.
		SDL_DrawTile(560, 20, spritesheet, /* Tile # */ 3, screen);
		SDL_DrawTile(100, 300, spritesheet, /* Tile # */ 4, screen);
		SDL_DrawTile(90, 290, spritesheet, /* Tile # */ 5, screen);
		SDL_DrawTile(120, 310, spritesheet, /* Tile # */ 6, screen);
		
		//Draw the mouse, as an animated sprite.
		int mouseX, mouseY;
		SDL_GetMouseState(&mouseX, &mouseY);
		
		//Cheap animation.
		if((SDL_GetTicks() % 500) < 250)
			SDL_DrawTile(mouseX, mouseY, spritesheet, /* Tile # */ 1, screen);
		else
			SDL_DrawTile(mouseX, mouseY, spritesheet, /* Tile # */ 2, screen);
		
		//Update the screen.
		SDL_Flip(screen);
		
		SDL_Delay(50);
	}
	
	SDL_ShowCursor(SDL_ENABLE);
	
	//Free the spritesheet.
	SDL_FreeTileset(spritesheet);
	
	//Free the tileset.
	SDL_FreeTileset(tileset);
	
	SDL_Quit();
	
	return 0;
}

Or, if you prefer to download the code, poorly drawn images included, you can do so here: http://www.mediafire.com/?mld1dkm2zrm (Requires SDL and SDL_image to run. I assume if you are interested in this, you already know how to use SDL, so I didn't bother including the DLLs) So, next time you are working on a SDL tile-based game, no need to rewrite the code to separate the tilesets, for the hundredth time. [smile]
0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0