Tag Archives: tutorial

SDL 2.0 Tutorial-06: Networking 3

In the last tutorial we setup a basic TCP server. In this tutorial we will start by modifying the server we setup and finish by writing a player client application. In order to write a client application with SDL 2.0 we will need a lot of the ideas which where presented in my first string of SDL tutorials (here). A lot of my personal preferences for setting up SDL have changed since writing those tutorials so instead of using code found within those tutorials I will be using a collection of C++ files I have settled into using (found here).

First let’s start with a few server-side modifications. Let’s start by adding a few extra packet flags:

//-----------------------------------------------------------------------------
#define FLAG_QUIT 0x0000
#define FLAG_WOOD_QUEST 0x0011
#define FLAG_WOOD_UPDATE 0x0010
#define FLAG_WOOD_GETTIME 0x0012

Now we have a packet flag for starting and completing a quest, as well as a packet flag for getting the amount of time left until the quest is completed. Next we will update our server-side “RecvData” function:

uint8_t* RecvData(int index, uint16_t* length) {
	uint8_t temp_data[MAX_PACKET];
	int num_recv = SDLNet_TCP_Recv(sockets[index], temp_data, MAX_PACKET);

	if(num_recv <= 0) {
		CloseSocket(index);

		const char* err = SDLNet_GetError();
		if(strlen(err) == 0) {
			printf("DB: client disconnectedn");
		} else {
			fprintf(stderr, "ER: SDLNet_TCP_Recv: %sn", err);
		}

		return NULL;
	} else {
		*length = num_recv;

		uint8_t* data = (uint8_t*) malloc(num_recv*sizeof(uint8_t));
		memcpy(data, temp_data, num_recv);

		return data;
	}
}

What I have done here is removed the work this function was doing to grab the packet flag from the start of the incoming socket data. This is because when a socket is ready for us to process it may have several packets all lined together. Therefore the data buffer returned by “RecvData” may contain several packets depending on how quickly the client is sending packets to us; so any work “RecvData” does to grab the flag from the first packet doesn’t help us identify the packet flags for all the other packets possibly following the first. Therefore the extra work being done isn’t necessary at this level.

Next we will add a new function called “ProcessData.” This function will continuously run on each packet we find within the data buffer returned by “RecvData.”

void ProcessData(int index, uint8_t* data, uint16_t* offset) {
	if(data == NULL) return;

	uint16_t flag = *(uint16_t*) &data[*offset];
	*offset += sizeof(uint16_t);

	switch(flag) {
		case FLAG_WOOD_UPDATE: {
			uint16_t send_offset = 0;
			uint8_t send_data[MAX_PACKET];

			memcpy(send_data+send_offset, &clients[index].amt_wood, sizeof(uint8_t));
			send_offset += sizeof(uint8_t);

			SendData(index, send_data, send_offset, FLAG_WOOD_UPDATE);
		} break;

		case FLAG_WOOD_GETTIME: {
			uint16_t send_offset = 0;
			uint8_t send_data[MAX_PACKET];

			uint32_t time_left;
			if(clients[index].questing) {
				time_left = WOOD_WAIT_TIME-(SDL_GetTicks()-clients[index].timer_wood);
			} else time_left = 0;

			memcpy(send_data+send_offset, &time_left, sizeof(uint32_t));
			send_offset += sizeof(uint32_t);

			SendData(index, send_data, send_offset, FLAG_WOOD_GETTIME);
		} break;

		case FLAG_WOOD_QUEST: {
			if(!clients[index].questing) {
				clients[index].questing = 1;
				clients[index].timer_wood = SDL_GetTicks();
			}
		} break;

		case FLAG_QUIT: {
			running = 0;
			printf("DB: shutdown by client id: %dn", index);
		} break;
	}
}

This is where we do the work and grab the packet’s flag. Everything here should look similar to what we where doing before when the server found client sockets which were ready with information to be processed – except for the fact that we have added two extra flags. The “GETTIME” flag checks if the player is currently questing; if not then the time left on the quest is 0, otherwise the server sends the time left on the quest to the player. The other flag is the “QUEST” flag; this flag checks whether the client is currently questing and starts the quest if they aren’t currently in a quest.

The finial server-side update we make will be to the main processing loop. First let’s crank up how often we check for ready sockets:

int num_rdy = SDLNet_CheckSockets(socket_set, 50);

If num_rdy <= 0 then we do not have any sockets ready for processing so we enter the server’s idle tasks. Previously we were simply adding four wood to the client’s resource count continuously. This is quite a silly thing to do! Instead let’s check for whether a client has completed any quests and if so we’ll send out a quest completed packet and a wood update packet. In addition, we’ll continuously send out “GETTIME” packets to keep the client updated with the server’s timer.

int ind;
for(ind=0; ind<MAX_SOCKETS; ++ind) {
	if(!clients[ind].in_use) continue;

	if(clients[ind].questing &&
		(SDL_GetTicks()-clients[ind].timer_wood)>WOOD_WAIT_TIME
	) {
		clients[ind].questing = 0;
		clients[ind].amt_wood += 4;
		SendData(ind, NULL, 0, FLAG_WOOD_QUEST);

		uint16_t send_offset = 0;
		uint8_t send_data[MAX_PACKET];

		memcpy(send_data+send_offset, &clients[ind].amt_wood, sizeof(uint8_t));
		send_offset += sizeof(uint8_t);

		SendData(ind, send_data, send_offset, FLAG_WOOD_UPDATE);
	}

	uint16_t send_offset = 0;
	uint8_t send_data[MAX_PACKET];

	uint32_t time_left;
	if(clients[ind].questing) {
		time_left = WOOD_WAIT_TIME-(SDL_GetTicks()-clients[ind].timer_wood);
	} else time_left = 0;

	memcpy(send_data+send_offset, &time_left, sizeof(uint32_t));
	send_offset += sizeof(uint32_t);

	SendData(ind, send_data, send_offset, FLAG_WOOD_GETTIME);
}

And for the final server touch we’ll update the code which runs if there are sockets ready to be processed. Notice that how we handle the server socket (for new connections) isn’t changing but how we handle the client sockets is changing.

if(SDLNet_SocketReady(server_socket)) {
	int got_socket = AcceptSocket(next_ind);
	if(!got_socket) {
		num_rdy--;
		continue;
	}

	// NOTE: get a new index
	int chk_count;
	for(chk_count=0; chk_count<MAX_SOCKETS; ++chk_count) {
		if(sockets[(next_ind+chk_count)%MAX_SOCKETS] == NULL) break;
	}

	next_ind = (next_ind+chk_count)%MAX_SOCKETS;
	printf("DB: new connection (next_ind = %d)n", next_ind);

	num_rdy--;
}

