Sviluppare un gioco in Python: Panda3D – input

Introduzione

In questo articolo vedremo come gestire gli input in Panda3D, facendo muovere il carosello presente nella scena anche nei precedenti esempi. Nel precedente articolo abbiamo visto come Panda ci consenta di definire dei task che il motore chiama per ogni frame (a seconda delle nostre indicazioni). Questo meccanismo, insieme alla gestione degli eventi, ci permette di interagire interamente con il mondo da noi creato.

I gestori di eventi sono particolari procedure (Event handlers) che vengono richiamate quando accade una particolare situazione, un particolare “evento”. Per questo motivo gli eventi sono gestiti da un apposito componente detto messenger, che richiama le opportune funzioni da noi definite per il particolare evento.

Per capire quale evento gestire dobbiamo dare delle direttive, cioè descrivere quali eventi sono accettati o no. Inoltre per configurare il messenger di un particolare oggetto è opportuno che quest’ultimo estenda DirectObject.

Ora possiamo vedere il codice ed analizzarlo.

Codice

1# -*- coding: utf-8 -*-
2 
3from direct.showbase.ShowBase import ShowBase
4from direct.showbase.DirectObject import DirectObject
5from pandac.PandaModules import *
6from sys import exit
7 
8class MyApp(ShowBase,DirectObject):
9    def __init__(self):
10        ShowBase.__init__(self)
11 
12        self.velocita = 0
13        self.gas = 0
14        self.maxVel = 100
15        self.accel = 25
16        self.gestGira = 25
17 
18        self.keyMap = {"w" : False,
19                       "s" : False,
20                       "a" : False,
21                       "d" : False,
22                       "mouse1" : False,
23                       "mouse3" : False}
24 
25        self.accept("w", self.tastoPremuto,["w", True])
26        self.accept("a", self.tastoPremuto,["a", True])
27        self.accept("s", self.tastoPremuto,["s", True])
28        self.accept("d", self.tastoPremuto,["d", True])
29 
30        self.accept("w-up", self.tastoPremuto,["w", False])
31        self.accept("a-up", self.tastoPremuto,["a", False])
32        self.accept("s-up", self.tastoPremuto,["s", False])
33        self.accept("d-up", self.tastoPremuto,["d", False])
34 
35        self.accept("mouse1", self.tastoPremuto, ["mouse1", True])
36        self.accept("mouse3", self.tastoPremuto, ["mouse3", True])
37        self.accept("mouse1-up", self.tastoPremuto, ["mouse1", False])
38        self.accept("mouse3-up", self.tastoPremuto, ["mouse3", False])
39 
40        self.accept("escape", exit)
41 
42        self.oggetto0 = self.loader.loadModel("models/cubo_giallo.egg")
43        self.oggetto0.reparentTo(self.render)
44        self.oggetto0.setPos(-3,7,3)
45        self.oggetto0.setScale(0.15,0.15,0.15)
46 
47        self.oggetto0_0 = self.loader.loadModel("models/cubo_giallo.egg")
48        self.oggetto0_0.reparentTo(self.render)
49        self.oggetto0_0.setPos(self.oggetto0,-5,10,5)
50        self.oggetto0_0.setScale(0.15,0.15,0.15)
51 
52        self.oggetto0_00 = self.loader.loadModel("models/cubo_rosso.egg")
53        self.oggetto0_00.reparentTo(self.render)
54        self.oggetto0_00.setPos(-5,10,5)
55        self.oggetto0_00.setScale(0.15,0.15,0.15)
56 
57        self.oggetto1 = self.loader.loadModel("models/cubo_rosso.egg")
58        self.oggetto1.reparentTo(self.render)
59        self.oggetto1.setPos(3,7,3)
60        self.oggetto1.setScale(0.15,0.15,0.15)
61        self.oggetto1_1 = self.loader.loadModel("models/cubo_rosso.egg")
62        self.oggetto1_1.reparentTo(self.oggetto1)
63        self.oggetto1_1.setPos(-5,10,5)
64        self.oggetto1_1.setScale(1,1,1)
65 
66        self.oggetto2 = self.loader.loadModel("models/carousel_base.egg.pz")
67        self.oggetto2.reparentTo(self.render)
68        self.oggetto2.setPos(0,7,2)
69        tex = self.loader.loadTexture("models/test_texture.png")
70        self.oggetto2.setTexture(tex,1)
71 
72        self.basePlane = self.loader.loadModel("models/base.egg")
73        self.basePlane.reparentTo(self.render)
74        self.basePlane.setPos(self.oggetto2,(0,0,0))
75        self.basePlane.setScale(5,5,5)
76 
77        self.setBackgroundColor(0,0,255)
78        self.disableMouse()
79        self.camera.reparentTo(self.oggetto2)
80        self.camera.setY(self.camera, -5)
81        self.camera.setZ(self.camera, 5)
82        self.camera.setP(self.camera,-5)
83 
84    self.setFrameRateMeter(True)
85    self.taskMgr.add(self.muoviCarosello,"Muovi Carosello")
86    self.taskMgr.add(self.debugTask, "Task Manager Print")
87 
88    def tastoPremuto(self,key,value):
89        self.keyMap[key] = value
90 
91    def cameraZoom(self, dir, dt):
92        if(dir == "in"):
93            self.camera.setY(self.camera, 10 * dt)
94        else:
95            self.camera.setY(self.camera, -10 * dt)
96 
97    def muoviCarosello(self,task):
98        dt = globalClock.getDt()
99        if( dt > .20):
100            return task.cont
101        if (self.keyMap["w"] is True):
102            self.gestioneGas("up",dt)
103        elif (self.keyMap["s"] is True):
104            self.gestioneGas("down",dt)
105 
106        if (self.keyMap["a"] is True):
107            self.gira("l", dt)
108        elif (self.keyMap["d"] is True):
109            self.gira("r", dt)
110 
111        if(self.keyMap["mouse1"] == True):
112            self.cameraZoom("in", dt)
113        elif(self.keyMap["mouse3"] == True):
114            self.cameraZoom("out", dt)
115 
116        if(self.mouseWatcherNode.hasMouse() == True):
117            mpos = self.mouseWatcherNode.getMouse()
118            self.camera.setP(mpos.getY() * 30)
119            self.camera.setH(mpos.getX() * -30)
120 
121        self.controlloVelocita(dt)
122        self.move(dt)
123 
124        return task.cont
125 
126    def move(self, dt):
127        mps = self.velocita * 1000 / 3600
128        self.oggetto2.setY(self.oggetto2, mps * dt)
129 
130    def debugTask(self,task):
131        print(self.taskMgr)
132        return task.cont
133 
134    def gestioneGas(self, dir, dt):
135        if(dir == "up"):
136            self.gas += .25 * dt
137            if(self.gas > 1 ): self.gas = 1
138        else:
139            self.gas -= .25 * dt
140            if(self.gas < -1 ): self.gas = -1
141 
142    def controlloVelocita(self, dt):
143        tempVel = (self.maxVel * self.gas)
144        if(self.velocita < tempVel):             if((self.velocita + (self.accel * dt)) > tempVel):
145                self.velocita = tempVel
146            else:
147                self.velocita += (self.accel * dt)
148        elif(self.velocita > tempVel):
149            if((self.velocita - (self.accel * dt)) < tempVel):
150                self.velocita = tempVel
151            else:
152                self.velocita -= (self.accel * dt)
153 
154    def gira(self, dir, dt):
155        tempGiro = self.gestGira * (3 -(self.velocita / self.maxVel))
156        if(dir == "r"):
157            tempGiro = -tempGiro
158        self.oggetto2.setH(self.oggetto2, tempGiro * dt)
159 
160app = MyApp()
161app.run()

Analisi

Per prima cosa abbiamo aggiunto l’import di DirectObject, così da poter estendere la nostra App. Le variabili dalla riga 12 alla 16 sono necessarie per la gestione del movimento del carosello.

Alla riga 18 troviamo un dizionario che ci permetterà di filtrare i vari input. Come potete vedere è composto da tutti i valori che vogliamo gestire, cioè la pressione dei tasti WASD più il pulsante sinistro e destro del mouse. Il movimento del mouse è gestito in un altro modo, vedremo più avanti come.

A questo punto dobbiamo specificare gli eventi uno per uno, scrivendo anche il metodo da richiamare una volta verificatosi l’evento. E’ possibile vedere questa implementazione dalla riga 25 fino alla riga 40. In questo caso la funzione è unica e non fa niente altro che aggiornare il nostro dizionario in base al tasto o pulsante premuto. Tra le parentesi quadre sono indicati i parametri da passare alla funzione prima della virgola (questa parte è opzionale, si può anche richiamare una semplice funzione, come facciamo per il pulsante “escape“).

Alla riga 72 possiamo notare che abbiamo aggiunto un nuovo componente, che formerà un piano su cui muoverci. Inoltre abbiamo modificato la telecamera così da farla rimanere ancorata al carosello (riga 79).

Successivamente abilitiamo il visualizzatore del frame rate ed aggiungiamo un task che gestisca il movimento del carosello (“Muovi Carosello”, linea 85).

Ora vediamo di preciso cosa fanno i metodi successivi:

  • tastoPremuto, come già detto in precedenza, aggiorna solamente il nostro dizionario per vedere quali eventi bisogna processare.
  • cameraZoom manipola la posizione della telecamera a seconda se stiamo premendo il tasto sinistro o destro del mouse.
  • muoviCarosello è il task che si occupa di monitorare il movimento del nostro oggetto. In base all’evento processato (controllando quindi nella keyMap) richiama l’opportuna procedura da eseguire. Da notare il differente controllo per muovere la telecamera con il mouse (linee 116-119), il quale cattura l’attuale posizione all’interno della finestra (linea 117) se il mouse si trova nella finestra di Panda3D (linea 116) e la utilizza per ruotare la telecamera (linee 118-119). Per finire controlliamo la velocità dell’oggetto e lo muoviamo.
  • move sposta l’oggetto2 rispetto il suo asse Y (dal punto di vista della nostra telecamera, è la direzione ‘avanti‘), contando il movimento come se fossero metri al secondo.
  • debugTask rimane solo per controllare i task gestiti da Panda3D.
  • gestioneGas regola appunto il “gas” che diamo tramite i tasti ‘W’ ed ‘S’. E’ un numero in virgola che va da un minimo di -1 ad un massimo di 1.
  • controlloVelocita si preoccupa di aggiornare la velocità del carosello e di non andare oltre il limiti da noi prefissati (self.maxVel * self.gas).
  • gira si occupa di ruotare l’oggetto nello spazio ad una certa velocità a seconda del tasto da noi premuto.

Conclusioni

Anche se inizialmente può sembrare un pò dispersivo, i controlli da noi inseriti sono molto semplici e di facile comprensione. Con poche righe di codice abbiamo fatto muovere il nostro oggetto nello spazio, dotandolo di una velocità propria non costante. Nei prossimi esempi continueremo su questa strada, cercando di manipolare scene e programmi più complessi.

Di seguito lascio il codice sorgente:

http://tinyurl.com/6f7d2dn

Press ESC to close