Sviluppare un gioco per Android – SDL example

Introduzione

In questa puntata vedremo come compilare SDL 2.0 per Android e come eseguire una piccola demo sugli eventi touch. Questo non vuol dire che tutto il lavoro successivo sarà localizzato intorno a lei, piuttosto è solo un esempio per farvi scoprire le potenzialità dell’NDK utilizzando una famosa libreria per scrivere giochi e/o programmi multipiattaforma.

Devo sottolineare il fatto che la libreria utilizzata in questo esempio è ancora sotto intenso sviluppo e non completa (sopratutto per quanto riguarda la nuova documentazione), ma visto che molte funzioni sono simili alla versione 1.2 credo che chi l’ha già utilizzata si troverà a proprio agio. Oltre a questo, chi già avesse seguito gli articoli su pygame, si ritroverà molti dei concetti logici affrontati in quei momenti, visto che si parlava comunque di SDL (ne approfitto per segnalarvi che è stato effettuato anche il porting su Android : http://pygame.renpy.org/).

SDL non è comunque un prodotto troppo acerbo per essere provato, guardate per esempio Negative Space per Android o Eufloria per Windows e iOS; per essere una libreria tutt’ora in costruzione questi due primi prodotti non sembrano niente male (sopratutto Eufloria), quindi penso valga la pena spendere qualche parola, così chi è veramente interessato potrà approfondire a sua discrezione.

La libreria di base comprende la gestione dello schermo, degli eventi e funzioni base per renderizzare sul video oltre ad alcune per gestire il timer.

SDL 2.0

Per prima cosa, controllate di aver già installato mercurial nel vostro pc, così da poter scaricare la libreria dove preferite attraverso il comando apposito da terminale (o cmd per gli utenti windows):

hg clone http://hg.libsdl.org/SDL

Una volta sincronizzata la cartella della libreria procedete come segue :

  1. Copiate la cartella nominata android-project dove preferite. Rinominate la cartella a vostro piacimento, visto che sarà il nuovo progetto.
  2. All’interno del vostro nuovo progetto copiate tutta la cartella SDL scaricata con mercurial in nomeprogetto/jni.
  3. Sostituite dentro la cartella nomeprogetto/jni/SDL/include il file SDL_config.h con SDL_config_android.h . Per farlo potete anche rinominare i file o copiare il contenuto di SDL_config_android.h all’interno di SDL_config.h .
  4. Modificate il file local.properties nella cartella del progetto aggiungendo la cartella di installazione del vostro SDK di Android :
    sdk.dir=C:\\Android\\android-sdk
  5. Nella cartella jni/src del progetto create un file di nome main.cpp e modificate l’Android.mk presente in questa cartella aggiungendolo nei LOCAL_SRC_FILES al posto di YourSourceHere.c e di $(SDL_PATH)/src/main/android/SDL_android_main.cpp . Riempite il file main.cpp con il codice sottostante.
  6. Aggiornate il progetto da linea di comando dopo aver cancellato il file build.xml :
    android update project -t android-10 -p .
  7. Successivamente, dopo aver copiato il main che trovate di seguito (se non lo avete ancora fatto), eseguite ndk-build e ant debug install per installarlo sul vostro device o sull’emulatore che dovete rispettivamente collegare o avviare.

Ecco il codice dell’esempio in cpp. Questo non è niente altro che il testgesture.c già presente tra gli esempi SDL nella cartella test, modificato in piccole parti.

1#ifdef __ANDROID__
2#include <jni.h>
3#include <SDL.h>
4#include "SDL_touch.h"
5#include "SDL_gesture.h"
6#include <time.h>
7#include <android/log.h>
8 
9/* Make sure we have good macros for printing 32 and 64 bit values */
10#ifndef PRIs32
11#define PRIs32 "d"
12#endif
13#ifndef PRIu32
14#define PRIu32 "u"
15#endif
16#ifndef PRIs64
17#ifdef __WIN32__
18#define PRIs64 "I64"
19#else
20#define PRIs64 "lld"
21#endif
22#endif
23#ifndef PRIu64
24#ifdef __WIN32__
25#define PRIu64 "I64u"
26#else
27#define PRIu64 "llu"
28#endif
29#endif
30 
31#define WIDTH 640
32#define HEIGHT 480
33#define BPP 4
34#define DEPTH 32
35 
36//MUST BE A POWER OF 2!
37#define EVENT_BUF_SIZE 256
38 
39#define VERBOSE 0
40 
41static SDL_Window *window;
42static SDL_Event events[EVENT_BUF_SIZE];
43static int eventWrite;
44static int colors[7] = {0xFF,0xFF00,0xFF0000,0xFFFF00,0x00FFFF,0xFF00FF,0xFFFFFF};
45 
46typedef struct {
47  float x,y;
48} Point;
49 
50typedef struct {
51  float ang,r;
52  Point p;
53} Knob;
54 
55static Knob knob;
56 
57void handler (int sig)
58{
59  SDL_Log ("exiting...(%d)", sig);
60  exit (0);
61}
62 
63void perror_exit (char *error)
64{
65  perror (error);
66  handler (9);
67}
68 
69void setpix(SDL_Surface *screen, float _x, float _y, unsigned int col)
70{
71  Uint32 *pixmem32;
72  Uint32 colour;
73  Uint8 r,g,b;
74  int x = (int)_x;
75  int y = (int)_y;
76  float a;
77 
78  if(x < 0 || x > screen->w) return;
79  if(y < 0 || y > screen->h) return;
80 
81  pixmem32 = (Uint32*) screen->pixels  + y*screen->pitch/BPP + x;
82 
83  SDL_memcpy(&colour,pixmem32,screen->format->BytesPerPixel);
84 
85  SDL_GetRGB(colour,screen->format,&r,&g,&b);
86  //r = 0;g = 0; b = 0;
87  a = (float)((col>>24)&0xFF);
88  if(a == 0) a = 0xFF; //Hack, to make things easier.
89  a /= 0xFF;
90  r = (Uint8)(r*(1-a) + ((col>>16)&0xFF)*(a));
91  g = (Uint8)(g*(1-a) + ((col>> 8)&0xFF)*(a));
92  b = (Uint8)(b*(1-a) + ((col>> 0)&0xFF)*(a));
93  colour = SDL_MapRGB( screen->format,r, g, b);
94 
95  *pixmem32 = colour;
96}
97 
98void drawLine(SDL_Surface *screen,float x0,float y0,float x1,float y1,unsigned int col) {
99  float t;
100  for(t=0;t<1;t+=(float)(1.f/SDL_max(SDL_fabs(x0-x1),SDL_fabs(y0-y1))))
101    setpix(screen,x1+t*(x0-x1),y1+t*(y0-y1),col);
102}
103 
104void drawCircle(SDL_Surface* screen,float x,float y,float r,unsigned int c)
105{
106  float tx,ty;
107  float xr;
108  for(ty = (float)-SDL_fabs(r);ty <= (float)SDL_fabs((int)r);ty++) {
109    xr = (float)sqrt(r*r - ty*ty);
110    if(r > 0) { //r > 0 ==> filled circle
111      for(tx=-xr+.5f;tx<=xr-.5;tx++) {
112    setpix(screen,x+tx,y+ty,c);
113      }
114    }
115    else {
116      setpix(screen,x-xr+.5f,y+ty,c);
117      setpix(screen,x+xr-.5f,y+ty,c);
118    }
119  }
120}
121 
122void drawKnob(SDL_Surface* screen,Knob k) {
123  drawCircle(screen,k.p.x*screen->w,k.p.y*screen->h,k.r*screen->w,0xFFFFFF);
124  drawCircle(screen,(k.p.x+k.r/2*cosf(k.ang))*screen->w,
125                (k.p.y+k.r/2*sinf(k.ang))*screen->h,k.r/4*screen->w,0);
126}
127 
128void DrawScreen(SDL_Surface* screen)
129{
130  int i;
131#if 1
132  SDL_FillRect(screen, NULL, 0);
133#else
134  int x, y;
135  for(y = 0;y < screen->h;y++)
136    for(x = 0;x < screen->w;x++)
137    setpix(screen,(float)x,(float)y,((x%255)<<16) + ((y%255)<<8) + (x+y)%255);
138#endif
139 
140  //draw Touch History
141  for(i = eventWrite; i < eventWrite+EVENT_BUF_SIZE; ++i) {
142    const SDL_Event *event = &events[i&(EVENT_BUF_SIZE-1)];
143    float age = (float)(i - eventWrite) / EVENT_BUF_SIZE;
144    float x, y;
145    unsigned int c, col;
146 
147    if(event->type == SDL_FINGERMOTION ||
148       event->type == SDL_FINGERDOWN ||
149       event->type == SDL_FINGERUP) {
150 
151      SDL_Touch* inTouch = SDL_GetTouch(event->tfinger.touchId);
152      if(inTouch == NULL) continue;
153 
154      x = ((float)event->tfinger.x)/inTouch->xres;
155      y = ((float)event->tfinger.y)/inTouch->yres;
156 
157      //draw the touch:
158      c = colors[event->tfinger.fingerId%7];
159      col = ((unsigned int)(c*(.1+.85))) | (unsigned int)(0xFF*age)<<24;
160        SDL_Rect rect;
161        rect.w = 40 ;
162        rect.h = 40 ;
163        rect.x = (x*screen->w)-20 ;
164        rect.y = (y*screen->h)-20 ;
165        SDL_FillRect(screen,&rect,col);
166    }
167  }
168 
169  if(knob.p.x > 0)
170    drawKnob(screen,knob);
171 
172  SDL_UpdateWindowSurface(window);
173}
174 
175SDL_Surface* initScreen(int width,int height)
176{
177  if (!window) {
178    window = SDL_CreateWindow("Gesture Test",
179                              SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
180                              WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE);
181  }
182  if (!window) {
183    return NULL;
184  }
185  return SDL_GetWindowSurface(window);
186}
187 
188int main(int argc, char* argv[])
189{
190  SDL_Surface *screen;
191  SDL_Event event;
192  SDL_bool quitting = SDL_FALSE;
193  SDL_RWops *src;
194 
195  //gesture variables
196  knob.r = .1f;
197  knob.ang = 0;
198 
199  if (SDL_Init(SDL_INIT_VIDEO) < 0 ) return 1;
200 
201  if (!(screen = initScreen(WIDTH,HEIGHT)))
202    {
203      SDL_Quit();
204      return 1;
205    }
206 
207  while(!quitting) {
208    while(SDL_PollEvent(&event))
209      {
210    //Record _all_ events
211    events[eventWrite & (EVENT_BUF_SIZE-1)] = event;
212    eventWrite++;
213 
214    switch (event.type)
215      {
216      case SDL_QUIT:
217        quitting = SDL_TRUE;
218        break;
219      case SDL_KEYDOWN:
220        switch (event.key.keysym.sym)
221          {
222          case SDLK_SPACE:
223        SDL_RecordGesture(-1);
224        break;
225          case SDLK_s:
226        src = SDL_RWFromFile("gestureSave","w");
227        SDL_Log("Wrote %i templates",SDL_SaveAllDollarTemplates(src));
228        SDL_RWclose(src);
229        break;
230          case SDLK_l:
231        src = SDL_RWFromFile("gestureSave","r");
232        SDL_Log("Loaded: %i",SDL_LoadDollarTemplates(-1,src));
233        SDL_RWclose(src);
234        break;
235          case SDLK_ESCAPE:
236        quitting = SDL_TRUE;
237        break;
238        }
239        break;
240      case SDL_WINDOWEVENT:
241            if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
242          if (!(screen = initScreen(0, 0)))
243          {
244        SDL_Quit();
245        return 1;
246          }
247            }
248        break;
249      case SDL_FINGERMOTION:
250#if VERBOSE
251        SDL_Log("Finger: %i,x: %i, y: %i",event.tfinger.fingerId,
252               event.tfinger.x,event.tfinger.y);
253#endif
254        {
255            SDL_Touch* inTouch = SDL_GetTouch(event.tfinger.touchId);
256            SDL_Finger* inFinger = SDL_GetFinger(inTouch,event.tfinger.fingerId);
257        }
258        break;
259      case SDL_FINGERDOWN:
260#if VERBOSE
261        SDL_Log("Finger: %"PRIs64" down - x: %i, y: %i",
262           event.tfinger.fingerId,event.tfinger.x,event.tfinger.y);
263#endif
264        break;
265      case SDL_FINGERUP:
266#if VERBOSE
267        SDL_Log("Finger: %"PRIs64" up - x: %i, y: %i",
268               event.tfinger.fingerId,event.tfinger.x,event.tfinger.y);
269#endif
270        break;
271      case SDL_MULTIGESTURE:
272#if VERBOSE
273        SDL_Log("Multi Gesture: x = %f, y = %f, dAng = %f, dR = %f",
274           event.mgesture.x,
275           event.mgesture.y,
276           event.mgesture.dTheta,
277           event.mgesture.dDist);
278        SDL_Log("MG: numDownTouch = %i",event.mgesture.numFingers);
279#endif
280        knob.p.x = event.mgesture.x;
281        knob.p.y = event.mgesture.y;
282        knob.ang += event.mgesture.dTheta;
283        knob.r += event.mgesture.dDist;
284        break;
285      case SDL_DOLLARGESTURE:
286        SDL_Log("Gesture %"PRIs64" performed, error: %f",
287           event.dgesture.gestureId,
288           event.dgesture.error);
289        break;
290      case SDL_DOLLARRECORD:
291        SDL_Log("Recorded gesture: %"PRIs64"",event.dgesture.gestureId);
292        break;
293      }
294      }
295    DrawScreen(screen);
296  }
297  SDL_Quit();
298  return 0;
299}
300 
301// Called before SDL_main() to initialize JNI bindings in SDL library
302extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls);
303 
304// Library init
305extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
306{
307    return JNI_VERSION_1_4;
308}
309 
310// Start up the SDL app
311extern "C" int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj)
312{
313    /* This interface could expand with ABI negotiation, calbacks, etc. */
314    SDL_Android_Init(env, cls);
315 
316    main(0,NULL);
317 
318}
319 
320#endif /* __ANDROID__ */

