Developer's Guide


Table of contents:

1 · Introduction


RetroGear is a simple and generic 2D game engine, made for a fast and simple retro games realization, like those of the 80's.

A simple game engine, developed on the most common tecniques and conventions used by videogame programmers, as keeping an easy internal framework with external projects.
Developed in order to offer the highest semplicity and completeness possible, RetroGear offers a wide range of features, as well as a comptact and minimal system for the management of various aspects of games, thus granting also the quick developing of videgaming applications in really short time, in a standard, clear and simple way.

The project is constantly updated and improved, therefore may be subject to several variations, anyway you're invited to mess with the code and shaping the engine according to your needs, and if you want, share with everyone else, author included if possible.

2 · Project Structure

2.1 · To begin

The main file of the project is main.c, at its core are initialized the standard mechanisms of the sub-system of the game engine and the SDL libraries.


/**
 * Before freeing system resources, you have to close the main
 * program cycle
 **/
void quitGame()
{
	quit = 1;
}

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

    // SDL internal systems initialization
    if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) != 0)
    {
        fprintf(stderr, "Can't initialize SDL: %s\n", SDL_GetError());
        exit(-1);
    }
    atexit(SDL_Quit);

    // Screen Initialization
    #ifdef DOUBLE_SCREEN

        double_screen = SDL_SetVideoMode(SCREEN_WIDTH*2, SCREEN_HEIGHT*2, 
                        0, SDL_HWSURFACE);
        if(double_screen == NULL)
        {
            fprintf(stderr, "Can't initialize SDL: %s\n", SDL_GetError());
            exit(-1);
        }
        
        screen = SDL_CreateRGBSurface(0,SCREEN_WIDTH,SCREEN_HEIGHT,32,0,0,0,0);
            
    #else

        screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, 0, SDL_HWSURFACE);
        if(screen == NULL)
        {
            fprintf(stderr, "Can't initialize SDL: %s\n", SDL_GetError());
            exit(-1);
        }

    #endif
	
    // Retrogear subsystems initialization
    init();

    // Parameters defined by the programmer
    mainInit();

    //Main game loop
    mainLoop();

    // Cleaning of allocated resources
    cleanUp();

    return 0;
}


In the init() function the RetroGear internal game sub-systems, and some other additional system values, necessary for the game engine proper behaviour, are initialized.

void init()
{
    //FPS per il gioco
    fps.frequency= 1000 / 100;

    quit = 0;
    Game.status = GAME;
    curr_menu = &main_menu;
	
    //Inizializzazione dei sottosistemi di gioco
    initFont();
    initAudio();
    initScore();
    initController();

    initTypewriter(&typewriter, FONT_W, (SCREEN_HEIGHT/2)+56, SCREEN_WIDTH-(FONT_W*3));
    initTransition(TILESIZE, transition_lines);

    setCurrentPlayer(&Player);
    initPlayer(curr_player);

    //Inizializzazione del livello
    initLevel();
    loadLevel(&level, "main");

    initCamera(&camera);
}


The extInit() function (extension init) is intended as a customizable extension of init(), to allow the programmers to customize any aspect of the game engine in its bootstrap, in a dedicated space.

void extInit()
{
	SDL_WM_SetCaption("RetroGame", "RetroGame");
	
	initMenu(menuptr, 1, 30, 10, "main", NULL, NULL);
	addMenuItem(menuptr, createItem(1,"New Game", white, doPreGame));
	addMenuItem(menuptr, createItem(5,"Quit", white, quitGame));
	alignMenuCenter(menuptr);
	alignMenuBottom(menuptr);
}


The quitGame() function does nothing more than inform the system that the application must be closed, by setting the game engine global flag quit to 1.
Any removals of dynamically allocated resources, will take place automatically in the function cleanUp(), just before the application closing.

The main_menu structure is an internal game engine structure, and provides a practical tool for the realization of menus for games, see the chapter 14 · Game menus for more information about


2.2 · Game states

mainLoop(), the game's main cycle, is the function that deals with the application and to handle the most common game states, generically.

Inside it, inputs are managed from peripherals (keyboard / gamepad) and game states are called up at regular intervals via an internal cycle.
The internal cycle keeps the application running at a constant speed on different power hardware. (See the chapter 8.7 · FPS management for further information.)
The main loop ends when the global flag quit is placed as 1.

The draw() function, similar to this, it deals with recalling the design functions suitable for the state of play of the moment.

void mainLoop()
{
	while(!quit)
	{
		keyboardInput();
		
		unsigned int maxl = 256;
		fps.now = SDL_GetTicks();
		fps.dtime += fps.now - fps.then;
		fps.then = fps.now;

		while (--maxl && fps.dtime >= LOGICMS)
		{
			switch(Game.status)
			{
				case MENU:
				 doTitleScreen();
				break;
				
				case PREGAME:
				 doPreGame();
				break;

				case GAME:
				 doGame();
				break;

				case LOST:
				 doLogic();
				break;

				case WIN:
				 doWin();
				break;

				case GAMEOVER:
				 doGameOver();
				break;

				case EDITOR:
				 doEditor();
				break;
			}

			fps.now = SDL_GetTicks();
			fps.dtime += fps.now - fps.then - LOGICMS;
			fps.then = fps.now;
		}
		
		// Gestisce gli stati di disegno sullo schermo
		draw();
		
		SDL_Delay(1);
	 }
}
Each status is associated with a particular function, conventionally named in the doStatus form for the logic, drawStatus for the rendering.
Each status function includes the logic desired by the programmer for that specific moment of play.
Everything is immediately presented with a minimal and generic logic, which for most of the cases finds its utility in any type of game you want to create.
The programmer is very free to use, change or simply expand what is already there in it.

In case of need for expansion, maintain and adopt the naming conventions already in use in the engine.


The game states currently managed are the following:

To make it easier for the programmer, some game status have a little control in them.
    if(Game.status!=STATUS)
    {
        setGameState(STATUS);
    }

This control, automatically sets the game status, based upon the function in which it is.
For example, inside doTitleScreen() function, the MENU status will be set, or inside doGame() function the GAME sttus and so on...

Despite all, there may be cases where the programmer may not want to force the game status value, in those cases, we can safely omitting this control, but we must care to properly set the new game status by hand, possibly using the setGameState (STATUS) function, to set the right logic.

3 · Graphics handling

In parallel with the management of the game states, rendering events related to the current state of the game are managed in a similar manner.

Using the draw() function, present in the draw.c file and called automatically in the main game loop, the programmer can manage what to draw during various game states.
void draw()
{
	if(transition.flag_active)
	{
		doTransition();
	}
	else
	{
		clearScreen();
	
		switch(Game.status)
		{
			case MENU:
				drawTitle();
				break;
			case PREGAME:
				drawPreGame();
				break;
			case GAME:
				drawGame();
				break;
			case LOST:
				drawGame();
				break;
			case WIN:
				drawWin();
				break;
			case GAMEOVER:
				drawGameOver();
				break;
		}
	}
	
	callback_DrawSystemMessages();

	//Aggiorna lo schermo
	#ifdef DOUBLE_SCREEN
		SDL_SoftStretch(screen, NULL, double_screen, NULL);
		SDL_Flip(double_screen);
	#else
		SDL_Flip(screen);
	#endif
}
Exactly as in the main game loop, the appropriate function to the current state of play will be launched, conventionally declared as drawStatus.

The programmer will be able to decide arbitrarily which logic to perform in the various functions made available, using both library functions and implementing their own.

As already mentioned in the chapter 2.1 · To begin, the system provides a software based screen stretching function, therefore this case is also managed in this function, calling up the SDL_SoftStretch() function if the screen has been requested, otherwise a simple call to the SDL_Flip() function on the main video surface. The draw.c file also includes an additional private callback function, callback_DrawSystemMessages().
void callback_DrawSystemMessages()
{
	if(sys_message != NULL)
	{
		drawString(screen, 8, SCREEN_HEIGHT-16, sys_message, red, 0);

		if(getSeconds(sys_timer.start_time) > 3)
		{
			strcpy(sys_message, "");
			sys_timer.start_time = 0;
		}
	}
}
This function shows any system notifications messages, of maximum one line at a time for 4 seconds, at the bottom of the game window.

The engine level editor uses this function, to notify the player of unleashed events, such as loading of a level file, saving a level or other.

3.1 · Draw simple graphics

The engine provides library functions for drawing basic geometric shapes, such as lines, circles and squares, as well as low-level management of individual pixels on drawing surfaces.

In the gfx library there are functions for drawing and general graphic manipulation, and a minimal color palette, both in rgb and hexadecimal format.
SDL_Surface *screen, *tile_sheet, *alpha_sheet;

/** Colori RGB **/
static const SDL_Color white = {255, 255, 255};
static const SDL_Color black = {0, 0, 0};
static const SDL_Color cyan  = {0, 255, 255};
static const SDL_Color blue  = {0, 0, 255};
static const SDL_Color yellow  = {255, 255, 0};
static const SDL_Color purple = {255, 0, 255};
static const SDL_Color red  = {255, 0, 0};
static const SDL_Color green  = {0, 255, 0};
static const SDL_Color gray  = {192, 192, 192};

