Advertisement
  1. Game Development
  2. Java

Introduzione a JavaFX per lo sviluppo del gioco

Scroll to top
Read Time: 18 min

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

JavaFX è un cross-platform GUI toolkit per Java ed è il successore delle librerie Java Swing. In questo tutorial, esploreremo le caratteristiche di JavaFX che lo rendono facile da usare per iniziare a programmare giochi in Java.

Questa esercitazione si presuppone che sai già come scrivere il codice in Java. Se non, check out imparare Java per Android, introduzione alla programmazione con Java Computer: 101 e 201, Head First Java, Greenfoot o imparare Java nel modo più duro per iniziare.

Installazione

Se si sviluppano già applicazioni con Java, è probabilmente non necessario scaricare nulla: JavaFX è stato incluso con il pacchetto standard del JDK (Java Development Kit) dal JDK versione 7u6 (agosto 2012). Se non hai aggiornato l'installazione di Java in un istante, testa al Java Scarica sito Web per la versione più recente.

Classi base Framework

Creazione di un programma di JavaFX inizia con la classe di applicazione, da cui sono estese tutte le applicazioni JavaFX. La classe principale dovrà chiamare il metodo launch(), che si chiamerà quindi il metodo init () e quindi il metodo Start (), attendere il termine dell'applicazione e quindi chiamare il metodo Stop (). Di questi metodi, solo il metodo Start () è astratto e deve essere sottoposto a override.

La classe Stage è il livello superiore contenitore JavaFX. Quando un'applicazione viene avviata, una fase iniziale viene creata e passata al metodo di inizio dell'applicazione. Fasi di controllo proprietà di base della finestra ad esempio titolo, icona, visibilità, ridimensionamento, modalità a schermo intero e decorazioni; quest'ultimo viene configurato utilizzando StageStyle. Ulteriori fasi possono essere costruiti come necessario. Dopo una fase è configurata e il contenuto viene aggiunto, viene chiamato il metodo Show ().

Sapendo tutto ciò, possiamo scrivere un esempio minimo che avvia una finestra in JavaFX:

1
import javafx.application.Application;
2
import javafx.stage.Stage;
3
4
public class Example1 extends Application 
5
{
6
    public static void main(String[] args) 
7
    {
8
        launch(args);
9
    }
10
11
    public void start(Stage theStage) 
12
    {
13
        theStage.setTitle("Hello, World!");
14
        theStage.show();
15
    }
16
}

Strutturazione di contenuti

Contenuto in JavaFX (ad esempio testo, immagini e l'interfaccia utente di controllo) è organizzato utilizzando una struttura di dati ad albero conosciuta come un grafico di scena, che raggruppa e organizza gli elementi di una scena grafica.

JavaFX Scene GraphJavaFX Scene GraphJavaFX Scene Graph
Rappresentazione di un grafo di scena di JavaFX.

Un elemento generale di un grafo di scena in JavaFX è chiamato un nodo. Ogni nodo in un albero ha un nodo singolo "padre", con l'eccezione di un nodo speciale designato come la "radice". Un gruppo è un nodo che può avere molti elementi del nodo "figlio". Trasformazioni grafiche (traslazione, rotazione e scala) e gli effetti applicati a un gruppo si applicano anche ai suoi figli. I nodi possono essere in stile utilizzando JavaFX Cascading Style Sheets (CSS), abbastanza simile al CSS utilizzato per la formattazione di documenti HTML.

La classe di scena contiene tutti i contenuti per un grafico di scena e richiede una radice nodo da impostare (in pratica, questo è spesso un gruppo). È possibile impostare la dimensione di una scena in particolare; in caso contrario, la dimensione di una scena sarà automaticamente calcolata sul suo contenuto. Un oggetto di scena deve essere passato alla fase (con il metodo di setScene()) per poter essere visualizzati.

Rendering della grafica

Rendering della grafica è particolarmente importante per i programmatori di giochi! In JavaFX, l'oggetto Canvas è un'immagine in cui possiamo disegnare testo, forme e immagini, utilizzando l'oggetto GraphicsContext associato. (Per gli sviluppatori che hanno familiari con il toolkit Java Swing, questo è simile all'oggetto Graphics passato al metodo Paint () nella classe JFrame.)

