Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Game Development
  2. Programming
Gamedevelopment

Creare un Neon Vector Shooter in jMonkeyEngine: Le Basi

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Cross-Platform Vector Shooter: jMonkeyEngine.
Make a Neon Vector Shooter in jMonkeyEngine: Enemies and Sounds

Italian (Italiano) translation by Chip (you can also view the original English article)

In questa serie di tutorials, vi spiegherò come creare un gioco ispirato a Geometry Wars, usando JMonkeyEngine Il jMonkeyEngine (in breve "jME") è un game engine 3D open source scritto in Java. potete trovare maggiori informazioni sul suo sito o nella mia guida Come imparare jMonkeyEngine.

Anche se il jMonkeyEngine è intrinsecamente un motore di gioco 3D, è possibile comunque crearci giochi 2D.

Post Correlati
Questa serie di tutorial è basata sulla serie di Michael Hoffman che spiega come fare lo stesso gioco in XNA:

I cinque capitoli del tutorial saranno dedicati ad alcuni componenti del gioco:

  1. Inizializzare la scena 2D, caricare e visualizzare alcuni elementi grafici, gestire l'input.
  2. Aggiungere nemici, collisioni ed effetti sonori.
  3. Aggiungere la GUI ed i buchi neri.
  4. Aggiungere qualche effetto particellare spettacolare.
  5. Aggiungere la griglia deformante come sfondo.

Come piccolo assaggio visivo, ecco il risultato finale dei nostri sforzi:


... Ed ecco i nostri risultati dopo questo primo capitolo:


La musica e gli effetti sonori che potete sentire in questi video sono stati creati da RetroModular e potrete leggere come sono stati realizzati qui.

Gli sprites sono di Jacob Zinman-Jeanes, il nostro designer qua su Tuts. Tutto il lavoro può essere trovato scaricando il file sorgente zip.

MonkeyBlaster_1_Sprites
Il font è Nova Square, di Wojciech Kalinowski.

Il tutorial è stato progettato per aiutarti ad imparare le basi del jMonkeyEngine e a creare il primo gioco con esso. Nello sfruttare le caratteristiche del motore, non useremo alcun strumento complicato per migliorare le prestazioni. Ogni volta che ci sarò uno strumento più avanzato per implementare una funzione, collegherò le opportune esercitazioni jME, ma mantenendo il tutorial comunque semplice. Quando guarderete maggiormente a jME, sarete poi in grado di costruire e migliorare la vostra versione di MonkeyBlaster.

Eccoci qui!


Panoramica

Il primo capitolo comprenderà il caricamento delle necessarie immagini, la gestione dell'input, la creazione del movimento della navetta del giocatore e lo sparo.

Per raggiungere questo obiettivo, avremo bisogno di tre classi:

  • MonkeyBlasterMain: La nostra classe principale che contiene il ciclo del gioco e il gameplay di base.
  • PlayerControl: questa classe determinerà il comportamento del giocatore.
  • BulletControl: simile al precedente, questo definisce il comportamento dei nostri proiettili.

Nel corso del tutorial getteremo la base del codice di gameplay nella MonkeyBlasterMain e la gestione gli oggetti sullo schermo principalmente attraverso i controlli e altre classi. Le caratteristiche speciali, come il suono, avranno anche loro delle classi.


Caricamento della navetta del giocatore

Se non avete ancora scaricato il jME SDK, è giunto il momento! Lo si può trovare nella homepage di jMonkeyEngine.

Creare un nuovo progetto (new project) nel jME SDK. Si genererà automaticamente la classe principale, che avrà un aspetto simile a questo:

Inizieremo sovrascrivendo simpleInitApp(). Questo metodo viene chiamato quando si avvia l'applicazione. Questo è il posto per settare tutti i componenti:

In primo luogo dovremo regolare un pò la telecamera dal momento che jME è fondamentalmente un motore di gioco 3D. La vista delle statistiche (stats view) nel secondo paragrafo può essere molto interessante, ma comunque si può togliere così.

Quando si avvia il gioco potete vedere... niente.

Bene, abbiamo bisogno di caricare il giocatore nel gioco! Creeremo un piccolo metodo per gestire il caricamento delle nostre entità:

All'inizio creiamo un nodo che conterrà l'immagine.

Suggerimento: il grafo della scena in jME è costituito da spatials (nodi, immagini, geometrie, e così via). Ogni volta che si aggiunge un qualcosa di spaziale (spatial) al guiNode, diventa visibile nella scena. Useremo il guiNode perché stiamo creando un gioco 2D. È possibile agganciare spatials ad altri spatials e quindi organizzare la scena. Per diventare un vero maestro dello scene graph (il grafo di scena), vi consiglio questo jME scene graph tutorial.

Dopo aver creato il nodo, carichiamo l'immagine e applichiarmo la texture appropriata. Applicare la dimensione giusta per l'immagine è abbastanza facile da capire, ma perché abbiamo bisogno di spostarla?