/** Colori Hex **/
#define RED 0xFF0000
#define BLUE 0x0000FF
#define GREY 0xC0C0C0
#define WHITE 0xFFFFFF
#define BLACK 0x000000
#define GREEN 0x008000
#define ORANGE 0xFF9D2E
#define PURPLE 0xFF00FF
#define YELLOW 0xFFFF00
#define COLORKEY 0x00FF00

#define SKYBLUE 0x8080FF

Uint32 get_pixel(SDL_Surface *surface, int x, int y);
void put_pixel(SDL_Surface *_ima, int x, int y, Uint32 pixel);
void replaceColor (SDL_Surface * src, Uint32 target, Uint32 replacement);
void drawFillRect(int x, int y, int w, int h, int color);
void drawRect(int x, int y, unsigned int w, unsigned int h, int color);
void drawGui(int x, int y, unsigned int w, unsigned int h, int bg, SDL_Color color);
SDL_Surface * loadImage(char *file, Uint32 key);


The present functions have the following utilities:
The declared colors as well as the functions are used by numerous other design functions inside the RetroGear system, we do not recommend elimination or alteration to avoid system errors.


3.2 · Sprites usage

RetroGear provides a generic structure for representing sprites, and the related animation system.

    typedef struct _Sprite {
        SDL_Surface *surface;
        int x, y;
        int w, h;
        int index;
        float animation_timer;
        float animation_speed;
    } Sprite;
The Sprite structure can be used for the free representation of graphic contents within the playing field and for the representation of game entities.
The structure is explained below.

	SDL_Rect dest;
	dest.x = 20;
	dest.y = 50;
	dest.w = 16;
	dest.h = 16;

	getSprite(skel_spr, 0, dest.w, dest.h, &dest);
Each SDL_Surface type structure must be correctly evaluated with the content of an image file BMP, which can be executed using the loadImage() function.
skel_spr=loadImage("data/skel.bmp", 0x00FF00);
As the first argument it accepts the relative path of the image file, with respect to the game executable, and as second it accepts the parameter the transparency color for the image expressed in hexadecimal.

As far as the dynamic design of animated entities is concerned, the internal function drawEntity(), called automatically by the internal mechanisms of the game engine for each single entity present in the list with a simple cycle while within the drawGame() function.

This function accepts a pointer to a Entity structure, from which it retrieves the associated sprite and draws the current frame.

4 · Game's entities


Every interactive object in the game is called entity, and its represented by the Entity structure which provides everything needed to manage and represent the entity itself within the program.

We can divide the entity into 4 groups of variables.
The first represents the real object, with some main properties such as the coordinates in the playing field, relative height and width, directions and previous coordinates of the last position, as well as additional variables such as score, number of lives, type, and internal timers.

The second group, of which the member sprite is part, provides a structure for representation of the entity on the screen, as a static or multiple sequence of images (See the chapter concerning the sprite).

The third group, "physical", is represented by additional variables, used for the implementation of physics on game entities, such as gravity and inertia for example.

The last group has utility variables for the game engine, such as a function pointer, "update", for calling the entity's update function, and a pointer to an "Entity" structure to implement a linked list.

4.1 · Game's entities management

The game entities, by default, are managed in a completely dynamic manner, they are automatically allocated at level file loading, or simply on explicit request of the programmer through the use of function createEntity().

This function accepts the main basic parameters required for a game entity to be usable correctly, including:

All entities created using this function will be allocated in memory dynamically, and managed via an internal linked list.
This list is handled by the pointers *headList and *tailList, which respectively are the first (head) and the last (tail) of the list.
These pointers are automatically initialized by the createEntity() function, that will check the content of the list and set them.
Even if the programmer is not very familiar with the Linked List, the system automatically manages these dynamic lists, as well as cleaning them.

Each entity on the list will be updated through the doEntities() function, which will the call the entity's update() callback function if its internal status is "ACTIVE", or it just will destroy and free the memory if its status is "DESTROY".

In case of loading a level file, the entities allocation will be performed automatically using the createEntityFromMap() function, called by the loadLevel() function.
For more information about, see the specific chapter.

4.2 · Definition of an entity

Each entity can present their own set of behaviors and logics, and RetroGear adopts a convention for the manage the most varied types of entities, based on the unique declaration of each of them.

A convetional code base, is adopted for the management of the entities and their possible status, along with their personal logics and data structures like sprites, sizes, speeds and so on.

To define an entity, we need to create their own source and header files, like this Skeleton example, Skel for short:

#ifndef _SKEL_H
#define _SKEL_H

#define SKEL_SPRITE_W 16
#define SKEL_SPRITE_H 16

#define SKEL_W 12
#define SKEL_H 12

#define MIN_H_SPEED -1.0f
#define MAX_H_SPEED 1.0f

#define MIN_V_SPEED -1.0f
#define MAX_V_SPEED 1.0f

SDL_Surface *skel_spr;

#include "entity.h"

void skel_create(int id, int x, int y);
void updateSkel(Entity *pobj);
void skel_clean();

#endif
    
Header will contain the global information and data structures that our entity needs, along with their handling functions, which by adopted convention are declared in the forms of [entityName]_create/clean and update[EntityName].

The core source code of our entity will contain the entity own logics, check this example:
    #include "skel.h"

    //Possible obtainable score
    #define SCORE 100
    #define TYPE 3

    static void onAnimate(Entity *pobj);
    static void onCollision(Entity *pobj);
    static void onDestroy(Entity *pobj);

    void skel_create(int id, int x, int y)
    {
        float speed = 0.8f;
        float gravity = 0.05f;
        float animation_speed = 0.05f;
        int lives = 0;

        createEntity(ENEMY, x, y, SKEL_W, SKEL_H, lives,
                     speed, gravity, &updateSkel);

        //Set up the sprite
        int sprite_diff_w = (SKEL_SPRITE_W - SKEL_W) / 2;
        int sprite_diff_h = (SKEL_SPRITE_H - SKEL_H) -1;

        //EntityList_Tail è l'ultima entity creata
        if( !EntityList_Tail->sprite.surface )
        {
            initSprite(&EntityList_Tail->sprite,
                       EntityList_Tail->x + sprite_diff_w,
                       EntityList_Tail->y + sprite_diff_h,
                       SKEL_W, SKEL_H,
                       animation_speed,
                       "data/skel.bmp");
        }

        //Initial speed and directions of the entity
        EntityList_Tail->hspeed = 0;
        EntityList_Tail->vspeed = 1;
        
        EntityList_Tail->direction_x = -1;
        EntityList_Tail->direction_y = 0;
    }


    /**
     * Custom entity animator
     **/
    static void onAnimate(Entity *pobj)
    {
        pobj->sprite.animation_timer += pobj->sprite.animation_speed;

        if (pobj->sprite.animation_timer > 2)
        {
            pobj->sprite.animation_timer = 0;
        }

        pobj->sprite.index = (pobj->direction_x < 0 ? 0 : SPRITE_FRAMES)+
                              abs(pobj->sprite.animation_timer);
    }

    static void onCollision(Entity *pobj)
    {
        Entity *current = EntityList_Head;
        while(current!=NULL)
        {
            if(current!=pobj && current->active && 
               current->status!=KILL && current->type!=COLLECTABLE)
            {
                if(rectCollision(current->x, current->y,
                                 current->w, current->h, 
                                 (int)(pobj->x)+pobj->direction_x,
                                 pobj->y, pobj->w, pobj->h))
                {
                    pobj->direction *=-1;
                }
            }
            current=current->next;
        }
    }

    static void onDestroy(Entity *pobj)
    {
        if(getSeconds(pobj->timer[0]) >= 1)
        {
            pobj->status=DESTROY;
            pobj->visible=0;
        }
    }

    void updateSkel(Entity *pobj)
    {
        if(pobj->status==KILL)
        {
            //Indice dell'immagine dell'entità sconfitta nello sprite sheet
            pobj->frame_index = DIE;		
            onDestroy(pobj);
            return;
        }

        if(rectCollision(Player.x, Player.y, Player.w, Player.h,
                    pobj->x, pobj->y, pobj->w, pobj->h))
        {
            pobj->ystart=pobj->y;
            //Impostiamo lo status a KILL
            pobj->status=KILL;			
            pobj->direction=0;

            createScore(pobj->x+camera.offsetX, pobj->y, 0.4f);
            addScore(getScore(points_index));
            points_index++;
            
            //Salviamo il momento in cui la entity viene schiacciata
            pobj->timer[0]=fps.t;	
            return;
        }

        moveEntity_X(pobj);
        animateEntity(pobj);
        //onAnimate(pobj);  //Custom animations
        onCollision(pobj);
    }
    
By convention, the creation of each game entity must take place through the appropriate "constructor", named by the convention of [EntityName]_create, that should will have to deal to setup the entity resources correctly, such as it's sprites, sound and default values.

Each new entity will be automatically included in the RetroGear global list of entities, thanks to the createEntity() function, and it's update, managed internally by the engine, through a callback to the defined update[EntityName] function.
The update callback function can be filled with standard RetroGear functions, or custom defined as well.

Entity statuses have their own static function, defined by convention as on[EventName].

By convention, three default callback functions are defined for states: onAnimate,onCollision,onDestroy.

You can use onAnimate to write your own animations logics, onCollision to perform custom actions on entity collision as well, and onDestroy to perform some actions before the entity destructions, like add some points.

