Introduzione
Per ora abbiamo gestito collisioni semplici, utilizzando le funzioni che la libreria pygame ci dava a disposizione, ma per un gioco completo e abbastanza appagante, ci vorrebbe qualcosa di più.
Una buona simulazione fisica può rendere un gioco piacevole molto più interessante e avvincente. Infatti bisogna ricordare che ci vogliono i giusti ingredienti per forgiare un gioco di successo, perché senza divertimento la bellezza non conta nulla (per questo giochiamo, per divertirci, giusto?). Più avanti tratteremo anche queste problematiche, ora però torniamo alla questione del giorno.
Sulla rete possiamo trovare molti engine fisici che possono venirci in aiuto. Costruire un motore che gestisce la fisica per il nostro videogioco può diventare una cosa molto ardua e dispendiosa in termini di tempo, per questo è meglio affidarsi a soluzioni già pronte (nessuno vi vieta di scriverne uno tutto vostro).
Tra gli engine disponibili più famosi che si possono utilizzare con python vi sono:
- pybox2d : http://code.google.com/p/pybox2d/
- pyode : http://pyode.sourceforge.net/
- pynewton : http://sourceforge.net/projects/pynewton/
- pymunk : http://code.google.com/p/pymunk/
Pyode e pynewton possono gestire le tre dimensioni, mentre pybox2d e pymunk sono solo engine per il 2D. Tra questi ho deciso di utilizzare pymunk per entrare nel mondo della simulazione fisica 2D, molto meno complessa del 3D. Con questo non vuol dire che pybox2d sia da meno, ma ho preferito utilizzare una libreria che non abbia problemi di esecuzione con sistemi windows a 64 bit (sicuramente saranno corretti con versioni future).
Installazione
Per installare pymunk basta procurarsi i sorgenti ed eseguire la procedura per l’installazione di un qualsiasi modulo python (le instruzioni si possono trovare anche all’interno del file zip che potete scaricare dalla sezione downloads), in questo caso:
python setup.py build_chipmunk
python setup.py install
Per gli utenti windows è consigliata l’installazione della versione 32 bit di pymunk per python 2.6.
Fatto questo potete provare se l’installazione è andata a buon fine digitando il comando “import pymunk” in una qualsiasi shell python, che non dovrebbe riportare nessun errore. All’interno dell’archivio che scaricate dal sito ufficiale c’è anche una cartella “examples” dove troverete dei programmi di esempio che potete utilizzare per verificare il corretto funzionamento della libreria e per imparare il suo utilizzo.
Pymunk
Per prima cosa, vedremo un codice di esempio e qualche concetto di base (si tratta della seguente guida):
- Corpi rigidi : i corpi rigidi perdono tutte le proprietà fisiche che ha un oggetto, come la massa, la posizione, la velocità ecc… Non ha una sua forma.
- Forme di collisione : assiociando una forma ad un corpo si possono definire le sembianze di quel corpo. Si possono associare più forme allo stesso corpo, per creare uno scheletro più complesso oppure si possono non associare delle forme, se necessario.
- Vincoli e giunti : si possono congiungere due corpi per vincolare il loro comportamento.
- Spaces (Spazi) : sono l’unità di base per la simulazione in pymunk (chipmunk). Agli spazi si possono aggiungere corpi, forme, vincoli per poi aggiornare lo spazio nel suo complesso.
Qui potete vedere alcuni esempi dei vincoli presenti in Chipmunk (e quindi in pymunk).
Questi sono i concetti chiavi descritti nel tutorial di pymunk, ma sono anche validi per l’utilizzo della libreria Chipmunk o di qualsia motore fisico. Ecco di seguito il codice che verrà analizzato:
Codice
import pygame,pymunk,sys from pygame.locals import * from pygame.color import * def add_L(space): rotation_center_body = pymunk.Body(pymunk.inf,pymunk.inf) rotation_center_body.position=(300,100) rotation_limit_body = pymunk.Body(pymunk.inf,pymunk.inf) rotation_limit_body.position = (200,100) body = pymunk.Body(100,10000) body.position = (300,100) l1 = pymunk.Segment(body, (-150,0),(300.,0.),5.0) l2 = pymunk.Segment(body, (-150,0),(-150.,150.),5.0) rotation_center_joint = pymunk.PinJoint(body, rotation_center_body, (0,0),(0,0)) joint_limit = 50 rotation_limit_joint = pymunk.SlideJoint(body,rotation_limit_body, (-100,0),(0,0),(0),joint_limit) l1.elasticity = 0.5 l2.elasticity = 0.5 l1.friction = 10 l2.friction = 10 space.add(l1,l2,body,rotation_center_joint,rotation_limit_joint) return (l1,l2) def draw_lines(screen, lines): for line in lines: body = line.body pv1 = body.position + line.a.rotated(body.angle) pv2 = body.position + line.b.rotated(body.angle) p1 = to_pygame(pv1) p2 = to_pygame(pv2) pygame.draw.lines(screen, THECOLORS["red"],False,[p1,p2],1) def to_pygame(p): return (int(p.x),int(-p.y+480)) def add_ball(space,p): mass = 1 radius = 12.5 inertia = pymunk.moment_for_circle(mass,0,radius) body = pymunk.Body(mass,inertia) body.position = p shape= pymunk.Circle(body,radius) shape.elasticity = 0.95 shape.friction = 10 space.add(body,shape) return shape def draw_ball(screen,ball): p = int(ball.body.position.x),480-int(ball.body.position.y) x,y=p angle = int(ball.body.angle) imballr = pygame.transform.rotozoom(imball,angle,1) screen.blit(imballr,(x-imballr.get_width()/2,y-imballr.get_width()/2)) pygame.draw.circle(screen, THECOLORS["green"],p, int(ball.radius),2) def run(): pygame.init() screen = pygame.display.set_mode((640,480),HWSURFACE | DOUBLEBUF,32) global imball imball = pygame.image.load("8ball.png").convert_alpha() clock = pygame.time.Clock() pymunk.init_pymunk() space = pymunk.Space() space.gravity = (0.0,-900.0) balls = [] lines = add_L(space) while True: for event in pygame.event.get(): if event.type == QUIT: sys.exit() elif event.type == KEYDOWN and event.key == K_ESCAPE: sys.exit() if event.type == MOUSEBUTTONDOWN and pygame.mouse.get_pressed() == (1,0,0): x,y = pygame.mouse.get_pos() y = 480-y ball_shape = add_ball(space,(x,y)) balls.append(ball_shape) screen.fill(THECOLORS["black"]) balls_to_remove = [] for ball in balls: if ball.body.position.y < 0: balls_to_remove.append(ball) draw_ball(screen, ball) for ball in balls_to_remove: space.remove(ball,ball.body) balls.remove(ball) draw_lines(screen,lines) pygame.draw.circle(screen,THECOLORS["blue"],(300,480-100),5) pygame.draw.circle(screen,THECOLORS["blue"],(200,480-100),50,2) space.step(1/60.) pygame.display.update() clock.tick(60) pygame.display.set_caption("Esempio Fisica. FPS = "+str(clock.get_fps())) if __name__ == '__main__': run()
Analisi
Prima di analizzare il codice, vorrei farvi notare che non contano molto le proprietà fisiche che hanno gli oggetti in se, ma conta piuttosto la resa finale. Quindi non meravigliatevi se troverete una gravità con -900.0 invece di -9.8.
- Importiamo per prima cosa tutte le librerie necessarie, quindi pygame, pymunk ecc…
- def add_L(space): questa funzione crea un corpo a forma di ‘L’ che ruota su un perno, limitato da una circonferenza, come si può vedere dalla figura. Rotation_center è il punto centrale dove la ‘L’ ruota, mentre rotation_limit è la circonferenza accanto. Entrambe sono un corpo pymunk statico (nel senso che hanno massa ed inerzia infinita). Body invece è la nostra ‘L’, che infatti è composta da due segmenti. Le coordinate dei segmenti si devono tenere conto dalla posizione di Body, quindi da bosy.position. Fatto questo, aggiungiamo quindi un vincolo di rotazione tra body (la nostra ‘L’) ed il punto centrale (il nostro rotation_center). Poi decidiamo il limite di movimento del prossimo nodo di tipo SlideJoint, per associarlo quindi alla nostra figura. Settiamo l’elasticità e l’attrito dei due segmenti e li aggiungiamo allo spazio pymunk (che verrà creato nel main principale). Infine ritorniamo le due linee, perché dovranno essere disegnate tramite pygame sullo schermo.
- def draw_lines(screen, lines): come potete immaginare, questa funzione stampa a video le linee create in precedenza. pv1 e pv2 sono il punto di inizio e di fine (o viceversa) di un segmento. Questi, prima di essere disegnati sullo schermo, devono essere convertiti secondo le coordinate di pygame. Infatti pymunk presenta un piano dove l’asse delle ordinate è orientato verso l’alto, mentre pygame ha l’asse ‘y’ orientato verso il basso (si parla del verso positivo). Una volta fatto questo si disegna tramite pygame.draw.lines la linea sullo schermo.
- def to_pygame(p): ritorna le coordinate corrette per pygame. In questo caso lo schermo è alto 480 pixel, quindi basta sottrarre a quelli le ordinate di pymunk per avere il corrispettivo giusto.
- def add_ball(space,p): con questa funzione creiamo una palla di massa 1 e di raggio 12.5. Bisogna settare il momento di inerzia dell’oggetto e poi definire la sua forma (necessaria per le collisioni). In questo caso è semplice perché possiamo utilizzare direttamente le funzioni presenti in pymunk, ma per oggetti più complessi si dovrà optare per altre soluzioni. Chiudiamo aggiungendo l’elasticità e l’attrito per poi inserire l’oggetto nello spazio pymunk e ritornare la forma (perché dovrà essere renderizzata sullo schermo).
- def draw_ball(screen,ball): convertiamo sul posto le coordinate e renderizziamo l’immagine di una palla numero 8 con un cerchio verde che la circonda.
- def run(): inizializziamo pygame, carichiamo l’immagine della palla e creiamo un orologio per il tempo di gioco. Inizializziamo pymunk ed un spazio con gravità dove potremo aggiungere i notri oggetti. Creiamo due liste per tenere conto delle figure da renderizzare sullo schermo. Tra gli eventi che controlliamo, abbiamo quello del tasto sinistro del mouse, che ha il compito di creare una palla nel punto dove siamo e di aggiungerla sia alla lista degli oggetti da renderizzare, sia allo spazio pymunk da noi creato. Il loop principale è molto semplice: si rende lo schermo nero e si crea una lista delle sfere che escono fuori dalla visuale (non vogliamo sprecare troppe risorse calcolando oggetti fisici fuori dallo schermo). Quindi se la coordinata ‘y’ di una sfera è minore di 0 nello spazio pymunk, si può aggiungere a quelle da rimuovere, altrimenti la si renderizza sullo schermo. L’altro ciclo rimuove la sfera dallo spazio e quindi anche dalla lista. Disegnamo poi le due linee (che formeranno la nostra ‘L’) e i due cerchi che rappresentano i nostri vincoli che abbiamo imposto in precedenza. Infine facciamo avanzare di uno step corrispondente a (1/60.) la simulazione nello spazio pymunk e di conseguenza aggiorniamo lo schermo per poi richiamare il tick dell’oggetto clock di pygame a 60 fps. L’ultima riga serve per vedere gli fps sulla barra del titolo della finestra.
Si consiglia di utilizzare un passo per lo step dello spazio pymunk costante e non dipendente dagli fps del gioco, per una migliore resa. Ricordate inoltre che senza una forma gli oggetti non possono collidere.
Conclusioni
Per ora non vi resta che provare il codice e sperimentare di vostra iniziativa, ma nel prossimo articolo vedremo altri due esempi. Spero di essere stato abbastanza chiaro, anche perché il pezzo non è di difficile comprensione ed è per la maggior parte uguale al tutorial presentato sul sito ufficiale. Anche da questa piccola simulazione possiamo comunque notare come un gioco di qualsiasi livello abbia bisogno di gestire degli eventi “fisici” tra oggetti, in particolare le collisioni. Se non vi siete annoiati troppo, non vi resta che aspettare il prossimo articolo.
Sorgenti
- Esempio 1 pymunk : http://dl.dropbox.com/u/16546001/AD/Esempio1%20pymunk.rar
Edit : i sorgenti sono stati aggiornati per permettere all’immagine della palla numero 8 di ruotare insieme alla circonferenza creata con pymunk (nel mondo “fisico”). Anche il codice nell’articolo è stato aggiornato, aggiungendo due linee di codice dopo la riga 54.
Ciao Mirco, ottimo articolo! Io come te sono un grande appassionato di programmazione videogiochi e sono molto impallinato con la fisica :D. Normalmente uso C# con XNA ed utilizzo il Bepu Physics, ed ho quindi bene chiare le performance sia su PC che su Xbox. La mia domanda è: quali sono le performance del phyton alle prese con quello che potrebbe essere lo scenario di un videogame di media complessità? I particolari calcoli che vengono effettuati per la fisica quanto risentono dell’architettura del phyton?
Ti chiedo questo perchè, nel mio caso, ci sono notevoli differenze tra la performance di certi tipi di collisioni sulle architetture x86 e quelle PowerPC.
Grazie.
La tua domanda mi ha fatto pensare alla prima volta che ho aperto il file di esempio box2d_pyramid, dove si simula una piramide fatta di cubi (la trovi nella cartella examples di pymunk). Infatti quella piccola simulazione sul mio pc occupa un core intero e viene renderizzata a circa 11 fps. Vedendo così la scena, non sono molto buone le prestazioni.
Però se pensi al fatto che si stanno calcolando le collisioni di circa 300 corpi (dinamici) soggetti alla gravità, la situazione cambia leggermente. Le cose sono un pò diverse se aggiungi ‘shape.elasticity = 1.1’ alla riga 55, che darà un fantastico effetto e un frame rate molto più alto perché complessivamente ci saranno molte meno collisioni da calcolare.
Sai benissimo che quando si aggiunge la ‘fisica’ in un videogioco si fanno delle scelte sia estetiche che prestazionali, come per esempio l’optare per un corpo statico invece di uno dinamico per l’aumento di prestazioni.
Detto questo, credo che in un gioco di media entità, tenendo conto che vi sono circa 100 oggetti dinamici nel mondo virtuale, l’impatto dell’architettura di python non è neanche visibile(parliamo di 2D), ma tutto dipende sempre dalla complessità della scena e da come è stata gestita.
Beh… un buon motore fisico come Bullet, gestisce tranquillamente un migliaio di oggetti dinamici su un computer di fascia media (quad core di un paio di generazioni fa a 2,4GHz), occupando la CPU per circa il 10% del tempo (il resto se lo mangia il rendering).
Chiaro che in Python ci siano delle limitazioni nelle prestazioni, ma se il gioco richiede pochi oggetti in scena goditi la velocità di sviluppo che ti offre il pitone e vai tranquillo. :-)
Bene grazie mille è proprio quello che volevo sapere. Si in effetti io sull’Xbox ho delle performance assurde (in 3D ovviamente) se rapportate all’esempio che hai proposto sopratutto usando oggetti semplici come sfere e cubi, ma è chiaro che l’intento di un gioco in phyton non sia quello di avere un mondo totalmente dinamico ed una grafica assurda ma piuttosto concentrarsi sul sempre più dimenticato, specie dai colossi del settore, “gameplay divertente”.
Anche nel 3d non è male python, guarda panda3D, che presto spero di trattare in questi articoli (utilizza ODE):
http://www.youtube.com/watch?v=y9NfchFTtQ0&feature=related
@TheKaneB
Il tuo commento mi ha fatto notare che il mio pc è passato da fascia media a quella bassa :-(
Se vi interessa ho fatto questo esperimento con pyglet:
project P.I.N.P. (Pinp Is Not Pong):
http://dl.dropbox.com/u/848361/pong_release/refactoring.zip
Screenshoot:
http://img16.imageshack.us/img16/1921/screenshot129658053978.png
TheKaneB ha avuto il privilegio di vedere anche il primissimo prototipo con una scia particellare che diciamo “non brillava in virilità” :P . Stavo pensando di ricavarci una serie di articoli anche se ancora non ne sono convinto al 100%.
Ahahah! Quella scia non si poteva guardare :-D sembrava ti giocare a ping pong usando Trilly come pallina XD
PS: Ma oggi il captcha serve per fermare i Bot o per fermare tutti quelli che non sono membri onorari del Mensa?
typo “…sembrava *di giocare…”