int ind;
for(ind=0; (ind<MAX_SOCKETS) && num_rdy; ++ind) {
	if(sockets[ind] == NULL) continue;
	if(!SDLNet_SocketReady(sockets[ind])) continue;

	uint8_t* data;
	uint16_t length;
				
	data = RecvData(ind, &length);
	if(data == NULL) {
		num_rdy--;
		continue;
	}

	int num_processed = 0;

	uint16_t offset = 0;
	while(offset < length) {
		num_processed++;
		ProcessData(ind, data, &offset);
	}

	printf("num_processed from ID: %d was %dn", ind, num_processed);
	fflush(stdout);

	free(data);
	num_rdy--;
}

I am also keeping track of the number of packets which the server processes for each of the ready clients for debugging purposes. Now the client-side stuff isn’t that different when it comes to the network side of things. A lot of the helper functions we have server-side will have analogous client-server versions. Here is the client-side code in its entirety:

/*
*/

#include "AdBase.h"
#include "AdLevel.h"
#include "AdScreen.h"
#include "AdSpriteManager.h"

//-----------------------------------------------------------------------------
#define MAX_PACKET 0xFF

//-----------------------------------------------------------------------------
#define FLAG_QUIT 0x0000
#define FLAG_WOOD_QUEST 0x0011
#define FLAG_WOOD_UPDATE 0x0010
#define FLAG_WOOD_GETTIME 0x0012

//-----------------------------------------------------------------------------
TCPsocket socket;
SDLNet_SocketSet socket_set;

//-----------------------------------------------------------------------------
int questing;
uint8_t amt_wood;
uint32_t timer_wood;

//-----------------------------------------------------------------------------
void CloseSocket(void) {
	if(SDLNet_TCP_DelSocket(socket_set, socket) == -1) {
		fprintf(stderr, "%sn", SDLNet_GetError());
		system("pause");
		exit(-1);
	}

	SDLNet_FreeSocketSet(socket_set);
	SDLNet_TCP_Close(socket);
}

//-----------------------------------------------------------------------------
void SendData(uint8_t* data, uint16_t length, uint16_t flag) {
	uint8_t temp_data[MAX_PACKET];

	int offset = 0;
	memcpy(temp_data+offset, &flag, sizeof(uint16_t));
	offset += sizeof(uint16_t);
	memcpy(temp_data+offset, data, length);
	offset += length;

	int num_sent = SDLNet_TCP_Send(socket, temp_data, offset);
	if(num_sent < offset) {
		fprintf(stderr, "ER: SDLNet_TCP_Send: %sn", SDLNet_GetError());
		CloseSocket();
	}
}

//-----------------------------------------------------------------------------
uint8_t* RecvData(uint16_t* length) {
	uint8_t temp_data[MAX_PACKET];
	int num_recv = SDLNet_TCP_Recv(socket, temp_data, MAX_PACKET);

	if(num_recv <= 0) {
		CloseSocket();

		const char* err = SDLNet_GetError();
		if(strlen(err) == 0) {
			printf("DB: server shutdownn");
		} else {
			fprintf(stderr, "ER: SDLNet_TCP_Recv: %sn", err);
		}

		return NULL;
	} else {
		*length = num_recv;

		uint8_t* data = (uint8_t*) malloc(num_recv*sizeof(uint8_t));
		memcpy(data, temp_data, num_recv);

		return data;
	}
}

//-----------------------------------------------------------------------------
void ProcessData(uint8_t* data, uint16_t* offset) {
	if(data == NULL) return;

	uint16_t flag = *(uint16_t*) &data[*offset];
	*offset += sizeof(uint16_t);

	switch(flag) {
		case FLAG_WOOD_UPDATE: {
			amt_wood = *(uint8_t*) &data[*offset];
			*offset += sizeof(uint8_t);
		} break;

		case FLAG_WOOD_GETTIME: {
			timer_wood = *(uint32_t*) &data[*offset];
			*offset += sizeof(uint32_t);
		} break;

		case FLAG_WOOD_QUEST: {
			// NOTE: quest completed
			questing = 0;
		} break;
	}
}

//-----------------------------------------------------------------------------
void InitNetwork(const char* pIP, int iPort) {
	IPaddress ip;
	if(SDLNet_ResolveHost(&ip, pIP, iPort) == -1) {
		fprintf(stderr, "%sn", SDLNet_GetError());
		system("pause");
		exit(-1);
	}

	socket = SDLNet_TCP_Open(&ip);
	if(socket == NULL) {
		fprintf(stderr, "%sn", SDLNet_GetError());
		system("pause");
		exit(-1);
	}

	socket_set = SDLNet_AllocSocketSet(1);
	if(socket_set == NULL) {
		fprintf(stderr, "%sn", SDLNet_GetError());
		system("pause");
		exit(-1);
	}

	if(SDLNet_TCP_AddSocket(socket_set, socket) == -1) {
		fprintf(stderr, "%sn", SDLNet_GetError());
		system("pause");
		exit(-1);
	}
}

//-----------------------------------------------------------------------------
bool CheckSocket(void) {
	if(SDLNet_CheckSockets(socket_set, 0) == -1) {
		fprintf(stderr, "%sn", SDLNet_GetError());
		system("pause");
		exit(-1);
	}

	return SDLNet_SocketReady(socket);
}

//-----------------------------------------------------------------------------
int SDL_main(int argc, char* argv[]) {
	if(AdBase::Init(8*40, 8*30, 3) == false) {
		fprintf(stderr, "ERROR: Failed to initiate.n");
		system("pause");
		return -1;
	}

	// TESTING
	InitNetwork("PUT_SERVER_IP_HERE!!!", 8099); // arg #2 is the port

	AdLevel* testLvl = new AdLevel();
	//

	SDL_Event sdlEvent = {};
	while(sdlEvent.type != SDL_QUIT) {
		SDL_PollEvent(&sdlEvent);

		AdScreen::Clear();

		// TESTING
		if(CheckSocket()) {
			uint16_t length, flag;
			uint8_t* data = RecvData(&length);

			uint16_t offset = 0;
			while(offset < length) {
				ProcessData(data, &offset);
			}

			free(data);
		}
		//

		// TESTING
		testLvl->Update(&sdlEvent);

		char string[0xFF];
		sprintf(string, "Wood: %d", amt_wood);

		SDL_Point pnt1 = {0, 0};
		SDL_Color color1 = {0xFF, 0x00, 0x00, 0x00};
		SDL_Surface* pSurf = AdSpriteManager::BuildSprite(string, color1);
		AdScreen::DrawSprite(pnt1, pSurf);
		SDL_FreeSurface(pSurf);

		pnt1.y += 8;
		sprintf(string, "Timer: %d", (int) ceil((double)timer_wood/1000.0f));
		pSurf = AdSpriteManager::BuildSprite(string, color1);
		AdScreen::DrawSprite(pnt1, pSurf);
		SDL_FreeSurface(pSurf);
		//

		//
		if(timer_wood == 0) {
			SDL_Point pnt2 = {128, 8};
			SDL_Rect rec2 = {pnt2.x, pnt2.y, 5*8, 8};

			if(
				testLvl->m_iMouseX>=rec2.x && testLvl->m_iMouseX<=(rec2.x+rec2.w) &&
				testLvl->m_iMouseY>=rec2.y && testLvl->m_iMouseY<=(rec2.y+rec2.h)
			) {
				SDL_Color color2 = {0x00, 0xFF, 0x00, 0x00};
				pSurf = AdSpriteManager::BuildSprite("Quest", color2);

				if(testLvl->m_bMouseLeft && !questing) {
					questing = 1;
					SendData(NULL, 0, FLAG_WOOD_QUEST);
				}
			} else {
				SDL_Color color2 = {0x00, 0x00, 0xFF, 0x00};
				pSurf = AdSpriteManager::BuildSprite("Quest", color2);
			}
			
			AdScreen::DrawSprite(pnt2, pSurf);
			SDL_FreeSurface(pSurf);
		}
		//

		AdScreen::Present();
	}

	// TESTING
	delete testLvl;

	SendData(NULL, 0, FLAG_QUIT);
	CloseSocket();
	//

	AdBase::Quit();

	return 0;
}