For more information on managing entity statuses, see chapter States of an entity

Regarding the destruction of entities, please refer to the chapter Removal of an entity

This basic example is the way to adopt to make new entities for your game, and every new entity must be also declared in the Makefile as well.

EXT_OBJ = skel.o

4.3 · Types of entities and states

Every entity can be associated within a type, for example Collectable, Enemy and so on, through the enumerative OBJECTS variable applied to type variable entity.
typedef enum
{
    PLAYER, COLLECTABLE, ENEMY, BULLET, WALL, OBSTACLE
} ENTITY_TYPE;
    


The use of enumeration allows a greater clear code, where an entity creation is intended to specify its nature within the game and to manage particular cases such as in the previous example.

    createEntity(ENEMY, x, y, SKEL_W, SKEL_H, lives,
                 speed, gravity, &updateSkel);
    [...]

    if(current!=pobj && current->active
       && current->status!=KILL && current->type!=COLLECTABLE)
    


The ENTITY_TYPE enumerative variable can be used can be used by creating the entity and checking for collisions with other entities. Here, for example, all collisions are ignored with any COLLECTABLE type objects, or objects like Power-Up, Bonus etc, do not affect the movement of entities.

Entities use also the status variable, as we have seen before.
This variable is intended as a status index for the entity's current activities, it considers what the entity is currently doing.

At the expense of the programmer, it would be a good practice to assign, according to the logic defined, at least one entity status among those provided by the entity.h file.

    typedef enum
    {
        MOVE, ACTION, JUMP, FALL, CLIMB, STAND, BLINK, KILL, DESTROY
    } ENTITY_STATUS;
    

For a moving entity, we could use for example the MOVE value, JUMP for jumping, KILL for a defeat as we have seen before, and DESTROY for one to be destroyed (strictly).

4.4 · Animate entities

Per convenzione, gli sprite delle entity, adottano uno standard di rappresentazione basato su di una mappa fotogrammi, come la seguente:



Ogni posizione numerata dello sprite sheet, corrisponde ad una determinata azione, o fotogramma dell'azione dell'entità.
Queste azioni sono indicizzate tramite enumerazione nel file entity.h, in maniera standard ed "universale", cercando di soddisfare le più svariate necessità.
enum
{
	//Left
	STANDLEFT = 0,
	WALKLEFT1 = 1,
	WALKLEFT2 = 2,
	WALKLEFT3 = 3,
	RETURN1 = 4,
	JUMPLEFT = 5,

	//Right
	STANDRIGHT = 6,
	WALKRIGHT1 = 7,
	WALKRIGHT2 = 8,
	WALKRIGHT3 = 9,
	RETURN2 = 10,
	JUMPRIGHT = 11,

	//Down
	STANDDOWN = 12,
	WALKDOWN1 = 13,
	WALKDOWN2 = 14,
	WALKDOWN3 = 15,
	RETURN3 = 16,

	//Up
	STANDUP = 18,
	WALKUP1 = 19,
	WALKUP2 = 20,
	WALKUP3 = 21,
	RETURN4 = 22,

	DIE = 17
} spriteSheet;
Nel caso in cui si voglia animare una qualche entità aderendo allo standard interno di RetroGear, ci si può affidare alla funzione interna animateEntity(), la quale provvederà a gestire la struttura RG_Sprite interna all'entità, calcolando il fotogramma di animazione corretto anche sulla base dello stato e movimento dell'oggetto.
Pensata per essere utile e funzionale in svariate tipologie di gioco, come ad esempio platform game o rpg, senza alcun intervento da parte del programmatore
La funzione attualmente non gestisce l'animazione per tutti i tipi di azione di gioco

Nel caso la funzione animateEntity() non soddisfi le vostre esigenze, si potrà in ogni caso scrivere una funzione di animazione propria all'interno della definizione dell'entità di gioco., l'uso di animateEntity() non è tassativo.

Per convenzione, si consiglia di utilizzare la funzione privata static void onAnimate(Entity *pobj), in cui il programmatore potrà definire nella maniera che preferisce la gestione dei singoli frame di animazione dell'entità.

static void onAnimate(Entity *pobj)
{
    pobj->sprite.animation_timer += pobj->sprite.animation_speed;

    if (pobj->sprite.animation_timer > 2)
    {
        pobj->sprite.animation_timer = 0;
    }

    pobj->sprite.index = (pobj->direction_x < 0 ? 0 : SPRITE_FRAMES)+
                          abs(pobj->sprite.animation_timer);
}

4.5 · Rimozione di una entità

Per rimuovere un'entità dal gioco oltre che dalla memoria, per convenzione adottata dal motore e sicurezza, ne si imposta lo status su DESTROY, ed il sistema provvederà in automatico a rimuoverla.

4.6 · L'entità Player

Nel motore di gioco è a disposizione sin da subito una struttura di tipo Entity per la rappresentazione del giocatore, la struttura Player.

Questa struttura è definita staticamente nel file player.h, dislocata dalla lista globale delle entità e gestita tramite apposita funzione di aggiornamento, updatePlayer().
Il suo identificativo numerico all'interno delle strutture di livello è il numero 2.

L'oggetto Player è definito in maniera analoga a quello delle entity mostrato nei capitoli precedenti, salvo l'assenza di un costruttore, e la presenza di funzionalità aggiuntive per la gestione dei comportamenti del giocatore.
Nel caso in cui sia necessario implementare logiche supplementari o modificare quelle pre-esistenti, il programmatore è invitato a farlo direttamente in questo file, potendo anche utilizzare le funzioni generiche della liberia entity.c all'occorrenza.

Per convenzione, l'accesso all'oggetto Player all'interno del motore di gioco, avviene tramite il puntatore curr_player, di cui si consiglia l'uso preferibilmente all'accesso diretto alla struttura Player.

//Funzioni private
void playerAction();
void movePlayerStatic();
void movePlayerDynamic();

/**
 * Reimposta il giocatore ai valori di default
 * 
 * @param Entity *player
 * 		Puntatore alla struttura del giocatore
 **/
void initPlayer(Entity *player)
{
    player->type = PLAYER;
    player->visible = 1;
    player->flag_active = 1;
    player->w= PLAYER_W;
    player->h= PLAYER_H;
    player->lives = 3;
    Player.speed = 0.05f;

    //Differenza tra dimensioni del giocatore e sprite
    int sprite_diff_w = (PLAYER_SPRITE_W - PLAYER_W) / 2;
    int sprite_diff_h = (PLAYER_SPRITE_H - PLAYER_H) -1;

	if( !player->sprite.surface )
	{
        initSprite(&player->sprite, player->x-sprite_diff_w,
                   player->y-sprite_diff_h, 16, 16, 0.09f, "data/player.bmp");
	}

	player->hspeed = 0;
	player->vspeed = 0;
    
        Player.gravity=0.1f;
    
	player->direction_x = 1;
	player->direction_y = 0;
}

void setPlayerPosition(int x, int y)
{
	curr_player->x = x;
	curr_player->y = y;
	curr_player->xstart = x;
	curr_player->ystart = y;
}

void playerExtraLife()
{
	curr_player->lives++;
	playSound(extralife_snd);
}

void playerAction()
{
	curr_player->sprite.index = 0;	//Action sprite index
	//Action time
	//Action function	
}

/**
 * Principale funzione di aggiornamento per il giocatore
 **/
void updatePlayer()
{
    scrollCameraX(&camera, curr_player->x);
    scrollCameraY(&camera, curr_player->y);

    animateEntity(curr_player, 0);
    movePlayerStatic();
}

/**
 * Move the player with dynamic speed
 **/
void movePlayerDynamic()
{
	/**
     * Movimento orizontale
     **/

	if (curr_gamepad->button_Left)
	{
		Player.direction_x = -1;
        Player.status = MOVE;

        Player.hspeed += Player.speed * Player.direction_x;
	}
	
	[...]

	/**
     * Movimento verticale
     **/
	if (curr_gamepad->button_A==PRESSED)// && !lockjump)
	{
        curr_gamepad->button_A = LOCKED;
        if(!isEntityOnFloor(&Player))
        {
            return;
        }

		//if the player isn't jumping already
		Player.vspeed = -2.6f;		//jump!
	}

	[...]

    doEntityGravity(&Player);
}

/**
 * Move the player with dynamic speed
 **/
void movePlayerStatic()
{
    RG_Point *point = NULL;

    //Move only if there's no obstacles
    //TODO: To be tested
    point = tileCollision(&Player, Player.x+Player.hspeed, Player.y);
    if( point != NULL )
    {
        Player.x += Player.hspeed;
        Player.y += Player.vspeed;
    }
    
    //Se il giocatore non è allineato con la griglia
	if(!isInTile(Player.x,Player.y))
	 return;
    
	//horizontal
	if (curr_gamepad->button_Left)
	{
		Player.direction_x = -1;
        Player.direction_y = 0;
        
        Player.hspeed = -1.0f;
	}
	
	[...]
}

void drawPlayer()
{
    if(Game.status < GAME) return;

    int dest_x = (int)curr_player->x-2 - camera.offsetX;
    int dest_y = (int)curr_player->y+1 - camera.offsetY;

    drawSprite(&curr_player->sprite, dest_x, dest_y);
	
    [...]
}
Al posto del costruttore, qui troviamo la funzione initPlayer(Entity *player), richiamata da init() in fase di avvio del motore di gioco, in cui viene inizializzata la stuttura del giocatore con valori di deault, adatti a svariate tipologie di movimento e gioco.