L'oggetto GraphicsContext contiene una ricchezza di capacità di personalizzazione potente. Per scegliere i colori per disegnare forme e testo, è possibile impostare il tratto e il riempimento (interior) colori (confine), che sono oggetti Paint: può trattarsi di un'unica tinta unita, una sfumatura di definito dall'utente (LinearGradient o RadialGradient) o anche un ImagePattern. Si può anche applicare uno o più oggetti stile effetto, quali illuminazione, ombra o GaussianBlur e modificare i tipi di carattere predefinito utilizzando la classe Font.

La classe Image lo rende facile da caricare immagini da una varietà di formati di file e disegnarli tramite la classe GraphicsContext. È facile costrutto generato proceduralmente immagini utilizzando la classe WritableImage insieme le classi PixelReader e PixelWriter.

Utilizzando queste classi, possiamo scrivere un degno molto più "Hello, World"-esempio di stile come segue. Per brevità, includeremo solo il metodo Start () qui (potrai saltare le istruzioni di importazione e il metodo Main ()); Tuttavia, codice sorgente di lavoro completo può essere trovato nel repository di GitHub che accompagna questa esercitazione.

1
public void start(Stage theStage) 
2
{
3
    theStage.setTitle( "Canvas Example" );
4
        
5
    Group root = new Group();
6
    Scene theScene = new Scene( root );
7
    theStage.setScene( theScene );
8
        
9
    Canvas canvas = new Canvas( 400, 200 );
10
    root.getChildren().add( canvas );
11
        
12
    GraphicsContext gc = canvas.getGraphicsContext2D();
13
        
14
    gc.setFill( Color.RED );
15
    gc.setStroke( Color.BLACK );
16
    gc.setLineWidth(2);
17
    Font theFont = Font.font( "Times New Roman", FontWeight.BOLD, 48 );
18
    gc.setFont( theFont );
19
    gc.fillText( "Hello, World!", 60, 50 );
20
    gc.strokeText( "Hello, World!", 60, 50 );
21
    
22
    Image earth = new Image( "earth.png" );
23
    gc.drawImage( earth, 180, 100 );
24
        
25
    theStage.show();
26
}

Il ciclo del gioco

Poi, abbiamo bisogno di rendere i nostri programmi dinamici, che significa che lo stato del gioco cambia nel tempo. Implementeremo un ciclo di gioco: un ciclo infinito che aggiorna gli oggetti del gioco e rende la scena sullo schermo, idealmente ad un tasso di 60 volte al secondo.

Il modo più semplice per realizzare questo in JavaFX è utilizzando la classe di AnimationTimer, dove un metodo (denominato handle()) può essere scritto che sarà chiamato ad un tasso di 60 volte al secondo, o come vicino a tale tasso, è possibile. (Questa classe non deve essere utilizzato esclusivamente a scopi di animazione; è capace di molto di più).

Utilizzando la classe AnimationTimer è un po' difficile: poiché è una classe astratta, non può essere creato direttamente — la classe deve essere estesa prima di poter creare un'istanza. Tuttavia, per i nostri esempi semplici, estenderemo la classe scrivendo una classe anonima interna. Questa classe interna dovrà definire il metodo astratto handle (), che verrà passato un solo argomento: l'ora di sistema corrente in nanosecondi. Dopo aver definito la classe interna, abbiamo immediatamente richiamare il metodo Start (), che inizia il ciclo. (Il ciclo può essere interrotto chiamando il metodo Stop ()).

Con queste classi, possiamo modificare il nostro esempio "Hello, World", creazione di un'animazione composta dalla terra in orbita intorno al sole su un'immagine di sfondo stellato.

