Fixed Rate Pig
时间:2006-05-26 来源:whtonline
------------------------------------------------------------
Fixed Rate Pig - a fixed logic frame rate demo
------------------------------------------------------------
* Copyright (C) 2004 David Olofson <[email protected]>
*
* This software is released under the terms of the GPL.
*
* Contact author for permission if you want to use this
* software, or work derived from it, under other terms.
*/ #include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include "engine.h"
/* Graphics defines */
#define SCREEN_W 800
#define SCREEN_H 600
#define TILE_W 32
#define TILE_H 32
#define MAP_W 25
#define MAP_H 17
#define FONT_SPACING 45
#define PIG_FRAMES 12 /* World/physics constants */
#define GRAV_ACC 4
#define JUMP_SPEED 28 /* Sprite collision groups */
#define GROUP_ENEMY 0x0001
#define GROUP_POWERUP 0x0002 typedef enum
{
POWER_LIFE,
POWER_BONUS1,
POWER_BONUS2
} POWERUPS;
typedef struct GAMESTATE
{
/* I/O */
PIG_engine *pe;
Uint8 *keys;
int nice;
int refresh_screen;
int jump; /* Sprites */
int lifepig;
int scorefont;
int glassfont;
int icons;
int stars;
int pigframes;
int evil;
int slime; /* Global game state */
int running;
int level;
int lives;
float lives_wobble;
float lives_wobble_time;
int score;
float score_wobble;
float score_wobble_time;
float dashboard_time;
int fun_count;
int enemycount;
int messages; /* Objects */
PIG_object *player; /* Statistics */
int logic_frames;
int rendered_frames;
} GAMESTATE;
static void add_life(GAMESTATE *gs);
static void remove_life(GAMESTATE *gs);
static void inc_score(GAMESTATE *gs, int v);
static void inc_score_nobonus(GAMESTATE *gs, int v);
static PIG_object *new_player(GAMESTATE *gs);
static void message(GAMESTATE *gs, const char *s);
static PIG_object *new_powerup(GAMESTATE *gs,
int x, int y, int speed, POWERUPS type);
static PIG_object *new_star(GAMESTATE *gs, int x, int y, int vx, int vy);
static PIG_object *new_evil(GAMESTATE *gs,
int x, int y, int speed);
static PIG_object *new_slime(GAMESTATE *gs,
int x, int y, int speed);
/*----------------------------------------------------------
Init, load stuff etc
----------------------------------------------------------*/ static int load_level(GAMESTATE *gs, int map)
{
const char *m;
const char *k;
if(map > 10)
map = 3;
gs->level = map;
pig_object_close_all(gs->pe);
gs->enemycount = 0;
gs->messages = 0;
switch(map)
{
case 1:
case 2:
case 3:
case 5:
case 6:
case 7:
case 9:
case 10:
k = "abcd" "efgh" "ijkl" /* Red, green, yellov */
"0123456789ABCDEFG" /* Sky */
"xyz"; /* Single R, G, Y */
break;
case 0:
case 4:
case 8:
k = "abcd" "efgh" "ijkl" /* Red, green, yellov */
"................."
"xyz" /* Single R, G, Y */
"-+012345..ABCDEF"; /* Night sky */
break;
}
switch(map)
{
case 0: m = "-------------ad----------"
"-abcd-x-ad--ad-abcd-acd--"
"-x----x--abcd--x----x--x-"
"-abd--x---ad---abd--x--x-"
"-x----x--abcd--x----x--x-"
"-x----x-ad--ad-abcd-abd--"
"----efhad-eh--egh-efh----"
"----y--y-y--y--y--y------"
"++++efh++efgh++y++eh+++++"
"0123y50y2y45y12y45y123450"
"ABCDyFAyCyEFyBCyEFeghDEFA"
"----ijkjl-ijkl--ijkjl----"
"----il--il-il--il--------"
"----ijkjl--il--il-ikl----"
"----il-----il--il--il----"
"----il----ijkl--ijkjl----"
"-------------------------";
break; case 1: m = "0000000000000000000000000"
"1111111111111111111111111"
"2222222222222222222222222"
"3333333333333333333333333"
"4444444444444444444444444"
"5555555555555555555555555"
"6666666666666666666666666"
"7777777ijkjkjjkjkl7777777"
"8888888888888888888888888"
"9999999999999999999999999"
"abcdAAAAAAAAAAAAAAAAAabcd"
"BBBBBBBBBBBBBBBBBBBBBBBBB"
"CCCCCCCCCCCCCCCCCCCCCCCCC"
"efgfgffgfgfgfgfggffgfgfgh"
"EEEEEEEEEEEEEEEEEEEEEEEEE"
"FFFFFFFFFFFFFFFFFFFFFFFFF"
"GGGGGGGGGGGGGGGGGGGGGGGGG";
new_evil(gs, 2, 0, 5);
new_evil(gs,22, 0, 5);
new_evil(gs, 5, 0, 7);
new_evil(gs,19, 0,-7);
break; case 2: m = "0000000000000000000000000"
"1111111111111111111111111"
"2222222222222222222222222"
"3333333333333333333333333"
"4444444444444444444444444"
"5555555555555555555555555"
"6666666666666666666666666"
"7777jjjjjjj777kkkkkkk7777"
"8888888888888888888888888"
"9999999999999999999999999"
"AAAbAAAAAAAAAAAAAAAAAbAAA"
"BBBBBBBBBBBBBBBBBBBBBBBBB"
"CCCCCCCCCCcccccCCCCCCCCCC"
"DDDDDDDDDDDDDDDDDDDDDDDDD"
"EEEEEEEEEEEEEEEEEEEEEEEEE"
"ggggggggggggggggggggggggg"
"GGGGGGGGGGGGGGGGGGGGGGGGG";
new_evil(gs, 8, 0, 5);
new_evil(gs,17, 0,-5);
new_evil(gs, 3, 0, 7);
new_evil(gs,22, 0,-7);
break; case 3: m = "0000000000000000000000000"
"1111111111111111111111111"
"2222222222222222222222222"
"3333333333333333333333333"
"4444444444444444444444444"
"5555555555555555555555555"
"6666666666666666666666666"
"7777777777777777777777777"
"8888888888888888888888888"
"9999kkkkk9999999kkkkk9999"
"AAAAAAAAAAAAAAAAAAAAAAAAA"
"BBBBBBBBBBBBBBBBBBBBBBBBB"
"CffCCCCCCCCCCCCCCCCCCCffC"
"DDDDDDDDDDDDDDDDDDDDDDDDD"
"EEEEEEEEEEEEEEEEEEEEEEEEE"
"cccccFFFbbbbbbbbbFFFccccc"
"GGGcGGGGGbGGGGGbGGGGGcGGG";
new_evil(gs, 7, 0, 5);
new_evil(gs, 18, 0,-5);
new_evil(gs, 1, 0, 7);
new_evil(gs, 24, 0,-7);
break; case 4: m = "-------------------------"
"-------------------------"
"-------------------------"
"-------------------------"
"-------------------------"
"-------------------------"
"---------------bbbbbb----"
"-------------------------"
"bbbbbb+++++++++++++++++++"
"01234501234501234501234bb"
"ABCDEFABCDEFABCDEFABCDEFA"
"--------jjjjj--------j---"
"-------------------------"
"-------------------f-----"
"-------------------------"
"-------------------------"
"fffffffffffffffffffffffff";
new_slime(gs,13, 0, 5);
new_evil(gs, 3, 0, 5);
new_evil(gs, 7, 0, 7);
new_evil(gs,20, 0, 5);
break; case 5: m = "0000000000000000000000000"
"1111111111111111111111111"
"2222222222222222222222222"
"3333333333333333333333333"
"4444444444444444444444444"
"5555555555555555555555555"
"6666666666666666666666666"
"77777777777jjj77777777777"
"8888888888888888888888888"
"9999999999999999999999999"
"AbbbbbbbbAAAAAAAbbbbbbbbA"
"BBBBBBBBBBBBBBBBBBBBBBBBB"
"CCCCCCCCCCCCCCCCCCCCCCCCC"
"DDbDDDDDDDDDDDDDDDDDDDbDD"
"EEEEEEEEEEEEEEEEEEEEEEEEE"
"FFFFFFFFFFFFFFFFFFFFFFFFF"
"fffffffffffffffffffffffff";
new_evil(gs, 8, 0, 5);
new_evil(gs,17, 0, 5);
new_slime(gs, 1, 0, 5);
new_slime(gs,24, 0,-5);
break; case 6: m = "0000000000000000000000000"
"1111111111111111111111111"
"2222222222222222222222222"
"3333333333333333333333333"
"4444444444444444444444444"
"5555555555555555555555555"
"6666666666666666666666666"
"7777777777777777777777777"
"8888888888888888888888888"
"99jjjjjjj9999999bbbbbbb99"
"AAAAAAAAAAAAAAAAAAAAAAAAA"
"BBBBBBBBBBBBBBBBBBBBBBBBB"
"CCCCCCCbbbbbCjjjjjCCCCCCC"
"DDDDDDDDDDDDDDDDDDDDDDDDD"
"EEEEEEEEEEEEEEEEEEEEEEEEE"
"jjjjjjjjjFFfffFFbbbbbbbbb"
"GzGGGGGzGGGGyGGGGxGGGGGxG";
new_evil(gs, 3, 0, 7);
new_evil(gs, 22, 0, 7);
new_slime(gs, 6, 0,-7);
new_slime(gs, 19, 0, 7);
break; case 7: m = "0000000000000000000000000"
"1111111111111111111111111"
"2222222222222222222222222"
"3333333333333333333333333"
"4444444444xxxxx4444444444"
"5555555555x555x5555555555"
"6666666666x666x6666666666"
"7777777xxxx777xxxx7777777"
"8888888x888888888x8888888"
"9999999x999999999x9999999"
"AAAAAAAxxxxAAAxxxxAAAAAAA"
"BBBBBBBBBBxBBBxBBBBBBBBBB"
"CCCCCCCCCCxCCCxCCCCCCCCCC"
"DDDDDDDDDDxxxxxDDDDDDDDDD"
"EEEEEEEEEEEEEEEEEEEEEEEEE"
"ijklFFFFFFFFFFFFFFFFFijkl"
"GGGijlGilGilGilGilGiklGGG";
new_slime(gs, 2, 0, -5);
new_slime(gs, 22, 0, 5);
new_evil(gs, 8, 0, 7);
new_evil(gs, 16, 0, -7);
break; case 8: m = "-------------------------"
"-------------------------"
"-------------------------"
"-------------------------"
"ijkl----------efgh-------"
"-------------------------"
"-------------------------"
"z----------------abcbcbbd"
"+++++++++++++++++++++++++"
"01z3450123450123450123450"
"ABCDEFABCefgfgfghFABCDEFA"
"----z--------------------"
"-------------------------"
"------z--------------ijkl"
"-------------------------"
"-------------------------"
"abdefghijkl---efghijklabd";
new_slime(gs, 5, 0, -5);
new_slime(gs, 20, 15, -5);
new_evil(gs, 1, 0, 7);
new_evil(gs, 20, 0, 10);
new_evil(gs, 15, 0, 7);
break; case 9: m = "0000000000000000000000000"
"1111111111111111111111111"
"2222222222222222222222222"
"3333333333333333333333333"
"4444444444444444444444444"
"555555555555z555555555555"
"66666666666ijl66666666666"
"7777777777ijlil7777777777"
"888888888ijlikkl888888888"
"99999999ijkjklikl99999999"
"AAAAAAAikjlijkjkjlAAAAAAA"
"BBBBBBiklijkjlijkjlBBBBBB"
"CCCCCijkjlikkjklikklCCCCC"
"DDDDijklijjklikjkjkklDDDD"
"EEEijkkjkjlikjkjlijjklEEE"
"FFijkjlilijkjklikjlikklFF"
"efggfggfgfgfggfgfgfgfgfgh";
new_evil(gs, 11, 0, 5);
new_evil(gs, 10, 0, 6);
new_evil(gs, 9, 0, 7);
new_evil(gs, 8, 0, 8);
new_evil(gs, 7, 0, 9);
new_evil(gs, 6, 0, 10);
new_evil(gs, 5, 0, 11);
new_evil(gs, 4, 0, 12);
new_evil(gs, 3, 0, 13);
new_slime(gs, 1, 0, 16);
new_slime(gs, 24, 0, -14);
break; case 10: m = "0000000000000000000000000"
"1111111111111111111111111"
"2222222222222222222222222"
"3333333333333333333333333"
"44444444444444444444bbbbb"
"5555555555555555555555555"
"6666666666666666666666666"
"777777777777777jjjjj77777"
"8888888888888888888888888"
"9999999999999999999999999"
"AAAAAAAAAAyyyyyAAAAAAAAAA"
"BBBBBBBBBBBBBBBBBBBBBBBBB"
"CCCCCCCCCCCCCCCCCCCCCCCCC"
"DDDDDbbbbbDDDDDDDDDDDDDDD"
"EEEEEEEEEEEEEEEEEEEEEEEEE"
"FFFFFFFFFFFFFFFFFFFFFFFFF"
"jjjjjGGGGGGGGGGGGGGGGGGGG";
new_evil(gs, 1, 0, 4);
new_evil(gs, 3, 0,-4);
new_evil(gs, 6, 0, 4);
new_evil(gs, 8, 0,-4);
new_evil(gs,16, 0, 4);
new_evil(gs,18, 0,-4);
new_evil(gs,21, 0, 4);
new_evil(gs,23, 0,-4);
break; default:
return -1;
}
pig_map_from_string(gs->pe->map, k, m);
gs->refresh_screen = gs->pe->pages;
return 0;
}
static GAMESTATE *init_all(SDL_Surface *screen)
{
int i;
PIG_map *pm;
GAMESTATE *gs = (GAMESTATE *)calloc(1, sizeof(GAMESTATE));
if(!gs)
return NULL; gs->running = 1; gs->pe = pig_open(screen);
if(!gs->pe)
{
fprintf(stderr, "Could not open the Pig Engine!\n");
free(gs);
return NULL;
}
gs->pe->userdata = gs; pig_viewport(gs->pe, 0, 0, SCREEN_W, MAP_H * TILE_H); i = gs->lifepig = pig_sprites(gs->pe, "lifepig.png", 0, 0);
i |= gs->scorefont = pig_sprites(gs->pe, "font.png", 44, 56);
i |= gs->glassfont = pig_sprites(gs->pe, "glassfont.png", 60, 60);
i |= gs->icons = pig_sprites(gs->pe, "icons.png", 48, 48);
i |= gs->stars = pig_sprites(gs->pe, "stars.png", 32, 32);
i |= gs->pigframes = pig_sprites(gs->pe, "pigframes.png", 64, 48);
i |= gs->evil = pig_sprites(gs->pe, "evil.png", 48, 48);
i |= gs->slime = pig_sprites(gs->pe, "slime.png", 48, 48);
if(i < 0)
{
fprintf(stderr, "Could not load graphics!\n");
pig_close(gs->pe);
free(gs);
return NULL;
}
for(i = gs->icons; i < gs->icons + 3*8; ++i)
pig_hotspot(gs->pe, i, PIG_CENTER, 45);
for(i = gs->pigframes; i < gs->pigframes + 12; ++i)
pig_hotspot(gs->pe, i, PIG_CENTER, 43);
for(i = gs->evil; i < gs->evil + 16; ++i)
pig_hotspot(gs->pe, i, PIG_CENTER, 46);
for(i = gs->slime; i < gs->slime + 16; ++i)
pig_hotspot(gs->pe, i, PIG_CENTER, 46); pm = pig_map_open(gs->pe, MAP_W, MAP_H);
if(!pm)
{
fprintf(stderr, "Could not create map!\n");
pig_close(gs->pe);
free(gs);
return NULL;
}
if(pig_map_tiles(pm, "tiles.png", TILE_W, TILE_H) < 0)
{
fprintf(stderr, "Could not load background graphics!\n");
pig_close(gs->pe);
free(gs);
return NULL;
} /* Mark tiles for collision detection */
pig_map_collisions(pm, 0, 12, PIG_ALL); /* Red, green, yellov */
pig_map_collisions(pm, 12, 17, PIG_NONE);/* Sky */
pig_map_collisions(pm, 29, 3, PIG_ALL); /* Single R, G, Y */ load_level(gs, 0);
return gs;
}
/*----------------------------------------------------------
Render the dashboard
----------------------------------------------------------*/
static void dashboard(GAMESTATE *gs)
{
SDL_Rect r;
int i, v;
float x;
float t = SDL_GetTicks() * 0.001;
r.x = 0;
r.y = SCREEN_H - 56;
r.w = SCREEN_W;
r.h = 56;
SDL_SetClipRect(gs->pe->surface, &r); /* Render "plasma bar" */
for(i = 0; i < 56; ++i)
{
float f1, f2, m;
SDL_Rect cr;
cr.x = 0;
cr.w = SCREEN_W;
cr.y = SCREEN_H - 56 + i;
cr.h = 1;
f1 = .25 + .25 * sin(t * 1.7 + (float)i / SCREEN_H * 42);
f1 += .25 + .25 * sin(-t * 2.1 + (float)i / SCREEN_H * 66);
f2 = .25 + .25 * sin(t * 3.31 + (float)i / SCREEN_H * 90);
f2 += .25 + .25 * sin(-t * 1.1 + (float)i / SCREEN_H * 154);
m = sin((float)i * M_PI / 56.0);
m = sin(m * M_PI * 0.5);
m = sin(m * M_PI * 0.5);
SDL_FillRect(gs->pe->surface,
&cr, SDL_MapRGB(gs->pe->surface->format,
((int)128.0 * f1 + 64) * m,
((int)64.0 * f1 * f2 + 64) * m,
((int)128.0 * f2 + 32) * m
));
} /* Draw pigs... uh, lives! */
x = -10;
for(i = 0; i < gs->lives; ++i)
{
x += 48 + gs->lives_wobble *
sin(gs->lives_wobble_time * 12) * .2;
pig_draw_sprite(gs->pe, gs->lifepig,
(int)x + gs->lives_wobble *
sin(gs->lives_wobble_time * 20 + i * 1.7),
SCREEN_H - 56/2);
} /* Print score */
x = SCREEN_W + 5;
v = gs->score;
for(i = 9; i >= 0; --i)
{
int n = v % 10;
x -= 39 - gs->score_wobble *
sin(gs->score_wobble_time * 15 + i * .5);
pig_draw_sprite(gs->pe, gs->scorefont + n, (int)x,
SCREEN_H - 56/2);
v /= 10;
if(!v)
break;
} pig_dirty(gs->pe, &r);
}
/*----------------------------------------------------------
Game logic event handlers
----------------------------------------------------------*/
static void before_objects(PIG_engine *pe)
{
GAMESTATE *gs = (GAMESTATE *)pe->userdata;
if(gs->lives_wobble > 0)
{
gs->lives_wobble *= 0.95;
gs->lives_wobble -= 0.3;
if(gs->lives_wobble < 0)
gs->lives_wobble = 0;
}
if(gs->score_wobble > 0)
{
gs->score_wobble *= 0.95;
gs->score_wobble -= 0.3;
if(gs->score_wobble < 0)
gs->score_wobble = 0;
}
++gs->logic_frames; if(0 == gs->level)
{
switch(gs->fun_count % 60)
{
case 17:
new_powerup(gs, 250, -20, -10, POWER_LIFE);
break;
case 29:
new_powerup(gs, 550, -20, 10, POWER_LIFE);
break;
case 37:
new_powerup(gs, 250, -20, 10, POWER_BONUS2);
break;
case 51:
new_powerup(gs, 550, -20, -10, POWER_BONUS1);
break;
}
if(150 == gs->fun_count % 300)
message(gs, "Press Space!");
++gs->fun_count;
}
}
typedef enum
{
WAITING,
WALKING,
FALLING,
KNOCKED,
NEXT_LEVEL,
DEAD
} OBJECT_states;
static void player_handler(PIG_object *po, const PIG_event *ev)
{
GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
switch(ev->type)
{
case PIG_PREFRAME:
switch(po->state)
{
case WAITING:
if(1 == po->age)
message(gs, "Get ready!");
else if(po->age > 50)
po->state = FALLING;
break;
case WALKING:
if(gs->keys[SDLK_LEFT])
{
po->ax = -(20 + po->vx) * .4;
po->target = 3 + po->age % 4 - 1;
if(5 == po->target)
po->target = 3;
}
else if(gs->keys[SDLK_RIGHT])
{
po->ax = (20 - po->vx) * .4;
po->target = 9 + po->age % 4 - 1;
if(11 == po->target)
po->target = 9;
}
else
{
po->ax = -po->vx * .8;
if(po->target >= 6)
po->target = (po->target + 1) %
PIG_FRAMES;
else if(po->target)
--po->target;
}
break;
case FALLING:
if(gs->keys[SDLK_LEFT])
po->ax = -(20 + po->vx) * .2;
else if(gs->keys[SDLK_RIGHT])
po->ax = (20 - po->vx) * .2;
else
po->ax = -po->vx * .2;
po->target = (po->target + 1) % PIG_FRAMES;
break;
}
po->timer[0] = 1;
break;
case PIG_TIMER0:
if(po->x < 0)
po->x = 0;
else if(po->x > po->owner->view.w - 1)
po->x = po->owner->view.w - 1;
switch(po->state)
{
case WALKING:
if(po->power)
--po->power;
po->image = po->target % PIG_FRAMES;
if(!pig_test_map(gs->pe, po->x, po->y + 1))
{
po->state = FALLING;
po->ay = GRAV_ACC;
}
if(gs->jump || gs->keys[SDLK_UP])
{
po->ay = 0;
po->vy = -JUMP_SPEED;
po->state = FALLING;
gs->jump = 0;
}
break;
case FALLING:
if(po->vy > 2)
po->power = 3;
po->ay = GRAV_ACC;
po->image = po->target;
break;
case KNOCKED:
po->power = 0;
po->ay = GRAV_ACC;
po->target = (po->target + 2) % PIG_FRAMES;
po->image = po->target;
po->ax = -po->vx * .2;
break;
case NEXT_LEVEL:
po->vx = (SCREEN_W / 2 - po->x) * .1;
po->target = (po->target + 1) % PIG_FRAMES;
po->image = po->target;
break;
case DEAD:
po->ax = po->ay = 0;
po->vx = po->vy = 0;
break;
}
if(gs->jump)
--gs->jump;
if(NEXT_LEVEL != po->state)
{
if(gs->enemycount <= 0)
{
message(gs, "Well Done!");
po->state = NEXT_LEVEL;
po->vy = 0;
po->ay = -1;
po->tilemask = 0;
po->hitgroup = 0;
po->timer[2] = 50;
}
}
break; case PIG_TIMER1:
/* Snap out of KNOCKED mode */
po->state = FALLING;
break; case PIG_TIMER2:
switch(po->state)
{
case NEXT_LEVEL:
add_life(gs);
pig_object_close(po);
load_level(gs, gs->level + 1);
new_player(gs);
break;
default:
pig_object_close(po);
if(!new_player(gs))
load_level(gs, 0);
break;
}
break; case PIG_HIT_TILE:
if(KNOCKED == po->state)
break; if(ev->cinfo.sides & PIG_TOP)
{
po->y = ev->cinfo.y;
po->vy = 0;
po->ay = 0;
}
po->state = WALKING;
break; case PIG_HIT_OBJECT:
if(KNOCKED == po->state)
break; switch(ev->obj->hitgroup)
{
case GROUP_ENEMY:
if((po->power && ev->cinfo.sides & PIG_TOP) ||
(po->vy - ev->obj->vy) >= 15)
{
/* Win: Stomp! */
inc_score(gs, ev->obj->score);
ev->obj->y = ev->cinfo.y + 10;
if(po->vy > 0)
ev->obj->vy = po->vy;
else
ev->obj->vy = 10;
ev->obj->ay = GRAV_ACC;
ev->obj->tilemask = 0;
ev->obj->hitgroup = 0;
if(gs->jump || gs->keys[SDLK_UP])
{
/* Mega jump! */
po->vy = -(JUMP_SPEED + 7);
gs->jump = 0;
}
else
{
/* Bounce a little */
po->vy = -15;
}
po->y = ev->cinfo.y;
po->ay = 0;
po->state = FALLING;
}
else
{
/* Lose: Knocked! */
po->vy = -15;
po->ay = GRAV_ACC;
po->state = KNOCKED;
po->timer[1] = 11;
new_star(gs, po->x, po->y - 20, -5, 3);
new_star(gs, po->x, po->y - 20, 2, -6);
new_star(gs, po->x, po->y - 20, 4, 4);
}
break;
case GROUP_POWERUP:
switch(ev->obj->score)
{
case POWER_LIFE:
add_life(gs);
message(gs, "Extra Life!");
break;
case POWER_BONUS1:
/* Double or 100k bonus! */
if(gs->score < 100000)
{
inc_score_nobonus(gs, gs->score);
message(gs, "Double Score!");
}
else
{
inc_score_nobonus(gs, 100000);
message(gs, "100 000!");
}
break;
case POWER_BONUS2:
inc_score_nobonus(gs, 1000);
message(gs, "1000!");
break;
}
ev->obj->state = DEAD;
ev->obj->tilemask = 0;
ev->obj->hitgroup = 0;
ev->obj->vy = -20;
ev->obj->ay = -2;
break;
}
break;
case PIG_OFFSCREEN:
/*
* Dead pigs don't care about being off-screen.
* A timer is used to remove them, and to continue
* the game with a new life.
*/
if(DEAD == po->state)
break;
if(po->y < 0) /* Above the playfield is ok. */
break;
if(gs->lives)
message(gs, "Oiiiiiiink!!!");
else
message(gs, "Game Over!");
po->state = DEAD;
po->timer[2] = 50;
default:
break;
}
}
static void powerup_handler(PIG_object *po, const PIG_event *ev)
{
GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
switch(ev->type)
{
case PIG_PREFRAME:
if(DEAD == po->state)
break;
po->ax = (po->target - po->vx) * .3;
po->ay = GRAV_ACC;
po->image = po->age % 8;
++po->power;
break;
case PIG_HIT_TILE:
if(DEAD == po->state)
break;
if(po->power > 2)
po->target = -po->target;
po->power = 0;
po->vy = 0;
po->ay = 0;
po->x = ev->cinfo.x + po->vx;
po->y = ev->cinfo.y;
break;
case PIG_OFFSCREEN:
if(po->y > SCREEN_H || (po->y < -100))
{
pig_object_close(po);
--gs->enemycount;
}
default:
break;
}
}
static void star_handler(PIG_object *po, const PIG_event *ev)
{
switch(ev->type)
{
case PIG_PREFRAME:
if(po->age >= 8)
pig_object_close(po);
else
po->image = po->age;
default:
break;
}
}
static void evil_handler(PIG_object *po, const PIG_event *ev)
{
GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
int look_x;
switch(ev->type)
{
case PIG_PREFRAME:
if(DEAD == po->state)
break;
po->ax = (po->target - po->vx) * .5;
po->ay = GRAV_ACC;
po->image = po->age % 16;
break;
case PIG_HIT_TILE:
if(DEAD == po->state)
break;
po->vy = 0;
po->ay = 0;
po->x = ev->cinfo.x + po->vx;
po->y = ev->cinfo.y;
break;
case PIG_OFFSCREEN:
if(po->y > SCREEN_H)
{
pig_object_close(po);
--gs->enemycount;
}
break;
case PIG_POSTFRAME:
if(DEAD == po->state)
break;
look_x = 10 + fabs(po->vx * 2);
if(po->target < 0)
look_x = -look_x;
if(!pig_test_map(po->owner, po->x + look_x, po->y + 1))
po->target = -po->target;
default:
break;
}
}
static void slime_handler(PIG_object *po, const PIG_event *ev)
{
GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
int look_x;
switch(ev->type)
{
case PIG_PREFRAME:
if(DEAD == po->state)
break;
po->ax = (po->target - po->vx) * .2;
po->ay = GRAV_ACC;
po->image = po->age % 16;
break;
case PIG_HIT_TILE:
po->vy = -(JUMP_SPEED + GRAV_ACC);
po->ay = 0;
po->y = ev->cinfo.y;
break;
case PIG_OFFSCREEN:
if(po->y > SCREEN_H)
{
pig_object_close(po);
--gs->enemycount;
}
break;
case PIG_POSTFRAME:
if(DEAD == po->state)
break;
/* Don't bother looking if we're close to a floor. */
if(pig_test_map_vector(po->owner,
po->x, po->y,
po->x, po->y + 48,
PIG_TOP, NULL))
break;
/* Turn around if there's no floor! */
look_x = 10 + fabs(po->vx * 4);
if(po->target < 0)
look_x = -look_x;
if(!pig_test_map_vector(po->owner,
po->x + look_x, po->y,
po->x + look_x, SCREEN_H,
PIG_TOP, NULL))
po->target = -po->target;
default:
break;
}
}
static void chain_head_handler(PIG_object *po, const PIG_event *ev)
{
GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
switch(ev->type)
{
case PIG_PREFRAME:
po->vx = (po->target - po->x) * .3;
po->vy = 15 * cos(po->age * .3) - 9;
if((gs->messages > 1) && (1 == po->state))
po->timer[1] = 0;
if(po->timer[1])
break;
case PIG_TIMER1:
switch(po->state)
{
case 0:
po->timer[1] = 35;
++po->state;
break;
case 1:
po->target = -SCREEN_W;
po->timer[1] = 50;
++po->state;
if(gs->messages > 0)
--gs->messages;
break;
case 2:
pig_object_close(po);
break;
}
default:
break;
}
} static void chain_link_handler(PIG_object *po, const PIG_event *ev)
{
PIG_object *target = pig_object_find(po, po->target);
switch(ev->type)
{
case PIG_PREFRAME:
if(target)
{
po->vx = ((target->x + FONT_SPACING) - po->x) * .6;
po->vy = (target->y - po->y) * .6 - 9;
}
else
pig_object_close(po);
default:
break;
}
}
/*----------------------------------------------------------
Accounting (score, lives etc)
----------------------------------------------------------*/ static void add_life(GAMESTATE *gs)
{
++gs->lives;
gs->lives_wobble += 10;
if(gs->lives_wobble > 15)
gs->lives_wobble = 15;
gs->lives_wobble_time = 0;
}
static void remove_life(GAMESTATE *gs)
{
--gs->lives;
gs->lives_wobble += 10;
if(gs->lives_wobble > 15)
gs->lives_wobble = 15;
gs->lives_wobble_time = 0;
}
static void inc_score_nobonus(GAMESTATE *gs, int v)
{
int os = gs->score;
gs->score += v;
while(v)
{
gs->score_wobble += 1;
v /= 10;
}
if(gs->score_wobble > 15)
gs->score_wobble = 15;
gs->score_wobble_time = 0;
if(os / 10000 != gs->score / 10000)
new_powerup(gs, SCREEN_W / 2, -20, -4, POWER_LIFE);
} static void inc_score(GAMESTATE *gs, int v)
{
int os = gs->score;
inc_score_nobonus(gs, v);
if(os / 5000 != gs->score / 5000)
new_powerup(gs, SCREEN_W / 2, -20, 8, POWER_BONUS1);
else if(os / 1000 != gs->score / 1000)
new_powerup(gs, SCREEN_W / 2, -20, -6, POWER_BONUS2);
}
static PIG_object *new_player(GAMESTATE *gs)
{
PIG_object *po;
if(!gs->lives)
return NULL; po = pig_object_open(gs->pe, SCREEN_W / 2, -50, 1);
if(!po)
return NULL; remove_life(gs);
po->ibase = gs->pigframes;
po->handler = player_handler;
po->hitmask = GROUP_POWERUP | GROUP_ENEMY;
return po;
}
static PIG_object *new_powerup(GAMESTATE *gs,
int x, int y, int speed, POWERUPS type)
{
PIG_object *po = pig_object_open(gs->pe, x, y, 1);
if(!po)
return NULL; ++gs->enemycount;
po->score = type;
po->ibase = gs->icons + 8 * po->score;
po->target = speed;
po->handler = powerup_handler;
po->tilemask = PIG_TOP;
po->hitgroup = GROUP_POWERUP;
return po;
}
static PIG_object *new_star(GAMESTATE *gs, int x, int y, int vx, int vy)
{
PIG_object *po = pig_object_open(gs->pe, x + vx, y + vy, 1);
if(!po)
return NULL; po->ibase = gs->stars;
po->ax = -vx * 0.3;
po->vx = vx * 3;
po->ay = -vy * 0.3;
po->vy = vy * 3;
po->handler = star_handler;
return po;
}
static PIG_object *new_evil(GAMESTATE *gs,
int x, int y, int speed)
{
PIG_object *po = pig_object_open(gs->pe,
x * TILE_W, y * TILE_H, 1);
if(!po)
return NULL; ++gs->enemycount;
po->ibase = gs->evil;
po->target = speed;
po->handler = evil_handler;
po->score = 200;
po->tilemask = PIG_TOP;
po->hitgroup = GROUP_ENEMY;
return po;
}
static PIG_object *new_slime(GAMESTATE *gs,
int x, int y, int speed)
{
PIG_object *po = pig_object_open(gs->pe,
x * TILE_W, y * TILE_H, 1);
if(!po)
return NULL; ++gs->enemycount;
po->ibase = gs->slime;
po->target = speed;
po->handler = slime_handler;
po->score = 300;
po->tilemask = PIG_TOP;
po->hitgroup = GROUP_ENEMY;
return po;
}
static PIG_object *new_chain_head(GAMESTATE *gs,
int x, int y, int image, int target_x)
{
PIG_object *po = pig_object_open(gs->pe, x, y, 1);
if(!po)
return NULL; po->ibase = image;
po->handler = chain_head_handler;
po->target = target_x;
return po;
}
static PIG_object *new_chain_link(GAMESTATE *gs,
int x, int y, int image, int target)
{
PIG_object *po = pig_object_open(gs->pe, x, y, 1);
if(!po)
return NULL; po->ibase = image;
po->handler = chain_link_handler;
po->target = target;
return po;
}
static void message(GAMESTATE *gs, const char *s)
{
int i = 0;
const int x = SCREEN_W + FONT_SPACING;
const int y = MAP_H * TILE_H - 30;
int tx = (SCREEN_W - ((signed)strlen(s) - 1) * FONT_SPACING) / 2;
PIG_object *po = NULL;
while(s[i])
{
int c = toupper(s[i]) - 32 + gs->glassfont;
if(0 == i)
po = new_chain_head(gs, x, y, c, tx);
else
po = new_chain_link(gs, x, y, c, po->id);
if(!po)
return;
++i;
}
++gs->messages;
}
static int start_game(GAMESTATE *gs)
{
if(0 != gs->level)
return 0; /* Already playing! --> */ gs->score = 0;
gs->lives = 5; if(load_level(gs, 1) < 0)
return -1; gs->player = new_player(gs);
if(!gs->player)
return -1; return 0;
}
/*----------------------------------------------------------
Input; events and game control keys
----------------------------------------------------------*/
static void handle_input(GAMESTATE *gs, SDL_Event *ev)
{
switch(ev->type)
{
case SDL_MOUSEBUTTONUP:
break;
case SDL_KEYDOWN:
switch(ev->key.keysym.sym)
{
case SDLK_UP:
gs->jump = 3;
break;
case SDLK_F1:
gs->pe->interpolation = !gs->pe->interpolation;
if(gs->pe->interpolation)
message(gs, "Interpolation: ON");
else
message(gs, "Interpolation: OFF");
break;
case SDLK_F2:
gs->pe->direct = !gs->pe->direct;
if(gs->pe->direct)
message(gs, "Rendering: Direct");
else
message(gs, "Rendering: Buffered");
break;
case SDLK_F3:
gs->pe->show_dirtyrects = !gs->pe->show_dirtyrects;
if(gs->pe->show_dirtyrects)
message(gs, "Dirtyrects: ON");
else
message(gs, "Dirtyrects: OFF");
break;
case SDLK_F4:
gs->nice = !gs->nice;
if(gs->nice)
message(gs, "Be Nice: ON");
else
message(gs, "Be Nice: OFF");
break;
case SDLK_SPACE:
start_game(gs);
default:
break;
}
break;
case SDL_KEYUP:
switch(ev->key.keysym.sym)
{
case SDLK_ESCAPE:
gs->running = 0;
default:
break;
}
break;
case SDL_QUIT:
gs->running = 0;
break;
}
}
static void handle_keys(GAMESTATE *gs)
{
}
static int break_received = 0; #ifndef RETSIGTYPE
#define RETSIGTYPE void
#endif
static RETSIGTYPE breakhandler(int sig)
{
/* For platforms that drop the handlers on the first signal... */
signal(SIGTERM, breakhandler);
signal(SIGINT, breakhandler);
break_received = 1;
#if (RETSIGTYPE != void)
return 0;
#endif
}
/*----------------------------------------------------------
main()
----------------------------------------------------------*/
int main(int argc, char* argv[])
{
SDL_Surface *screen;
GAMESTATE *gs;
int i;
int bpp = 0;
int last_tick, start_time, end_time;
int dashframe;
float logic_fps = 20.0;
int flags = SDL_DOUBLEBUF | SDL_HWSURFACE; SDL_Init(SDL_INIT_VIDEO);
atexit(SDL_Quit);
signal(SIGTERM, breakhandler);
signal(SIGINT, breakhandler); for(i = 1; i < argc; ++i)
{
if(strncmp(argv[i], "-s", 2) == 0)
flags &= ~SDL_DOUBLEBUF;
else if(strncmp(argv[i], "-f", 2) == 0)
flags |= SDL_FULLSCREEN;
else
bpp = atoi(&argv[i][1]);
} screen = SDL_SetVideoMode(SCREEN_W, SCREEN_H, bpp, flags);
if(!screen)
{
fprintf(stderr, "Failed to open screen!\n");
return 1;
} SDL_WM_SetCaption("Fixed Rate Pig", "Pig");
SDL_ShowCursor(0); gs = init_all(screen);
if(!gs)
return 1; gs->keys = SDL_GetKeyState(&i); gs->logic_frames = 0;
gs->rendered_frames = 0;
gs->pe->before_objects = before_objects; pig_start(gs->pe, 0);
gs->refresh_screen = gs->pe->pages;
start_time = last_tick = SDL_GetTicks();
while(gs->running)
{
int tick;
float frames, dt;
SDL_Event ev; /* Handle input */
while(SDL_PollEvent(&ev) > 0)
handle_input(gs, &ev);
handle_keys(gs);
if(break_received)
gs->running = 0; /* Calculate time since last update */
tick = SDL_GetTicks();
dt = (tick - last_tick) * 0.001;
frames = dt * logic_fps; /* Run the game logic */
pig_animate(gs->pe, frames); /*
* Limit the dashboard frame rate to 15 fps
* when there's no wobbling going on.
*
* The 'dashframe' deal is about keeping the
* pages in sync on a double buffered display.
*/
if(gs->lives_wobble || gs->score_wobble ||
(gs->dashboard_time > 1.0/15.0))
{
dashframe = gs->pe->pages;
gs->dashboard_time = 0;
}
if(dashframe)
{
--dashframe;
dashboard(gs);
} /* Update sprites */
if(gs->refresh_screen)
{
--gs->refresh_screen;
pig_refresh_all(gs->pe);
}
else
pig_refresh(gs->pe); /* Make the new frame visible */
pig_flip(gs->pe); /* Update statistics, timers and stuff */
++gs->rendered_frames;
gs->lives_wobble_time += dt;
gs->score_wobble_time += dt;
gs->dashboard_time += dt; last_tick = tick;
if(gs->nice)
SDL_Delay(10);
} /* Print some statistics */
end_time = SDL_GetTicks();
i = end_time - start_time;
printf(" Total time running: %d ms\n", i);
if(!i)
i = 1;
printf("Average rendering frame rate: %.2f fps\n",
gs->rendered_frames * 1000.0 / i);
printf(" Average logic frame rate: %.2f fps\n",
gs->logic_frames * 1000.0 / i); pig_close(gs->pe);
return 0;
} ==================dirty.c==============
/*
------------------------------------------------------------
Fixed Rate Pig - a fixed logic frame rate demo
------------------------------------------------------------
* Copyright (C) 2004 David Olofson <[email protected]>
*
* This software is released under the terms of the GPL.
*
* Contact author for permission if you want to use this
* software, or work derived from it, under other terms.
*/
#include <stdlib.h>
#include "engine.h"
/* Approximate worth of one dirtyrect in pixels. */
#define PIG_WORST_MERGE 300
/*
* If the merged result gets at most this many percent
* bigger than the larger of the two input rects,
* accept it as Perfect.
*/
#define PIG_INSTANT_MERGE 10
PIG_dirtytable *pig_dirty_open(int size)
{
PIG_dirtytable *pdt = (PIG_dirtytable *)malloc(sizeof(PIG_dirtytable));
if(!pdt)
return NULL;
pdt->size = size;
pdt->rects = (SDL_Rect *)calloc(size, sizeof(SDL_Rect));
if(!pdt->rects)
{
free(pdt);
return NULL;
}
pdt->count = 0;
pdt->best = 0;
return pdt;
}
void pig_dirty_close(PIG_dirtytable *pdt)
{
free(pdt->rects);
free(pdt);
}
void pig_mergerect(SDL_Rect *from, SDL_Rect *to)
{
int x1 = from->x;
int y1 = from->y;
int x2 = from->x + from->w;
int y2 = from->y + from->h;
if(to->x < x1)
x1 = to->x;
if(to->y < y1)
y1 = to->y;
if(to->x + to->w > x2)
x2 = to->x + to->w;
if(to->y + to->h > y2)
y2 = to->y + to->h;
to->x = x1;
to->y = y1;
to->w = x2 - x1;
to->h = y2 - y1;
}
void pig_intersectrect(SDL_Rect *from, SDL_Rect *to)
{
int Amin, Amax, Bmin, Bmax;
Amin = to->x;
Amax = Amin + to->w;
Bmin = from->x;
Bmax = Bmin + from->w;
if(Bmin > Amin)
Amin = Bmin;
to->x = Amin;
if(Bmax < Amax)
Amax = Bmax;
to->w = Amax - Amin > 0 ? Amax - Amin : 0;
Amin = to->y;
Amax = Amin + to->h;
Bmin = from->y;
Bmax = Bmin + from->h;
if(Bmin > Amin)
Amin = Bmin;
to->y = Amin;
if(Bmax < Amax)
Amax = Bmax;
to->h = Amax - Amin > 0 ? Amax - Amin : 0;
}
void pig_dirty_add(PIG_dirtytable *pdt, SDL_Rect *dr)
{
int i, j, best_i, best_loss;
/*
* Look for merger candidates.
*
* We start right before the best match we
* had the last time around. This can give
* us large numbers of direct or quick hits
* when dealing with old/new rects for moving
* objects and the like.
*/
best_i = -1;
best_loss = 100000000;
if(pdt->count)
i = (pdt->best + pdt->count - 1) % pdt->count;
for(j = 0; j < pdt->count; ++j)
{
int a1, a2, am, ratio, loss;
SDL_Rect testr;
a1 = dr->w * dr->h;
testr = pdt->rects[i];
a2 = testr.w * testr.h;
pig_mergerect(dr, &testr);
am = testr.w * testr.h;
/* Perfect or Instant Pick? */
ratio = 100 * am / (a1 > a2 ? a1 : a2);
if(ratio < PIG_INSTANT_MERGE)
{
/* Ok, this is good enough! Stop searching. */
pig_mergerect(dr, &pdt->rects[i]);
pdt->best = i;
return;
}
loss = am - a1 - a2;
if(loss < best_loss)
{
best_i = i;
best_loss = loss;
pdt->best = i;
}
++i;
i %= pdt->count;
}
/* ...and if the best result is good enough, merge! */
if((best_i >= 0) && (best_loss < PIG_WORST_MERGE))
{
pig_mergerect(dr, &pdt->rects[best_i]);
return;
}
/* Try to add to table... */
if(pdt->count < pdt->size)
{
pdt->rects[pdt->count++] = *dr;
return;
}
/* Emergency: Table full! Grab best candidate... */
pig_mergerect(dr, &pdt->rects[best_i]);
}
void pig_dirty_merge(PIG_dirtytable *pdt, PIG_dirtytable *from)
{
int i;
for(i = 0; i < from->count; ++i)
pig_dirty_add(pdt, from->rects + i);
}
===================dirty.h==============
/*
------------------------------------------------------------
Fixed Rate Pig - a fixed logic frame rate demo
------------------------------------------------------------
* Copyright (C) 2004 David Olofson <[email protected]>
*
* This software is released under the terms of the GPL.
*
* Contact author for permission if you want to use this
* software, or work derived from it, under other terms.
*/
#ifndef PIG_DIRTY_H
#define PIG_DIRTY_H
/* A table of dirtyrects for one display page */
typedef struct PIG_dirtytable
{
int size; /* Table size */
SDL_Rect *rects; /* Table of rects */
int count; /* # of rects currently used */
int best; /* Merge testing starts here! */
} PIG_dirtytable;
PIG_dirtytable *pig_dirty_open(int size);
void pig_dirty_close(PIG_dirtytable *pdt);
/* Add rectangle 'dr' to table 'pdt' */
void pig_dirty_add(PIG_dirtytable *pdt, SDL_Rect *dr);
/* Merge table 'from' into 'pdt' */
void pig_dirty_merge(PIG_dirtytable *pdt, PIG_dirtytable *from);
/* Extend 'to' to a new rect that includes both 'from' and 'to' */
void pig_mergerect(SDL_Rect *from, SDL_Rect *to);
/* Clip 'to' into a rect that is the intersection of 'from' and 'to' */
void pig_intersectrect(SDL_Rect *from, SDL_Rect *to);
#endif /* PIG_DIRTY_H */
==================engine.c================
/*
------------------------------------------------------------
Fixed Rate Pig - a fixed logic frame rate demo
------------------------------------------------------------
* Copyright (C) 2004 David Olofson <[email protected]>
*
* This software is released under the terms of the GPL.
*
* Contact author for permission if you want to use this
* software, or work derived from it, under other terms.
*/
#include <stdlib.h>
#include <string.h>
#include "engine.h"
#include "SDL_image.h"
/* Size of sprite frame table */
#define PIG_MAX_SPRITES 1024
/*
* Actually remove an objects. Used internally,
* to remove objects that have been marked for
* destruction.
*/
static void close_object(PIG_object *po);
/*----------------------------------------------------------
Engine
----------------------------------------------------------*/
PIG_engine *pig_open(SDL_Surface *screen)
{
PIG_engine *pe = (PIG_engine *)calloc(1, sizeof(PIG_engine));
if(!pe)
return NULL;
pe->screen = screen;
if(!pe->screen)
{
pig_close(pe);
return NULL;
}
if((pe->screen->flags & SDL_HWSURFACE) == SDL_HWSURFACE)
{
pe->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE,
screen->w, screen->h,
screen->format->BitsPerPixel,
screen->format->Rmask,
screen->format->Gmask,
screen->format->Bmask,
screen->format->Amask);
if(!pe->buffer)
{
pig_close(pe);
return NULL;
}
pe->surface = pe->buffer;
}
else
pe->surface = screen;
pe->pages = 1 + ((screen->flags & SDL_DOUBLEBUF) == SDL_DOUBLEBUF);
pe->interpolation = 1;
pe->time = 0.0;
pe->view.w = pe->surface->w;
pe->view.h = pe->surface->h;
pe->sprites = (PIG_sprite **)calloc(PIG_MAX_SPRITES,
sizeof(PIG_sprite *));
if(!pe->sprites)
{
pig_close(pe);
return NULL;
}
pe->pagedirty[0] = pig_dirty_open(128);
pe->workdirty = pig_dirty_open(256);
if(!pe->pagedirty[0] || !pe->workdirty)
{
pig_close(pe);
return NULL;
}
if(pe->pages > 1)
{
pe->pagedirty[1] = pig_dirty_open(128);
if(!pe->pagedirty[1])
{
pig_close(pe);
return NULL;
}
}
return pe;
}
void pig_close(PIG_engine *pe)
{
if(pe->sprites)
{
int i;
for(i = 0; i < pe->nsprites; ++i)
if(pe->sprites[i])
{
if(pe->sprites[i]->surface)
SDL_FreeSurface(pe->sprites[i]->surface);
free(pe->sprites[i]);
}
free(pe->sprites);
}
while(pe->objects)
close_object(pe->objects);
if(pe->map)
pig_map_close(pe->map);
if(pe->buffer)
SDL_FreeSurface(pe->buffer);
if(pe->pagedirty[0])
pig_dirty_close(pe->pagedirty[0]);
if(pe->pagedirty[1])
pig_dirty_close(pe->pagedirty[1]);
if(pe->workdirty)
pig_dirty_close(pe->workdirty);
free(pe);
}
void pig_viewport(PIG_engine *pe, int x, int y, int w, int h)
{
pe->view.x = x;
pe->view.y = y;
pe->view.w = w;
pe->view.h = h;
}
int pig_sprites(PIG_engine *pe, const char *filename, int sw, int sh)
{
int x, y, count, handle;
SDL_Surface *tmp = IMG_Load(filename);
if(!tmp)
{
fprintf(stderr, "Could not load '%s'!\n", filename);
return -1;
}
handle = pe->nsprites;
if(!sw)
sw = tmp->w;
if(!sh)
sh = tmp->h;
/* Disable blending, so we get the alpha channel COPIED! */
SDL_SetAlpha(tmp, 0, 0);
count = 0;
for(y = 0; y <= tmp->h - sh; y += sh)
for(x = 0; x <= tmp->w - sw; x += sw)
{
SDL_Rect r;
SDL_Surface *tmp2;
PIG_sprite *s;
if(pe->nsprites >= PIG_MAX_SPRITES)
{
fprintf(stderr, "Sprite bank full!\n");
return -1;
}
s = (PIG_sprite *)calloc(1, sizeof(PIG_sprite));
if(!s)
return -1;
s->w = sw;
s->h = sh;
s->hotx = sw / 2;
s->hoty = sh / 2;
s->radius = (sw + sh) / 5;
tmp2 = SDL_CreateRGBSurface(SDL_SWSURFACE,
sw, sh, 32,
0xff000000, 0x00ff0000,
0x0000ff00, 0x000000ff);
SDL_SetAlpha(tmp2, 0, 0);
r.x = x;
r.y = y;
r.w = sw;
r.h = sh;
SDL_BlitSurface(tmp, &r, tmp2, NULL);
SDL_SetAlpha(tmp2, SDL_SRCALPHA | SDL_RLEACCEL,
SDL_ALPHA_OPAQUE);
s->surface = SDL_DisplayFormatAlpha(tmp2);
if(!s->surface)
{
fprintf(stderr, "Could not convert sprite %d"
" of '%s'!\n",
count, filename);
return -1;
}
SDL_FreeSurface(tmp2);
pe->sprites[pe->nsprites] = s;
++pe->nsprites;
++count;
}
SDL_FreeSurface(tmp);
return handle;
}
int pig_hotspot(PIG_engine *pe, int frame, int hotx, int hoty)
{
if((frame < 0 ) || (frame >= pe->nsprites))
return -1;
switch(hotx)
{
case PIG_UNCHANGED:
break;
case PIG_MIN:
pe->sprites[frame]->hotx = 0;
break;
case PIG_CENTER:
pe->sprites[frame]->hotx = pe->sprites[frame]->w / 2;
break;
case PIG_MAX:
pe->sprites[frame]->hotx = pe->sprites[frame]->w;
break;
default:
pe->sprites[frame]->hotx = hotx;
break;
}
switch(hoty)
{
case PIG_UNCHANGED:
break;
case PIG_MIN:
pe->sprites[frame]->hoty = 0;
break;
case PIG_CENTER:
pe->sprites[frame]->hoty = pe->sprites[frame]->h / 2;
break;
case PIG_MAX:
pe->sprites[frame]->hoty = pe->sprites[frame]->h;
break;
default:
pe->sprites[frame]->hoty = hoty;
break;
}
return 0;
}
int pig_radius(PIG_engine *pe, int frame, int radius)
{
if((frame < 0 ) || (frame >= pe->nsprites))
return -1;
pe->sprites[frame]->radius = radius;
return 0;
}
void pig_start(PIG_engine *pe, int frame)
{
PIG_object *po = pe->objects;
pe->time = (double)frame;
pe->frame = frame;
while(po)
{
po->ip.gx = po->ip.ox = po->x;
po->ip.gy = po->ip.oy = po->y;
po->ip.gimage = po->ibase + po->image;
po = po->next;
}
}
static void run_timers(PIG_engine *pe, PIG_object *po)
{
int i;
for(i = 0; i < PIG_TIMERS; ++i)
if(po->timer[i])
{
--po->timer[i];
if(!po->timer[i])
{
PIG_event ev;
ev.type = PIG_TIMER0 + i;
po->handler(po, &ev);
if(!po->id)
return;
}
}
}
static void test_offscreen(PIG_engine *pe, PIG_object *po, PIG_sprite *s)
{
PIG_event ev;
int hx, hy, w, h;
if(s)
{
hx = s->hotx;
hy = s->hoty;
w = s->w;
h = s->h;
}
else
hx = hy = w = h = 0;
ev.cinfo.sides = (po->y - hy < -h) << PIG_TOP_B;
ev.cinfo.sides |= (po->y - hy >= pe->view.h) << PIG_BOTTOM_B;
ev.cinfo.sides |= (po->x - hx < -w) << PIG_LEFT_B;
ev.cinfo.sides |= (po->x - hx >= pe->view.w) << PIG_RIGHT_B;
if(ev.cinfo.sides)
{
float dx = po->x - po->ip.ox;
float dy = po->y - po->ip.oy;
if(ev.cinfo.sides & PIG_TOP)
{
ev.cinfo.y = 0;
if(dy)
ev.cinfo.x = po->ip.ox - dx * po->ip.oy / dy;
}
else if(ev.cinfo.sides & PIG_BOTTOM)
{
ev.cinfo.y = pe->view.h - 1;
if(dy)
ev.cinfo.x = po->ip.ox + dx *
(ev.cinfo.y - po->ip.oy) / dy;
}
if(ev.cinfo.sides & PIG_LEFT)
{
ev.cinfo.x = 0;
if(dx)
ev.cinfo.y = po->ip.oy - dy * po->ip.ox / dx;
}
else if(ev.cinfo.sides & PIG_RIGHT)
{
ev.cinfo.x = pe->view.w - 1;
if(dx)
ev.cinfo.y = po->ip.oy + dy *
(ev.cinfo.x - po->ip.ox) / dx;
}
ev.type = PIG_OFFSCREEN;
po->handler(po, &ev);
}
}
/* Test for stationary sprite/sprite collision */
static void sprite_sprite_one(PIG_object *po, PIG_object *po2, float t, float hitdist)
{
float dx, dy, dsquare;
PIG_event ev;
int sides;
float ix = po->ip.ox * (1 - t) + po->x * t;
float iy = po->ip.oy * (1 - t) + po->y * t;
float ix2 = po2->ip.ox * (1 - t) + po2->x * t;
float iy2 = po2->ip.oy * (1 - t) + po2->y * t;
dx = ix - ix2;
dy = iy - iy2;
dsquare = dx*dx + dy*dy;
if(dsquare >= hitdist*hitdist)
return; /* Nothing... --> */
if(fabs(dsquare) < 1)
sides = PIG_ALL;
else
{
float d = sqrt(dsquare);
dx /= d;
dy /= d;
if(dx < -0.707)
sides = PIG_LEFT;
else if((dx > 0.707))
sides = PIG_RIGHT;
else
sides = 0;
if(dy < -0.707)
sides |= PIG_TOP;
else if((dy > 0.707))
sides |= PIG_BOTTOM;
}
ev.type = PIG_HIT_OBJECT;
ev.cinfo.ff = 0.0;
ev.cinfo.x = ix;
ev.cinfo.y = iy;
ev.cinfo.sides = sides;
if(po->hitmask & po2->hitgroup)
{
ev.obj = po2;
po->handler(po, &ev);
}
if(po2->id && (po2->hitmask & po->hitgroup))
{
int s;
ev.cinfo.x = ix2;
ev.cinfo.y = iy2;
s = ((sides >> PIG_LEFT_B) & 1) << PIG_RIGHT_B;
s |= ((sides >> PIG_RIGHT_B) & 1) << PIG_LEFT_B;
s |= ((sides >> PIG_TOP_B) & 1) << PIG_BOTTOM_B;
s |= ((sides >> PIG_BOTTOM_B) & 1) << PIG_TOP_B;
ev.cinfo.sides = s;
ev.obj = po;
po2->handler(po2, &ev);
}
}
/*
* Check 'po' against all subsequent objects in the list.
* The testing is step size limited so that neither object
* moves more than 25% of the collision distance between tests.
* (25% should be sufficient for correct direction flags.)
*/
static void test_sprite_sprite(PIG_engine *pe, PIG_object *po, PIG_sprite *s)
{
int image;
PIG_object *po2, *next2;
for(po2 = po->next; po2; po2 = next2)
{
float hitdist, d, dmax, t, dt;
next2 = po2->next;
if(!po->id || !po2->id)
break;
/* Check collision groups and masks */
if(!(po->hitmask & po2->hitgroup) &&
!(po2->hitmask & po->hitgroup))
continue;
/* Calculate minimum distance */
hitdist = s ? s->radius : 0;
image = po2->ibase + po2->image;
if((image >= 0) && (image < pe->nsprites))
hitdist += pe->sprites[image]->radius;
if(hitdist < 1)
hitdist = 1;
/* Calculate number of testing steps */
dmax = fabs(po->ip.ox - po->x);
d = fabs(po->ip.oy - po->y);
dmax = d > dmax ? d : dmax;
d = fabs(po2->ip.ox - po2->x);
dmax = d > dmax ? d : dmax;
d = fabs(po2->ip.oy - po2->y);
dmax = d > dmax ? d : dmax;
if(dmax > 1)
dt = hitdist / (dmax * 4);
else
dt = 1;
/* Sweep test! */
for(t = 0; t < 1; t += dt)
sprite_sprite_one(po, po2, t, hitdist);
}
}
/*
* Returns a non-zero value if the tile at (x, y) is marked for
* collisions on the side indicated by 'mask'.
*/
static __inline__ int check_tile(PIG_map *m, int x, int y, int mask)
{
int mx, my;
/*
* Must check < 0 first! (Division rounds
* towards zero - not downwards.)
*/
if(x < 0 || y < 0)
return PIG_NONE;
mx = x / m->tw;
my = y / m->th;
if(mx >= m->w || my >= m->h)
return PIG_NONE;
return m->hit[my * m->w + mx] & mask;
}
int pig_test_map(PIG_engine *pe, int x, int y)
{
int mx, my;
if(x < 0 || y < 0)
return PIG_NONE;
mx = x / pe->map->tw;
my = y / pe->map->th;
if(mx >= pe->map->w || my >= pe->map->h)
return PIG_NONE;
return pe->map->hit[my * pe->map->w + mx];
}
/*
* Simple implementation that checks only for top edge collisions.
* (Full top/bottom/left/right checks with proper handling of
* corners and rows of tiles is a lot more complicated, so I'll
* leave that out for now, rather than hacking something simple
* but incorrect.)
*/
int pig_test_map_vector(PIG_engine *pe, int x1, int y1, int x2, int y2,
int mask, PIG_cinfo *ci)
{
PIG_cinfo lci;
PIG_map *m = pe->map;
int x, y;
int dist = 2000000000L;
if(!ci)
ci = &lci;
ci->sides = 0;
if((mask & PIG_TOP) && (y1 < y2))
{
/* Test for tiles that can be hit from the top */
for(y = y1 + m->th - y1 % m->th; y <= y2; y += m->th)
{
x = x1 + (x2 - x1) * (y - y1) / (y2 - y1);
if(check_tile(m, x, y + 1, PIG_TOP))
{
dist = (x-x1) * (x-x1) + (y-y1) * (y-y1);
ci->x = x;
ci->y = y - 1;
ci->sides |= PIG_TOP;
break;
}
}
}
if(ci->sides)
ci->ff = sqrt((x2 - x1) * (x2 - x1) +
(y2 - y1) * (y2 - y1) / dist);
return ci->sides;
}
static void test_sprite_map(PIG_engine *pe, PIG_object *po, PIG_sprite *s)
{
PIG_event ev;
if(pig_test_map_vector(pe, po->ip.ox, po->ip.oy, po->x, po->y,
po->tilemask, &ev.cinfo))
{
ev.type = PIG_HIT_TILE;
po->handler(po, &ev);
}
}
static void run_logic(PIG_engine *pe)
{
PIG_object *po, *next;
int image;
/* Shift logic coordinates */
for(po = pe->objects; po; po = po->next)
{
po->ip.ox = po->x;
po->ip.oy = po->y;
}
if(pe->before_objects)
pe->before_objects(pe);
for(po = pe->objects; po; po = next)
{
PIG_event ev;
/*
* We must grab the next pointer before
* we call any event handlers, as they
* may cause objects to remove themselves!
*/
next = po->next;
ev.type = PIG_PREFRAME;
po->handler(po, &ev);
}
for(po = pe->objects; po; po = next)
{
PIG_sprite *s;
next = po->next;
image = po->ibase + po->image;
if((image >= 0) && (image < pe->nsprites))
s = pe->sprites[image];
else
s = NULL;
/* Move! */
po->vx += po->ax;
po->vy += po->ay;
po->x += po->vx;
po->y += po->vy;
/* Check and handle events */
if(po->handler)
{
run_timers(pe, po);
if(po->id)
test_offscreen(pe, po, s);
if(po->id && (po->hitmask || po->hitgroup))
test_sprite_sprite(pe, po, s);
if(po->id && po->tilemask)
test_sprite_map(pe, po, s);
}
}
for(po = pe->objects; po; po = next)
{
next = po->next;
if(po->id)
{
PIG_event ev;
ev.type = PIG_POSTFRAME;
po->handler(po, &ev);
++po->age;
}
}
if(pe->after_objects)
pe->after_objects(pe);
}
void pig_animate(PIG_engine *pe, float frames)
{
/* Advance logic time */
int i = floor(pe->time + frames) - floor(pe->time);
while(i--)
{
run_logic(pe);
++pe->frame;
}
pe->time += frames;
}
void pig_dirty(PIG_engine *pe, SDL_Rect *dr)
{
SDL_Rect r;
r.x = 0;
r.y = 0;
r.w = pe->surface->w;
r.h = pe->surface->h;
if(dr)
pig_intersectrect(dr, &r);
if(r.w && r.h)
pig_dirty_add(pe->pagedirty[pe->page], &r);
}
static void tile_area(PIG_engine *pe, SDL_Rect *r)
{
SDL_Rect cr;
int x, y, startx, starty, maxx, maxy, tilesperrow;
cr = *r;
cr.x += pe->view.x;
cr.y += pe->view.y;
SDL_SetClipRect(pe->surface, &cr);
startx = r->x / pe->map->tw;
starty = r->y / pe->map->th;
maxx = (r->x + r->w + pe->map->tw - 1) / pe->map->tw;
maxy = (r->y + r->h + pe->map->th - 1) / pe->map->th;
if(maxx > pe->map->w - 1)
maxx = pe->map->w - 1;
if(maxy > pe->map->h - 1)
maxy = pe->map->h - 1;
tilesperrow = pe->map->tiles->w / pe->map->tw;
for(y = starty; y <= maxy; ++y)
for(x = startx; x <= maxx; ++x)
{
SDL_Rect from, to;
int c = pe->map->map[y * pe->map->w + x];
from.x = c % tilesperrow * pe->map->tw;
from.y = c / tilesperrow * pe->map->th;
from.w = pe->map->tw;
from.h = pe->map->th;
to.x = pe->view.x + x * pe->map->tw;
to.y = pe->view.y + y * pe->map->th;
SDL_BlitSurface(pe->map->tiles, &from,
pe->surface, &to);
}
}
void remove_sprites(PIG_engine *pe)
{
SDL_Rect r;
PIG_sprite *s;
PIG_object *po, *next;
/*
* Remove all objects, using the information that
* remains from the last frame. The actual removal
* is done by drawing over the sprites with tiles
* from the map.
*
* We assume that most objects don't overlap. If
* they do that a lot, we could use a "dirty map"
* to avoid rendering the same tiles multiple times
* in the overlapping areas.
*/
for(po = pe->objects; po; po = next)
{
next = po->next;
if((po->ip.gimage < 0) || (po->ip.gimage >= pe->nsprites))
continue;
s = pe->sprites[po->ip.gimage];
r.x = po->ip.gx - s->hotx;
r.y = po->ip.gy - s->hoty;
r.w = s->w;
r.h = s->h;
pig_intersectrect(&pe->view, &r);
if(r.w && r.h)
tile_area(pe, &r);
/*
* Delete dead objects *after* they've
* been removed from the rendering buffer!
*/
if(!po->id)
close_object(po);
}
}
void draw_sprites(PIG_engine *pe)
{
PIG_dirtytable *pdt;
PIG_sprite *s;
PIG_object *po;
float fframe = pe->time - floor(pe->time);
SDL_SetClipRect(pe->surface, &pe->view);
/* Swap the work and display/back page dirtytables */
pdt = pe->workdirty;
pe->workdirty = pe->pagedirty[pe->page];
pe->pagedirty[pe->page] = pdt;
/* Clear the display/back page dirtytable */
pdt->count = 0;
/* Update positions and render all objects */
po = pe->objects;
while(po)
{
/* Calculate graphic coordinates */
if(pe->interpolation)
{
po->ip.gx = po->ip.ox * (1 - fframe) + po->x * fframe;
po->ip.gy = po->ip.oy * (1 - fframe) + po->y * fframe;
}
else
{
po->ip.gx = po->x;
po->ip.gy = po->y;
}
po->ip.gimage = po->ibase + po->image;
/* Render the sprite! */
if((po->ip.gimage >= 0) && (po->ip.gimage < pe->nsprites))
{
SDL_Rect dr;
s = pe->sprites[po->ip.gimage];
dr.x = po->ip.gx - s->hotx + pe->view.x;
dr.y = po->ip.gy - s->hoty + pe->view.y;
SDL_BlitSurface(pe->sprites[po->ip.gimage]->surface,
NULL, pe->surface, &dr);
/*
* We use the clipped rect for the dirtyrect!
*/
if(dr.w && dr.h)
pig_dirty_add(pdt, &dr);
}
po = po->next;
}
/* Merge the display/back page table into the work table */
pig_dirty_merge(pe->workdirty, pdt);
}
void pig_refresh(PIG_engine *pe)
{
remove_sprites(pe);
draw_sprites(pe);
}
void pig_refresh_all(PIG_engine *pe)
{
tile_area(pe, &pe->view);
pig_dirty(pe, NULL);
draw_sprites(pe);
}
static void show_rects(PIG_engine *pe, PIG_dirtytable *pdt)
{
int i;
Uint32 color;
if(!pe->buffer)
{
pe->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE,
pe->screen->w, pe->screen->h,
pe->screen->format->BitsPerPixel,
pe->screen->format->Rmask,
pe->screen->format->Gmask,
pe->screen->format->Bmask,
pe->screen->format->Amask);
if(!pe->buffer)
return;
pe->surface = pe->buffer;
tile_area(pe, &pe->view);
}
if(!pe->buffer)
return;
pe->direct = 0;
for(i = 0; i < pdt->count; ++i)
{
SDL_Rect r;
r = pdt->rects[i];
r.x -= 32;
r.y -= 32;
r.w += 64;
r.h += 64;
SDL_BlitSurface(pe->buffer, &r, pe->screen, &r);
}
color = SDL_MapRGB(pe->screen->format, 255, 0, 255);
for(i = 0; i < pdt->count; ++i)
{
SDL_Rect r;
r = pdt->rects[i];
r.h = 1;
SDL_FillRect(pe->screen, &r, color);
r.y += pdt->rects[i].h - 1;
SDL_FillRect(pe->screen, &r, color);
r = pdt->rects[i];
r.w = 1;
SDL_FillRect(pe->screen, &r, color);
r.x += pdt->rects[i].w - 1;
SDL_FillRect(pe->screen, &r, color);
}
}
void pig_flip(PIG_engine *pe)
{
PIG_dirtytable *pdt = pe->workdirty;
int i;
SDL_SetClipRect(pe->surface, NULL);
if(pe->show_dirtyrects)
{
show_rects(pe, pdt);
for(i = 0; i < pdt->count; ++i)
{
pdt->rects[i].x -= 32;
pdt->rects[i].y -= 32;
pdt->rects[i].w += 64;
pdt->rects[i].h += 64;
pig_intersectrect(&pe->buffer->clip_rect, &pdt->rects[i]);
}
}
else if(pe->surface == pe->buffer)
for(i = 0; i < pdt->count; ++i)
SDL_BlitSurface(pe->buffer, pdt->rects + i,
pe->screen, pdt->rects + i);
if((pe->screen->flags & SDL_HWSURFACE) == SDL_HWSURFACE)
{
SDL_Flip(pe->screen);
if(pe->pages > 1)
pe->page = 1 - pe->page;
}
else
SDL_UpdateRects(pe->screen, pdt->count, pdt->rects);
if(pe->direct)
pe->surface = pe->screen;
else
pe->surface = pe->buffer ? pe->buffer : pe->screen;
}
void pig_draw_sprite(PIG_engine *pe, int frame, int x, int y)
{
SDL_Rect dr;
if(frame >= pe->nsprites)
return;
dr.x = x - pe->sprites[frame]->hotx + pe->view.x;
dr.y = y - pe->sprites[frame]->hoty + pe->view.y;
SDL_BlitSurface(pe->sprites[frame]->surface, NULL,
pe->surface, &dr);
}
/*----------------------------------------------------------
Map
----------------------------------------------------------*/
PIG_map *pig_map_open(PIG_engine *pe, int w, int h)
{
if(pe->map)
pig_map_close(pe->map);
pe->map = (PIG_map *)calloc(1, sizeof(PIG_map));
if(!pe->map)
return NULL;
pe->map->owner = pe;
pe->map->w = w;
pe->map->h = h;
pe->map->hit = (unsigned char *)calloc(w, h);
if(!pe->map->hit)
{
pig_map_close(pe->map);
return NULL;
}
pe->map->map = (unsigned char *)calloc(w, h);
if(!pe->map->map)
{
pig_map_close(pe->map);
return NULL;
}
return pe->map;
}
void pig_map_close(PIG_map *pm)
{
PIG_engine *pe = pm->owner;
if(pm->tiles)
SDL_FreeSurface(pm->tiles);
free(pm->hit);
free(pm->map);
free(pe->map);
pe->map = NULL;
}
int pig_map_tiles(PIG_map *pm, const char *filename, int tw, int th)
{
SDL_Surface *tmp;
pm->tw = tw;
pm->th = th;
tmp = IMG_Load(filename);
if(!tmp)
{
fprintf(stderr, "Could not load '%s'!\n", filename);
return -1;
}
pm->tiles = SDL_DisplayFormat(tmp);
if(!pm->tiles)
{
fprintf(stderr, "Could not convert '%s'!\n", filename);
return -1;
}
SDL_FreeSurface(tmp);
return 0;
}
void pig_map_collisions(PIG_map *pm, unsigned first, unsigned count, PIG_sides sides)
{
int i;
if(first > 255)
return;
if(first + count > 255)
count = 255 - first;
for(i = first; i < first + count; ++i)
pm->hitinfo[i] = sides;
}
/*
* Load a map from a string (one byte/tile). 'trans'
* is a string used for translating 'data' into integer
* tile indices. Each position in 'trans' corresponds
* to one tile in the tile palette.
*/
int pig_map_from_string(PIG_map *pm, const char *trans, const char *data)
{
int x, y, z;
/* Load the map */
z = 0;
for(y = 0; y < pm->h; ++y)
for(x = 0; x < pm->w; ++x)
{
const char *f;
int c = data[z];
if(!c)
{
fprintf(stderr, "Map string too short!\n");
return -1;
}
f = strchr(trans, c);
if(!f)
{
fprintf(stderr, "Character '%c' not in"
" the translation string!\n",
c);
return -1;
}
pm->map[z] = f - trans;
++z;
}
/* Generate collision map */
for(y = 0; y < pm->h; ++y)
for(x = 0; x < pm->w; ++x)
pm->hit[y * pm->w + x] =
pm->hitinfo[pm->map[y * pm->w + x]];
return 0;
}
/*----------------------------------------------------------
Object
----------------------------------------------------------*/
static PIG_object *get_object(PIG_engine *pe)
{
PIG_object *po;
if(pe->object_pool)
{
po = pe->object_pool;
pe->object_pool = po->next;
memset(po, 0, sizeof(PIG_object));
}
else
{
po = (PIG_object *)calloc(1, sizeof(PIG_object));
if(!po)
return NULL;
}
po->id = ++pe->object_id_counter;
return po;
}
static void free_object(PIG_object *po)
{
po->prev = NULL;
po->next = po->owner->object_pool;
po->owner->object_pool = po;
po->id = 0;
}
PIG_object *pig_object_open(PIG_engine *pe, int x, int y, int last)
{
PIG_object *po = get_object(pe);
if(!po)
return NULL;
po->owner = pe;
po->tilemask = PIG_ALL;
po->hitmask = 0;
po->hitgroup = 0;
if(last && pe->objects)
{
PIG_object *lo = pe->objects;
while(lo->next)
lo = lo->next;
po->prev = lo;
po->next = NULL;
lo->next = po;
}
else
{
po->prev = NULL;
po->next = pe->objects;
if(po->next)
po->next->prev = po;
pe->objects = po;
}
po->x = x;
po->y = y;
po->ip.ox = x;
po->ip.oy = y;
return po;
}
static void close_object(PIG_object *po)
{
if(po == po->owner->objects)
po->owner->objects = po->next;
else if(po->prev)
po->prev->next = po->next;
if(po->next)
po->next->prev = po->prev;
free_object(po);
}
void pig_object_close(PIG_object *po)
{
if(!po->id)
fprintf(stderr, "Object %p closed more than once!\n", po);
po->id = 0; /* Mark for eventual removal and destruction */
}
void pig_object_close_all(PIG_engine *pe)
{
while(pe->objects)
close_object(pe->objects);
}
PIG_object *pig_object_find(PIG_object *start, int id)
{
PIG_object *pob, *pof;
if(start)
pob = pof = start;
else
{
pof = start->owner->objects;
while(pof)
{
if(pof->id == id)
return pof;
pof = pof->next;
}
return NULL;
}
while(1)
{
if(pob)
{
if(pob->id == id)
return pob;
pob = pob->prev;
}
if(pof)
{
if(pof->id == id)
return pof;
pof = pof->next;
}
else
if(!pob)
return NULL;
}
}
==================engine.h===================
/*
------------------------------------------------------------
Fixed Rate Pig - a fixed logic frame rate demo
------------------------------------------------------------
* Copyright (C) 2004 David Olofson <[email protected]>
*
* This software is released under the terms of the GPL.
*
* Contact author for permission if you want to use this
* software, or work derived from it, under other terms.
*/
#ifndef PIG_ENGINE_H
#define PIG_ENGINE_H
#include "SDL.h"
#include <math.h>
#ifndef M_PI
# define M_PI 3.14159265358979323846 /* pi */
#endif
#include "dirty.h"
/*----------------------------------------------------------
Game Engine
----------------------------------------------------------*/
typedef struct PIG_object PIG_object;
typedef struct PIG_engine PIG_engine;
/* Interpolated point */
typedef struct PIG_ipoint
{
/* From the last logic frame: */
float ox, oy; /* Position */
/* From the last/current rendered frame: */
int gimage; /* Sprite frame index */
float gx, gy; /* Interpolated position */
} PIG_ipoint;
/*
* Game logic events
*
* PREFRAME:
* Occurs once per logic frame, before collision and
* off-screen detection, and before timer handlers.
*
* TIMERx:
* Occurs whenever timer x expires. Timers are one-
* shot, but can be reloaded by the handler for
* periodic action. Timer events are handled before
* before collision and off-screen detection.
*
* HIT_TILE:
* Occurs when the hot-spot of an object hits a
* marked side of a tile, and the corresponding bit
* in 'tilemask' is set.
*
* HIT_OBJECT:
* Occurs when the collision circle of an object
* intersects the collision circle of another object,
* provided one or more bits in 'hitgroup' of the
* other object matches bits in 'hitmask'.
*
* OFFSCREEN:
* Occurs when an object is off-screen. This takes
* in account the hot-spot and bounding rectangle of
* the current sprite frame.
*
* POSTFRAME:
* Occurs once per logic frame, after collision
* detection, off-screen detection and all other
* events.
*
*/
#define PIG_TIMERS 3
typedef enum
{
PIG_PREFRAME,
PIG_TIMER0,
PIG_TIMER1,
PIG_TIMER2,
PIG_HIT_TILE,
PIG_HIT_OBJECT,
PIG_OFFSCREEN,
PIG_POSTFRAME
} PIG_events;
typedef enum
{
PIG_NONE = 0,
/* Bit positions */
PIG_TOP_B = 0,
PIG_BOTTOM_B = 1,
PIG_LEFT_B = 2,
PIG_RIGHT_B = 3,
/* Masks */
PIG_TOP = 1 << PIG_TOP_B,
PIG_BOTTOM = 1 << PIG_BOTTOM_B,
PIG_LEFT = 1 << PIG_LEFT_B,
PIG_RIGHT = 1 << PIG_RIGHT_B,
/* Combined masks */
PIG_TL = PIG_TOP | PIG_LEFT,
PIG_TR = PIG_TOP | PIG_RIGHT,
PIG_BL = PIG_BOTTOM | PIG_LEFT,
PIG_BR = PIG_BOTTOM | PIG_RIGHT,
PIG_ALL = 0xf,
} PIG_sides;
typedef enum
{
PIG_UNCHANGED = -10000000,
PIG_MIN = -10000001,
PIG_CENTER = -10000002,
PIG_MAX = -10000003
} PIG_values;
/* Collision info */
typedef struct
{
float ff; /* Fractional frame */
int x, y; /* Exact position */
PIG_sides sides; /* Side of tile hit */
} PIG_cinfo;
typedef struct PIG_event
{
PIG_events type;
/* For HIT_TILE, HIT_OBJECT and OFFSCREEN: */
PIG_cinfo cinfo; /* Detailed collision info */
/* For HIT_OBJECT: */
PIG_object *obj; /* Which object? */
} PIG_event;
/* Logic object */
struct PIG_object
{
PIG_engine *owner;
PIG_object *next, *prev;
int id; /* Unique ID. 0 means "free". */
int ibase; /* Sprite frame base index */
int image; /* Sprite frame offset */
float x, y; /* Position */
float vx, vy; /* Speed */
float ax, ay; /* Acceleration */
PIG_ipoint ip;
int tilemask; /* Sprite/tile mask [PIG_ALL] */
int hitmask; /* Sprite/sprite mask [0] */
int hitgroup; /* Sprite/sprite group [0] */
int timer[PIG_TIMERS]; /* Down-counting timers */
int age; /* Age timer (logic frames) */
int score;
int power;
int target;
int state;
void (*handler)(PIG_object *po, const PIG_event *ev);
void *userdata;
};
/* Level map */
typedef struct PIG_map
{
PIG_engine *owner;
int w, h; /* Size of map (tiles) */
unsigned char *map; /* 2D aray of tile indices */
unsigned char *hit; /* 2D aray of collision flags */
int tw, th; /* Size of one tile (pixels) */
SDL_Surface *tiles; /* Tile palette image */
unsigned char hitinfo[256]; /* Collision info for the tiles */
} PIG_map;
/* Sprite frame */
typedef struct PIG_sprite
{
int w, h; /* Size of sprite (pixels) */
int hotx, hoty; /* Hot-spot offset (pixels) */
int radius; /* Collision zone radius (pixels) */
SDL_Surface *surface;
} PIG_sprite;
/* Engine */
struct PIG_engine
{
/* Video stuff */
SDL_Surface *screen;
SDL_Surface *buffer; /* For h/w surface displays */
SDL_Surface *surface; /* Where to render to */
int pages; /* # of display VRAM buffers */
SDL_Rect view; /* Viewport pos & size (pixels) */
int page; /* Current page (double buffer) */
PIG_dirtytable *pagedirty[2]; /* One table for each page */
PIG_dirtytable *workdirty; /* The work dirtytable */
/* "Live" switches */
int interpolation;
int direct; /* 1 ==> render directly to screen */
int show_dirtyrects;
/* Time */
double time; /* Logic time (frames) */
int frame; /* Logic time; integer part */
/* Background graphics */
PIG_map *map;
/* Sprites and stuff */
PIG_object *objects;
PIG_object *object_pool;
int object_id_counter;
int nsprites;
PIG_sprite **sprites;
/* Logic frame global handlers */
void (*before_objects)(PIG_engine *pe);
void (*after_objects)(PIG_engine *pe);
/* Space for user data */
void *userdata;
};
/*
* Engine
*/
PIG_engine *pig_open(SDL_Surface *screen);
void pig_close(PIG_engine *pe);
/* Set viewport size and position */
void pig_viewport(PIG_engine *pe, int x, int y, int w, int h);
/* Start engine at logic time 'frame' */
void pig_start(PIG_engine *pe, int frame);
/*
* Load a sprite palette image. The image is chopped up into
* sprites, based on 'sw' and 'sh', and added as new frames
* in the sprite bank. Default values:
* Hot-spot: (sw/2, sh/2)
* Collision radius: 0.2 * (sw + sh)
*
* Passing 0 for 'sw' and/or 'sh' makes pig_sprites() take
* the respective value from the image width and/or height.
*
* Returns the index of the first frame loaded.
*/
int pig_sprites(PIG_engine *pe, const char *filename, int sw, int sh);
/* Set hot-spot of sprite 'frame' to (hotx, hoty) */
int pig_hotspot(PIG_engine *pe, int frame, int hotx, int hoty);
/* Set sprite/sprite collision zone radius of 'frame' */
int pig_radius(PIG_engine *pe, int frame, int radius);
/* Advance logic time by 'frames' logic frames */
void pig_animate(PIG_engine *pe, float frames);
/*
* Manually add a dirtyrect for pig_refresh().
* 'dr' can be outside the engine viewport.
*/
void pig_dirty(PIG_engine *pe, SDL_Rect *dr);
/*
* Do what's needed to deal with the dirtyrects
* and then make the new frame visible.
*/
void pig_flip(PIG_engine *pe);
/*
* Refresh the viewport and any additional dirtyrects.
*
* Note that this does not refresh the entire viewport;
* only the areas that have actually changed!
*/
void pig_refresh(PIG_engine *pe);
/*
* Refresh the whole viewport, including sprites.
*/
void pig_refresh_all(PIG_engine *pe);
/* Render a sprite "manually", bypassing the engine */
void pig_draw_sprite(PIG_engine *pe, int frame, int x, int y);
/*
* Get the collision flags for the tile at (x, y),
* where the unit of x and y is pixels. The return
* is the PIG_sides flags for the tile, or PIG_NONE
* if (x, y) is outside the map.
*/
int pig_test_map(PIG_engine *pe, int x, int y);
/*
* Find the first "collidable" tile side when going from
* (x1, y1) to (x2, y2). 'mask' determines which tile sides
* are considered for collisions.
*
* Returns the side(s) hit, if any tile was hit. If the return
* is non-zero, the PIG_cinfo struct at 'ci' contains detailed
* information about the collision.
*/
int pig_test_map_vector(PIG_engine *pe, int x1, int y1, int x2, int y2,
int mask, PIG_cinfo *ci);
/*
* Map
*/
PIG_map *pig_map_open(PIG_engine *pe, int w, int h);
void pig_map_close(PIG_map *pm);
/* Load a tile palette image */
int pig_map_tiles(PIG_map *pm, const char *filename, int tw, int th);
/*
* Set tile collision info for 'count' tiles, starting at
* 'first'. Each tile in the tile palette has a set of
* PIG_sides flags that determine which sides the tile are
* considered for sprite/map collisions.
*/
void pig_map_collisions(PIG_map *pm, unsigned first, unsigned count,
PIG_sides sides);
/*
* Load a map from a string (one byte/tile). 'trans'
* is a string used for translating 'data' into integer
* tile indices. Each position in 'trans' corresponds
* to one tile in the tile palette.
*/
int pig_map_from_string(PIG_map *pm, const char *trans, const char *data);
/*
* Object
*/
/*
* Create an object with the initial position (x, y). If
* 'last' is 1, the object will end up last in the
* processing and rendering order, otherwise, first.
*
* Note that relative processing order is very important
* to objects that chase each other and stuff like that!
* If they're placed in the "wrong" order, the tracking
* objects get an extra frame of reaction time, which is
* annoying if it's not what you intend.
*/
PIG_object *pig_object_open(PIG_engine *pe, int x, int y, int last);
/*
* Delete an object.
*
* Note that objects are never actually deleted. Instead,
* they are placed in a free pool, where pig_object_open()
* looks for objects to recycle.
*
* In fact, they are not even freed when you ask for it,
* but rather kept around until the next rendered frame,
* so they can be removed from the screen correctly.
*/
void pig_object_close(PIG_object *po);
/*
* Close all objects.
*/
void pig_object_close_all(PIG_engine *pe);
/*
* Find object by 'id', starting at object 'start'.
*
* The search starts at 'start' and is done in both
* directions in parallel, assuming that the matching
* object is near 'start' in the list. (It usually is
* when dealing with linked objects.)
*
* Returns NULL if the object was not found.
*/
PIG_object *pig_object_find(PIG_object *start, int id);
#endif /* PIG_ENGINE_H */