Le funzioni di movimento sono gestite da due funzioni private interne al sorgente, movePlayerStatic() e movePlayerDynamic(), richiamate dalla funzione di aggiornamento della entity, updatePlayer().

La prima funzione fornisce un movimento "statico", in cui il giocatore si muoverà a velocità costante ed un tile alla volta nelle quattro direzioni, un movimento tipico dei giochi RPG, che definisco "a griglia"
Nel caso si voglia disabilitare il movimento "a griglia", si commentino le righe sottostanti:
	if(!isInTile(Player.x,Player.y))
        return;
Qui verrà aggiunta in futuro una macro per abilitare/disabilitare il comportamento in fase di compilazione
La seconda funzione, fornisce un movimento "dinamico", in cui il giocatore si muoverà a velocità incrementale orizontalmente, e verticalmente con gestione della gravità, sino ad un massimo definito con le costanti:
    #define MIN_H_SPEED -1.0f
    #define MAX_H_SPEED 1.0f

    #define MIN_V_SPEED -1.0f
    #define MAX_V_SPEED 1.0f
La funzione drawPlayer(), si occupa di disegnare il giocatore nella giusta posizione all'interno del campo di gioco, specie in presenza di scrolling attivo, e all'occorrenza di avere una rappresentazione di debug per esso.
La funzione è parte integrante del motore di gioco, e viene usata nel file draw.c.

Sono fornite anche una serie di funzioni standard parallele per la gestione degli stati d'azione (attacco/altro), e aumento delle vite del giocatore.

La funzione privata playerAction() è pensata per contenere tutte le logiche relative agli stati d'azione del giocatore, come ad esempio momenti di attacco, lancio di proiettili, uso della spada o altro, e relative logiche di animazione.

La funzione playerExtraLife() permette di avere un'interfaccia comune in tutto il motore di gioco, per l'incremento delle vite della entity giocatore attualmente in uso.

RetroGear tende a fornire un'insieme minimo di logica al programmatore, il quale potrà a seconda dei casi e delle necessità, plasmare a proprio piacere, tenendo però in conto che eventuali logiche pre impostate sono atte a mantenere continuativo e funzionale il lifecycle del gioco.

Può esservi la necessità di controllare alcuni status particolari del giocatore, come ad esempio in un platform game, l'avvenuta collisione con il suolo.
In questi casi, invece di agire sulla struttura Entity, appesantendola con ulteriori variabili di nessuna utilità per tutti gli altri oggetti del gioco, tranne Player, possiamo ricorrere a variabili private all'interno del sorgente player.c ed usarle tranquillamente nella logica delle funzioni.

4.7 · Gestione multigiocatore

Nel caso si voglia implementare un sistema di multiplayer, in cui più giocatori si alternano uno alla volta nel completare i livelli di gioco, si potrà ridefinire la struttura Player nel file player.h, in un array di giocatori.
Entity Player[2];
Entity *curr_player;
Tramite l'uso del puntatore curr_player, si potranno gestire eventi e sistemi di gioco, senza dover riscrivere alcuna logica, potendo utilizzare una sola variabile per più entità di tipo giocatore.
Attualmente il sistema multiplayer è solo una bozza, non è ancora effettivo

5 · Gestione dei font

Per la rappresentazione dei font, RetroGear utilizza uno sprite sheet composto da un minimo di 4 righe ed un massimo di 32 colonne, dai caratteri di dimensione 8x8 pixel.



Il font fornito di default dal motore di gioco, è ispirato a quello del Nintendo NES, inserendo il minimo set di caratteri necessario al programmatore per poter scrivere messaggi alfanumerici e rappresentare alcuni simboli.

La definizione della grandezza e larghezza di ogni singolo carattere, viene specificata nel file font.h, tramite le costanti FONT_W e FONT_H, di default valorizzate entrambe ad 8.

L'inizializzazione del font, avviene nella funzione initFont(), dichiarata all'interno del file font.c, e richiamata in fase di avvio del motore da util().

5.1 · Using Fonts and writing

Per la scrittura di testi o singoli caratteri su schermo, sono fornite due funzioni di libreria, drawChar() e drawString()

La funzione drawChar(), è la principale funzione di disegno del testo.
Permette il disegno di un singolo carattere su schermo, impostandone anche colore e trasparenza di sfondo.

drawChar(int dest_x, int dest_y, int asciicode, SDL_Color color, int alpha);

La funzione drawString(), permette la rapprestazione di stringhe.
Essa si appoggia alla funzione drawChar(), richiamandola per ogni singolo carattere della stringa da disegnare e passandogli i parametri necessari.
Accetta i seguenti argomenti:

Alcuni esempi di utilizzo:

	//Esempio di cursore per menù di gioco
	drawChar(screen, menuptr->items[menuptr->curr_item].x-10, 
			 menuptr->items[menuptr->curr_item].y, '*', white, 1);

	//Esempio su come mostrare il numero di vite rimaste al giocatore
	sprintf(message,"%d", Player.lives);
	drawString(screen, 115, 117, message, white, 1);

	//Esempio di testo libero
	drawString(screen, 10, 20, "Hello World!", white, 1);

	

Entrambe le funzioni, fanno uso internamente delle costanti FONT_W e FONT_H per gestire la grandezza dei singoli caratteri.

5.2 · Using the Typewriter system

Il sistema Typewriter é un sottosistema del motore di gioco, che permette la stampa di testo a schermo, con un effetto macchina da scrivere.

6 · Sounds and Musics management

RetroGear utilizza la libreria SDL_mixer per fornire funzionalità audio di base, che comprendono l'esecuzione di semplici effetti sonori e musiche di sottofondo, oltre che la possibilità di eseguirli, interromperli e caricarli in memoria in qualsiasi momento.

L'inizializzazione del sistema sonoro avviene nella funzione initAudio(), all'interno del file sfx.c.
void initAudio()
{
	audio_rate = 22050;			//Frequenza di playback
	audio_format = AUDIO_S16; 	//Formato dell'audio
	audio_channels = 2;			//2 canali = stereo
	audio_buffers = 4096;		//Dimensione del buffer per i file sonori
	
	//Inizializzazione SDL_Mixer
	if(Mix_OpenAudio(audio_rate, audio_format, audio_channels, audio_buffers)) {
		printf("Unable to initialize audio: %s\n", Mix_GetError());
		exit(1);
	}

	//Caricamente effetti sonori
	collectable_snd = loadSound("snd/collectable.wav");
	stomp_snd = loadSound("snd/stomp.wav");
	jump_snd = loadSound("snd/jump.wav");
	action_snd = loadSound("snd/action.wav");
	extralife_snd = loadSound("snd/extra_life.wav");

	//Caricamento musiche
	title_music = loadMusic("snd/title_theme.wav");
	pregame_music = loadMusic("snd/pregame_theme.wav");
	game_music = loadMusic("snd/level_theme.wav");
	gameover_music = loadMusic("snd/gameover_theme.wav");
	goal_music = loadMusic("snd/goal.wav");
}
	

La funzione initAudio(), oltre che ad inizializzare la libreria SDL_Mixer, provvede anche al caricamento di effetti sonori standard, pensati per venire incontro alla possibili principali esigenze di un gioco. Queste variabili sono definite nel file sfx.h, di tipo Mix_Chunk per gli effetti sonori, e Mix_Music per le musiche di gioco.
I nomi utilizzati per le variabili sonore, adottano la convenzione azione_snd per gli effetti, e status_music per le musiche.

Per gli effetti sonori abbiamo le seguenti variabili standard:
Per le musiche abbiamo le seguenti variabili standard:

6.1 · Game states and sounds

Di default il motore di gioco, prevede l'esecuzione di ognuna di queste musiche nello stato di gioco idoneo, controllandone l'eventuale esecuzione tramite un semplice controllo del tipo:
	if(!isMusicPlaying())
	{
		playMusic(title_music, 0);
	}
	
Per motivi di logica e flusso del programma, l'esecuzione diretta delle musiche di gioco per alcuni status, in particolare GAME e LOST, viene relegata alla funzione doPregame(), che in questo caso funzionerà da sparti acque.
void doPreGame()
{
	if(Game.status!=PREGAME)
	{
		setGameState(PREGAME);
	}

	//Stop any music from the game
	if(isMusicPlaying())
	{
		pauseMusic();
	}

	if(getSeconds(timer.start_time) > 2)
	{
		//Reset generic timer
		timer.start_time = 0;
		//Let's play!
		setGameState(GAME);
		playMusic(game_music, 0);
		return;
	}
	
	if(Player.lives==0)
	{
		playMusic(gameover_music, 0);
		setGameState(GAMEOVER);
		return;
	}
}
	
Sezione in attesa di correzione

Qual'ora si decida di non volere alcuna musica in un determinato status di gioco, si può evitarne l'esecuzione omettendone il richiamo dalla funzione di status apposita nel file game.c.
Si consulti il capitolo 2.2 · Stati di gioco per maggiori informazioni.
Il sistema sonoro è incompleto, probabilmente in futuro verrà riscritto

6.2 · Load and play sound effects and music