1
public void start(Stage theStage) 
2
{
3
    theStage.setTitle( "Timeline Example" );
4
5
    Group root = new Group();
6
    Scene theScene = new Scene( root );
7
    theStage.setScene( theScene );
8
9
    Canvas canvas = new Canvas( 512, 512 );
10
    root.getChildren().add( canvas );
11
12
    GraphicsContext gc = canvas.getGraphicsContext2D();
13
14
    Image earth = new Image( "earth.png" );
15
    Image sun   = new Image( "sun.png" );
16
    Image space = new Image( "space.png" );
17
18
    final long startNanoTime = System.nanoTime();
19
20
    new AnimationTimer()
21
    {
22
        public void handle(long currentNanoTime)
23
        {
24
            double t = (currentNanoTime - startNanoTime) / 1000000000.0; 
25
26
            double x = 232 + 128 * Math.cos(t);
27
            double y = 232 + 128 * Math.sin(t);
28
29
            // background image clears canvas

30
            gc.drawImage( space, 0, 0 );
31
            gc.drawImage( earth, x, y );
32
            gc.drawImage( sun, 196, 196 );
33
        }
34
    }.start();
35
36
    theStage.show();
37
}

Ci sono modi alternativi per implementare un ciclo di gioco in JavaFX. Un approccio leggermente più lungo (ma più flessibile) implica la classe Timeline, ovvero una sequenza di animazione composto da un insieme di oggetti fotogramma chiave. Per creare un ciclo di gioco, la sequenza temporale deve essere impostata per ripetere all'infinito, e solo un singolo fotogramma chiave è richiesto, con relativa durata impostata a 0,016 secondi (per raggiungere 60 cicli al secondo). Questa implementazione può essere trovata nel file Example3T.java nel repository di GitHub.

Animazione basata su telaio

Un altro componente di programmazione comunemente necessarie gioco è basato su frame di animazione: visualizzazione di una sequenza di immagini in rapida successione per creare l'illusione del movimento.

Supponendo che tutte le animazioni in ciclo e tutti i fotogrammi visualizzare per lo stesso numero di secondi, un'implementazione di base potrebbe essere semplice come segue:

1
public class AnimatedImage
2
{
3
    public Image[] frames;
4
    public double duration;
5
    
6
    public Image getFrame(double time)
7
    {
8
        int index = (int)((time % (frames.length * duration)) / duration);
9
        return frames[index];
10
    }
11
}

Per integrare questa classe nell'esempio precedente, potremmo creare un animato UFO, l'inizializzazione dell'oggetto utilizzando il codice:

1
AnimatedImage ufo = new AnimatedImage();
2
Image[] imageArray = new Image[6];
3
for (int i = 0; i < 6; i++)
4
    imageArray[i] = new Image( "ufo_" + i + ".png" );
5
ufo.frames = imageArray;
6
ufo.duration = 0.100;

... e, all'interno di AnimationTimer, aggiungendo la riga di codice:

1
gc.drawImage( ufo.getFrame(t), 450, 25 ); 

.... at nel punto appropriato. Per un esempio di codice funzionante completo, vedere il file Example3AI.java nel repository di GitHub.

Gestione dell'Input dell'utente

Rilevazione e l'elaborazione dell'input utente in JavaFX è semplice. Le azioni dell'utente che possono essere rilevate dal sistema, quali pressioni di tasti e click del mouse, sono chiamate eventi. In JavaFX, queste azioni automaticamente determina la generazione di oggetti (ad esempio KeyEvent e MouseEvent) che memorizzano i dati associati (ad esempio, premuto il tasto effettivo o la posizione del puntatore del mouse). Qualsiasi classe di JavaFX che implementa la classe EventTarget, ad esempio una scena, può "ascoltare" per eventi e gestirli; Negli esempi che seguono, vi mostreremo come impostare una scena per elaborare gli eventi vari.

Sfogliando la documentazione per la classe di scena, ci sono molti metodi che ascolta per la gestione di diversi tipi di input provenienti da fonti diverse. Per esempio, il metodo setOnKeyPressed() può assegnare un EventHandler che verrà attivato quando viene premuto un tasto, il metodo setOnMouseClicked() può assegnare un EventHandler che si attiva quando viene premuto un pulsante del mouse, e così via. La classe EventHandler serve uno scopo: per incapsulare un metodo (chiamato handle()) che viene chiamato quando si verifica l'evento corrispondente.