Osservazioni

Da notare le ultime fuzioni che estendono quelle già presenti nel sorgente java di SDL che si trova in nomeprogettosrc/org/libsdl/app. In particolare nell’ultima viene fatta l’inizializzazione della libreria tramite il comando SDL_Android_Init . Da quel momento in poi, è possibile utilizzare la libreria SDL in tutte le sue parti (alcune librerie sono esterne e vanno aggiunte al progetto e sono sdl image,mixer,net,rtf e ttf. Per questo esempio non sono necessarie) senza modificare il nostro codice a seconda della piattaforma (il processo di init della libreria può essere differente a seconda dell’OS utilizzato).

Il funzionamento del programma è molto semplice : si collezionano gli eventi che a noi interessano (SDL_FINGERDOWN, SDL_FINGERUP, SDL_MULTIGESTURE, SDL_FINGERMOTION) in un buffer (events) che verrà successivamente analizzato durante la fase di rendering (funzione DrawScreen). A seconda della lunghezza del buffer utilizzata vedremo sullo schermo la storia degli eventi da noi creati, che sono rappresentati creando dei quadrati sul punto in cui è stato effettuato il tocco dello schermo.

Questo test prevede anche l’utilizzo del multigesture, che in questo caso fa ingrandire e rimpicciolire una “figura” a forma di occhio a seconda dei nostri gesti con due dita.

Come potete vedere la struttura del loop è identica a quelle viste nelle guide su pygame, con un ciclo principale ed un altro annidato per gli eventi con i dovuti accorgimenti per gestire i vari input (ricordate che questo file viene utilizzato come test delle librerie, non si sta implementando niente di particolare) .

Conclusioni

Con questa base è possibile aggiungere features come il supporto per openGL ES ed il rendering accelerato. Se avete dei progetti già scritti in SDL potreste provare a riportarli su questo nuovo dispositivo.

Come ho già detto l’articolo è solo a titolo dimostrativo, non approfondirò al momento i molti altri aspetti inerenti a questa libreria. Piuttosto mi premeva il fatto di lasciarvi qualcosa di nuovo tra le mani, anche se è molto difficile andare oltre a semplici dimostrazioni per questioni di tempo.

Alcuni titoli come Doom o Return to castle Wolfenstein per Android utilizzano sempre SDL e visto il recente interesse per videogiochi multipiattaforma credo che sarà sempre più interessante imparare ad utilizzare una libreria del genere.

Sarei curioso di conoscere i vostri pareri riguardo SDL e i traguardi da essa raggiunti. Vi aspetto dunque nei commenti e nei prossimi articoli.

Press ESC to close