Quando si carica un'immagine in jME, il centro di rotazione non è nel mezzo, ma piuttosto in un angolo del quadro. Ma possiamo spostare l'immagine per metà della sua larghezza a sinistra e metà della sua altezza verso l'alto, e aggiungerla a un altro nodo. Quindi, quando ruotiamo il nodo principale, l'immagine stessa viene ruotata attorno al suo centro.

Il passo successivo è l'aggiunta di un materiale per l'immagine. Un materiale determina come sarà visualizzata l'immagine. In questo esempio, usiamo il materiale predefinito della GUI e impostare il metodo BlendMode a AlphaAdditive. Questo significa che sovrapponendo le parti trasparenti di più immagini otterremo più luminosità. Questo sarà utile in seguito per effettuare esplosioni 'più brillanti'.

Infine, aggiungiamo l'immagine al nodo e lo restituiamo.

Ora dobbiamo aggiungere il player al guiNode. Estendiamo simpleInitApp un pò:

In breve: carichiamo il player, configuriamo alcuni dati, spostiamolo al centro dello schermo, e agganciamolo al guiNode per renderlo visibile.

UserData è semplicemente per collegare un pò di dati a qualsiasi spaziale. In questo caso aggiungiamo un valore booleano e lo chiamiamo alive (vita), in modo da poter vedere in alto se il giocatore è vivo. Lo useremo più tardi.

Ora, avviamo il programma! Si dovrebbe vedere il giocatore in mezzo. Al momento è abbastanza noioso, lo ammetto. Allora aggiungiamo un pò di azione!


Gestione dell'Input e movimento del Player

L'input in jMonkeyEngine è abbastanza semplice dopo poco che lo si usa. Iniziamo implementando un Action Listener:

Ora, per ogni tasto, aggiungeremo la mappatura dell'input e un listener (ascoltatore) in simpleInitApp():

Ogni volta che un tasto viene premuto o rilasciato, il metodo onAction viene chiamato. Prima di entrare nel merito di cosa fare nella pratica quando si preme qualche tasto, abbiamo bisogno di aggiungere un controllo al nostro giocatore.

Info: I Controls (controlli) rappresentano alcuni comportamenti degli oggetti nella scena. Ad esempio, è possibile aggiungere una FighterControl e un IdleControl ad una IA dei nemici. A seconda della situazione, è possibile attivare/disattivare o attaccare/staccare controlli.

Il nostro PlayerControl semplicemente si prenderà cura di spostare il giocatore ogni volta che premeremo un tasto, ruotandolo nella direzione giusta e assicurandosi che il giocatore non lasci lo schermo.

Ecco qui:

Va bene; ora diamo uno sguardo al codice pezzo per pezzo.

In primo luogo, inizializziamo alcune variabili, definite in quale direzione e quanto velocemente il giocatore si sta muovendo, e fino a che punto ruota. Poi, impostiamo screenWidth (larghezza di schermo) e screenHeight (altezza dello schermo), ne avremo bisogno nel prossimo metodo.

controlUpdate(float tpf) viene chiamato automaticamente dal jME ad ogni ciclo di aggiornamento. La variabile tpf indica il tempo dopo l'ultimo aggiornamento. E' necessario per controllare la velocità: se alcuni computer richiedono il doppio del tempo per calcolare un aggiornamento rispetto ad altri, il giocatore deve muoversi due volte più lontano per un singolo aggiornamento in tali computer.

Ora per la prima istruzione if:

Verifichiamo se il giocatore sta salendo e, in caso affermativo, controlliamo se può salire ulteriormente. Se è abbastanza lontano dal bordo, dobbiamo semplicemente spostarlo verso pò più in alto.

Ora sulla rotazione:

Ruotiamo il giocatore indietro di LastRotation e puntando nella sua direzione originale. Siamo in grado, da questa direzione, di ruotare il giocatore nel verso in vogliamo che guardi. Infine, salviamo la rotazione effettiva.

Usiamo lo stesso tipo di logica per tutte e quattro le direzioni. Il metodo di reset() è qui solo per impostare tutti i valori a zero ancora una volta, da utilizzare per respawning del giocatore.

Così, finalmente abbiamo il controllo per il player. È il momento di aggiungerlo allo spatial attuale. Basta aggiungere la seguente riga al metodo simpleInitApp():

L'oggetto settings viene incluso nella classe SimpleApplication. Contiene i dati relativi alle impostazioni di visualizzazione del gioco.

Se lanciamo il gioco adesso, non accade ancora nulla. Dobbiamo dire al programma cosa fare quando viene premuto uno dei tasti mappati. Per fare questo, facciamo un override del metodo onAction:

Per ogni tasto premuto, diciamo al PlayerControl il nuovo stato del tasto. Ora è finalmente giunto il momento di avviare il nostro gioco e vedere qualcosa che si muove sullo schermo!