Quando si crea un EventHandler, è necessario specificare il tipo di evento che gestisce: è possibile dichiarare un EventHandler<KeyEvent> o un EventHandler<MouseEvent>, per esempio.</MouseEvent> </KeyEvent> Inoltre, EventHandlers vengono spesso creati come anonime classi interne, come vengono in genere utilizzati solo una volta (quando vengono passati come argomento a uno dei metodi elencati sopra).

Gestione degli eventi di tastiera

L'input dell'utente viene spesso elaborato all'interno del ciclo di gioco principale, e quindi deve essere tenuto un registro di quali tasti sono correntemente attivi. Un modo per eseguire questa operazione è creando un ArrayList di String oggetti. Quando inizialmente si preme un tasto, aggiungiamo la rappresentazione di stringa di KeyCode di KeyEvent all'elenco; Quando il tasto viene rilasciato, abbiamo rimuoverlo dall'elenco.

Nell'esempio che segue, la tela contiene due immagini dei tasti freccia; ogni volta che viene premuto un tasto, l'immagine corrispondente diventa verde.


Il codice sorgente è contenuto nel file Example4K.java nel repository di GitHub.

1
public void start(Stage theStage) 
2
    {
3
        theStage.setTitle( "Keyboard Example" );
4
5
        Group root = new Group();
6
        Scene theScene = new Scene( root );
7
        theStage.setScene( theScene );
8
9
        Canvas canvas = new Canvas( 512 - 64, 256 );
10
        root.getChildren().add( canvas );
11
12
        ArrayList<String> input = new ArrayList<String>();
13
14
        theScene.setOnKeyPressed(
15
            new EventHandler<KeyEvent>()
16
            {
17
                public void handle(KeyEvent e)
18
                {
19
                    String code = e.getCode().toString();
20
21
                    // only add once... prevent duplicates

22
                    if ( !input.contains(code) )
23
                        input.add( code );
24
                }
25
            });
26
27
        theScene.setOnKeyReleased(
28
            new EventHandler<KeyEvent>()
29
            {
30
                public void handle(KeyEvent e)
31
                {
32
                    String code = e.getCode().toString();
33
                    input.remove( code );
34
                }
35
            });
36
37
        GraphicsContext gc = canvas.getGraphicsContext2D();
38
39
        Image left = new Image( "left.png" );
40
        Image leftG = new Image( "leftG.png" );
41
42
        Image right = new Image( "right.png" );
43
        Image rightG = new Image( "rightG.png" );
44
45
        new AnimationTimer()
46
        {
47
            public void handle(long currentNanoTime)
48
            {
49
                // Clear the canvas

50
                gc.clearRect(0, 0, 512,512);
51
52
                if (input.contains("LEFT"))
53
                    gc.drawImage( leftG, 64, 64 );
54
                else
55
                    gc.drawImage( left, 64, 64 );
56
57
                if (input.contains("RIGHT"))
58
                    gc.drawImage( rightG, 256, 64 );
59
                else
60
                    gc.drawImage( right, 256, 64 );
61
            }
62
        }.start();
63
64
        theStage.show();
65
    }

Gestione degli eventi del Mouse

Ora diamo un'occhiata a un esempio che si concentra sulla classe MouseEvent piuttosto che la classe KeyEvent. In questo mini-gioco, il giocatore guadagna un punto ogni volta che il bersaglio è cliccato.


Poiché il EventHandlers sono classi interne, tutte le variabili che utilizzano devono essere finale o "efficacemente finale", che significa che le variabili non possono essere reinizializzate. Nell'esempio precedente, i dati è stati passati il EventHandler per mezzo di un oggetto ArrayList, i cui valori possono essere modificati senza reinizializzazione (tramite i metodi Add e Remove ()).

Tuttavia, nel caso di tipi di dati di base, i valori non possono essere modificati una volta inizializzato. Se si desidera l'oggetto EventHandler per accedere ai tipi di dati di base che vengono modificati altrove nel programma, è possibile creare una classe wrapper che contiene variabili pubbliche o metodi getter/setter. (Nell'esempio che segue, IntValue è una classe che contiene una variabile int pubblico chiamata valore.)

