Advertisement
  1. Game Development
  2. Programming

Creare un Neon Vector Shooter in jMonkeyEngine: Le Basi

Scroll to top
Read Time: 16 min
This post is part of a series called Cross-Platform Vector Shooter: jMonkeyEngine.
Make a Neon Vector Shooter in jMonkeyEngine: Enemies and Sounds

() translation by (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_SpritesMonkeyBlaster_1_SpritesMonkeyBlaster_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:

1
package monkeyblaster;
2
3
import com.jme3.app.SimpleApplication;
4
import com.jme3.renderer.RenderManager;
5
6
public class MonkeyBlasterMain extends SimpleApplication {
7
8
    public static void main(String[] args) {
9
        Main app = new Main();
10
        app.start();
11
    }
12
13
    @Override
14
    public void simpleInitApp() {
15
16
    }
17
18
    @Override
19
    public void simpleUpdate(float tpf) {
20
21
    }
22
23
    @Override
24
    public void simpleRender(RenderManager rm) {
25
26
    }
27
}

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

1
    @Override
2
    public void simpleInitApp() {
3
//        setup camera for 2D games

4
        cam.setParallelProjection(true);
5
        cam.setLocation(new Vector3f(0,0,0.5f));
6
        getFlyByCamera().setEnabled(false);
7
8
//        turn off stats view (you can leave it on, if you want)

9
        setDisplayStatView(false);
10
        setDisplayFps(false);
11
    }

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à:

1
    private Spatial getSpatial(String name) {
2
        Node node = new Node(name);
3
//        load picture

4
        Picture pic = new Picture(name);
5
        Texture2D tex = (Texture2D) assetManager.loadTexture("Textures/"+name+".png");
6
        pic.setTexture(assetManager,tex,true);
7
8
//        adjust picture

9
        float width = tex.getImage().getWidth();
10
        float height = tex.getImage().getHeight();
11
        pic.setWidth(width);
12
        pic.setHeight(height);
13
        pic.move(-width/2f,-height/2f,0);
14
15
//        add a material to the picture

16
        Material picMat = new Material(assetManager, "Common/MatDefs/Gui/Gui.j3md");
17
        picMat.getAdditionalRenderState().setBlendMode(BlendMode.AlphaAdditive);
18
        node.setMaterial(picMat);
19
20
//        set the radius of the spatial

21
//        (using width only as a simple approximation)

22
        node.setUserData("radius", width/2);
23
24
//        attach the picture to the node and return it

25
        node.attachChild(pic);
26
        return node;
27
    }

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ò:

1
//        setup the player

2
        player = getSpatial("Player");
3
        player.setUserData("alive",true);
4
        player.move(settings.getWidth()/2, settings.getHeight()/2, 0);
5
        guiNode.attachChild(player);

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:

1
public class MonkeyBlasterMain extends SimpleApplication implements ActionListener {

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

1
        inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_LEFT));
2
        inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_RIGHT));
3
        inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_UP));
4
        inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_DOWN));
5
        inputManager.addMapping("return", new KeyTrigger(KeyInput.KEY_RETURN));
6
        inputManager.addListener(this, "left");
7
        inputManager.addListener(this, "right");
8
        inputManager.addListener(this, "up");
9
        inputManager.addListener(this, "down");
10
        inputManager.addListener(this, "return");

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:

1
public class PlayerControl extends AbstractControl {
2
    private int screenWidth, screenHeight;
3
4
//    is the player currently moving?

5
    public boolean up,down,left,right;
6
//    speed of the player

7
    private float speed = 800f;
8
//    lastRotation of the player

9
    private float lastRotation;
10
11
    public PlayerControl(int width, int height) {
12
        this.screenWidth = width;
13
        this.screenHeight = height;
14
    }
15
16
    @Override
17
    protected void controlUpdate(float tpf) {
18
//        move the player in a certain direction

19
//        if he is not out of the screen

20
        if (up) {
21
            if (spatial.getLocalTranslation().y < screenHeight - (Float)spatial.getUserData("radius")) {
22
                spatial.move(0,tpf*speed,0);
23
            }
24
            spatial.rotate(0,0,-lastRotation + FastMath.PI/2);
25
            lastRotation=FastMath.PI/2;
26
        } else if (down) {
27
            if (spatial.getLocalTranslation().y > (Float)spatial.getUserData("radius")) {
28
                spatial.move(0,tpf*-speed,0);
29
            }
30
            spatial.rotate(0,0,-lastRotation + FastMath.PI*1.5f);
31
            lastRotation=FastMath.PI*1.5f;
32
        } else if (left) {
33
            if (spatial.getLocalTranslation().x  > (Float)spatial.getUserData("radius")) {
34
                spatial.move(tpf*-speed,0,0);
35
            }
36
            spatial.rotate(0,0,-lastRotation + FastMath.PI);
37
            lastRotation=FastMath.PI;
38
        } else if (right) {
39
            if (spatial.getLocalTranslation().x < screenWidth - (Float)spatial.getUserData("radius")) {
40
                spatial.move(tpf*speed,0,0);
41
            }
42
            spatial.rotate(0,0,-lastRotation + 0);
43
            lastRotation=0;
44
        }
45
    }
46
47
    @Override
48
    protected void controlRender(RenderManager rm, ViewPort vp) {}
49
50
//    reset the moving values (i.e. for spawning)

51
    public void reset() {
52
        up = false;
53
        down = false;
54
        left = false;
55
        right = false;
56
    }
57
}

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

