Ora che abbiamo preso dimestichezza con le librerie di Cocos2d è ora di affrontare un argomento un po’ più tosto, introduciamo infatti in questa lezione il nostro primo motore fisico, Box2d.
Ma cos’è un motore fisico? Si tratta di un insieme di classi e di utility che permettono ai programmatori un notevolissimo risparmio di tempo nello sviluppo di tutti quei giochi dov’è, in qualche modo, presente della fisica.
Supponete infatti di voler realizzare una banalissima applicazione in cui una pallina si muove in un labirinto seguendo l’inclinazione del device, insomma qualcosa di simile a questo:
Potremmo seguire le istruzioni presenti in questo articolo, ma non sarebbero sufficienti, perché in quell’esempio non sono considerati né gli ostacoli lungo il percorso né viene considerata l’accelerazione della pallina, che nell’esempio si muove di moto rettilineo uniforme, mentre nella realtà una pallina su un piano si muove di moto rettilineo uniformemente accelerato. Se vogliamo rendere la nostra pallina un minimo realistica dovremmo quindi tenere traccia della velocità, della accelerazione gravitazionale (9.8 m/s2 ) e dell’angolo di inclinazione.
Ok, lo ammetto le ultime frasi avevano come unico scopo quello di farvi apprezzare il lavoro fatto dai ragazzi che sviluppano i motori fisici, muovere una pallina su un piano non sarebbe poi tutto sto lavoro, però avete sicuramnte capito il senso.
Torniamo quindi al nostro framework: Box2d. Parliamo di questo piuttosto che di altri perché questo framework ha la comodità di essere distribuito direttamente con l’installer di cocos2d, il che rende praticamente nulla la fase di setup e configurazione.
In realtà cocos2d viene distribuito con ben 2 motori fisici, il primo è appunto Box2d, il secondo è chipmunk, quale scegliere è praticamente solo una questione di gusti dell’utente.
I motori fisici del calibro di Box2D non mappano certamente tutta la realtà fisica all’interno di un videogioco, ma propongono una fisica semplificata il cui scopo non è quello di dimostrare la relatività generale di Einstein, ma dare comunque una parvenza di realtà all’interno del videogioco. La più evidente tra queste semplificazioni è che questi motori fisici ragionano in termini di “corpi rigidi”.
Vi ricordate il professore di fisica che disegna un blocco alla lavagna e parte con la solfa “Sia C un corpo rigido indeformabile di massa uniforme posto su un piano inclinato…” ecco in pratica box2D fa lo stesso genere di approssimazioni. Qualunque oggetto nella realtà non è né completamente rigido e né le sue prorietà (densità, massa) sono omogenee per tutto il corpo. Box2d ignora queste sottigliezze e caratterizza un corpo in base a soli 3 parametri
- Density (paragonabile al peso)
- Friction (qualcosa simile all’attrito)
- Restitution (questa proprietà determina quanto rimbalza uno oggetto)
A questi parametri va aggiunta la shape ovvero la forma che il corpo occupa nello spazio bidimensionale.
Esiste un tipo più evoluto di body e si ottiene collegando insieme più body attraverso i joints. Per esempio la figura di un uomo può essere costituita da 6 body (testa, busto, 2 braccia e 2 gambe) tenuti insieme da 5 joints (2 spalle, 2 anche e 1 collo) i joint specificano quanto e come i due corpi possono muoversi rispettivamente, in questo modo, per esempio, è possibile applicare una forza al busto dell’uomo e tutto il corpo si sposterà. Di quanto e come si sposterà la testa è, appunto, una proprietà del joint.
Potete vedere un esempio in questo sito:
http://www.emanueleferonato.com/2009/04/06/two-ways-to-make-box2d-cars/
Come installare Box2D
Il primo passo per utilizzare Box2D è installare la libreria Cocos2d, il secondo è..hum..non c’è un secondo passo! Troverete già tutto pronto e funzionante!
Per chi invece vuol far tutto da se l’home del progetto è questa: http://code.google.com/p/box2d/ potete scaricare lo zip con i sorgenti ed importarlo dentro Xcode, purtroppo essendo scritto interamente in C++ la sua compilazione richiede qualche attenzione in più, quindi il mio consiglio è quello di utilizzare la versione distribuita attraverso Cocos2d.
La nostra prima applicazione con Box2d
La difficoltà maggiore nell’uso di Box2D è che è scritto interamente in C++, quindi per utilizzarlo è necessario apprendere giusto un po’ di sintassi del C++, niente paura non c’è bisogno né di studiare a memoria il libro dell’inventore del C++ Bjarne Stroustrup (http://www.stroustrup.com/) né tantomeno di imparare la pronuncia del suo cognome 😀 (che per inciso trovate qui http://www.stroustrup.com/pronounciation.wav )
Partiamo quindi creando l’hello world per Box2d.
Il primo passo è quello di creare un nuovo progetto con Xcode e selezionare il tipo di progetto “cocos2d ios with box2d”
Chiamate il progetto “HelloBox2d” e salvatelo in una directory a vostro piacimento.
Eseguitelo ed il risultato sarà questo:
Tappando sullo schermo potete far apparire delle nuove scatole che cadranno e interagiranno seguendo più o meno la legge di gravità.
Esaminiamo più in dettaglio il progetto
Tra i file inseriti automaticamente all’interno del nostro progetto possiamo notare subito una particolarità: i file hanno estensione .mm
Questo perché è necessario specificare ad Xcode che all’interno di questi file sarà presente anche del codice c++ (quello appunto di Box2d) e Xcode provvederà a compilarli in un modo leggermente diverso.
Possiamo ottenre lo stesso risultato in un modo diverso, basta infatti selezionare il file e specificare come File Type “Objective-C++ source”:
La classe più interessante in questo progetto è HelloWorldLayer dove notiamo che è stato importato il file principale di box2d con:
#import "Box2D.h"
La costante appena sotto, PTM_RATIO è valore di defautl del rapporto punti/metri. Non abbiamo detto che box2d utilizza al suo interno una rappresentazione degli oggetti utilizzando il sistema MKS (meter / Kilogram / second) quindi un oggetto disegnato all’interno del nostro videogioco non misurerà 200 punti, ma 6 metri e 25 cm. Il rapporto di conversione tra punti nello schermo e metri è dato appunto da PTM_RATIO. Di default un oggetto largo un metro occuperà 32 punti nello schermo.
ATTENZIONE: punti! non pixel! da quando Apple ha prodotto i device con schermi retina dobbiamo assolutamente fare questa distinsione.
Tra le variabli di istanza troviamo
b2World* world;
world sarà il nostro oggetto che conterrà tutto il nostro mondo relativo a questa scena.
Passiamo ad analizzare l’implementazione
Il metodo più interessante è initPhysics, dove il motore fisico viene inizializzato, vediamolo riga per riga.
CGSize s = [[CCDirector sharedDirector] winSize];
In questo modo recuperiamo la dimensione dello schermo.
b2Vec2 gravity;
gravity.Set(0.0f, -10.0f);
b2Vec2 è una struttura C la cui dichiarazione si trova nel file b2Math.h. Rappresenta un vettore colonna di una matrice ed è costituito da due elementi.
In questo caso ne viene dichiarato uno che servirà successivamente per il setup della gravità, il valore standard in questo caso è (0, -10). Possiamo intervenire su questi parametri per generare una gravità personalizzata, basta ricordare che valori positivi di y indicano una gravità che va verso l’alto mentre x indicano che va verso destra. Rispettivamente invece verso il basso e verso sinistra per valori negativi di y e di x.
world = new b2World(gravity);
Qui avviene l’effettiva inizializzazione del mondo utilizzando il vettore gravità dichiarato alla riga precedente.
Possiamo notare qui una differente sintassi tra il C++ e Obj-c nell’allocare e inizializzare un oggetto.
// Do we want to let bodies sleep?
world->SetAllowSleeping(true);
In questo modo stiamo richiamando una funzione dell’oggetto world dichiarando di voler abilitare lo “sleeping”.
Questa impostazione permette a box2d di rendere inattivi “sleeping” gli oggetti che in pratica hanno raggiunto la condizione di equilibrio, in questo modo si possono ottimizzare i calcoli.
world->SetContinuousPhysics(true);
Come per il parametro precedente, ma questa volta attiva il continuos collision detection, una modalità in cui il riconoscimento delle collisioni è più preciso.
m_debugDraw = new GLESDebugDraw( PTM_RATIO );
world->SetDebugDraw(m_debugDraw);
uint32 flags = 0;
flags += b2Draw::e_shapeBit;
flags += b2Draw::e_jointBit;
flags += b2Draw::e_aabbBit;
flags += b2Draw::e_pairBit;
flags += b2Draw::e_centerOfMassBit;
m_debugDraw->SetFlags(flags);
Queste righe servono per attivare alcune funzionalità di debug, potete anche commentarle o eliminarle.
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0, 0);
b2Body* groundBody = world->CreateBody(&groundBodyDef);
Iniziamo a creare i body del nostro mondo, dobbiamo creare le pareti intorno allo schermo per evitare che gli oggetti possano volare via.
Iniziamo definento un b2BodyDef, una struttura in grado di mantere le informazioni sul corpo.
Il corpo poi viene effettivamente creato dalla classe world utilizzando la funzione CreateBody, alla quale si passa come parametro la definizione del corpo.
b2EdgeShape groundBox;
Dichiarmiamo una b2EdgeShape (ne servirebbero 4 diverse, una per ogni lato, ma in questo esempio ricicleremo sempre la stessa variabile).
groundBox.Set(b2Vec2(0,0), b2Vec2(s.width/PTM_RATIO,0));
groundBody->CreateFixture(&groundBox,0);
creiamo un lato, la parte in basso e creiamo la fixture assegnando 0 come densità (il pagimento è un corpo statico, la densità non serve.
// top
groundBox.Set(b2Vec2(0,s.height/PTM_RATIO), b2Vec2(s.width/PTM_RATIO,s.height/PTM_RATIO)); groundBody->CreateFixture(&groundBox,0);
// left
groundBox.Set(b2Vec2(0,s.height/PTM_RATIO), b2Vec2(0,0));
groundBody->CreateFixture(&groundBox,0);
// right
groundBox.Set(b2Vec2(s.width/PTM_RATIO,s.height/PTM_RATIO), b2Vec2(s.width/PTM_RATIO,0));
groundBody->CreateFixture(&groundBox,0);
Creiamo anche gli altri 3 lati ed ecco che abbiamo finito il nostro “recinto”.
La creazione dei corpi dinamici (le scatole) avviene all’interno del metodo addNewSpriteAtPosition
Il codice è piuttosto comprensibile, vediamone le parti più importanti.
PhysicsSprite *sprite = [PhysicsSprite spriteWithTexture:spriteTexture_ rect:CGRectMake(32 * idx,32 * idy,32,32)];
[parent addChild:sprite];
sprite.position = ccp( p.x, p.y);
Viene creata una sprite da una texture random tra quattro e la si aggiunge alla scena.
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(p.x/PTM_RATIO, p.y/PTM_RATIO);
b2Body *body = world->CreateBody(&bodyDef);
Viene creato un corpo dinamico seguendo la stessa tecnica vista nel metodo precedente, prima si crea un b2BodyDef, si impostano le proprietà e poi si chiede all’oggetto world di creare un body secondo le specifiche passate come parametro.
// Define another box shape for our dynamic body.
b2PolygonShape dynamicBox;
dynamicBox.SetAsBox(.5f, .5f);//These are mid points for our 1m box
Si crea la shape per questo corpo, che è indipendente dalla sprite.
// Define the dynamic body fixture.
b2FixtureDef fixtureDef;
fixtureDef.shape = &dynamicBox;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.3f;
body->CreateFixture(&fixtureDef);
Si crea una fixture ovvero un contenitore per quelle proprietà come densità e frizione da associare al corpo e alla shape.
[sprite setPhysicsBody:body];
Si associa la sprite al body, in questo modo abbiamo chiuso il cerchio, la sprite è collegata al body, il body è collegato alla sua shape e alla sua fixture.
Come introduzione a Box2d direi che ci siamo, ma prima di lasciarvi vi invito a sperimentare e a fare qualche prova per conto vostro, i risultati potrebbero essere divertenti, un esempio?
Provate ad aggiungere alla fixtureDef la riga:
fixtureDef.restitution = 2;
In questo modo quando il body urterà un altro corpo piuttosto che perdere energia l’acquisterà, in un attimo vedrete le shape schizzare sempre più velocemente all’interno dello schermo dell’iphone, in violazione di tutti i principi basilari di meccanica newtoniana!
4 Responses to “9. Introduzione a Box2d: la fisica nei videogame”
3 Ottobre 2012
09. Introduzione a Box2D | Corso gratuito di programmazione videogame per iPhone e iPad - iPhone Italia Blog[…] In questa lezione proveremo e analizzeremo nel dettaglio il progetto di esempio incluso con il framework Box2d. In questo modo scopriremo gli ingredienti necessari per dare realismo ad un elemento del nostro gioco e impareremo a costruire e configurare il nostro mondo immaginario dove oggetti, anche diversi, interagiranno tra loro. Siete pronti dunque per affrontare questo interessante tema? La nona lezione vi attende al seguente indirizzo. […]
3 Ottobre 2012
09. Introduzione a Box2D | Corso gratuito di programmazione videogame per iPhone e iPad | Vivi Capena[…] In questa lezione proveremo e analizzeremo nel dettaglio il progetto di esempio incluso con il framework Box2d. In questo modo scopriremo gli ingredienti necessari per dare realismo ad un elemento del nostro gioco e impareremo a costruire e configurare il nostro mondo immaginario dove oggetti, anche diversi, interagiranno tra loro. Siete pronti dunque per affrontare questo interessante tema? La nona lezione vi attende al seguente indirizzo. […]
6 Novembre 2012
AndreaOttimo articolo 🙂
6 Aprile 2013
gabrielequalcosa di piu approfondito su box2d?