Per il caricamento di effetti sonori e musiche, sono disponibili le funzioni di libreria loadSound() e loadMusic().
Entrambe le funzioni accettano come unico argomento una stringa, contenente il nome del file da caricare ed il suo percorso relativo.

my_snd = loadSound("snd/sound.wav");

my_music = loadMusic("snd/music.wav");

Una volta caricato l'effetto sonoro o musica desiderati, si può procedere alla loro esecuzione tramite una semplice chiamata alle funzioni di libreria playSound() e playMusic().
La funzione playSound() accetta in argomento un puntatore ad un'oggetto di tipo Mix_Chunk, mentre la funzione playMusic() accetta in argomento un puntatore ad un'oggetto di tipo Mix_Music, oltre che un valore intero di flag per gestire il numero di ripetizioni.
playSound(my_snd);

playMusic(my_music, 0);

Per quanto riguarda le musiche, vi è la possibilità anche di effettuare controlli sul loro stato di esecuzione, e all'occorrenza interromperlo, riprenderlo o terminarlo del tutto.
void pauseMusic();
void resumeMusic();
int isMusicPlaying();

6.3 · Cleaning allocated sound resources

Ogni effetto sonoro e musica caricata, viene allocato dinamicamente in memoria, urge quindi la necessità alla terminazione del programma di liberare anche queste risorse come accade per quelle grafiche.
Per ripulire il sistema dalle risorse sonore allocate e terminare correttamente il sistema sonoro della libreria SDL_Mixer, si ricorre alle funzioni destroySound(), destroMusic() e Mix_CloseAudio().

Il motore di gioco provvede in maniera automatica a deallocare tutti gli effetti sonori e musiche standard, all'interno della funzione cleanUp() nel file util.c.
	destroyMusic(title_music);
	destroyMusic(pregame_music);
	destroyMusic(game_music);
	destroyMusic(goal_music);
	
	destroySound(player_die_snd);

	//Free default sounds
	destroySound(collectable_snd);
	destroySound(stomp_snd);
	destroySound(bounce_snd);
	destroySound(jump_snd);
	destroySound(action_snd);

	Mix_CloseAudio();
	


In maniera analoga, il programmatore potrà liberare la memoria da risorse extra definite in un secondo momento nella funzione cleanUp(), che verrà richiamata in automatico al termine del programma.

7 · Input handling

RetroGear fornisce un sistema centralizzato per la gestione dell'input del giocatore, in simultanea sia da tastiera che da gamepad, tramite una struttura gamepad virtuale, accessibile da tutta l'applicazione.

Questa struttura è intermediaria per la gestione di eventi pressione/rilascio dei tasti sulle periferiche di input fisiche, con le librerie SDL e meccanismi interni per la gestione di eventi di input.

La struttura del gamepad virtuale si rif#acute; alla configurazione del classico gamepad del Nintendo NES.
La mappatura per la tastiera con i relativi valori di SDLK, é definita all'interno del file controls.h:
#define DEAD_ZONE 3200  

#define BUTTON_A SDLK_x
#define BUTTON_B SDLK_z

#define BUTTON_START SDLK_RETURN
#define BUTTON_SELECT SDLK_LSHIFT

typedef enum
{
    NOPRESS=0, PRESSED=1, LOCKED=-1
} INPUT_KEY_STATUS;

typedef struct _Gamepad {
	int button_A, button_B;
	int button_Start, button_Select;
	int button_Left, button_Right, button_Up, button_Down;	
} Gamepad;

//Gamepad virtuali
Gamepad gamepad; //[2];

//Gamepad virtuale corrente (Per la gestione di sistemi multiplayer)
Gamepad *curr_gamepad;

SDL_Joystick *joystick_ptr; // Joystick device pointer

SDL_Event input_event;
Uint8 *keystate; // keyboard state

void initController();
void inputHandler();
int konamiCode();
void cleanInput();
	
Di default i tasti associati alla tastiera sono Z e X per i tasti A e B, mentre Right Shift ed Invio sono associati rispettivamente ai tasti Select e Start del gamepad.
Per quanto riguarda il gamepad fisico, i tasti 1 e 2 sono associati ai tasti A e B, mentre 8 e 9 ai tasti Select e Start.

La gestione dell'input avviene nella funzione di sistema inputHandler(), richiamata nel ciclo principale di gioco, la quale provvederà a gestire l'input tramite la funzione più idonea per la periferica di provenienza.
Per la gestione degli input da tastiera vi sarà la funzione privata handle_keyboard(), per gli eventi gamepad handle_joystick(), per gli eventi del gamepad fisico, e handle_mouse() per gli eventi relativi al mouse.
Quest'ultimo avrà una struttura propria, Mouse, che ricalcherà esattamente la struttura di un classico mouse a due tasti, senza appoggiarsi alla struttura gamepad virtuale.

Ogni interazione con la struttura Gamepad deve avvenire tramite l'apposito puntatore curr_gamepad, pensato per aiutare il programmatore nella gestione di più periferiche di input, nel caso di giochi multiplayer ad esempio.

7.1 · Usage of the virtual gamepad

Per gestire l'input all'interno dell'applicativo, ci si affida all'apposito puntatore curr_gamepad, come segue:

#include "controls.h"

if (curr_gamepad->button_A)
{
    //Input continuo senza interruzioni
}
else
{
    //Input terminato
}

if (curr_gamepad->button_A)
{
    //Interrompiamo la ripetizione dell'input
    curr_gamepad->button_A = 0;
}
La struttura gamepad virtuale, mantiene uno stato di permanenza dell'input, che altrimenti per natura via del sistema interno alle librerie SDL, si perderebbe ad ogni ciclo del programma.
Volendo impedire la ripetizione dell'input all'interno del nostro programma, si potrà tranquillamente valorizzare a 0 il membro specifico della struttura gamepad virtuale, non appena questa risulti premuto, esattamente come fatto nel secondo if dell'esempio.

La struttura gamepad virtuale è utilizzata promiscuamente sia da tastiera che gamepad/joystick fisici, anche in simultanea.
Gli input da tastiera verranno gestiti in tempo reale con quelli di periferiche di gioco fisiche, quali per l'appunto gamepad/joystick.

Al momento non sono previsti meccanismi di mapping dinamico dell'input, ne meccanismi per il gioco in multiplayer in simultanea.
Il multiplayer attualmente prevede l'alternanza dei due giocatori, interfacciati con le stesse perifiche di input, configurate allo stesso modo.

7.2 · Definition of a cheat

Molti giochi prevedono la presenza di trucchi, o cheat in inglese, che permettono al giocatore di sbloccare extra, avere dei bonus e vantaggi di sorta.
Uno dei cheat più famosi nella storia dei videogiochi è sicuramente il Konami Code, presente in tantissimi giochi retro e non.
RetroGear ne fornisce una semplice implementazione da poter usare nei propri giochi, tramite la funzione konamiCode().

Grazie ad una variabile statica locale, index, la funzione terrà conto della sequenza dei tasti internamente, non richiedendo la delegazione della gestione esternamente.

Probabilmente questa funzione verrà riscritta o eliminata dal progetto

7.3 · Usage of the virtual mouse

La libreria per l'implementazione ed uso del mouse virtuale è mouse.h.
In questa libreria è dichiarata un'apposita struttura, atta a rappresentare un mouse virtuale dotato di soli due pulsanti, destro e sinistro.

Nelle variabili x e y della struttura, vengono salvate le coordinate attuali del puntatore, mentre nelle variabili leftButton e rightButton, le pressioni dei tasti destro e sinistro.
L'utilizzo del mouse virtuale, è identico a quello delle periferiche di input, e l'accesso alla struttura avviene direttamente.
if(Mouse.leftButton)
{
//Tasto sinistro premuto
}
La gestione del mouse al momento è molto semplicista e preliminare, in futuro potrebbero essere implementate e aggiunte funzionalità extra e supporto per la gestione del terzo tasto e della rotellina.

8 · Score's management

RetroGear fornisce al programmatore un sistema apposito per la gestione e rappresentazione dei punteggi, tra cui l'oggetto scoreType, uno sprite sheet per i punteggi in una sequenza standard, ed una variabile enumerativa per il supporto al programmatore in fase di sviluppo.

typedef struct _scoreType
{
  int xstart, ystart;   //Coordinate iniziali
  int speed;            //Velocità di movimento

  Sprite sprite;        //Sprite
  short int status;

  struct _scoreType *next;
} scoreType;

scoreType *scoreHead, *scoreTail;

enum { pts100,
       pts200,
       pts400,
       pts500,
       pts800,
       pts1000,
       pts2000,
       pts4000,
       pts5000,
       pts8000,
       pts1UP,
       pts2UP,
       pts3UP,
       pts4UP,
       pts5UP
    } SCORES;

Gli oggetti scoreType rappresentano graficamente il punteggio nel campo di gioco, si può ricorrere alla funzione void createScore, che come per le entità, provvederà ad allocare in memoria il nuovo oggetto e ad aggiungerlo in una lista apposita.

Il ciclo di vita degli oggetti è delegato alla funzione doScore(), richiamata nello status GAME del motore di gioco. (Si consulti il capitolo Stati di gioco per maggiori informazioni)
void createScore(int x, int y, int speed, int sprite_index);