All the SDL graphics processing and input handling is being wrapped with the custom files I wrote and linked at the beginning of this post. If you find these posts helpful or would like me to dive deeper into a particular topic covered here please send me an email at “info@stephenmeier.net”.

SDL 2.0 Tutorial-05: Networking 2

In the last tutorial we finished with a hallow if-statement within our processing loop.

int running = 1;
while(running) {
	int num_rdy = SDLNet_CheckSockets(socket_set, 1000);

	if(num_rdy <= 0) {
		// NOTE: none of the sockets are ready
	} else {
		// NOTE: some number of the sockets are ready
	}
}

If there aren’t any sockets which we need to process (i.e. num_rdy <= 0 ) then we’ll loop over all the currently connected client sockets and perform any tasks which may be pending. This is basically the server’s chance to catch up with game state and perform any security checks. This will work well for a simple TCP game where the server and client need not be in sync the entire time. For something more real-time we would prioritize game state and perhaps use UDP instead.

For now let’s add four wood to the resource count for each connected client.

int ind;
for(ind=0; ind<MAX_SOCKETS; ++ind) {
	if(!clients[ind].in_use) continue;
	clients[ind].amt_wood += 4;
}

Remember this will run every second assuming there aren’t any sockets to be processed (i.e. num_rdy <= 0 ). Why am I saying it’ll run every second? It is because of the following function call:

SDLNet_CheckSockets(socket_set, 1000);

The second argument to this function is 1000, which is the number of milliseconds this function will block while waiting for a socket connection.

Now, if there are sockets ready to be processed we’ll have to check whether it is a client socket or the server socket. If the server socket is ready then we likely have a new client connection. For the client connections we’ll have to check each one until we find the ones which are ready and then process the data that we have received from them.

First let’s handle the case when the server socket is ready:

if(SDLNet_SocketReady(server_socket)) {
	int got_socket = AcceptSocket(next_ind);
	if(!got_socket) {
		num_rdy--;
		continue;
	}

	// NOTE: get a new index
	int chk_count;
	for(chk_count=0; chk_count<MAX_SOCKETS; ++chk_count) {
		if(sockets[(next_ind+chk_count)%MAX_SOCKETS] == NULL) break;
	}

	next_ind = (next_ind+chk_count)%MAX_SOCKETS;
	printf("DB: new connection (next_ind = %d)n", next_ind);

	num_rdy--;
}

Here the function int AcceptSocket(int index); is a shorthand for accepting a new socket connection using sockets[index] and clients[index] . This function returns whether or not the connection was successfully accepted. Here is the function itself:

int AcceptSocket(int index) {
	if(sockets[index]) {
		fprintf(stderr, "ER: Overriding socket at index %d.n", index);
		CloseSocket(index);
	}

	sockets[index] = SDLNet_TCP_Accept(server_socket);
	if(sockets[index] == NULL) return 0;

	clients[index].in_use = 1;
	if(SDLNet_TCP_AddSocket(socket_set, sockets[index]) == -1) {
		fprintf(stderr, "ER: SDLNet_TCP_AddSocket: %sn", SDLNet_GetError());
		exit(-1);
	}

	return 1;
}

The function void CloseSocket(int index); was presented in the previous part of this tutorial. After we accept the new client connection we also update the next_ind variable so that we are ready for the next client connection. As it’s currently written, if a new client connects when the server is full then another client will be kicked to make room for the new connection.

Now let’s process any clients which might have data for us.

int ind;
for(ind=0; (ind<MAX_SOCKETS) && num_rdy; ++ind) {
	if(sockets[ind] == NULL) continue;
	if(!SDLNet_SocketReady(sockets[ind])) continue;

	uint8_t* data;
	uint16_t flag;
	uint16_t length;
				
	data = RecvData(ind, &length, &flag);
	if(data == NULL) {
		num_rdy--;
		continue;
	}

	switch(flag) {
		case FLAG_WOOD_UPDATE: {
			uint16_t offset = 0;
			uint8_t send_data[MAX_PACKET];

			memcpy(send_data+offset, &clients[ind].amt_wood, sizeof(uint8_t));
			offset += sizeof(uint8_t);

			SendData(ind, send_data, offset, FLAG_WOOD_UPDATE);
		} break;

		case FLAG_QUIT: {
			running = 0;
			printf("DB: shutdown by client id: %dn", ind);
		} break;
	}

	free(data);
	num_rdy--;
}

What we are doing here is looping over all the client sockets and checking to see whether a client is connected and whether that connected client has some information ready for us. We also want to make sure that we aren’t working harder than we have to by breaking out of the for-loop when num_rdy == 0 ; because at that point we have processed all the ready client sockets so there is no point in checking any remaining client connections for information.

Once we know that we have a connected socket and that the socket have information ready for us, we fill a buffer with the data they sent us. This is what the uint8_t* RecvData(int index, uint16_t* length, uint16_t* flag); function does for us:

uint8_t* RecvData(int index, uint16_t* length, uint16_t* flag) {
	uint8_t temp_data[MAX_PACKET];
	int num_recv = SDLNet_TCP_Recv(sockets[index], temp_data, MAX_PACKET);

	if(num_recv <= 0) {
		CloseSocket(index);
		const char* err = SDLNet_GetError();
		if(strlen(err) == 0) {
			printf("DB: client disconnectedn");
		} else {
			fprintf(stderr, "ER: SDLNet_TCP_Recv: %sn", err);
		}

		return NULL;
	} else {
		int offset = 0;
		*flag = *(uint16_t*) &temp_data[offset];
		offset += sizeof(uint16_t);

		*length = (num_recv-offset);

		uint8_t* data = (uint8_t*) malloc((num_recv-offset)*sizeof(uint8_t));
		memcpy(data, &temp_data[offset], (num_recv-offset));

		return data;
	}
}

This function gets data from sockets[index] and returns a pointer to a new buffer. The in/out function arguments length and flag are the length of the newly allocated buffer and the packet flag for the information we are receiving from the client.

If the client sends us a request for the amount of wood they current possess (i.e. FLAG_WOOD_UPDATE ) then we reply by sending them a packet with the information they are requesting. To send information to the connected client we use the following shorthand function:

void SendData(int index, uint8_t* data, uint16_t length, uint16_t flag) {
	uint8_t temp_data[MAX_PACKET];

	int offset = 0;
	memcpy(temp_data+offset, &flag, sizeof(uint16_t));
	offset += sizeof(uint16_t);
	memcpy(temp_data+offset, data, length);
	offset += length;

	int num_sent = SDLNet_TCP_Send(sockets[index], temp_data, offset);
	if(num_sent < offset) {
		fprintf(stderr, "ER: SDLNet_TCP_Send: %sn", SDLNet_GetError());
		CloseSocket(index);
	}
}

For debugging purposes the client can also shutdown the server with a FLAG_QUIT . In the next tutorial we will write a simple client which will display the amount of wood the client current has. This amount will continue to increase because the server currently adds four wood each time it enters idle processing. Once we have achieved this we’ll add a “quest” button and timer on the client-side and a few more packet flags allowing the client to request the amount of time left on a quest and to start a quest. The server will check for when a quest completes and will increase the amount of wood for that client as a result.

Till next time!

SDL 2.0 Tutorial-04: Networking 1

For the next few posts I plan to run through some of the basics of TCP communication using the SDLNet library. The goal here is to write a simple “resource” game where the client sends a “quest” request and, after some amount of time has passed, the quest is considered complete and the client’s resource count increases a little. This series will provide an excellent setup for developing a player driven economy type game.

Our first goal will be to write a server which manages all the client connections. To keep things simple we will be working with one single resource; here we are using wood as the resource. We’ll start with a few #define ‘s

//-----------------------------------------------------------------------------
#define MAX_PACKET 0xFF
#define MAX_SOCKETS 0x10

//-----------------------------------------------------------------------------
#define WOOD_WAIT_TIME 5000

//-----------------------------------------------------------------------------
#define FLAG_QUIT 0x0000
#define FLAG_WOOD_UPDATE 0x0010

Here we have three different type of #define ‘s

  • MAX_PACKET and MAX_SOCKETS define the maximum size of the TCP packets we are willing to process and the maximum number of clients we will allow online at once.
  • WOOD_WAIT_TIME is the amount of time in milliseconds to wait before the quest for obtaining additional wood units is complete.
  • FLAG_QUIT and FLAG_WOOD_UPDATE are 16 bit packet flags used to determine the type of packet we are receiving and we then process the rest of the packet’s data based on this flag.

Next we need a structure which we can use to keep all the client’s information separate from one another.

typedef struct {
	int in_use;
	int questing;
	uint8_t amt_wood;
	uint32_t timer_wood;
} Client;

Here we have a few important variables which we need in order to efficiently work with each of the clients which connect to the server. in_use allows us to determine whether the current client is being used by an active socket connection or not. This will prevent us from running checks on client structures which don’t have corresponding network connections. questing is a simple boolean which tells us whether the current client is running a wood fetching quest or not. amt_wood is the amount of the wood resource which the current client has. timer_wood is a timestamp for when the wood fetching quest for this particular client was started – this will help us determine when the wood fetching quest has been completed.

Next we need some global state variables which will make writing our little server a bit easier.

int next_ind = 0;
TCPsocket server_socket;
Client clients[MAX_SOCKETS];
SDLNet_SocketSet socket_set;
TCPsocket sockets[MAX_SOCKETS];

next_ind is the index into the sockets and clients arrays for the next player that will connect to the server. When one client connects next_ind gets updated so that we never overwrite a connected clients information.

First thing we do in int main(int argc, char** argv); is initialize a few SDL components and the SDLNet library.

if(SDL_Init(SDL_INIT_TIMER|SDL_INIT_EVENTS) != 0) {
	fprintf(stderr, "ER: SDL_Init: %sn", SDL_GetError());
	exit(-1);
}

if(SDLNet_Init() == -1) {
	fprintf(stderr, "ER: SDLNet_Init: %sn", SDLNet_GetError());
	exit(-1);
}

Next we’ll want to open the server’s TCP socket on a specific port, here I’m using port 8099. Any function specific information can be found within the SDLNet online API.

IPaddress ip;
if(SDLNet_ResolveHost(&ip, NULL, 8099) == -1) {
	fprintf(stderr, "ER: SDLNet_ResolveHost: %sn", SDLNet_GetError());
	exit(-1);
}

server_socket = SDLNet_TCP_Open(&ip);
if(server_socket == NULL) {
	fprintf(stderr, "ER: SDLNet_TCP_Open: %sn", SDLNet_GetError());
	exit(-1);
}

Finally, before the main processing loop, we will setup an SDLNet socket set and add the server_socket to the socket set so that we may be informed when a net connection is looking to be accepted by the server.

socket_set = SDLNet_AllocSocketSet(MAX_SOCKETS+1);
if(socket_set == NULL) {
	fprintf(stderr, "ER: SDLNet_AllocSocketSet: %sn", SDLNet_GetError());
	exit(-1);
}

if(SDLNet_TCP_AddSocket(socket_set, server_socket) == -1) {
	fprintf(stderr, "ER: SDLNet_TCP_AddSocket: %sn", SDLNet_GetError());
	exit(-1);
}

It’s worth noting that the size of the socket set is MAX_SOCKETS+1 , the plus one accounts for the addition of the server socket. The main processing loop is setup like the following (currently hollow):

int running = 1;
while(running) {
	int num_rdy = SDLNet_CheckSockets(socket_set, 1000);

	if(num_rdy <= 0) {
		// NOTE: none of the sockets are ready
	} else {
		// NOTE: some number of the sockets are ready
	}
}

While the server is running we check for any sockets from the socket set which might have packets for us to process; we check the socket set for a second (1000 milliseconds). If there aren’t any sockets ready to be processed then we use this time to run other various server activities like checking is quests have been completed, etc. If there is some number of sockets ready to be processed then we process those sockets.

In the next tutorial we will fill in these two parts of the if-statement above; this is where the real magic is! To clean up everything after the server is done running we will run the following code.

if(SDLNet_TCP_DelSocket(socket_set, server_socket) == -1) {
	fprintf(stderr, "ER: SDLNet_TCP_DelSocket: %sn", SDLNet_GetError());
	exit(-1);
} SDLNet_TCP_Close(server_socket);

int i;
for(i=0; i<MAX_SOCKETS; ++i) {
	if(sockets[i] == NULL) continue;
	CloseSocket(i);
}

SDLNet_FreeSocketSet(socket_set);
SDLNet_Quit();
SDL_Quit();

The missing piece here is the void CloseSocket(int index); function which is basically just a shorthand for closing a client socket and is stated below:

void CloseSocket(int index) {
	if(sockets[index] == NULL) {
		fprintf(stderr, "ER: Attempted to delete a NULL socket.n");
		return;
	}

	if(SDLNet_TCP_DelSocket(socket_set, sockets[index]) == -1) {
		fprintf(stderr, "ER: SDLNet_TCP_DelSocket: %sn", SDLNet_GetError());
		exit(-1);
	}

	memset(&clients[index], 0x00, sizeof(Client));
	SDLNet_TCP_Close(sockets[index]);
	sockets[index] = NULL;
}