Felici di capire le basi degli input e la gestione dei comportamenti che ne seguono, è il momento di fare la stessa cosa, questa volta, per i proiettili.


Aggiungiamo gli spari

Se vogliamo vedere qualche reale azione, abbiamo bisogno di poter sparare ai nemici. Seguiremo la stessa procedura di base, come nel passaggio precedente: gestione dell'input, creazione di qualche proiettile e l'aggiunta del loro comportamento.

Al fine di gestire l'input del mouse, implementeremo un altro listener:

Prima che accada qualcosa, abbiamo bisogno di aggiungere la mappatura ed il listener come abbiamo fatto l'ultima volta. Lo faremo nel metodo simpleInitApp(), a fianco dell'altra inizializzazione degli input:

Ogni volta che si clicca con il mouse, il metodo onAnalog viene chiamato. Prima iniziare a sparare abbiamo bisogno di implementare un piccolo metodo a supporto, Vector3f getAimDirection(), che ci darà la direzione dello sparo sottraendo la posizione del giocatore da quella del mouse:

Suggerimento: Quando si agganciano oggetti al guiNode, l'unità di movimento locale è pari a un pixel. Questo rende per noi più facile calcolare la direzione, dal momento che anche la posizione del cursore è specificata in unità di pixel.

Ora che abbiamo una direzione verso cui sparare, cerchiamo implementare gli spari:

Okay, allora, proseguiamo:

Se il giocatore è vivo e il pulsante del mouse viene premuto, il nostro codice prima controlla se l'ultimo colpo è stato sparato almeno 83 ms fa (bulletCooldown è una variabile long inizializzata nell'inizializzazione della classe). Se è così, possiamo sparare e calcoliamo la direzione giusta per prendere la mira e l'offset.

Vogliamo rilasciare due proiettili gemelli, uno accanto all'altro, quindi dovremo aggiungere un pò di offset a ciascuno di essi. Un adeguato offset è ortogonale alla direzione dello sparo, che è facilmente ottenibile commutando il valori di x e y e negando (n.d.a. cambiargli il segno) uno di essi. Il secondo sarà semplicemente una negazione del primo.

Il resto dovrebbe apparire piuttosto familiare: inizializziamo il proiettile utilizzando il nostro metodo di getSpatial fin dall'inizio. Poi dobbiamo traslarlo al posto giusto e collegarlo al nodo. Ma aspetta, quale nodo?

Organizzeremo le nostre entità in nodi specifici, quindi ha senso creare un nodo a cui potremo collegare tutti i nostri proiettili. Per visualizzare i figli di quel nodo dovremo agganciarlo al guiNode.

L'inizializzazione nel simpleInitApp() è piuttosto semplice:

Se andiamo avanti e avviamo il gioco, sarete in grado di vedere i proiettili apparire, ma non si muovono! Se desiderate verificare da voi, fermatevi di leggere e pensate da soli a quello che dobbiamo fare per farli muovere.

...

Capito?

Abbiamo bisogno di aggiungere un controllo ad ogni proiettile che si prenda cura del suo movimento. Per farlo, creeremo un'altra classe chiamata BulletControl:

Un rapido sguardo alla struttura della classe mostra che è abbastanza simile alla classe del PlayerControl. La differenza principale è che non abbiamo alcun tasto da controllare e non abbiamo una variabile direction. Dobbiamo semplicemente spostare il proiettile nella sua direzione e ruotarlo di conseguenza.

Nel l'ultimo blocco controlliamo se il proiettile è al di fuori dei limiti dello schermo e, in caso affermativo, lo togliamo dal suo nodo padre per eliminare l'oggetto.

Potreste aver preso questa chiamata di metodo:

Si riferisce ad un breve metodo statico di supporto matematico nella classe principale. Ne ho creato due, una converte un angolo in un vettore nello spazio 2D e l'altro converte i vettori in un valore di angolo.

Suggerimento: Se vi sentite abbastanza confusi da tutte quelle operazioni vettoriali, fatevi un favore scavando qualche tutorial sulla matematica vettoriale. E' essenziale sia nello spazio 2D che 3D. Già che ci siete, dovreste cercare anche la differenza tra gradi e radianti. E se desiderate approfondire di più la programmazione di un gioco 3D, anche i quaternioni sono impressionanti...

Ora torniamo alla panoramica principale: abbiamo creato un input listener (gestore dell'input), inizializzato due proiettili, e creato una classe per il controllo del proiettile BulletControl. L'unica cosa rimasta è aggiungere un BulletControl ad ogni proiettile durante l'inizializzazione:

Ora il gioco è molto più divertente!



Conclusioni

Anche se non è granché volare in giro e sparare qualche proiettile, almeno possiamo fare qualcosa. Ma non disperate, col prossimo tutorial avrete più difficoltà cercando di sfuggire alle crescenti orde di nemici!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.