//Esempio d'uso
createScore( ((int)pobj->x-camera.offsetX),
             ((int)pobj->y-camera.offsetY),
             1,
             pts100);
addScore( 100 );
La funzione addScore() incrementa il punteggio di gioco con il valore specificato.
La funzione createScore realizza un oggetto di tipo scoreType, assegnandone lo sprite sheet standard di default, mostrando solo il fotogramma specificat.

Per aiutare il programmatore nella localizzazione del corretto fotogramma nello sprite sheet dei punteggi, si potrà ricorrere alla variabile enumerativa SCORES, che fornirà valori parlanti per la sua localizzazione.

8.1 · Incremental points management

Molti giochi offrono la possibilità per il giocatore di incrementare il proprio punteggio di gioco al verificarsi di alcune situazioni ripetute, come ad esempio per un platform game il rimbalzare tra un nemico e l'altro senza toccare terra, oppure per uno shooter game la distruzione consecutiva di una serie di nemici.

Questi punteggi non sempre sono multipli di quelli precedenti, e quindi non sempre possono vantare una linearità nel calcolo, per questo motivo il motore di gioco mette a disposizione un pratico array con una sequenza standard di punteggio, e funzioni per la gestione automatica di questi casi speciali, basati sulla sequenza offerta dallo sprite sheet standard dei punteggi.

createScore( ((int)pobj->x-camera.offsetX),
             ((int)pobj->y-camera.offsetY),
             1,
             points_index);
addScore( getScore() );
La sequenza dei punteggi ottenibili è dichiarata nell'array privato points all'interno di score.c.

static int points[] = {100, 200, 400, 500, 800, 1000, 2000, 4000, 5000, 8000};

La funzione getScore(), provvede a ritornare il valore di punteggio attualmente puntato dall'indice globale points_index, oltre che incrementarlo sino alla soglia 8000 punti, oltre la quale vengono assegnate un numero di vite extra al giocatore, da un minimo di 1 ad un massimo di 5.
Raggiunto il numero massimo di vite extra, pts5UP, l'indice points_index non viene più incrementato, dovrà essere premura del programmatore, effettuare il reset manuale dell'indice definendo le proprie logiche.
int getScore()
{
    if(points_index>=pts1UP)
    {
        if(points_index>=pts5UP)
        {
            points_index = pts5UP;
        }

        // 1up...
        playerExtraLife(points_index);
    }
    else if(points_index<=pts8000)
    {
        //Per punteggi normali, ritorniamo il valore di punteggio
        return points[points_index++];
    }
	
	return 0;
}

9 · Level management

La gestione dei livelli di gioco, avviene tramite la libreria level, la quale fornisce un'apposita struttura per la gestione e rappresentazione dei livelli in genere, oltre che funzionalità per il caricamento/salvataggio di essi, in appositi file.

La struttura standard per la rappresentazione grafica del livello, è la struttura Level, formata da 300 colonne e 300 righe su 3 strati (layers), ognuno pensato per uno specifico utilizzo.
#define COLS 300
#define ROWS 300
#define LAYERS 3

#define SOLID_LAYER 0
#define BACKG_LAYER 1
#define ALPHA_LAYER 2

unsigned int level_index;   //Indice del livello corrente

typedef struct _Level
{
    char name[20];
    char description[20];
    char theme[5];
    char song_title[10];
    //~ char bkgd_image[100];
    int bkgd_red, bkgd_green, bkgd_blue;
    int map[LAYERS][ROWS][COLS];
    unsigned int cols, rows;
    unsigned int curr_layer;
    unsigned int num_layers;
    int flag_complete;	//Flag completamento livello
} Level;
La struttura di livello può essere popolata manualmente da codice, accedendo ai membri della struttura, o tramite la definizione di appositi file di livello.
Semplici file di testo con estensione .map, di default residenti e caricati nella cartella maps, che ogni progetto dovrà contenere.
level title
level description
tile_sheet
backgroun_music.wav
0,255,255
2
7,7
0,0,0,0,0,0,0,
0,2,0,0,0,0,0,
0,0,0,0,0,0,0,
0,0,0,0,0,0,0,
0,0,0,0,0,0,0,
0,0,0,0,0,3,0,
0,0,0,0,0,0,0,

2,2,2,2,2,2,2,
2,2,2,2,2,1,2,
2,2,1,1,2,1,2,
2,2,1,1,1,1,2,
2,1,1,1,1,1,2,
2,1,1,1,1,7,2,
2,2,2,2,2,2,2,
Ogni riga del file si interfaccia con un membro specifico della struttura del livello:

RigaMembro strutturaDescrizione
1char name[20]Titolo del livello
2char description[20]Descrizione del livello
3char theme[5]Tema grafico da utilizzare
4char song_title[10]Nome del file audio di sottofondo
5int bkgd_red, bkgd_green, bkgd_blueValore RGB del colore di sfondo
6unsigned int num_layersNumero di layer utilizzati dal livello
7unsigned int cols, rowsNumero di righe e colonne utilizzate dal livello (massimo 300x300)

Le sequenze numeriche separate da virgola (csv), rappresenteranno ognuna i layers del livello.
Per il primo gruppo di valori, ogni numero corrisponderà ad una entità, per il secondo e il terzo, ogni numero corrisponderà ad un fotogramma specifico nel tile sheet relativo al proprio strato.

9.2 · Caricamento di un livello da file

Per il caricamento di un livello da file, occorre salvarlo nella cartella maps, ed usare la funzione loadLevel(Level* plevel, char *filename), la quale accetta in argomento la struttura Level da popolare e il nome del file di livello, senza estensione.
    loadLevel(&level, "main");
Il motore di gioco in fase di inizializzazione e avvio, ricerca e carica di default il file main.map attraverso la funzione init().
Si consulti il capitolo 2.1 · Per cominciare, per maggiori informazioni.

La creazione di eventuali entity, verrà gestita dalla funzione createEntityFromMap(), richiamata automaticamente all'interno di loadLevel(), per ogni valore diverso da 0 presente nel SOLID_LAYER.

Si consulti il capitolo 4.2 · Definizione di una entità per maggiori informazioni.
void createEntityFromMap(int id, int x, int y)
{
	switch(id)
	{
		case PLAYER_ID:	//ID riservato all'oggetto Player
			setPlayerPosition(x, y);
		break;

		case 3:
			badguy_create(x, y);
		break;

		case 4:
			coin_create(x, y);
		break;
	}
}
La funzione createEntityFromMap() di default viene proposta con la gestione della sola entity Player, ogni altra entity dovrà essere definita manualmente dal programmatore, richiamando l'apposito costruttore.

9.3 · Disegno di un livello

Il disegno del livello è delegato alla funzione drawTileMap(Level *plevel), la quale si occuperà di rappresentare graficamente gli strati BACKG_LAYER e ALPHA_LAYER.

Questa funzione fa uso del sistema interno Camera, per disegnare solo la porzione attualmente visibile del livello.
In caso di scrolling attivo, la porzione è determinata dalla posizione del giocatore all'interno del livello, in alternativa sarà determinata dal numero massimo di tile visualizzabili nella risoluzione della finestra.
Si veda il capitolo Gestione delle visuali per maggiori informazioni.

Il programmatore tenga a mente, che anche giochi privi di scorrimento, necessitano la corretta inizializzazione del sottosistema interno Camera, all'interno della funzione init().
Si veda il capitolo 2.1 · Per cominciare per maggiori informazioni.

L'utilizzo del sottosistema interno Camera potrà essere reso opzionale

10 · Collision management

Per gestire l'interazione tra giocatore, entità di gioco e livelli, è a disposizione una libreria dedicata, collision.c.
La libreria presenta una funzionalità specifica per vari tipi di collisioni, tra cui:
In futuro potrebbero essere implementate altre tipologie di collisione

10.1 · Rect Collision

La rect collision, è una collisione basata sulla sovrapposizione di due aree rettangolari (rect) di dimensioni variabili, calcolando eventuali punti di intersezione tra le coordinate di essi.

Il calcolo viene effettuato dalla funzione rectCollision(), che accetta in argomento una coppia di coordinate e dimensioni:
if(rectCollision(Player.x, Player.y, Player.w, Player.h,
		 pobj->x, pobj->y, pobj->w, pobj->h))
{
	//Un qualche tipo di azione...
}
	
La funzione esegue un semplice controllo sulle coordinate dei rettangoli, nel caso in cui le coordinate rappresentino una intersezione tra i due rettangoli che rappresentano, si ha una collisione, come rappresentato nell'immagine sottostante.
In caso di collissione viene ritornato il valore 1, altrimenti 0.

10.2 · Tile Collision

La tile collision, è una collisione tra entità di gioco e tiles.
Essa si basa sul calcolo della distanza in pixel tra le coordinate di un'entità e la posizione dei tiles, considerate in pixel a loro volta.

La funzione tileCollision(), si occupa della gestione di questa tipologia di collisioni.
Essa accetta in argomento un puntatore ad entità e le coordinate presso cui controllare eventuali collisioni.



RG_Point *point = NULL;     //Coordinate della collisione

if(curr_player->hspeed<0)
{
    //Collide a sinistra?
    point = tileCollision(pobj, floorf(pobj->x+pobj->hspeed), pobj->y);
    if( point != NULL )
    {
        pobj->x= point->x+TILESIZE;      //Posizioniamo la entity accanto al tile
    }
    else
    {
        pobj->x += pobj->hspeed;         //Muoviamo la entity
    }
}
    //Collide a destra?