For the next tutorial we’ll pick up from here and fill in the if-statement mentioned earlier. Once the server-side code has been setup to process a simple player request to perform a quest to gather wood, we’ll start working on a simple client-side quest button, quest timer, and resource counter. If there is time or interest we will add persistence to the resource count for each player using SQLite.

Obj Mesh Importer

Today I am sharing a bit of code I use to read very basic vertex and texture coordinate information from an obj file. The .obj format is explained in detail here. It is pretty well supported across most 3D-modeling software and is currently my go-to format for 3D mesh information.

In order to keep the vertex information file format agnostic (in case you decide to add other file formats later), I designed an intermediate layer of abstraction between the .obj file and OpenGL.

typedef struct {
	GLfloat* pos;   // (x,y,z)
	GLfloat* tex;   // (u,v)
	GLfloat* norm;  // (x,y,z)
	GLsizeiptr num; // total num of verts
} VertInfo;

typedef struct {
	GLuint* pos;    // 3 pos inds per
	GLuint* tex;    // 3 tex inds per
	GLuint* norm;   // 3 norm inds per
	GLsizeiptr num; // num of triangles
} IndInfo;

This works well for my current needs but there are a few simplifications I have made about the obj. An obj file may contain an optional coordinate (x, y, z [,w]) but I am ignoring that it might exist. Another simplification is that the texture coordinates have two variables, not three. These are reasonable assumptions to make since they basically ignore optional arguments that I won’t use anyways. The bigger assumption is that all the face definitions are triangles. This isn’t the case, even with the files I’ll be using, but OpenGL is really fast with triangles to I’m converting the quadrilaterals to triangles. This current iteration only accepts quadrilaterals but triangle faces would be trivial to add.

Here is the actually importer.

void loadObjFile(VertInfo** verts, IndInfo** inds, const char* filename) {
	if(*verts) {
		free((*verts)->pos);
		(*verts)->pos = NULL;
		free((*verts)->tex);
		(*verts)->tex = NULL;
		free((*verts)->norm);
		(*verts)->norm = NULL;

		(*verts)->num = 0;
	} else *verts = (VertInfo*) calloc(0x01, sizeof(VertInfo));

	if(*inds) {
		free((*inds)->pos);
		(*inds)->pos = NULL;
		free((*inds)->tex);
		(*inds)->tex = NULL;
		free((*inds)->norm);
		(*inds)->norm = NULL;
		
		(*inds)->num = 0;
	} else *inds = (IndInfo*) calloc(0x01, sizeof(IndInfo));

	size_t n = 0;
	char** lines = NULL;

	if(filename==NULL) return;
	FILE* fp = fopen(filename, "r");

	do {
		char c;
		int colInd = 0;

		lines = (char**) realloc(lines, ++n*sizeof(char*));
		lines[n-1] = NULL;

		do {
			fread(&c, sizeof(char), 1, fp);

			// NOTE: strip newlines
			if(c!='n') {
				if(colInd>0) {
					// NOTE: strip extra spaces
					if(!(lines[n-1][colInd-1]==' ' && c==' ')) {
						lines[n-1] = (char*) realloc(lines[n-1], ++colInd*sizeof(char));
						lines[n-1][colInd-1] = c;
					}
				} else {
					lines[n-1] = (char*) realloc(lines[n-1], ++colInd*sizeof(char));
					lines[n-1][colInd-1] = c;
				}
			}
		} while(c!='n');

		// NOTE: end with a null terminator
		lines[n-1] = (char*) realloc(lines[n-1], ++colInd*sizeof(char));
		lines[n-1][colInd-1] = '';

		if(lines[n-1][0]=='#') {
			// NOTE: ignore comments

			free(lines[n-1]);
			lines[n-1] = NULL;

			lines = (char**) realloc(lines, --n*sizeof(char*));
		} else if(lines[n-1][0]=='') {
			// NOTE: ignore blank lines

			free(lines[n-1]);
			lines[n-1] = NULL;

			lines = (char**) realloc(lines, --n*sizeof(char*));
		}
	} while(!feof(fp));

	fclose(fp);
	fp = NULL;

	size_t numPos = 0;
	size_t numTex = 0;

	int i;
	for(i=0; i<n; i++) {
		// NOTE: parse the file

		printf("%sn", lines[i]);

		if(lines[i][0]=='v' && lines[i][1]==' ') {
			// NOTE: parse the vertex and add it to the array

			(*verts)->num++;

			numPos += 3;
			(*verts)->pos = (GLfloat*) realloc((*verts)->pos, numPos*sizeof(GLfloat));

			GLfloat x = 0, y = 0, z = 0;
			sscanf(lines[i], "v %f %f %f", &x, &y, &z);
			printf("[%f, %f, %f]n", x, y, z);

			(*verts)->pos[numPos-3] = x;
			(*verts)->pos[numPos-2] = y;
			(*verts)->pos[numPos-1] = z;

		} else if(lines[i][0]=='v' && lines[i][1]=='t' && lines[i][2]==' ') {
			// NOTE: parse the texture coordinates

			numTex += 2;
			(*verts)->tex = (GLfloat*) realloc((*verts)->tex, numTex*sizeof(GLfloat));

			GLfloat u = 0, v = 0;
			sscanf(lines[i], "vt %f %f", &u, &v);
			printf("[%f, %f]n", u, v);

			(*verts)->tex[numTex-2] = u;
			(*verts)->tex[numTex-1] = v;
			
		} else if(lines[i][0] == 'f' && lines[i][1] == ' ') {
			// NOTE: parse the face elements

			int c, num = 0;
			GLuint* indices = NULL;

			for(c=1; c<strlen(lines[i]); c++) {

				// TODO: will have to handle the case when normals are left out
				// or when texture indices are left out

				// TODO: currently I am assuming 4 indices per face which makes
				// two triangles (i.e. 6 elements)

				if(lines[i][c]==' ') {
					num += 3;
					indices = (GLuint*) realloc(indices, num*sizeof(GLuint));

					GLuint vi = 0, vti = 0, vni = 0;
					sscanf(&lines[i][c], " %d/%d/%d", &vi, &vti, &vni);
					printf("[%d, %d, %d]n", vi, vti, vni);

					indices[num-3] = vi;
					indices[num-2] = vti;
					indices[num-1] = vni;
				}
			}

			if(num==4*3) {

				// TODO: currently only setting the position index but I need
				// to set them all eventually

				(*inds)->num += 6;

				(*inds)->pos = (GLuint*) realloc((*inds)->pos, (*inds)->num*sizeof(GLuint));
				(*inds)->tex = (GLuint*) realloc((*inds)->tex, (*inds)->num*sizeof(GLuint));
				(*inds)->norm = (GLuint*) realloc((*inds)->norm, (*inds)->num*sizeof(GLuint));

				int pos0 = indices[0];
				int pos1 = indices[3];
				int pos2 = indices[6];
				int pos3 = indices[9];

				(*inds)->pos[(*inds)->num-6] = pos0-1;
				(*inds)->pos[(*inds)->num-5] = pos1-1;
				(*inds)->pos[(*inds)->num-4] = pos2-1;
				(*inds)->pos[(*inds)->num-3] = pos2-1;
				(*inds)->pos[(*inds)->num-2] = pos3-1;
				(*inds)->pos[(*inds)->num-1] = pos0-1;
			}

			free(indices);
			indices = NULL;
		}

		printf("n");
	}

	for(i=0; i<n; i++) {
		free(lines[i]);
		lines[i] = NULL;
	}

	free(lines);
	lines = NULL;
}

