Comprendere il python in bge – Estendere una classe ed applicarla alla logica di gioco

Nel precedente capitolo abbiamo fatto un accenno ai moduli ed in particolare alle classi. Le classi sono molto frequenti ed importanti nell’ambito della programmazione in quanto permettono di generalizzare i comportamenti di un oggetto.

Un esempio di applicazione pratica in un gioco potrebbe essere quella di un’auto. Il comportamento di un’auto è sempre lo stesso, accelera, frena, sterza, cambia le marce … le auto però hanno allo stesso tempo caratteristiche differenti tra di loro ed il modello, la velocità, il rapporto tra le marce, il tempo di frenata, il consumo delle gomme sono solo alcune delle caratteristiche che differenziano un’auto dall’altra.
Tutto questo viene tradotto a livello di programmazione definendo una classe base “Automobile” dove all’interno della classe è possibile creare delle proprietà (ovvero le caratteristiche dell’automobile) ed i metodi (ovvero il comportamento dell’automobile) .

Le classi hanno metodi parametrizzabili ovvero possiamo passare al comportamento generico dell’oggetto (metodi) le caratterstiche uniche (proprietà) definite nell’oggetto stesso.

Per farvi comprendere meglio il problema, apriamo blender, cambiamo il motore di render in game engine e creiamo un cubo ed un text ed imparentiamo i due oggetti.

classe_img1

Aggiungiamo al cubo due proprietà. la prima, cube_alias, verrà valorizzata con la marca dell’auto mentre la seconda, cube_speed, verrà valorizzata con il valore della velocità che vogliamo dare all’auto.

classe_img2

Aggiungiamo un nuovo blocco di testo per inizializzare la classe che stiamo per andare a scrivere e lo rinominiamo init_auto

# Importiamo alcuni moduli utili per richiamare
# un file esterno all'interno dello script
import os
import sys
import bpy

# Importiamo l'oggetto logic dal modulo bge
from bge import logic

# Recupero il percorso dove abbiamo salvato 
# oppure salveremo gli scripts ed i moduli 
# esterni al file blend
scriptsRoot = bpy.path.abspath("//../scripts/")

# Il percorso degli script viene
# impostato come default così
# da poter importare i moduli
# salvati all'interno di questa cartella

sys.path.append(scriptsRoot)

# Importo il modulo contente la classe
# che andremo a scrivere

import automobile

# Identifico il proprietario
owner = logic.getCurrentController().owner

# dico all'oggetto di acquisire le 
# proprietà ed i metodi della mia classe
owner = automobile.automobile(owner)

# setto le proprietà speed ed alias così da 
# poterle riutilizzare all'interno della
# classe

owner.setAutoSpeed(owner["cube_speed"])
owner.setAutoAlias(owner["cube_alias"])

Creiamo le condizioni di inizializzazione inserendo un sensor Always legato ad un controller python script che lancerà lo script init_auto

classe_img3

creiamo un nuovo blocco di testo in blender game engine oppure apriamo un editor di testo (un buon editor ed anche open source è notepad++) per creare un nuovo file di testo e salviamolo come “automobile.py” .

#Importiamo il modulo bge per accedere 
#agli oggetti ed ai metodi del game engine
import bge
#Definiamo la classe automobile e passiamole 
#come parametro "bge.types.KX_GameObject".
#la nostra classe estenderà quella standard
 