else if(pobj->hspeed>0)
{
    point = tileCollision(pobj, ceilf(pobj->x+pobj->hspeed), pobj->y);
    if( point != NULL )
    {
        pobj->x= point->x-pobj->w;
    }
    else
    {
        pobj->x += pobj->hspeed;
    }
}
	
Nell'esempio viene gestito il caso di movimento orizzontale di una entity, la quale potrà muoversi liberamente in caso di assenza di collisioni nella direzione di movimento, a velocità variabile, in caso contrario ritrovarsi bloccata davanti ad un ostacolo del livello.

Eventuali collisioni vengono controllate dal punto di partenza della entitá, sino al suo punto di arrivo, rappresentati in immagine dalla linea rossa.
Nel caso in cui vi sia tra il punto di partenza e il punto di arrivo, sia presente un ostacolo, l'entità verrà riposizionata il più vicino possibile al tile, evitando che l'ostacolo venga superato.



La posizione in pixel dei tiles di livello, viene calcolata automaticamente dalla funzione.
In caso di collisione, viene ritornata una struttura RG_Point, rappresentate le coordinate presso cui è stata rilevata la collisione, oppure null in caso di mancata collisione.

11 · Camera's management

La gestione dello scrolling nei livelli di gioco, è delegata alla variabile camera, di tipo RG_Rect, presente nella libreria camera.c.
Parte integrante del motore di gioco, è istanziata in maniera statica all'interno del motore di gioco, ed è utilizzata di default dalla funzione di disegno del livello.

#define CENTER_X ((SCREEN_WIDTH - TILESIZE) / 2)
#define CENTER_Y ((SCREEN_HEIGHT - TILESIZE) / 2)

RG_Rect camera;
Questa struttura tiene traccia dell'area attualmente visibile al giocatore nella finestra di gioco, permettendo la rappresentazione parziale del livello sullo schermo, limitandola alle sole parti specificate dal programmatore.
Queste coordinate, dette offset, possono essere impostate ai valori di posizione di un'entità di gioco, tipo Player ad esempio, o a valori interi di altra natura.

La variabile Camera è utilizzata anche nel caso in cui il gioco non presenti livelli scrollabili, mostrando al giocatore solo la porzione iniziale del livello, pari alla grandezza della finestra del programma.
Si veda il capitolo 10.1 · Disegno del livello per maggiori informazioni.



Lo scrolling del livello di gioco viene gestito dalle funzioni scrollCameraX(RG_Rect *pcam, int x) e scrollCameraY(RG_Rect *pcam, int y), che rispettivamente gestiranno in maniera indipendente lo scrolling orizzontale e verticale.
Entrambe le funzioni accettano come argomenti un puntatore ad una struttura di tipo RG_Rect (come la variabile camera fornita dal sistema ad esempio), ed un valore intero su cui basare lo spostamento della visuale.

Lo spostamento della visuale avviene al raggiungimento della metá dello schermo, definito dalle costanti CENTER_X e CENTER_Y, da parte del valore intero di coordinata specificato.
// Aggiornamento posizione Camera X/Y
pcam->x += (x - pcam->x - CENTER_X);
pcam->y += (y - pcam->y - CENTER_Y);

// Aggiornamento posizione Camera max X/Y
pcam->x = (TILESIZE*curr_level->cols - SCREEN_WIDTH);
pcam->y = (TILESIZE*curr_level->rows - SCREEN_HEIGHT);
I valori di offset saranno sempre aggiornati rispetto al valore di intero passato in argomento alla funzione di scrolling.
Il valore di offset di inizio sará pari alla posizione di scrolling, meno il valore di costante metá schermo.
Il valore di offset massimo sará pari alla lunghezza della mappa, meno la dimensione della finestra.

11.1 · Free scrolling

Lo scrolling di default è limitato alle dimensioni del livello, per tanto raggiungendo i limiti orizzontali e verticali di esso, non si avrá piú alcun spostamento nel suo disegno.
Tuttavia può esservi la necessità in alcuni casi di svincolarsi da questo limite, per tanto il sistema interno di Camera prevede la possibilità di eliminare questo vincolo semplicemente dichiarando la costante NO_SCROLL_BOUND nel file config.h.



11.2 · Lazy scrolling

É possibile ritardare lo scorrimento sul livello, tramite la dichiariazione della costante LAZY_SCROLL nel file config.h.
///Abilita lo scrolling "pigro"
#define LAZY_SCROLL
	
Questo effetto, ritarderá lo scorrimento del livello rispetto alle coordinate di riferimento per l'aggiornamento.

Attualmente l'implementazione dello scrolling "pigro" non è stata testata a sufficienza, non si hanno dati certi su eventuali esiti sul programma nel lungo termine.

11.3 · Monitoring of scrolling area

Il sottosistema Camera, fornisce anche funzionalità di "monitoraggio" dello schermo, permettendo tramite la funzione isInCamera(RG_rect *pcam, RG_rect area), di controllare la presenza di un qualche tipo di oggetto all'interno degli offset di camera.

Il controllo è basato su di una semplice Rect Collion tra la variabile camera ed una RG_rect, determinante l'area occupata da un elemento.
Da questa funzione dipendono le funzioni di libreria createEntity(), countEntityOnCamera(), presenti nel file entity.c.

12 · Timer management

La gestione del tempo all'interno di RetroGear, è vincolata al tempo di esecuzione del ciclo di gioco principale.

Sono a disposizione del programmatore funzionalità per il calcolo del tempo passato, rispetto ad un determinato momento.

Le funzioni get_time_elapsed(int time) e get_time_elapsed_ms(int time), calcolano e ritornano, rispettivamente in secondi e millisecondi, la differenza di tempo passato rispetto ad un determinato momento, passato in argomento.

pobj->timer[0] = fps.t;

if(get_time_elapsed(pobj->timer[0]) >= 5)
{
    //Sono passati 5 secondi
}

if(get_time_elapsed(pobj->timer[0]) >= 5000)
{
    //Sono passati 5 secondi
}
	
Il valore passato in argomento, sará il timestamp di un determinato momento all'interno del gioco, che potrà essere il tempo di clock calcolato dal ciclo di gioco (fps.t).

Il ciclo di gioco principale, cercando di mantenere l'esecuzione del programma costante, garantisce un livello di errore relativamente basso nel calcolo del tempo, anche su diversi hardware.

Le libreria SDL fornisce internamente dei timer specifici e più elaborati, per ogni eventuale necessitá consultate la documentazione ufficiale della libreria.

13 · Game events management

La gestione degli eventi di gioco in RetroGear, è gestita attraverso apposite strutture chiamate Event, le quali si occuperanno di fornire o registrare, informazioni utili alle logiche di gioco dinamiche.

typedef enum
{
    WARP_EVT, DIALOGUE_EVT, OBJECT_EVT, GENERIC_EVT
} EVENT_TYPE;

typedef struct _Event {
	char mapname[20];
	unsigned int evt_x, evt_y;
	char parameters[100];
	int flag_save;
	struct _Event *next;
} Event;

Event *eventsGeneric,   //Lista di eventi generica
      *eventsWarp;      //Lista di eventi warp
La struttura tipo di un evento di gioco, presenta le seguenti informazioni:
Ogni evento sarà identificato in maniera univoca dalle proprie coordinate, permettendo al programmatore di recuperare un evento specifico per la posizione attuale di un'entità o di livello.
Le informazioni in esso contenute, rappresentate dalla stringa dei parametri, potranno essere utilizzate dalle entità di gioco, per modificare le proprie logiche interne in maniera dinamica, gestire eventuali punti di transizione tra livelli, o come semplici registri di valori da mantenersi durante il gioco in maniera dinamica.

Tutte le liste degli eventi ad ogni caricamento di livello, saranno azzerate, tralasciando solo i singoli eventi impostati come permanenti. Ogni tipologia di evento, sarà incluso in una propria lista dedicata, rappresentata da apposito puntatore di tipo Event, tra cui *eventsGeneric ed *eventsWarp.

13.2 · Events type

Tra le tipologie di evento attualmente disponibili e definibili dal programmatore vi sono le seguenti:

TipologiaIdentificativoDescrizione
GenericGENERIC_EVTEventi generici, informazioni di qualsiasi natura non specifica.
WarpWARP_EVTEventi di transizione, informazioni specifiche per il passaggio da un livello ad un altro
DialogDIALOG_EVTEventi di dialogo, informazioni riguardo i dialoghi tra giocatore e entitÁ di gioco

Queste valori enumerati, saranno utilizzati internamente dal motore di gioco per il caricamento automatico degli eventi dai file appositi, nelle liste appropriate.
La gestione su liste separate, è pensata per limitare l'uso delle risorse e i tempi di lettura di liste molto lunghe.

Sono previste anche funzioni di gestione standard di alcune tipologie di eventi, come doWarp() e doDialog(), che implementeranno una logica standard, rispettivamente per la transizione da un livello ad un altro, e apertura di finestre di dialogo.

13.3 · Defining a generic event