I added printf() outputs for debugging purposes. This function will build the structs and set the pointers so that we can then work on this abstraction layer to build the information OpenGL needs to render the object.

To create the structs just place the following calls.

IndInfo* inds = NULL;
VertInfo* verts = NULL;

loadObjFile(&verts, &inds, "test.obj");

The neat thing about this current setup is that the index information can be passed directly to OpenGL as an element buffer.

// NOTE: allocate a GPU buffer for the element data
GLuint eleBuffer;
glGenBuffers(1, &eleBuffer);

// NOTE: bind to the element buffer so that we may send our data to the GPU
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eleBuffer);

// NOTE: send our element data to the GPU and set as STAIC
glBufferData(GL_ELEMENT_ARRAY_BUFFER, inds->num*sizeof(GLuint), inds->pos, GL_STATIC_DRAW);

On the other hand, the OpenGL vertex information is highly specific to the type of effects you plan to render with your objects. Here I’m just using the position and texture information but if you plan to add lighting or some other type of rendering effect you’ll have to customize the OpenGL vertex information to fit your needs.

GLfloat glVerts[5*verts->num];
memset(glVerts, 0x00, 5*verts->num*sizeof(GLfloat));

int tempInd = 0;
for(; tempInd<verts->num; tempInd++) {
	glVerts[5*tempInd+0] = verts->pos[3*tempInd+0];
	glVerts[5*tempInd+1] = verts->pos[3*tempInd+1];
	glVerts[5*tempInd+2] = verts->pos[3*tempInd+2];

	glVerts[5*tempInd+3] = verts->tex[2*tempInd+0];
	glVerts[5*tempInd+4] = verts->tex[2*tempInd+1];
}

// NOTE: allocate an array buffer on the GPU
GLuint verBuffer;
glGenBuffers(1, &verBuffer);

// NOTE: bind to the array buffer so that we may send our data to the GPU
glBindBuffer(GL_ARRAY_BUFFER, verBuffer);

// NOTE: send our vertex data to the GPU and set as STAIC
glBufferData(GL_ARRAY_BUFFER, 5*verts->num*sizeof(GLfloat), glVerts, GL_STATIC_DRAW);

And that is pretty much it! Now you can create complicated meshes using something like Anim8or or Blender and render them with your own OpenGL implementation.

SDL 2.0 Tutorial-03: Metasprites

So last week we were able to generate a collection of textures and draw them to the screen. In terms of memory this isn’t very efficient. This week we will be creating one texture from a meta SDL_Surface* . This will be my last part of this basic tutorial, if there are any topics you would like discussed please email me at “info@stephenmeier.net”. If you do occasionally read these posts please let me know which posts you find interesting!


/* Stephen's Tutorials (https://stephenmeier.net/)
gcc main.c -o game.exe -I./include -L./lib -lSDL2main -lSDL2
running on gcc 4.8.1, SDL 2.0.1
*/
//----------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>

#include "game.h"

//----------------------------------------------------------------------
int main(int argc, char* argv[]) {

	Game.init();

	int x = Game.screen.w/2-8, y = Game.screen.h/2-8;
	SDL_Rect rect = {0, 0, 8*2, 8*2};

	int i, j;
	int** grid = (int**) malloc(sizeof(int*)*2);
	for(j=0; j<2; j++) {
		grid[j] = (int*) malloc(sizeof(int)*2);
		for(i=0; i<2; i++) {
			grid[j][i] = 2;
		}
	}

	SDL_Texture* spr = Game.gfx.buildSpr(2, 2, grid);

	SDL_Event event;
	while(Game.running) {
		while(SDL_PollEvent(&event)) {
			switch(event.type) {
				case SDL_QUIT: {
					Game.running = SDL_FALSE;
				} break;
			}
		}

		SDL_RenderClear(Game.screen.renderer);

		rect.x = 0+x, rect.y = 0+y;
		Game.screen.drawSpr(spr, &rect);

		SDL_RenderPresent(Game.screen.renderer);
	}

	Game.gfx.destroySpr(spr);

	Game.quit();

	return 0;
}

// Stephen's Tutorials (https://stephenmeier.net/)
//----------------------------------------------------------------------
#ifndef _GAME_H_
#define _GAME_H_

//----------------------------------------------------------------------
#define SCREEN_w 640
#define SCREEN_H 480
#define SCREEN_NAME "Prototype"
#define SCREEN_SCALE 1
#define SDL_MAIN_HANDLED

//----------------------------------------------------------------------
#include "SDL2/SDL.h"

//----------------------------------------------------------------------
void game_init(void);
void game_quit(void);

SDL_Texture* gfx_buildSpr(int w, int h, int** grid);
void gfx_destroySpr(SDL_Texture* spr);

void screen_drawSpr(SDL_Texture* spr, SDL_Rect* rect);

struct {
	// define "attributes"
	SDL_bool running;

	struct {
		unsigned int w;
		unsigned int h;
		const char* name;
		SDL_Window* window;
		SDL_Renderer* renderer;
		void (*drawSpr)(SDL_Texture* spr, SDL_Rect* rect);
	} screen;

	struct {
		unsigned int n;
		SDL_Surface** spritesheet;
		SDL_Texture* (*buildSpr)(int w, int h, int** grid);
		void (*destroySpr)(SDL_Texture* spr);
	} gfx;

	// define "methods"
	void (*init)(void);
	void (*quit)(void);
} Game = {
	SDL_FALSE,
	{
		SCREEN_SCALE*SCREEN_w,
		SCREEN_SCALE*SCREEN_H,
		SCREEN_NAME,
		NULL, NULL,
		screen_drawSpr
	},
	{
		0,
		NULL,
		gfx_buildSpr,
		gfx_destroySpr
	},
	game_init,
	game_quit
};