class automobile(bge.types.KX_GameObject):
    # Le proprietà di un oggetto vengono definite
    # subito dopo nel secondo livello di 
    # indentazione e quì gli vengono assegnate
    # dei valori di default
    # dichiaro le proprietà della classe come 
    # private in quanto non serve che siano 
    # visibili al di fuori della classe
    _autoSpeed = 1.0
    _autoAlias = ''
    _aliasObj = None
    
    # Dopo la dichiarazione delle proprietà 
    # vengono definiti i metodi della classe
    # oppure delle funzioni.
        
    def __init__(self, oldowner) :
                
        # metodo standard per inizializzare
        # la classe. Viene invocato solo e 
        # soltanto quando l'oggetto viene 
        # creato in memoria
 
        self.setChildren()
        # è utile, per leggere e scrivere 
        # all'interno di proprietà private
        # e\o protette, creare delle funzioni 
        # di set (scrittura) e get (lettura). 
    def setChildren(self) : 
    
        # funzione che identifica gli oggetti imparentati
        # all'owner e li salva all'interno di una proprietà.
        # questo ci serve per avere sempre a disposizione 
        # gli oggetti imparentati all'owner ed 
        # all'occorrenza, cambiarne le proprietà. 
        # In questo caso l'unico oggetto imparentato è
        # un oggetto di testo che segnalerà al giocatore
        # la marca dell'auto. 
        for oChild in self.children :
            
            self._aliasObj = oChild       
  
        # abbiamo utilizzato un "ciclo for" in quanto 
        # children è un oggetto dizionario che contiene 
        # tutti gli oggetti imparentati all'owner.
        
        # Il python ha questi oggetti contenitori 
        # chiamati dizionari (dictionaries 
        # oppure abbreviato dict) che 
        # utilizzano delle chiavi di puntamento 
        # per recuperare le informazioni salvate
        # in memoria. Blender fa largo uso di 
        # questi oggetti e quindi vi troverete
        # molto spesso a doverli gestire
    def setAutoAlias(self,cAlias) :
        
        # funzione che ci permette di memorizzare all'interno
        # della proprietà l'alias dell'automobile 
        # (eventualmente la marca ) e cambiare la proprietà 
        # text dell'oggetto imparentato
 
        self._autoAlias = cAlias
        self._aliasObj.text = cAlias
    def setAutoSpeed (self,nSpeed) :
    
        # funzione che ci permette di memorizzare all'interno
        # della proprietà la velocità dell'automobile. 
        
        # Notiamo che sia per la funzione setAutoAlias
        # che per questa stessa funzione, passiamo due 
        # parametri. il primo, self, indica l'oggetto
        # in cui siamo. la seconda, è la proprietà 
        # specificata all'interno dell'oggetto e passata
        # alla classe. Vedi script init_auto  
		
        self._autoSpeed = nSpeed

    def moveAuto(self) : 
 
        # Controlliamo se il sensor "Keyboard" 
        # manda un impulso positivo al 
        # controller python che invoca il metodo
        # della classe
 
        if self.sensors["Keyboard"].positive :
 
        # Impostiamo le coordinate spaziali 
        # X,Y,Z dell'array "dLoc" legato 
        # all'actuator "Motion" legato allo 
        # script che invoca questo metodo.
 
        # Notiamo che stò utilizzando il valore 
        # precedentemente salvato nella proprietà
        # _autoSpeed per far muovere l'oggetto 
        # sull'asse Y

               self.actuators["Motion"].dLoc = [0.0, self._autoSpeed, 0.0]
        # Attivo l'actuator "Motion" per fare 
        # muovere l'oggetto

	       self.controllers["Motion"].activate(self.actuators["Motion"])
   
        else :
       
        # in caso di impulso negativo dal 
        # sensor "Keyboard", disattivo l'actuator
        # così da far fermare l'oggetto
	       self.controllers["Motion"].deactivate(self.actuators["Motion"])

Dopo aver creato la classe base per il nostro oggetto, andiamo ad aggiungere tutti i mattoni logici previsti per far muovere sull’asse y il nostro cubo ovvero i sensors Always e keyboard collegati ad actuator motion tramite un’altro script che invocherà il metodo “moveAuto” della classe automobile.

from bge import logic
owner = logic.getCurrentController().owner
owner.moveAuto()

se tutto è impostato correttamente dovreste trovare i logic bricks disposti come in immagine

classe_img4

 

Premiamo “p” per avviare il game engine e verifichiamo che non ci siano errori nei vari script ed una volta che siamo certi che la logica sia corretta, duplichiamo il cubo ed il text 2 / 3 volte creando una specie di griglia di partenza ed associamo alle proprietà di ogni cubo valori differenti.

classe_img5

premendo nuovamente “p”  vediamo che gli oggetti text imparentati al cubo assumeranno i nomi che abbiamo dato all’interno della proprietà cube_alias e premendo il tasto “w” i cubi si muoveranno ad una velocità pari a quella assegnata nella proprietà cube_speed.

La stessa classe, per 5 oggetti differenti, si comporta differentemente in base alle proprietà assegnata ad ogni singolo cubo. Questo ci risparmia tantissimo tempo in termini di programmazione.

Un’altro esempio di applicazione pratica in gioco potrebbe essere quello di un proiettile oppure di un’arma. l’arma avrà come comportamento generico la collisione, dove la collisione sottrarrà punti vita all’oggetto colpito ma avrà caratteristiche differenti come ad esempio il danno di base.

Ricordate sempre che la classe è una di quelle cose che in programmazione viene sviluppata e cresce in base alle esigenze vengono a crearsi nel tempo. Cercate ad ogni modo di avere sempre le idee chiare su cosa fare e su come organizzare proprietà e metodi. Cercate di utilizzare nomi che vi facciano ricordare per cosa viene utilizzata una proprietà od un metodo così vi sarà più semplice manutenere il codice. Ricordate in oltre che il python è un linguaggio abbastanza rigido e necessita una corretta indentazione del codice ed il rispetto di variabili,metodi,proprietà,oggetti “case sensitive” ovvero se chiamo una variabile “cVariabile” sarà riconosciuta dall’interprete sempre e solo come “cVariabile”. Altre forme come “Cvariabile” oppure “cvariabile” non saranno riconosciute valide.

Penso di aver detto tutto il necessario per farvi digerire l’argomento classi e la loro utilità nello sviluppo delle logiche di gioco, potete scaricare da questo link una demo di quanto detto sopra, buon divertimento.