1
public void start(Stage theStage) 
2
{
3
    theStage.setTitle( "Click the Target!" );
4
5
    Group root = new Group();
6
    Scene theScene = new Scene( root );
7
    theStage.setScene( theScene );
8
9
    Canvas canvas = new Canvas( 500, 500 );
10
11
    root.getChildren().add( canvas );
12
13
    Circle targetData = new Circle(100,100,32);
14
    IntValue points = new IntValue(0);
15
16
    theScene.setOnMouseClicked(
17
        new EventHandler<MouseEvent>()
18
        {
19
            public void handle(MouseEvent e)
20
            {
21
                if ( targetData.containsPoint( e.getX(), e.getY() ) )
22
                {
23
                    double x = 50 + 400 * Math.random(); 
24
                    double y = 50 + 400 * Math.random();
25
                    targetData.setCenter(x,y);
26
                    points.value++;
27
                }
28
                else
29
                    points.value = 0;
30
            }
31
        });
32
33
    GraphicsContext gc = canvas.getGraphicsContext2D();
34
35
    Font theFont = Font.font( "Helvetica", FontWeight.BOLD, 24 );
36
    gc.setFont( theFont );
37
    gc.setStroke( Color.BLACK );
38
    gc.setLineWidth(1);
39
40
    Image bullseye = new Image( "bullseye.png" );
41
42
    new AnimationTimer()
43
    {
44
        public void handle(long currentNanoTime)
45
        {
46
            // Clear the canvas

47
            gc.setFill( new Color(0.85, 0.85, 1.0, 1.0) );
48
            gc.fillRect(0,0, 512,512);
49
50
            gc.drawImage( bullseye, 
51
                targetData.getX() - targetData.getRadius(),
52
                targetData.getY() - targetData.getRadius() );
53
54
            gc.setFill( Color.BLUE );
55
56
            String pointsText = "Points: " + points.value;
57
            gc.fillText( pointsText, 360, 36 );
58
            gc.strokeText( pointsText, 360, 36 );
59
        }
60
    }.start();
61
62
    theStage.show();
63
}

Il codice sorgente completo è contenuto nel repository di GitHub; la classe principale è Example4M.java.

Creazione di una classe di base Sprite con JavaFX

Nei videogiochi, uno sprite è il termine per una singola entità visiva. Di seguito è un esempio di una classe Sprite che memorizza un'immagine e posizione, nonché informazioni di velocità (per entità mobile) e informazioni di larghezza/altezza da utilizzare quando si calcolano i riquadri di delimitazione ai fini del rilevamento delle collisioni. Ci sono anche i metodi getter/setter standard per la maggior parte di questi dati (omessi per brevità) e alcuni metodi standard necessari nello sviluppo del gioco:

  • Update (): calcola la nuova posizione basata sulla velocità dello Sprite.
  • Render: disegna il socio di immagine sulla tela (tramite la classe GraphicsContext) utilizzando la posizione come coordinate.
  • getBoundary(): restituisce un oggetto JavaFX Rectangle2D, utile nel rilevamento delle collisioni a causa sua intersects metodo.
  • Intersects(): determina se il riquadro di delimitazione di questo Sprite si interseca con quella di un altro Sprite.
1
public class Sprite
2
{
3
    private Image image;
4
    private double positionX;
5
    private double positionY;    
6
    private double velocityX;
7
    private double velocityY;
8
    private double width;
9
    private double height;
10
11
    // ...

12
    // methods omitted for brevity

13
    // ...

14
15
    public void update(double time)
16
    {
17
        positionX += velocityX * time;
18
        positionY += velocityY * time;
19
    }
20
21
    public void render(GraphicsContext gc)
22
    {
23
        gc.drawImage( image, positionX, positionY );
24
    }
25
26
    public Rectangle2D getBoundary()
27
    {
28
        return new Rectangle2D(positionX,positionY,width,height);
29
    }
30
31
    public boolean intersects(Sprite s)
32
    {
33
        return s.getBoundary().intersects( this.getBoundary() );
34
    }
35
}

Il codice sorgente completo è incluso in Sprite.java nel repository di GitHub.

Utilizzando la classe Sprite

