Sviluppare un gioco in Python: Panda3D – task manager

Introduzione

In questo articolo parleremo del Task Manager di Panda3D. Infatti il nostro Panda gestisce gli eventi in modo particolare, quasi come fossero gli eventi del nostro OS (è solo una similitudine, non prendetela alla lettera).

Questa parte è fondamentale per introdurre successivamente la gestione dei vari input e delle animazioni. Nell’esempio effettueremo solo delle rotazioni dei poligoni che abbiamo già inserito, ma potete anche provare a spostare uno di essi.

Il task manager è messo a disposizione da Showbase o da DirectStart, ma tecnicamente è un modulo che può essere importato da direct.task. Questo perché è possibile creare più task manager, ma noi utilizzeremo quello creato di default per gestire la scena di base.

Gli eventi, o tasks, sono gestiti come se fossero dei thread, ma vengono eseguiti tutti uno alla volta, una volta per ciclo, all’interno dell’evento main. Questo semplifica la gestione di essi e si elimina il problema dell’accesso simultaneo.

Le funzioni che devono essere definite come task, hanno bisogno di un parametro. Quest’ultimo è necessario per effettuare operazioni come far continuare l’evento al succesivo ciclo, terminare l’evento, controllare da quanto tempo il task è in esecuzione nell’ultimo ciclo ecc…

Detto questo, passiamo al codice ed analizziamo con dei semplici esempi quello che è stato detto nell’introduzione.

Codice


from direct.showbase.ShowBase import ShowBase
from pandac.PandaModules import *

class MyApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        self.oggetto0 = self.loader.loadModel("models/cubo_giallo.egg")
        self.oggetto0.reparentTo(self.render)
        self.oggetto0.setPos(-3,7,3)
        self.oggetto0.setScale(0.15,0.15,0.15)

        self.oggetto0_0 = self.loader.loadModel("models/cubo_giallo.egg")
        self.oggetto0_0.reparentTo(self.render)
        self.oggetto0_0.setPos(self.oggetto0,-5,10,5)
        self.oggetto0_0.setScale(0.15,0.15,0.15)

        self.oggetto0_00 = self.loader.loadModel("models/cubo_rosso.egg")
        self.oggetto0_00.reparentTo(self.render)
        self.oggetto0_00.setPos(-5,10,5)
        self.oggetto0_00.setScale(0.15,0.15,0.15)
        #self.oggetto0_00.setHpr(50,50,50)

        self.oggetto1 = self.loader.loadModel("models/cubo_rosso.egg")
        self.oggetto1.reparentTo(self.render)
        self.oggetto1.setPos(3,7,3)
        self.oggetto1.setScale(0.15,0.15,0.15)
        self.oggetto1_1 = self.loader.loadModel("models/cubo_rosso.egg")
        self.oggetto1_1.reparentTo(self.oggetto1)
        self.oggetto1_1.setPos(-5,10,5)
        self.oggetto1_1.setScale(1,1,1)

        self.oggetto2 = self.loader.loadModel("models/carousel_base.egg.pz")
        self.oggetto2.reparentTo(self.render)
        self.oggetto2.setPos(0,7,2)
        tex = self.loader.loadTexture("models/test_texture.png")
        self.oggetto2.setTexture(tex,1)

        self.setBackgroundColor(0,0,255)
        self.disableMouse()
        self.useDrive()

        self.taskMgr.doMethodLater(3,self.moveOg2,"Muovi Tutto")
        self.taskMgr.doMethodLater(3,self.moveOgDone,"Muovi Cubo Figlio")
        self.taskMgr.doMethodLater(3,self.moveOgStop,"Muovi Cubo e Stop")
        self.taskMgr.doMethodLater(0.25,self.moveOgAgain,"Muovi Carosello")
        self.taskMgr.doMethodLater(7,self.rimuviTsk,"Rimuvi Task")
        self.taskMgr.doMethodLater(13,self.rimuviTskALL,"Rimuvi Task ALL",uponDeath = self.afterdeath)
        self.taskMgr.add(self.debugTask, "Task Manager Print")

    def rimuviTsk(self,task):
        self.taskMgr.remove("Muovi Cubo e Stop")
        return

    def rimuviTskALL(self,task):
        self.taskMgr.removeTasksMatching("Muovi *")
        return

    def debugTask(self,task):
        print(self.taskMgr)
        return task.cont

    def moveOgAgain(self,task):
        dt = globalClock.getDt()
        if dt > .20 :
            return task.cont
        self.oggetto2.setH(self.oggetto2,100*dt)
        return task.again

    def moveOgDone(self,task):
        dt = globalClock.getDt()
        if dt > .20 :
            return task.cont
        self.oggetto1_1.setHpr(self.oggetto1_1,250)
        return task.done #Equivalente a regurn

    def afterdeath(self,task):
        self.taskMgr.add(self.moveOg, "AFTER")

    def moveOg(self,task):
        dt = globalClock.getDt()
        if dt > .20 :
            return task.cont
        print(dt)
        self.oggetto2.setH(self.oggetto2,100*dt)
        return task.cont

    def moveOg2(self,task):
        dt = globalClock.getDt()
        if dt > .20 :
            return task.cont
        print(dt)
        self.oggetto0.setP(self.oggetto0,10*dt)
        self.oggetto1.setR(self.oggetto1,10*dt)
        self.oggetto0_0.setHpr(self.oggetto0_0,10*dt)
        return task.cont

    def moveOgStop(self,task):
        self.oggetto0_00.setHpr(self.oggetto0_00,10)
        return task.cont