1
    private int screenWidth, screenHeight;
2
3
//    is the player currently moving?

4
    public boolean up,down,left,right;
5
//    speed of the player

6
    private float speed = 800f;
7
//    lastRotation of the player

8
    private float lastRotation;
9
10
    public PlayerControl(int width, int height) {
11
        this.screenWidth = width;
12
        this.screenHeight = height;
13
    }

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:

1
        if (up) {
2
            if (spatial.getLocalTranslation().y < screenHeight - (Float)spatial.getUserData("radius")) {
3
                spatial.move(0,tpf*speed,0);
4
            }

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:

1
            spatial.rotate(0,0,-lastRotation + FastMath.PI/2);
2
            lastRotation=FastMath.PI/2;

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():

1
player.addControl(new PlayerControl(settings.getWidth(), settings.getHeight()));

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:

1
    public void onAction(String name, boolean isPressed, float tpf) {
2
        if ((Boolean) player.getUserData("alive")) {
3
            if (name.equals("up")) {
4
               player.getControl(PlayerControl.class).up = isPressed;
5
            } else if (name.equals("down")) {
6
                player.getControl(PlayerControl.class).down = isPressed;
7
            } else if (name.equals("left")) {
8
                player.getControl(PlayerControl.class).left = isPressed;
9
            } else if (name.equals("right")) {
10
                player.getControl(PlayerControl.class).right = isPressed;
11
            }
12
        }
13
    }

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:

1
public class MonkeyBlasterMain extends SimpleApplication implements ActionListener, AnalogListener {

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:

1
        inputManager.addMapping("mousePick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
2
        inputManager.addListener(this, "mousePick");

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:

1
    private Vector3f getAimDirection() {
2
        Vector2f mouse = inputManager.getCursorPosition();
3
        Vector3f playerPos = player.getLocalTranslation();
4
        Vector3f dif = new Vector3f(mouse.x-playerPos.x,mouse.y-playerPos.y,0);
5
        return dif.normalizeLocal();
6
    }
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:

1
    public void onAnalog(String name, float value, float tpf) {
2
        if ((Boolean) player.getUserData("alive")) {
3
            if (name.equals("mousePick")) {
4
                //shoot Bullet

5
                if (System.currentTimeMillis() - bulletCooldown > 83f) {
6
                    bulletCooldown = System.currentTimeMillis();
7
8
                    Vector3f aim = getAimDirection();
9
                    Vector3f offset = new Vector3f(aim.y/3,-aim.x/3,0);
10
11
//                    init bullet 1

12
                    Spatial bullet = getSpatial("Bullet");
13
                    Vector3f finalOffset = aim.add(offset).mult(30);
14
                    Vector3f trans = player.getLocalTranslation().add(finalOffset);
15
                    bullet.setLocalTranslation(trans);
16
                    bullet.addControl(new BulletControl(aim, settings.getWidth(), settings.getHeight()));
17
                    bulletNode.attachChild(bullet);
18
19
//                    init bullet 2

20
                    Spatial bullet2 = getSpatial("Bullet");
21
                    finalOffset = aim.add(offset.negate()).mult(30);
22
                    trans = player.getLocalTranslation().add(finalOffset);
23
                    bullet2.setLocalTranslation(trans);
24
                    bullet2.addControl(new BulletControl(aim, settings.getWidth(), settings.getHeight()));
25
                    bulletNode.attachChild(bullet2);
26
                }
27
            }
28
        }
29
    }

Okay, allora, proseguiamo:

1
                if (System.currentTimeMillis() - bulletCooldown > 83f) {
2
                    bulletCooldown = System.currentTimeMillis();
3
4
                    Vector3f aim = getAimDirection();
5
                    Vector3f offset = new Vector3f(aim.y/3,-aim.x/3,0);

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.

1
//                    init bullet 1

2
                    Spatial bullet = getSpatial("Bullet");
3
                    Vector3f finalOffset = aim.add(offset).mult(30);
4
                    Vector3f trans = player.getLocalTranslation().add(finalOffset);
5
                    bullet.setLocalTranslation(trans);
6
                    bullet.addControl(new BulletControl(aim, settings.getWidth(), settings.getHeight()));
7
                    bulletNode.attachChild(bullet);
8
9
//                    init bullet 2

10
                    Spatial bullet2 = getSpatial("Bullet");
11
                    finalOffset = aim.add(offset.negate()).mult(30);
12
                    trans = player.getLocalTranslation().add(finalOffset);
13
                    bullet2.setLocalTranslation(trans);
14
                    bullet2.addControl(new BulletControl(aim, settings.getWidth(), settings.getHeight()));
15
                    bulletNode.attachChild(bullet2);

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.

1
//                    init bullet 1

2
                    Spatial bullet = getSpatial("Bullet");
3
                    Vector3f finalOffset = aim.add(offset).mult(30);
4
                    Vector3f trans = player.getLocalTranslation().add(finalOffset);
5
                    bullet.setLocalTranslation(trans);
6
                    bullet.addControl(new BulletControl(aim, settings.getWidth(), settings.getHeight()));
7
                    bulletNode.attachChild(bullet);
8
9
//                    init bullet 2

10
                    Spatial bullet2 = getSpatial("Bullet");
11
                    finalOffset = aim.add(offset.negate()).mult(30);
12
                    trans = player.getLocalTranslation().add(finalOffset);
13
                    bullet2.setLocalTranslation(trans);
14
                    bullet2.addControl(new BulletControl(aim, settings.getWidth(), settings.getHeight()));
15
                    bulletNode.attachChild(bullet2);

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:

1
//        setup the bulletNode

2
        bulletNode = new Node("bullets");
3
        guiNode.attachChild(bulletNode);

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:

1
public class BulletControl extends AbstractControl {
2
    private int screenWidth, screenHeight;
3
4
    private float speed = 1100f;
5
    public Vector3f direction;
6
    private float rotation;
7
8
    public BulletControl(Vector3f direction, int screenWidth, int screenHeight) {
9
        this.direction = direction;
10
        this.screenWidth = screenWidth;
11
        this.screenHeight = screenHeight;
12
    }
13
14
    @Override
15
    protected void controlUpdate(float tpf) {
16
//        movement

17
        spatial.move(direction.mult(speed*tpf));
18
19
//        rotation

20
        float actualRotation = MonkeyBlasterMain.getAngleFromVector(direction);
21
        if (actualRotation != rotation) {
22
            spatial.rotate(0,0,actualRotation - rotation);
23
            rotation = actualRotation;
24
        }
25
26
//        check boundaries

27
        Vector3f loc = spatial.getLocalTranslation();
28
        if (loc.x > screenWidth || 
29
            loc.y > screenHeight ||
30
            loc.x < 0 ||
31
            loc.y < 0) {
32
            spatial.removeFromParent();
33
        }
34
    }
35
36
    @Override
37
    protected void controlRender(RenderManager rm, ViewPort vp) {}
38
}

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.

1
        Vector3f loc = spatial.getLocalTranslation();
2
        if (loc.x > screenWidth || 
3
            loc.y > screenHeight ||
4
            loc.x < 0 ||
5
            loc.y < 0) {
6
            spatial.removeFromParent();
7
        }

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:

1
MonkeyBlasterMain.getAngleFromVector(direction);

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.

1
    public static float getAngleFromVector(Vector3f vec) {
2
        Vector2f vec2 = new Vector2f(vec.x,vec.y);
3
        return vec2.getAngle();
4
    }
5
6
    public static Vector3f getVectorFromAngle(float angle) {
7
        return new Vector3f(FastMath.cos(angle),FastMath.sin(angle),0);
8
    }
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:

1
bullet.addControl(new BulletControl(aim, settings.getWidth(), settings.getHeight()));

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
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
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.