Con l'assistenza della classe Sprite, possiamo facilmente creare una semplice raccolta di gioco in JavaFX. In questo gioco, si assume il ruolo di una valigetta senziente, cui obiettivo è quello di raccogliere i molti sacchi di denaro che sono stati lasciati in giro da un precedente proprietario negligente. I tasti freccia spostano il giocatore intorno allo schermo.

Questo codice prende in prestito pesantemente da esempi precedenti: impostazione di tipi di carattere per visualizzare il punteggio, memorizzazione di input da tastiera con un oggetto ArrayList, l'implementazione del ciclo del gioco con un AnimationTimer e creazione di classi wrapper per valori semplici che devono essere modificati durante il ciclo di gioco.

Un segmento di codice del particolare interesse comporta la creazione di un oggetto Sprite per il giocatore (valigetta) e un oggetti ArrayList di Sprite per i collectibles (sacchi di denaro):

1
Sprite briefcase = new Sprite();
2
briefcase.setImage("briefcase.png");
3
briefcase.setPosition(200, 0);
4
5
ArrayList<Sprite> moneybagList = new ArrayList<Sprite>();
6
7
for (int i = 0; i < 15; i++)
8
{
9
    Sprite moneybag = new Sprite();
10
  moneybag.setImage("moneybag.png");
11
	double px = 350 * Math.random() + 50;
12
	double py = 350 * Math.random() + 50;          
13
	moneybag.setPosition(px,py);
14
	moneybagList.add( moneybag );
15
}

Un altro segmento di codice di interesse è la creazione di AnimationTimer, che ha il compito di:

  • calcolo del tempo trascorso dall'ultimo aggiornamento
  • impostare la velocità del giocatore a seconda attualmente premuti i tasti
  • eseguire il rilevamento di collisione tra il giocatore e il collezionismo e aggiornare il punteggio e l'elenco di oggetti da collezione, in questo caso (un iteratore viene utilizzato anziché ArrayList direttamente per evitare un'eccezione di modifiche simultanee durante la rimozione di oggetti da l'elenco)
  • rendendo il sprite e testo sulla tela
1
new AnimationTimer()
2
{
3
    public void handle(long currentNanoTime)
4
	{
5
		// calculate time since last update.

6
		double elapsedTime = (currentNanoTime - lastNanoTime.value) / 1000000000.0;
7
		lastNanoTime.value = currentNanoTime;
8
		
9
		// game logic

10
		
11
		briefcase.setVelocity(0,0);
12
		if (input.contains("LEFT"))
13
			briefcase.addVelocity(-50,0);
14
		if (input.contains("RIGHT"))
15
			briefcase.addVelocity(50,0);
16
		if (input.contains("UP"))
17
			briefcase.addVelocity(0,-50);
18
		if (input.contains("DOWN"))
19
			briefcase.addVelocity(0,50);
20
			
21
		briefcase.update(elapsedTime);
22
		
23
		// collision detection

24
		
25
		Iterator<Sprite> moneybagIter = moneybagList.iterator();
26
		while ( moneybagIter.hasNext() )
27
		{
28
			Sprite moneybag = moneybagIter.next();
29
			if ( briefcase.intersects(moneybag) )
30
			{
31
				moneybagIter.remove();
32
				score.value++;
33
			}
34
		}
35
		
36
		// render

37
		
38
		gc.clearRect(0, 0, 512,512);
39
		briefcase.render( gc );
40
		
41
		for (Sprite moneybag : moneybagList )
42
			moneybag.render( gc );
43
44
		String pointsText = "Cash: $" + (100 * score.value);
45
		gc.fillText( pointsText, 360, 36 );
46
		gc.strokeText( pointsText, 360, 36 );
47
	}
48
}.start();

Come al solito, codice completo può essere trovato nel file di codice associato (Example5.java) nel repository di GitHub.

Prossimi passi

Conclusione

In questo tutorial, ti ho introdotto alle classi JavaFX che sono utili nella programmazione di giochi. Abbiamo lavorato attraverso una serie di esempi di complessità crescente, che culmina in un gioco basato su sprite collezione-stile. Ora si è pronti per neanche indagare alcune delle risorse elencate sopra, o in immersione e iniziare a creare il proprio gioco. Buona fortuna a voi nei vostri sforzi!

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.