app = MyApp()
app.run()

Analisi

Il codice iniziale è stato già analizzato nella precedente puntata, le novità iniziano dalla linea 43. Vediamo infatti che richiamiamo il metodo DoMethodLater del task manager. Questo metodo prende vari parametri, tra i quali il tempo di attesa prima di chiamare la funzione, la funzione da richiamare ed il nome che vogliamo dare al task.

Il metodo add invece, aggiunge semplicemente il task da noi definito, passandogli il metodo ed il nome con cui vogliamo chiamare l’evento.

Ora vediamo in dettaglio i vari metodi creati che definiranno i nostri task:

  • rimuoviTsk : questo metodo rimuove il task che ha come nome “Muovi Cubo e Stop”. In questo modo fermerà il movimento del cubo in questione. Ritornando semplicemente None, il task non verrà eseguito al prossimo ciclo. Il che equivale a scrivere return task.done.
  • rimuoviTaskALL : elimina tutti i task che iniziano con la parola “Muovi “.
  • debugTask : qui stampiamo sulla consolle lo stato del task manager, per tenere conto delle operazioni eseguite. L’evento ritorna task.cont perché vogliamo che al prossimo ciclo il task non venga ucciso, così l’evento verrà riprocessato.
  • moveOgAgain : muove il nostro carosello, ma ritorna task.again. Questo significa che al prossimo ciclo sarà richiamato con lo stesso tempo di attesa con cui è stato avviato, nel nostro caso 0.25. Ecco perché il movimento del carosello inizialmente è a tratti e non continuo. Stampiamo anche il delta time su consolle.
  • moveOgDone : questo metodo muove il “cubo rosso figlio” sulla destra e poi si interrompe. Non verrà più processato ed infatti quel cubo rimarrà fermo (per modo di dire, vedremo tra poco perché).
  • afterdeath : è un metodo che viene richiamto dopo che l’evento “Rimuvi Task ALL” è concluso ed eliminato (ucciso) dal task manager. In questo caso si aggiunge un nuovo evento al task manager per poi ritornare None (da notare che questa volta non abbiamo scritto nulla come ritorno, ma l’evento verrà ucciso come se scrivessimo task.done).
  • moveOg : questa funzione viene chiamata dopo che “Rimuvi Task ALL” è stato ucciso ed avrà l’effetto di ruotare il carosello in senso antiorario in continuità. Da notare che prendiamo dall’orologio globale il tempo che intercorre tra un frame e l’altro. Questo perché vogliamo che il movimento dei nostri poligoni risulti uguale (in velocità) su tutte le macchine e che quindi la velocità di esecuzione non dipenda solo dalla potenza di calcolo dell’hardware.
  • moveOg2 : valgolo le stesse cose dette per moveOg, soltanto che il metodo muove dei differenti oggetti.
  • moveOgStop : questa funzione muove un oggetto senza “sincronizzarlo” con il delta time, ovvero il tempo che intercorre tra un frame e l’altro. Noteremo quindi che un cubo ruoterà più velocemente degli altri.

Conclusioni

Come noterete, il cubo figlio è legato ai movimenti del padre. Questo significa che, anche se l’evento che lo ha ruotato è concluso ed è stato eliminato, deve per forza seguire la rotazione del cubo padre.

Un’altra cosa che dovete notare è la differenza di velocità del cubo non sincronizzato, che smetterà di muoversi quando verrà eseguito il task rimuoviTsk.

E’ stato ancora più semplice eliminare tutti gli eventi che riguardano un movimento tramite rimuoviTskALL, lasciando alla fine solo il carosello che ruota.

L’esempio mi sembra abbastanza completo per capire questi concetti ed il funzionamento del task manager che, non dimenticate, può essere anche più di uno.

Nei prossimi capitoli vedremo come gestire gli input, così da poter interagire con le nostre creazioni e colloquiare con l’utente.

Di seguito rilascio i sorgenti:

http://tinyurl.com/5rzb7c7

Press ESC to close