//----------------------------------------------------------------------
void game_init(void) {
	if(SDL_Init(SDL_INIT_EVERYTHING)!=0) {
		printf("SDL error -> %sn", SDL_GetError());
		exit(1);
	}

	unsigned int w = Game.screen.w;
	unsigned int h = Game.screen.h;
	const char* name = Game.screen.name;

	Game.screen.window = SDL_CreateWindow(
		name,
		SDL_WINDOWPOS_CENTERED,
		SDL_WINDOWPOS_CENTERED,
		w, h, 0
	);

	Game.screen.renderer = SDL_CreateRenderer(
		Game.screen.window, -1,
		SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC
	);

	SDL_Surface* surface = SDL_LoadBMP("spritesheet.bmp");
	int n = ((surface->w/8)*(surface->h/8)+1);

	Game.gfx.n = n;
	Game.gfx.spritesheet = (SDL_Surface**) malloc(sizeof(SDL_Surface*)*n);

	int i, x, y;
	SDL_Rect rect = {0, 0, 8, 8};
	for(i=0; i<n; i++) {
		Game.gfx.spritesheet[i] = SDL_CreateRGBSurface(0, 8, 8, 24, 0x00, 0x00, 0x00, 0x00);
		SDL_SetColorKey(Game.gfx.spritesheet[i], 1, 0xFF00FF);
		SDL_FillRect(Game.gfx.spritesheet[i], 0, 0xFF00FF);
		if(i!=0) {
			x = (i-1)%(surface->w/8);
			y = (i-x)/(surface->w/8);
			rect.x = x*8;
			rect.y = y*8;
			SDL_BlitSurface(surface, &rect, Game.gfx.spritesheet[i], NULL);
		}
	}

	SDL_FreeSurface(surface);

	Game.running = SDL_TRUE;
}

//----------------------------------------------------------------------
void game_quit(void) {
	int i;
	for(i=0; i<Game.gfx.n; i++)
		SDL_FreeSurface(Game.gfx.spritesheet[i]);
	free(Game.gfx.spritesheet);
	Game.gfx.spritesheet = NULL;

	SDL_DestroyRenderer(Game.screen.renderer);
	Game.screen.renderer = NULL;

	SDL_DestroyWindow(Game.screen.window);
	Game.screen.window = NULL;

	SDL_Quit();
}

//----------------------------------------------------------------------
SDL_Texture* gfx_buildSpr(int w, int h, int** grid) {
	SDL_Surface* surface = SDL_CreateRGBSurface(0, 8*w, 8*h, 24, 0x00, 0x00, 0x00, 0x00);
	SDL_SetColorKey(surface, 1, 0xFF00FF);
	SDL_FillRect(surface, 0, 0xFF00FF);

	int i, j;
	SDL_Rect rect = {0, 0, 8, 8};
	for(j=0; j<h; j++) {
		for(i=0; i<w; i++) {
			rect.x = i*8;
			rect.y = j*8;
			SDL_BlitSurface(Game.gfx.spritesheet[grid[j][i]], NULL, surface, &rect);
		}
	}

	SDL_Texture* tex = SDL_CreateTextureFromSurface(Game.screen.renderer, surface);
	SDL_FreeSurface(surface);

	return tex;
}

//----------------------------------------------------------------------
void gfx_destroySpr(SDL_Texture* spr) {
	SDL_DestroyTexture(spr);
}

//----------------------------------------------------------------------
void screen_drawSpr(SDL_Texture* spr, SDL_Rect* rect) {
	SDL_RenderCopy(Game.screen.renderer, spr, NULL, rect);
}

#endif

There are many ways to structure game logic, and for a simple 2D game being written in C it comes down to mostly personal preference.

SDL 2.0 Tutorial-02: Almighty spritesheet

Last week we were able to create a window and renderer. This week we are going to read in a spritesheet image, separate it into individual 8×8 sprites and draw them to the screen.

First thing we need to do is read in the spritesheet. Here I’m using strict SDL2 so we’ll use the .bmp format with an alpha color key. Setting an alpha color key allows us to use an RGB value which acts as an alpha channel. When SDL looks over the image pixels and finds our RGB key it will replace the color with a transparent pixel. This saves on memory because instead of writing pixels in a 32-bit RGBA value we can use a 24-bit RGB value. This is only necessary if you are restricted to just SDL, ideally you would use the SDL2_image package. You can find out more about SDL2_image here.

Alright, let’s start by allocating memory for our “gfx” (graphics) module.

struct {
	// define "attributes"
	SDL_bool running;

	struct {
		unsigned int w;
		unsigned int h;
		const char* name;
		SDL_Window* window;
		SDL_Renderer* renderer;
	} screen;

	struct {
		unsigned int n;
		SDL_Surface** spritesheet;
	} gfx;

	// define "methods"
	void (*init)(void);
	void (*quit)(void);
} Game = {
	SDL_FALSE,
	{
		SCREEN_SCALE*SCREEN_w,
		SCREEN_SCALE*SCREEN_H,
		SCREEN_NAME,
		NULL, NULL
	},
	{0, NULL},
	game_init,
	game_quit
};


This takes our original Game “soliton” and adds a “gfx” module. The graphics module contains unsigned int n;  which is the number of 8×8 sprites found within our .bmp spritesheet, and SDL_Surface** spritesheet; which is an array of SDL surfaces. Each 8×8 sprite will receive its own SDL surface. Later we will arrange these surfaces into textures to be drawn with out renderer.

Next we will read in “spritesheet.bmp”, set the color key to 0xFF00FF and populate the spritesheet array with surfaces. This code should be placed within the void game_init(void); function.

SDL_Surface* surface = SDL_LoadBMP("spritesheet.bmp");
int n = ((surface->w/8)*(surface->h/8)+1);

Game.gfx.n = n;
Game.gfx.spritesheet = (SDL_Surface**) malloc(sizeof(SDL_Surface*)*n);

int i, x, y;
SDL_Rect rect = {0, 0, 8, 8};
for(i=0; i<n; i++) {
	Game.gfx.spritesheet[i] = SDL_CreateRGBSurface(0, 8, 8, 24, 0x00, 0x00, 0x00, 0x00);
	SDL_SetColorKey(Game.gfx.spritesheet[i], 1, 0xFF00FF);
	SDL_FillRect(Game.gfx.spritesheet[i], 0, 0xFF00FF);
	if(i!=0) {
		x = (i-1)%(surface->w/8);
		y = (i-x)/(surface->w/8);
		rect.x = x*8;
		rect.y = y*8;
		SDL_BlitSurface(surface, &rect, Game.gfx.spritesheet[i], NULL);
	}
}

SDL_FreeSurface(surface);

This will give us an array of sprite surfaces with the first sprite (spritesheet[0] ) being a completely transparent surface. This is done on purpose so that the surface array an easily be integrated with the Tiled map editor, which we will discuss in future tutorials.

Next we will move to our int main(int argc, char* argv[]); function where we will create textures for the sprites we wish to render. Ideally, the textures should be allocated for each room/level during the loading screen. This works well for a 2D game but isn’t required as most computers can handle the dynamic allocation without much problem. After modifying the code we are left with the following. If you would like to use the same spritesheet as I’m using here you can get it from here.

