Insetti luminosi in volo

Continuiamo a lavorare sulla serie di articoli sull'animazione indicati nella sezione Learn di JavaFX.

Vediamo ora quello titolato "Using Paths and Timelines Together (Firefly)"

Scopo dell'applicazione é quello di usare le classi Timeline e Path per creare un fondale su cui si muovono in modo casuale una quantità di cerchi (a simboleggiare degli insetti) che si accendono e spengono in modo anch'esso causale.

Si può vedere il codice in esecuzione in questa pagina.

Il codice originale é organizzato in quattro file:
  • Main: dichiara lo stage e la scena su cui agiscono gli insetti;
  • Fly: classe che estende CustomNode per definire i nostri insetti;
  • Lighter: classe per dire a un insetto quando si deve illuminare;
  • Flight: classe per definire il volo casuale di un insetto

Usare due classi per definire il moto dell'insetto m'é sembrato sovrabbondante, e perciò ho spostato il codice di Lighter in Flight, che ora si occupa di tutte le caratteristiche del volo.

Poi ho cambiato il moto degli insetti, in originale si muovono tracciando una cubica Bezier (classe CubicCurveTo), io ho preferito una semplice retta, ispirandomi al volo della mosca; ho ridotto il numero degli oggetti volanti da venti a sei, e ho fatto altre piccole modifiche.

Insetto: prima versione

Diamo una prima semplice definizione della classe Fly, che rappresenta il nostro insetto, al fine di permetterci di scrivere il main del nostro progetto:

package fireflies;

import javafx.scene.CustomNode;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;

public class Fly extends CustomNode {
public-init var where: Rectangle;

def fly: Circle = Circle {
radius: 6, strokeWidth: 2, stroke: Color.AQUAMARINE
}

override function create() : Node {
return fly
}
}

La variabile where, che identifica l'area in cui é confinato al volo l'insetto, al momento non é utilizzata.

Stage e scena

Nella scena dello stage mettiamo un rettangolo e gli insetti che sono creati "on the fly" (é il caso di dirlo) con un ciclo for.

package fireflies;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;

def area = Rectangle {
x: 0, y: 0, height: 400, width: 400
fill: Color.DARKBLUE
}

Stage {
title: "Fire Flies"
resizable: false
scene: Scene {
fill: Color.BLACK
content: [
area,
for(i in [1..6]) { Fly { where: area } }
]
}
}

Interessante notare che Main.fx é già completo. La logica é tutta nella classe Flight che viene pilotata dalla classe Fly.

Il volo: prima versione

La classe Flight richiede che vengano inizializzate queste variabili:
  • fly: l'insetto corrente, oggetto del volo
  • maxX: massima ascissa raggiungibile in volo
  • maxY: massima ordinata
  • min: minima ascissa/ordinata

E rende disponibile flyColor, che é il colore dell'insetto, che faremo variare nel tempo.

Definiamo poi tre variabili private:
  • randomLightTime: tempo in cui l'insetto non s'illumina
  • endX: ascissa del punto terminale del percorso corrente
  • endY: ordinata del punto terminale del percorso corrente


Definiamo infine due funzioni, che al momento non fanno nulla, ma che gestiranno il movimento e l'illuminazione dell'insetto:

import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.util.Math;

public class Flight {
public-init var fly : Node;

public-init var maxX;
public-init var maxY;
public-init var min : Number;

public var flyColor = Color.BLACK;

var randomLightTime : Duration = Duration.valueOf((3000 * Math.random()) + 2000);
var endX = maxX/2;
var endY = maxY/2;

public function createAndPlayAnimation() : Void {
}

public function startLighter() : Void {
}
}


Insetto completo

Aggiungiamo il colore riempitivo dell'insetto, che é legato al colore come viene determinato nella classe Flight.
Definiamo il volo, passando i parametri richiesti. Nota che max e min sono generati in modo che l'insetto non possa attraversare i limiti del rettangolo in cui é ristretto.
La funzione create(), prima di ritornare l'insetto, fa partire il volo e la gestione del colore.

public class Fly extends CustomNode {
public-init var where: Rectangle;

def fly: Circle = Circle {
radius: 6, strokeWidth: 2, stroke: Color.AQUAMARINE
fill: bind flight.flyColor;
}

def flight = Flight {
fly: fly;
maxX: where.width - fly.radius;
maxY: where.height - fly.radius;
min: fly.radius;
}

override function create() : Node {
flight.createAndPlayAnimation();
flight.startLighter();
return fly
}
}


Il volo dell'insetto

Vediamo la funzione createAndPlayAnimation() nella classe Flight.

Generiamo il punto iniziale e finale del percorso e generiamolo usando la classe PathTransition, nota che la durata di questo tratto di volo é casuale, ma dura almeno un decimo di secondo (per evitare movimenti eccessivamente veloci), nel campo action viene specificata la funzione da eseguire al termine dell'esecuzione della funzione corrente - che é poi ancora la stessa.


import javafx.animation.transition.AnimationPath;
import javafx.animation.transition.PathTransition;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.LineTo;

...
public function createAndPlayAnimation() : Void {
def startX = endX;
def startY = endY;

endX = Math.max(min, maxX * Math.random());
endY = Math.max(min, maxY * Math.random());

PathTransition {
node: fly;
path: AnimationPath.createFromPath(
Path {
elements: [
MoveTo { x: startX, y: startY },
LineTo { x: endX, y: endY }
]
}
)

duration: Duration.valueOf(100 + (1000 * Math.random()))
action: function() {createAndPlayAnimation();}
}.play();
}

Aggiungendo questa funzione la nostra applicazione prende vita e i nostri insetti si metto a volare per tutto il loro spazio.

Luci in volo

Scriviamo il codice della startLighter() nella classe Flight.

In realtà é una funzione piuttosto semplice: crea un timeline e la fa partire.
La timeline si ripete in modo indefinito con autoreverse.
Da notare che per la definizione dei KeyFrame non si é potuta usare la costruzione semplificata "at() ..." dato che come tempo vogliamo usare una variabile, mentre l'at richiede una duration letterale.


import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;

public function startLighter() : Void {
Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames: [
KeyFrame {
time: randomLightTime
values: [ flyColor => Color.BLACK ]
},
KeyFrame {
time: randomLightTime + .5s;
values: [ flyColor => Color.YELLOW tween Interpolator.EASEBOTH ]
}
];
}.play();
}

Con questa funzione il nostro programma é completo.

Nessun commento:

Posta un commento