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

# -*- coding: utf-8 -*-

from direct.showbase.ShowBase import ShowBase
from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import *
from sys import exit

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

        self.velocita = 0
        self.gas = 0
        self.maxVel = 100
        self.accel = 25
        self.gestGira = 25

        self.keyMap = {"w" : False,
                       "s" : False,
                       "a" : False,
                       "d" : False,
                       "mouse1" : False,
                       "mouse3" : False}

        self.accept("w", self.tastoPremuto,["w", True])
        self.accept("a", self.tastoPremuto,["a", True])
        self.accept("s", self.tastoPremuto,["s", True])
        self.accept("d", self.tastoPremuto,["d", True])

        self.accept("w-up", self.tastoPremuto,["w", False])
        self.accept("a-up", self.tastoPremuto,["a", False])
        self.accept("s-up", self.tastoPremuto,["s", False])
        self.accept("d-up", self.tastoPremuto,["d", False])

        self.accept("mouse1", self.tastoPremuto, ["mouse1", True])
        self.accept("mouse3", self.tastoPremuto, ["mouse3", True])
        self.accept("mouse1-up", self.tastoPremuto, ["mouse1", False])
        self.accept("mouse3-up", self.tastoPremuto, ["mouse3", False])

        self.accept("escape", exit)

        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.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.basePlane = self.loader.loadModel("models/base.egg")
        self.basePlane.reparentTo(self.render)
        self.basePlane.setPos(self.oggetto2,(0,0,0))
        self.basePlane.setScale(5,5,5)

        self.setBackgroundColor(0,0,255)
        self.disableMouse()
        self.camera.reparentTo(self.oggetto2)
        self.camera.setY(self.camera, -5)
        self.camera.setZ(self.camera, 5)
        self.camera.setP(self.camera,-5)

	self.setFrameRateMeter(True)
	self.taskMgr.add(self.muoviCarosello,"Muovi Carosello")
	self.taskMgr.add(self.debugTask, "Task Manager Print")

    def tastoPremuto(self,key,value):
        self.keyMap[key] = value

    def cameraZoom(self, dir, dt):
        if(dir == "in"):
            self.camera.setY(self.camera, 10 * dt)
        else:
            self.camera.setY(self.camera, -10 * dt)

    def muoviCarosello(self,task):
        dt = globalClock.getDt()
        if( dt > .20):
            return task.cont
        if (self.keyMap["w"] is True):
            self.gestioneGas("up",dt)
        elif (self.keyMap["s"] is True):
            self.gestioneGas("down",dt)

        if (self.keyMap["a"] is True):
            self.gira("l", dt)
        elif (self.keyMap["d"] is True):
            self.gira("r", dt)

        if(self.keyMap["mouse1"] == True):
            self.cameraZoom("in", dt)
        elif(self.keyMap["mouse3"] == True):
            self.cameraZoom("out", dt)

        if(self.mouseWatcherNode.hasMouse() == True):
            mpos = self.mouseWatcherNode.getMouse()
            self.camera.setP(mpos.getY() * 30)
            self.camera.setH(mpos.getX() * -30)

        self.controlloVelocita(dt)
        self.move(dt)

        return task.cont

    def move(self, dt):
        mps = self.velocita * 1000 / 3600
        self.oggetto2.setY(self.oggetto2, mps * dt)

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

    def gestioneGas(self, dir, dt):
        if(dir == "up"):
            self.gas += .25 * dt
            if(self.gas > 1 ): self.gas = 1
        else:
            self.gas -= .25 * dt
            if(self.gas < -1 ): self.gas = -1

    def controlloVelocita(self, dt):
        tempVel = (self.maxVel * self.gas)
        if(self.velocita < tempVel):             if((self.velocita + (self.accel * dt)) > tempVel):
                self.velocita = tempVel
            else:
                self.velocita += (self.accel * dt)
        elif(self.velocita > tempVel):
            if((self.velocita - (self.accel * dt)) < tempVel):
                self.velocita = tempVel
            else:
                self.velocita -= (self.accel * dt)

    def gira(self, dir, dt):
        tempGiro = self.gestGira * (3 -(self.velocita / self.maxVel))
        if(dir == "r"):
            tempGiro = -tempGiro
        self.oggetto2.setH(self.oggetto2, tempGiro * dt)

app = MyApp()
app.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