int main(int argc, char* argv[]) {

	Game.init();

	int x = Game.screen.w/2-8, y = Game.screen.h/2-8;
	
	SDL_Rect rect = {0, 0, 8*2, 8*2};
	SDL_Texture* texture1 = SDL_CreateTextureFromSurface(Game.screen.renderer, Game.gfx.spritesheet[17]);
	SDL_Texture* texture2 = SDL_CreateTextureFromSurface(Game.screen.renderer, Game.gfx.spritesheet[18]);
	SDL_Texture* texture3 = SDL_CreateTextureFromSurface(Game.screen.renderer, Game.gfx.spritesheet[29]);
	SDL_Texture* texture4 = SDL_CreateTextureFromSurface(Game.screen.renderer, Game.gfx.spritesheet[30]);

	SDL_Event event;
	while(Game.running) {
		while(SDL_PollEvent(&event)) {
			switch(event.type) {
				case SDL_QUIT: {
					Game.running = SDL_FALSE;
				} break;
			}
		}

		SDL_RenderClear(Game.screen.renderer);

		rect.x = 0+x, rect.y = 0+y;
		SDL_RenderCopy(Game.screen.renderer, texture1, NULL, &rect);

		rect.x = 8*2+x, rect.y = 0+y;
		SDL_RenderCopy(Game.screen.renderer, texture2, NULL, &rect);

		rect.x = 0+x, rect.y = 8*2+y;
		SDL_RenderCopy(Game.screen.renderer, texture3, NULL, &rect);

		rect.x = 8*2+x, rect.y = 8*2+y;
		SDL_RenderCopy(Game.screen.renderer, texture4, NULL, &rect);

		SDL_RenderPresent(Game.screen.renderer);
	}

	SDL_DestroyTexture(texture1);
	SDL_DestroyTexture(texture2);
	SDL_DestroyTexture(texture3);
	SDL_DestroyTexture(texture4);

	Game.quit();

	return 0;
}


This will draw a chest in the middle of the screen. Here we are creating a texture for each 8×8 sprite. This is highly inefficient, what we will learn to do later is create a surface with all the 8×8 sprites and then create a texture from this bigger surface. This will help a lot but for now we are fine to do this. The last step here is to clean up the sprite array.

void game_quit(void) {
	int i;
	for(i=0; i<Game.gfx.n; i++)
		SDL_FreeSurface(Game.gfx.spritesheet[i]);
	free(Game.gfx.spritesheet);
	Game.gfx.spritesheet = NULL;

	SDL_DestroyRenderer(Game.screen.renderer);
	Game.screen.renderer = NULL;

	SDL_DestroyWindow(Game.screen.window);
	Game.screen.window = NULL;

	SDL_Quit();
}

That’s it for now, at this point you should be able to play and experiment on your own, till next week. Good luck!

SDL 2.0 Tutorial-01: The game window

Last week we discussed how to setup a basic game loop. We were missing a few critical components, one being the window! This week we are going to learn how to create a basic window and renderer using SDL. SDL 2.0 works hard to get us the optimal display for the system we are working on. Most often that means OpenGL or DirectX, although, SDL provides a wrapper to a build-in renderer to make drawing easy – so we won’t be considered with what type of renderer we get. If you wish to work on a 3D game (or 2D with shaders/effects) it is possible to do with SDL but you’ll have to write a custom renderer which communicates with OpenGL/DirectX.

First thing we’ll want to do is take last week’s anonymous “Game” structure and add a module for our screen. We’ll only use one screen here so the module will adhere to the same anonymous struct style which we used for “Game”.

struct {
	// define "attributes"
	SDL_bool running;
	struct {
		unsigned int w;
		unsigned int h;
		const char* name;
		SDL_Window* window;
		SDL_Renderer* renderer;
	} screen;

	// define "methods"
	void (*init)(void);
	void (*quit)(void);
} Game = {
	SDL_FALSE,
	{
		SCREEN_SCALE*SCREEN_w,
		SCREEN_SCALE*SCREEN_H,
		SCREEN_NAME,
		NULL,
		NULL
	},
	game_init,
	game_quit
};

So now we have access to Game.screen who’s properties are w the width of the window, h the height of the window, name the string which will appear and the top of our window, window a pointer to our SDL window, and renderer a pointer to our SDL renderer. The width, height, scale, and name are specified as follows in the game header file.

#define SCREEN_w 640
#define SCREEN_H 480
#define SCREEN_SCALE 1
#define SCREEN_NAME "Prototype"

Defining our variables in this way allows for the w, h, scale, and name to be accessed from other points within our code base. Now we need to rewrite void game_init(void); and void game_quit(void); to get our screen to display and to clean up any memory left when we’re finished.

void game_init(void) {
	if(SDL_Init(SDL_INIT_EVERYTHING)!=0) {
		printf("SDL error -> %sn", SDL_GetError());
		exit(1);
	}

	unsigned int w = Game.screen.w;
	unsigned int h = Game.screen.h;
	const char* name = Game.screen.name;

	Game.screen.window = SDL_CreateWindow(
		name,
		SDL_WINDOWPOS_CENTERED,
		SDL_WINDOWPOS_CENTERED,
		w, h, 0
	);
	Game.screen.renderer = SDL_CreateRenderer(
		Game.screen.window, -1,
		SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC
	);

	Game.running = SDL_TRUE;
}

Here we make the necessary library calls to get a window and renderer. The SDL_WINDOWPOS_CENTERED flag will center our window when it pops up for the player. It is possible to setup a fullscreen window however if you are using pixel art this isn’t recommended for reasons that will become obvious once we write code for setting up our sprites. To destroy our renderer and window we’ll code void game_quit(void); the following way.

void game_quit(void) {
	SDL_DestroyRenderer(Game.screen.renderer);
	SDL_DestroyWindow(Game.screen.window);

	Game.screen.window = NULL;
	Game.screen.renderer = NULL;

	SDL_Quit();
}

At this point if you are encountering errors it is recommended to wrap the library function returns with if-statements to print any errors to stdout as we have done in the past. Now we need to modify our main game loop to provide continuous rendering as well as exiting functionality. It’s quite interesting to note that a window not polling for events (i.e. keypresses, mouse movements, etc.) will show as being non-responsive by the operating system. If on the other hand you are polling for events but aren’t listening for when the window is closed (i.e. red X at top right on windows) then the window will remain open and the only way to close it will require Ctrl+Alt+Delete. Let’s write a new game loop which polls for events and listens for when the window is closed.

int main(int argc, char* argv[]) {

	Game.init();

	SDL_Event event;
	while(Game.running) {
		while(SDL_PollEvent(&event)) {
			switch(event.type) {
				case SDL_QUIT: {
					Game.running = SDL_FALSE;
				} break;
			}
		}

		SDL_RenderClear(Game.screen.renderer);
		SDL_RenderPresent(Game.screen.renderer);
	}

	Game.quit();

	return 0;
}

Here we are simply polling for events and clearing/presenting an empty renderer. That’s all for this week! Next week we’ll look at how to read in a single image, split it into an array of sprites and display those sprites based on a gird/map. If you are getting the hang of C development I highly recommend picking up a GCW handheld – think of it as a hobby expense! Take care.