Per definire un evento all'interno del gioco, si può ricorrere alla funzione addEvent(), la quale accetta in argomento i seguenti parametri:
Un esempio di utilizzo è il seguente:
void playerThink()
{
    int col, row;
    //Posizione attuale in tile
    col = pixelsToTiles( Player.x );
    row = pixelsToTiles( Player.y );

    //Controlliamo la presenza di un punto di transizione
    if(curr_level->map[SOLID_LAYER][row][col] == WARP)
    {
        //Richiediamo l'evento, se presente, il puntatore sarà diverso da NULL
        Event *warp = getEvent(eventsWarp, curr_level->name, col, row);
        
        if(warp)
        {
            unsigned int dest_x, dest_y;
            char dest_map[10];

            /**
             * Il parametro dell'evento è nel formato "char*,int,int"
             * La stringa "%[^,]" significa "Leggi sino a che non incontri una virgola"
             **/
            sscanf(warp->parameters, "%[^','],%d,%d", dest_map, &dest_x, &dest_y);

            //Eseguiamo azioni di routine e facciamo uso dei parametri recuperati
            cleanEntities();
            loadLevel(curr_level, dest_map);
            initCamera(&camera);
            setPlayerPosition(dest_x, dest_y);
        }
    }

    think = 1;
}
Nell'esempio sovrastante, si gestisce la situazione in cui il giocatore abbia raggiunto un punto di transizione, si recuperano dall'evento apposito, il nome della mappa di destinazione e le coordinate a cui si vuole che il giocatore sia piazzato nel nuovo livello.

In maniera analoga, si potrebbero registrare e tenere traccia di eventuali azioni compiute dal giocatore nei vari livelli, come ad esempio la raccolta di oggetti che non dovranno ripresentarsi al ritorno nel livello stesso, l'apertura di porte o passaggi che dovranno essere mantenuti durante l'avventura e via dicendo.
Sarà a discrezione del programmatore farne l'utilizzo più congeniale alle sue esigenze.

13.4 · Defining a warp event

Questo capitolo attualmente è una bozza in attesa di revisione

Gli eventi warp, sono utilizzati per il passaggio da un livello ad un altro.

Questo tipo di eventi viene caricato in un'apposita lista di tipo Event, nominata eventsWarp, al momento del caricamento del file di livello, da appositi file .evt.

Il formato del parametro per questi eventi, per convenzione dovrà essere nella forma nome mappa,x,y, per essere compatibile con la funzione di libreria dedicata all'evento.

Ogni evento presente in questa lista, terrá traccia dei punti di transizione da un livello ad un altro, fornendo il nome del livello da raggiungere (nome del file senza estensione) e relative coordinate a cui posizionare il giocatore nella nuova destinazione.

Esempio: 3,4,underground,1,2

Nell'esempio sovrastante, il punto di transizione si trova al tile riga 4, colonna 3 il giocatore sarà posizionato al tile riga 2, colonna 1 nella mappa di destinazione, chiamata underground.

I punti di warp all'interno dei livelli, saranno caratterizzati dal valore univoco e riservato, 3, definito dalla costante WARP_TILE, e posizionati sul layer SOLID del livello stesso.

Gli eventi di warp potranno essere gestiti in maniera standard tramite la funzione di libreria doWarp(), presente in level.c, la quale accetterà in argomento i parametri char *mapname, unsigned int col e unsigned int row, implementando una logica standard di transizione da un livello ad un altro, con tanto di effetti sonori e grafici.

La funzione doWarp(), una voltra trovato un evento valido in lista, si occupa di:
  1. Eseguire un effetto grafico di transizione ed un effetto sonoro, entrambi impostati di default
  2. Ripulire la lista delle entità allocate
  3. Caricare il nuovo file di livello
  4. Inizializzare l'oggetto Camera
  5. Impostare le coordinate del giocatore nel nuovo livello
La funzione doWarp() non viene richiamata in automatico dal sistema, dovrà essere premura del programmatore richiamarla qual'ora ne vorrà fare uso, in alternativa, si può scrivere una propria versione del gestore dell'evento come nell'esempio seguente:

void playerThink()
{
    int col, row;
    //Posizione attuale in tile
    col = pixelsToTiles( Player.x );
    row = pixelsToTiles( Player.y );

    //Controlliamo la presenza di un punto di transizione
    if(curr_level->map[SOLID_LAYER][row][col] == WARP)
    {
        //Richiediamo l'evento, se presente, il puntatore sarà diverso da NULL
        Event *warp = getEvent(eventsWarp, curr_level->name, col, row);
        
        if(warp)
        {
            unsigned int dest_x, dest_y;
            char dest_map[10];

            /**
             * Il parametro dell'evento è nel formato "char*,int,int"
             * La stringa "%[^,]" significa "Leggi sino a che non incontri una virgola"
             **/
            sscanf(warp->parameters, "%[^','],%d,%d", dest_map, &dest_x, &dest_y);

            //Eseguiamo azioni di routine e facciamo uso dei parametri recuperati
            cleanEntities();
            loadLevel(curr_level, dest_map);
            initCamera(&camera);
            setPlayerPosition(dest_x, dest_y);
        }
    }

    think = 1;
}
Nell'esempio sovrastante, si gestisce la situazione in cui il giocatore abbia raggiunto un punto di transizione, si recuperano dall'evento apposito il nome della mappa di destinazione e le coordinate a cui si vuole che il giocatore sia piazzato nel nuovo livello.

13.5 · Eventi di dialogo

Questo capitolo attualmente è una bozza in attesa di revisione

Gli eventi di tipo dialogo, sono utilizzati per la rappresentazione di testi o messaggi nel gioco.
A differenza delle altre tipologie di evento, il valore parameters qui rappresenterà interamente il testo parte del dialogo.
Si potrà ad esempio associare un messaggio al verificarsi di una determinata azione, al raggiungimento di una specifica posizione nella mappa o nell'interazione con personaggi non utilizzabili del gioco, come accade nei giochi di tipo RPG.

14 · Game menus

Una parte importante dell'interazione con un gioco, è rilegata a menú di sorta, specialmente nel caso di giochi RPG, in cui questa interazione è il cuore di quasi tutto l'intero gameplay.

RetroGear mette a disposizione del programmatore strutture ideonee alla rappresentazione di menú e relativi contenuti, oltre che funzionalità specifiche per la creazione, gestione e interazione con essi, tra cui:

I menú di gioco all'interno di RetroGear sono composti da due strutture, Menu che si occupa di definire e rappresentare il menú vero e proprio, ed Item che si occupa di rappresentare i contenuti del menú, ovvero le voci selezionabili.
typedef struct _Menu {
	int flag_active, flag_title, flag_border;
	int x, y;
	unsigned int w, h;
	char name[20];
	char cursor;
	int rows, cols;
	unsigned int page_start;
	int num_items, max_items;
	int curr_item;
	SDL_Color color;
	struct _Item *items;
	struct _Menu *previous, *next;
	void (*update)();
	void (*draw)();
} Menu;

typedef struct _Item
{
	int flag_used;
	int x, y;
	char name[15];
	//~ char *description;
	int	value;
	SDL_Color color;
	void (*func)();
	//Icon stuff
	//~ SDL_Surface *icon;
	//~ float frame_index, animation_speed;
} Item;

Menu main_menu, *curr_menu;
L'astrazione del menú su due strutture diverse, è pensata per permettere di avere maggiore flessibilità nella realizzazione dei menù di gioco, oltre che ad avere la possibilità di poter anche intercambiare i contenuti tra menù, usando una sola struttura di menù e più strutture Item, o di realizzare menù personalizzati sulla base delle proprie esigenze.

Supponendo che un qualsiasi gioco tipo, abbia bisogno di almeno un menù di gioco, magari nella schermata dei titoli, RetroGear fornisce fin da subito una variabile di tipo Menu chiamata main_menu e popolata di default nella funzione mainInit del file main.c.
Questo menù presenterà le sole voci New Game e Quit, impostate di default rispettivamente per avviare lo status GAME o terminare l'esecuzione del programma.

Per agevolare la gestione dei menù, specie di quello attualmente in uso, in qualsiasi punto del programma, è presente un pratico puntatore a strutture Menu chiamato curr_menu.
L'intento di questo puntatore è quello di agevolare il programmatore nel passaggio da un menù ad un altro, utilizzando solo una variabile comune ma valorizzata con indirizzi di menù diversi.
Il motore prenderà carico del passaggio da menù a menù, nel caso di menù collegati, valorizzando automaticamente questa variabile, ma il programmatore potrà tranquillamente valorizzare questa variabile in un qualunque momento sulla base di una propria logica.

La struttura Item è pensata per poter fornire in futuro anche la possibilità di mostrare icone (anche animate) e descrizioni per ogni singola voce di menú, ma attualmente le funzioni standard di gestione e disegno non implementano queste funzionalità, che saranno aggiunte in futuro.

15.1 · Tipologie di menú

I menú disponibili in RetroGear, sono pensati per soddisfare tutte le principali necessità dello sviluppatore nella realizzazione del proprio gioco.
Tra le tipologie di menú realizzabili in RetroGear troviamo:

15.2 · Struttura di un menú

La struttura per i menú di gioco è definita nel file menu.h e presenta le seguenti variabili:

15.3 · Struttura dei contenuti del menú (Item)

La struttura per i contenuti dei menú di gioco è definita nel file menu.h e presenta le seguenti variabili: