Randomizzazione 3 – Python

Come annunciato alla fine dell’articolo precedente andrò a spiegare com’è possibile randomizzare la scena usando uno script Python. Il sistema a Mattoni è efficacie e semplice da implementare, ma come già detto su alcune righe degli articoli già scritti su questo tema ‘se si hanno numerosi oggetti è tedioso inserire Mattoni su Mattoni’. Lo script che vedrai oggi non fa altro che replicare in automatico quello che fanno i Mattoni, l’unica differenza è quella sopra citata.

Teoria

Come per la procedura a Mattoni anche lo script avrà un numero randomizzato in automatico da Blender e sempre in automatico verrà creata una stringa per l’inserimento dell’oggetto desiderato. In questo tutorial, infatti, userò un modulo di Python che si chiama random da cui estrapolerò soltanto la funzione randint, questo per evitare che Blender debba caricare tutte le righe del modulo. Tale funzione randomizza un numero integer (intero, senza vigola).

Note

Se si vuole approfondire l’argomento sulla funzione random si può leggere questa pagina. Ti ricordo che su questo sito non è presente un corso di Python. Se non sai come funziona il Python all’interno di Blender ti consiglio di dare un’occhiata alla pagina Blender e Python o alla categoria sul Python dove troverai alcuni articoli sul linguaggio inerenti al motore di gioco di Blender. Per questo tutorial puoi, come sempre, usare un tuo file oppure seguire passo passo col file che puoi scaricare QUA che è lo stesso usato nell’articolo precedente (quindi puoi anche usare quello) soltanto che non ha i Mattoni.

Preparazione del file

Una volta scaricato il file ti trovi davanti alla scena principale con il player, un piano e la empty a cubo, oltre ovviamente agli oggetti obbligatori che sono telecamera e luci senza il quale la scena o non viene inizializzata o risulta nera. Sull’Outliner (finestra di sinistra) trovi tutti gli oggetti della scena, compresi quelli sui diversi layer. Una cosa fondamentale per la riuscita dello script è il nome degli oggetti che verranno inseriti, no questa volta non ti dirò che rinominare gli oggetti è vitale ma che rinominarli in modo corretto è essenziale, questo perché il numero random che verrà creato sarà inserito nella parte finale del nome dell’oggetto, quindi è importantissimo rinominare gli oggetti in modo appropriato. Naturalmente ho fatto diverse prove prima di scrivere questo articolo e ho riscontrato che :

  • Usare nomi troppo lunghi non è consigliato.
  • Usare lo zero come prefisso del numero, per esempio piano_01, piano_02 ecc ecc, è buono solo se si hanno 10 oggetti, questo comprende l’oggetto piano_00 (questi sono solo esempi)
  • Usare numeri interi è la migliore soluzione, anche perché si lavora con una integer randomizzata, questo se si hanno nel proprio file oggetti di numero superiore a 10.

Iniziamo come sempre dai tasselli che compongono il piano di gioco, se conosci il file sai che si trovano nel secondo layer; se noti bene gli oggetti hanno il nome floor_ e un numero con il prefisso 0, come detto prima non sarebbe una buona procedura se si hanno più di 10 oggetti, ma supponiamo che gli oggetti che servono a noi sono questi, e non di più, quindi i nomi vanno benissimo. Questo tutorial sarà diviso in due parti principali (oltre a questa lunga prefazione), nella prima parte mi occuperò di inserire i piani o tasselli per creare la mappa, nella seconda parte (quella più interessante) inserirò degli oggetti statici, tramite una Empty (come nell’articolo precedente) ma questa volta la Empty apparirà sul piano di gioco in maniera random, cioè verrà posizionata in un intervallo XY in modo da fare apparire l’oggetto in maniera casuale sul terreno di gioco. Gli oggetti da inserire si chiamano obj_ e un numero, come puoi vedere il primo oggetto non ha un numero, in questo modo l’oggetto potrebbe non apparire mai sul piano di gioco, quindi rinominalo aggiungendo _0, puoi anche cambiare il numero degli oggetti dopo l’underscore, da 0,1,2,3 a 1,2,3,4 questo non incide sull’andamento dello script. Ma iniziamo.

Randomizzare i tasselli

La prima cosa da fare è dividere verticalmente il menù della Proprietà di destra e cambiare la nuova finestra nel Text Editor. Crea un nuovo script e rinominalo come meglio credi, io l’ho chiamato random_floor.py. Scriverò prima lo script, spiegandolo, e poi passeremo a inserire i Mattoni che servono. Come sempre in alto devi scrivere le varie chiamate e le variabili globali che servono a fare funzionare lo script, in questo caso abbiamo qualche riga in più

import bge
from random import randint

### variabili globali
scene = bge.logic.getCurrentScene()
cont = bge.logic.getCurrentController()
spawner = cont.owner
  • Alla riga 1 importo il modulo bge.
  • Alla riga 2 importo dal modulo random solo la funzione randint.
  • Alla riga 5 richiamo la scena corrente, per poter più avanti usare una delle sue funzioni.
  • Alla riga 6 e 7 ci sono le solite variabili globali del controller e del proprietario del controller.
# random integer 0 - 5
num_random = randint(0,5)

Su questa riga dichiaro una variabile num_random e gli assegno il risultato della funzione randint che prevede due parametri uno iniziale e uno finale, infatti il risultato della funzione sarà un numero tra 0 e 5 (in questo caso specifico, se abbiamo più oggetti possiamo impostare un’intervallo diverso). E’ la stessa procedura che usa l’Actuator Random. Se vuoi puoi fare una prova stampando nella console la variabile num_random. Scrivi nella riga sotto print (num_random), fai in modo che la console di sistema sia a vista e fai partire il motore di gioco diverse volte. Blender ti mostrerà il numero che esce dalla funzione randint compreso da 0 e 5. Questa riga non è importante ai fini dello script, quindi puoi aggiunge davanti al print il simbolo # per commentare la riga e quindi evitare che venga processata.

A questo punto dello script abbiamo bisogno di altre due variabili, la prima è il prefisso con il nome dell’oggetto, in questo caso (qua ritorna il fattore rinominare appropriatamente gli oggetti che si vuole fare apparire) i nostri oggetti hanno un prefisso floor_0 e un numero sequenziale, quindi inserisci la seguente riga nello script

# nome dell'oggetto da inserire
obj = "floor_0"

Ho assegnato alla variabile obj una stringa con il prefisso dei tasselli. Adesso devo solo assegnare ad un’altra variabile il risultato della somma delle due variabili precedenti in modo da completare il nome dell’oggetto che verrà inserito.

# somma delle due variabili
obj_spawn = (obj + str(num_random))

Dichiaro un’altra variabile obj_spawn ad essa assegno il risultato della somma di obj + la variabile num_random, che come sai contiene il risultato della randomizzazione di un numero, sotto forma di stringa, infatti la funzione str converte i numeri in stringhe di testo, in questo modo la variabile appena dichiarata obj_spawn è una stringa (perché la somma di due stringhe da come risultato una stringa) con il nome completo di uno dei tasselli, specificando il risultato è floor_0 + 2, oppure floor_0 + 0, ecc ecc, dipende dal risultato del numero randomizzato.

Ultimo passo è quello di aggiungere l’oggetto appena creato dalla fusione delle due variabili alla scena. Per farlo uso una funzione del modulo scene richiamato all’inizio nelle variabili globali

# inserisco l'oggetto
scene.addObject(obj_spawn, spawner, 0)

Questa riga richiama il modulo e la funzione addObject che richiede 3 parametri, il primo è l’oggetto da inserire (che come sai è ricavato dalla fusione di due variabili), il nome dell’oggetto che deve fare apparire l’oggetto nel primo parametro (in questo caso la Empty) e l’ultimo parametro è il tempo di vita dell’oggetto inserito in frame, 0 è di default ed è l’infinito.
Lo script è pronto, per verificare se funziona inserisci alla Empty un Sensor Always e collegalo ad un Controller Python, scegli l’unico script presente nella lista. Adesso salva e fai partire il motore di gioco diverse volte, come puoi vedere la Empty fa apparire un tassello diverso ad ogni avvio, esattamente come per la versione a Mattoni, ma con una riduzione di essi decisamente notevole, sopratutto se nel tuo gioco hai parecchi oggetti da fare apparire. Per finire non ti resta che duplicare la Empty quante volte vuoi, questo dipende da quanto grande vuoi che sia la tua mappa. Ti mostro il risultato della mia mini mappa randomizzata. Naturalmente è solo un esempio.

Randomizzare la posizione

Come anticipato in questa seconda parte vedrai come posizionare la Empty che crea gli oggetti in modo casuale nel tassello. Questa tecnica è utile per poter randomizzare in modo del tutto casuale la posizione sugli assi XY del piano, naturalmente questa parte è strettamente legata a questo esempio specifico, ma questa tecnica può essere usata in vari modi, per esempio per fare apparire oggetti in modo sparso nella mappa. Ma iniziamo.

Come fatto nell’esempio precedente scriverò e spiegherò per prima lo script e poi inserirò Mattoni e oggetti. Una cosa importantissima da sapere è la dimensione del tassello, puoi ottenere questo dato dalla Property Shelf premendo il tasto N con il curosore del mouse sulla 3D View, il dato lo trovi sotto la voce Dimensions sulla tab Transform, come mostro nella figura seguente

Come vedi la dimensione del nostro piano (il tassello) è di 16 Blender Unit, non ha importanza la dimensione reale possono essere 16m come 16cm per questo esempio non fa testo basta che tu sappia le dimensioni di XY dell’oggetto. Una volta raccolto il dato puoi creare un nuovo script, io l’ho chiamato spawner_rand_pos.py, in alto allo script uso le stesse chiamate dello script precedente

import bge
from random import randint

Uso una integer (numero intero) perché voglio che la Empty si sposti nella griglia e perché è più facile da gestire di una float (numeri con virgola). Scrivo le variabili globali

### variabili globali
cont = bge.logic.getCurrentController()
empty = cont.owner

Il punto principale dello script è dato dalla randomizzazione di due numeri, uno per l’asse X e uno per quella Y, con la funzione randint che come sai richiede due parametri (per ogni asse). Apro una piccola parentesi per farti capire come si scelgono i due numeri da inserire, in modo che se il tuo tassello ha una dimensione diversa da quella di questo esempio sai come gestire il tutto senza dover impazzire.
Come sai il tassello ha una dimensione di 16 unità, siccome la Empty verrà imparentata al tassello (come nei tutorial precedenti) il punto di origine del tassello (pivot) diventa l’origine del mondo per la Empty, questa è una regola costante nelle parentele ” l’origine del mondo per il figlio è il pivot del padre “, quindi se l’origine è il pivot del piano (il punto arancione che si trova al centro del piano quando viene selezionato) le coordinate X e Y saranno -8 e 8 in entrambe le assi (8+8 fa 16), questo sposterà la Empty fino al bordo del piano nelle due direzioni. Torniamo allo script.

Siccome la Empty creerà degli oggetti che hanno una dimensione, gli oggetti si trovano nel quarto layer, tale dimensione deve essere tenuta in considerazione per il risultato finale, ma per ora lasciamo i parametri come li abbiamo calcolati, ovvero -8 e 8, il codice è questo

# random integer per la pos di X e Y
x_random = randint(-8, 8)
y_random = randint(-8, 8)

Ho dichiarato due variabili e assegnatogli il risultato di due randomizzazione di interi con i parametri sopra citati. Nel commento ho scritto che sono delle integer per la pos (posizione), in realtà non lo sono, sono semplicemente due numeri casuali, ma per questa specifica tecnica lo diventeranno. Adesso non devo fare altro che inserire i due numeri nella funzione localPosition per l’oggetto proprietario, la Empty, così

# imposto la posizione usando i num random
empty.localPosition = [x_random, y_random, 0.0]

Con questa riga dico a Blender che la posizione locale dell’oggetto Empty è uguale alle due variabili estratte casualmente e alla posizione 0.0 sull’asse Z, questo ultimo dato è esclusivamente legato a dove si trova il pivot dei miei oggetti, se vai sul layer 4 e selezioni gli oggetti puoi notare che il pivot si trova nella coordinata 0.0 dell’asse Z. Fai molta attenzione a questo parametro perché se usi un valore non consono gli oggetti possono apparire più in alto o più in basso. Lo script è completo.

Passa al layer 2 e inserisci una Empty (il proprietario dello script), io l’ho rinominata obj_spawner, aggiungi un Sensor Always e un Controller Python e seleziona lo script appena creato, collegali tra di loro. Per ora hai solo randomizzato la posizione della Empty obj_spawner, il passo successivo è quello di richiamare in maniera random uno degli oggetti del layer 4, per farlo basta duplicare lo script usato per i tasselli e cambiare il nome degl’oggetti che verranno creati, il valori da randomizzare e il nome del creatore, anche se non è necessario.
Quindi crea un’altro script, io l’ho chiamato obj_spawn.py, copia lo script spawn_floor.py per intero e incollalo nel nuovo script. Nella riga con il commento # nome dell’oggetto da inserire cambia il nome da floor_0 a obj_ questo perché gli oggetti hanno il prefisso senza lo 0, cambia il secondo esponente della randomizzazione da 5 a 3, perché gli oggetti hanno un numero sequenziale da 0 a 3, lo script sarebbe pronto, per evitare confusione, come detto prima, rinomino il proprietario dello script da spawner a obj_spawner, tanto per essere preciso. Lo script finale è questo

import bge
from random import randint

### variabili globali
scene = bge.logic.getCurrentScene()
cont = bge.logic.getCurrentController()
obj_spawner = cont.owner

# random integer 0 - 5
num_random = randint(0,3)
#print (num_random)

# nome dell'oggetto da inserire
obj = "obj_"

# somma delle due variabili
obj_spawn = (obj + str(num_random))

# inserisco l'oggetto
scene.addObject(obj_spawn, obj_spawner, 0)

Aggiungi un’altro Controller Python e seleziona lo script appena creato e collegalo al Sensor Always già presente, in questo modo nello stesso momento che obj_spawner prende una posizione random crea anche un’oggetto random scegliendolo tra i quattro del layer 4. L’ultimo passo è imparentare la obj_spawner ad uno dei tasselli, puoi creare altre Empty e assegnargli gli script spawn_rand_pos.py e obj_spawner.py in modo da imparentarli ad altri tasselli e avere più oggetti nella scena, o duplicare quello esistente e ottenere più oggetti nello stesso tassello. La questa tecnica è ottima per randomizzare oggetti, nemici e quanto altro in modo quasi autonomo, perché non è un sistema procedurale, cioè un’algoritmo che crea gli oggetti e la mappa in maniera totalmente casuale, come in molti giochi, per fare una mappa procedurale c’è bisogno di un’alta conoscenza del codice e del motore di gioco, a me manca la prima, come sai e come ho detto altre volte non sono un programmatore.

Con questo articolo corposo si chiude qua la serie dedicata alla randomizzazione, come sempre fai molta pratica, sperimenta e sopratutto cerca di fare tue le informazioni lette e di usarle al meglio.