Another 2D sample:
how to build a
Platform Game!
Un modo evoluto di uso dei Texture
Texture Atlas
kenney.nl
(vedi anche opengameart.org)
AtlasMaker
Scegliere e gestire le nostre risorse
Immagini e descrittori
<atlas numImages="47">
<texture file="ken.tga" trans="true">
<image name="alienBlue" x="323" y="71" width="66" height="92"/>
<image name="alienBlue_duck" x="125" y="374" width="67" height="72"/>
<image name="alienBlue_jump" x="191" y="71" width="66" height="93"/>
<image name="alienBlue_stand" x="257" y="71" width="66" height="92"/>
<image name="alienBlue_walk1" x="125" y="71" width="66" height="93"/>
...
Elenco e coordinate / offset
Mappa di gioco
Semplice file di testo
Remap dei tile con elementi nel Texture Atlas
vs
Map Makers: Layer, gestione tile, mask di collisione
vs
Animazioni
Meccanismo dinamico (usando naming)
<image name="alienBlue_walk1" x="125" y="71" width="66" height="93"/>
<image name="alienBlue_walk2" x="0" y="272" width="67" height="96"/>
<image name="worm_walk1" x="389" y="141" width="63" height="23"/>
<image name="worm_walk2" x="0" y="488" width="63" height="23"/>
Oltre la _drawSPRITE
typedef struct TEXATLI{
float x,y,w,h;
float bx,by,rw,rh;
char nm[64];
}TEXATLI;
typedef struct TEXATL{
Texture texID;
int cnt;
TEXATLI*tai;
}TEXATL;
int TEXATL_findanim(TEXATL*t,const char*name,int*nframes);
int gfx_drawSPRITETEXATL(float x,float y,TEXATL*t,int id,float ratio,float alpha,int dir);
Area di gioco vs Schermo
Il nostro mondo è più grande di quello che vediamo
Disegnare la mappa
algoritmo simile al clone di Pac-Man
void drawWORLD(WORLD*w,INGAMESCENE*s)
Concetto e gestione di Viewport
schermo = porzione della mappa intorno al player
Disegnare la mappa
usando un offset
typedef struct tagMAP{
char *map; // elementi mappa
int w,rw,h; // dimensione (in elementi)
float sx,sy; // dimensione (in pixel)
float ox,oy; // offset
float tx,ty; // dimensione in pixel degli elementi
}MAP;
void resyncRESIZE(INGAMESCENE*s)
{
int id;
os_gfxratio=os_video_h/320.0f;
id=TEXATL_find(&s->textID,"sandCenter");
s->map.tx=s->map.ty=s->textID.tai[id].w*os_gfxratio;
s->map.sx=s->map.w*s->map.tx;
s->map.sy=s->map.h*s->map.ty;
}
Cambiare la Viewport
in modo che il player sia al centro
(quando possibile)
void adjustVIEWPORT(MAP*g_map,ACTOR*a)
{
g_map->ox=max(a->x,os_video_w/2);
g_map->oy=max(a->y,os_video_h/2);
g_map->ox=min(g_map->ox,g_map->sx-os_video_w/2);
g_map->oy=min(g_map->oy,g_map->sy-os_video_h/2);
g_map->ox-=os_video_w/2;
g_map->oy-=os_video_h*2/3;
g_map->ox=(float)floor(g_map->ox);
g_map->oy=(float)floor(g_map->oy);
}
Interagire con il Mondo
Gestione collisioni
usando tiles vs bitmask
Strutture e funzioni
typedef struct tagINGAMESCENE{
MAP map;
int maptiles[256];
int maptilesmask[256];
TEXATL textID;
Texture backID;
}INGAMESCENE;
int checkGROUND(INGAMESCENE*s,ACTOR*a,float dx)
int checkCOLLISIONS(INGAMESCENE*s,ACTOR*a,float*dx,float*dy)
Non sono destra-sinistra
Saltare (ovvero spinta e gravità)
int PLAYER_action(WORLD*w,INGAMESCENE*s,ACTOR*a)
...
if((w->keys&dirJUMP)||(a->anim=='j'))
{
if(a->anim!='j')
{
a->anim='j';
a->curFrame=a->baseFrame=TEXATL_findanim(&s->textID,"alienBlue_jump",&a->nFrames);
a->vy=-15*os_gfxratio;
}
dy+=a->vy;
a->vy+=0.75f*os_gfxratio;
if(a->vy>15*os_gfxratio)
a->vy=15*os_gfxratio;
if(w->keys&dirLEFT)
{
dx=-15.0f*0.33f*os_gfxratio;
a->dir=1;
}
else
if(w->keys&dirRIGHT)
{
dx=15.0f*0.33f*os_gfxratio;
a->dir=0;
}
}
Actors vs Tiles
I diamanti non sono per sempre
(e neppure le monete)
Callback e collisione
int DIAMOND_action(WORLD*w,INGAMESCENE*s,ACTOR*a)
int COIN_action(WORLD*w,INGAMESCENE*s,ACTOR*a)
Diamanti
int DIAMOND_action(WORLD*w,INGAMESCENE*s,ACTOR*a)
{
AREA2D bD,bP;
getACTORBOUND(w,s,a,&bD);
getACTORBOUND(w,s,g_player,&bP);
if(AREA2D_intersect(&bD,&bP))
{
w->tempscore+=20;
return 0;
}
else
return 1;
}
Aggiungiamo attori
Animazione e movimento degli antagonisti
Altri elementi animati
Screenshot
Ciclo di gioco
Il concetto di "perdere una vita"
non è necessariamente univoco
(ricominciare vs continuare)
(necessità di momento di invulnerabilità)
Collisione
tra antagonisti
e attore principale
int WORM_action(WORLD*w,INGAMESCENE*s,ACTOR*a)
...
getACTORBOUND(w,s,a,&bW);
getACTORBOUND(w,s,g_player,&bP);
if(AREA2D_intersect(&bW,&bP))
if((g_player->status&2)==0)
{
g_player->statustimer=os_getMilliseconds()+2000;
g_player->status|=2;
w->lifes--;
if(w->lifes==0)
EVENT_set(w,event_GAMEOVER);
else
{
g_player->anim='j';
g_player->curFrame=g_player->baseFrame=TEXATL_findanim(&s->textID,"alienBlue_jump",&g_player->nFrames);
g_player->vy=-10*os_gfxratio;
}
}
Stabilire come si finisce
un livello
(e cosa capita dopo)
HUD ed eventi
Grafica ad hoc
Gestione del passaggio di stati
Eventi
Punti, vite e v-Pad
Home
(sweet home)
Cosa manca ancora
nella fase di gioco
Sconfiggere gli antagonisti
Effetti (particles / scene animate)
Cosa manca ancora
nella fase pre-gioco
selezione dei mondi
audio
help/tutorial
(credits/leaderboard/ADMob)
Altre considerazioni
(sempre sui giochi 2D)
Quando l'area di gioco
è più grande dello schermo
(e non solo più larga)
(Disegno con doppio offset)
Ordinare gli elementi da disegnare
Altre tipologie di mappe
(isometriche)
Mappe "Irregolari"
bitmask / slopes / vectorial
(vedere qui)