View Full Version : [JAVA]Animazioni
Ragazzi non ho ben chiaro come vengono effettuate le animazioni in java(ho appena iniziato), cioè bisogna per forza cancellare quello che si è appena disegnato e ridisegnarlo "di poco" spostato?
Ditemi che non è così...
Ho letto qualcosa tipo doppio buffer, c'è nessuno che mi da qualche info in più? in rete non sono riuscito a trovare quclsa di veramente chiaro...
Dai raga help!!!!!!! :help:
:fagiano: :D
L'alternativa allo spostare di poco per volta qualcosa sta nel proiettare l'immagine a video, afferrare il monitor e correre su e giù per la stanza. Inspiegabilmente è pratica poco comune.
Il doppio buffer è una tecnica usata per impacchettare le operazioni di disegno in un solo comando (quello che invia una richiesta di sostituzione del buffer correntemente proiettato a video). Si fa semplicemente eseguendo le singole operazioni di disegno in una regione di memoria non proiettata per poi ordinare la proiezione del risultato globale delle singole operazioni.
Altrimenti detto, disegni tutto su un'immagine fuori schermo e poi disegni quell'immagine sullo schermo.
L'AWT Java offre un gradevole precotto, BufferStrategy, che incorpora l'accelerazione hardware delle superfici (se disponibile), buffer multipli e il meccanismo di sincronizzazione per la sostituzione degli stessi. La documentazione Java è loquace al riguardo: in sintesi si tratta di disegnare usando il contesto grafico (Graphics) restituito da un BufferStrategy anzichè sovrascrivere il metodo paintComponent usando il suo argomento Graphics.
Circa l'animazione, in ogni caso animi mutando ciclicamente un qualche stato (non necessariamente la posizione pur sembrandomi questa la via più intuitiva). Con un minimo d'ordine al sistema che vuoi realizzare il fatto che per muovere qualcosa sposti un poco per volta delle coordinate passa praticamente subito in sordina.
Io vorrei utlizzare lo swing non l'awt e il doppio buffer, hai mica un pezzo di codice che mi mostri come fare una semplice animazione con questa tecnica?
Che non sia un'applet xò.
Se preferisci usare Swing allora sei a cavallo. E' "double buffered, hw accelerated, page flipped e chi più ne ha più ne metted" di suo. Idealmente è meno performante perchè oltre al pacco "performance" ti becchi anche il raffinato ma non certo leggero meccanismo per la gestione di bordi, contenimento, decorazioni e via dicendo.
Ho romanzato il codice per cui mi limito ad incollarlo. Naturalmente per qualsiasi curiosità son qui.
Drawable.java
package it.tukano.anisample;
/** Un che di disegnabile */
public interface Drawable {
void draw(java.awt.Graphics2D g);
}
Updatable.java
package it.tukano.anisample;
/** Un che di aggiornabile */
public interface Updatable {
void update();
}
Sprite.java
package it.tukano.anisample;
/** Sprite è qualcosa che ha un aspetto e un'animazione (in questo caso
solo animazione per spostamento) */
public interface Sprite extends Drawable {
void move(float dx, float dy);
void setLocation(float x, float y);
}
HorribleSprite.java
package it.tukano.anisample;
import java.awt.*;
import java.awt.geom.*;
/** Quando il nome dice tutto */
public class HorribleSprite implements Sprite {
private Point2D.Float location = new Point2D.Float();
private Rectangle2D.Float shape = new Rectangle2D.Float(0, 0, 20, 20);
public void move(float dx, float dy) {
shape.setRect(
shape.getX() + dx, shape.getY() + dy,
shape.getWidth(), shape.getHeight());
}
public void setLocation(float x, float y) {
shape.setRect(
x, y,
shape.getWidth(), shape.getHeight());
}
public void draw(Graphics2D g) {
g.setColor(Color.RED);
g.fill(shape);
}
}
Behavior.java
package it.tukano.anisample;
import java.awt.*;
/** Definizione di un comportamento applicabile ad uno Sprite */
public class Behavior implements Updatable {
private float speed = 0.1f; //pixel per millisecondo
private long time0, time1, dTime;
private Sprite sprite;
public Behavior(Sprite s) {
sprite = s;
}
/** Applica allo sprite uno spostamento calcolato in base al tempo
trascorso dall'ultima invocazione di questo metodo. */
public void update() {
if(time0 == 0) {
time0 = time1 = System.currentTimeMillis();
} else {
time1 = System.currentTimeMillis();
}
dTime = time1 - time0;
time0 = time1;
float dx = speed * dTime;
sprite.move(dx, 0);
}
}
Updater.java
package it.tukano.anisample;
import java.awt.event.*;
import java.util.*;
/** Qualcosa che aggiorna periodicamente qualcos'altro */
public class Updater {
/** Controlla l'inizio e la fine del ciclo di aggiornamento */
private volatile boolean doUpdate;
/** Periodo di attesa tra un passaggio e l'altro nel ciclo di
aggiornamento */
private int updateDelay;
/** Elenco degli oggetti aggiornabili da questo oggetto */
private ArrayList<Updatable> updatables = new ArrayList<Updatable>();
private Runnable updateTask = new Runnable() {
public void run() {
while(doUpdate) {
updateAll();
waitForDelay();
}
}
};
/** @param delay ritardo del ciclo di aggiornamento */
public Updater(int delay) {
updateDelay = delay;
}
/** definizione del compito periodico */
private void updateAll() {
for(Updatable u : updatables) {
u.update();
}
}
/** definizione dell'attesa per il ritardo periodico */
private void waitForDelay() {
try {
Thread.sleep(updateDelay);
} catch(InterruptedException ex) {
System.err.println("Threadus interruptus!");
doUpdate = false;
}
}
/** Aggiunge un elenco di updatable alla lista di aggiornamento */
public void appendUpdatables(Updatable...u) {
updatables.addAll(Arrays.asList(u));
}
/** Aggiunge un updatable alla lista degli aggiornabili in posizione
position (0 = prima del primo elemento della lista)*/
public void addUpdatable(Updatable u, int position) {
updatables.add(position, u);
}
/** Avvia il ciclo di aggiornamento */
public void start() {
if(!doUpdate) {
doUpdate = true;
new Thread(updateTask).start();
} else {
System.out.println("Updater on line...");
}
}
/** Ferma il ciclo di aggiornamento */
public void stop() {
if(doUpdate) {
doUpdate = false;
} else {
System.out.println("Updater offline");
}
}
}
Screen.java
package it.tukano.anisample;
import java.awt.*;
import java.util.*;
import javax.swing.*;
/** Lo schermo è un oggetto che proietta dei drawable ed è aggiornabile */
public class Screen implements Updatable {
private ArrayList<Drawable> drawables = new ArrayList<Drawable>();
/** Questo è il componente Swing che concretamente proietterà i
Drawable sullo schermo...
@see #draw*/
private JComponent component = new JPanel() {
protected void paintComponent(Graphics g) {
super.paintComponent(g);
draw((Graphics2D)g);
}
};
public void addDrawable(Drawable...d) {
drawables.addAll(Arrays.asList(d));
}
public void update() {
component.repaint();
}
public JComponent getComponent() {
return component;
}
/** ... e questo è il metodo in cui è disegnato ciò che il componente
Swing proietta. */
private void draw(Graphics2D g) {
g.setColor(Color.BLACK);
g.fillRect(0, 0, component.getWidth(), component.getHeight());
for(Drawable d : drawables) {
d.draw(g);
}
}
}
Main.java
package it.tukano.anisample;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Main {
/** C.d. punto d'entrata dell'applicazione. */
public static void main(String...args) {
new Main().startAndShow();
}
/** Un vezzo personale :D */
private Main() {}
/** Nell'ordine, crea uno HorribleSprite, crea un Behavior (horrible
pure quello), crea uno Screen, assegna una dimensione allo screen
(400x400 px), aggiunge lo sprite come "oggetto che sarà disegnato sullo
schermo", crea un Updater con un periodo ideale di 24ms, aggiunge
allo updater lo schermo e il behavior come "oggetti che saranno
aggiornati dall'aggiornatore", crea una finestra, collega alla finestra
qualcosa che reagirà quanto questa sarà chiusa (per chiudere effettivamente
la finestra e fermare l'aggiornatore), aggiunge il componente dello schermo
alla finestra, impacchetta tutto e lo spara sullo schermo. */
private void startAndShow() {
HorribleSprite sprite = new HorribleSprite();
Behavior simpleBehavior = new Behavior(sprite);
Screen screen = new Screen();
screen.getComponent().setPreferredSize(new Dimension(400, 400));
screen.addDrawable(sprite);
Updater updater = new Updater(24);
updater.appendUpdatables(simpleBehavior, screen);
JFrame window = new JFrame("AniSample");
bindWindowListener(window, updater);
window.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
window.getContentPane().add(screen.getComponent());
packAndShow(window, updater);
}
/** Assegna una dimensione al frame in base al suo contenuto, mostra
la finestra sullo schermo e avvia lo updater */
private void packAndShow(final JFrame frame, final Updater updater) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.pack();
frame.setVisible(true);
updater.start();
}
});
}
/** Aggiunge un ascolatore alla chiusura della finestra frame. */
private void bindWindowListener(final JFrame frame, final Updater updater) {
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
shutdownApp(frame, updater);
}
});
}
/** Invocato alla chiusura della finestra.la distrugge
e ferma lo updater. Può comportare spegnimento
dolce della JVM (anzi, l'idea è proprio quella) */
private void shutdownApp(JFrame frame, Updater upd) {
upd.stop();
frame.dispose();
}
}
L'idea (non mia anche se non ricordo più dove l'ho letta) è che uno sprite è qualcosa che ha un'immagine ed una posizione ed è disegnabile. Il comportamento di uno sprite, che è quello che anima, è un che di aggiornabile idealmente connesso ad uno Sprite (nell'esempio Behavior aggiorna la posizione dello sprite). Lo schermo disegna una serie di cose disegnabili (Drawable) e l'aggiornatore aggiorna una serie di cose aggiornabili. Lo schermo è aggiornabile, il comportamento è aggiornabile, lo sprite è disegnabile...alla fine della fiera vedi un quadrato rosso che parte per la milano sanremo (che, come tutti sanno, si trova a destra dello schermo :D).
Intanto ti ringrazio tantissimo per il codice! Vorrei sapere, ma questo è lo stretto necessario per fare un'animazione del genere? O hai aggiunte delle piccole finezze tue? :D
Io sono proprio alle primi armi con il java e quindi mi sarebbe utile visionare un codice il pìù asciutto possibile, e senza metodi di programazzione troppo avanzati.
Vedo che hai fatto largo uso delle interfaccie, le quali ancora non mi sono chiarissime ma che sto cmq cercando di capire.
Se riesci a ridurlo ulteriormente mi saresti di grande aiuto!
Sul significato di "stretto necessario" potremmo iniziare qui una diatriba da concludersi a barbe bianche :D.
Il codice precedente non si avvicina neanche ad un che di orientato agli oggetti. E' facile osservare come l'oggetto Main sia in verità indistinto da HorribleSprite, Screen, Updater e Behavior. Abbi cuore, l'ho scritto in fretta :D.
Si può fare di meno (sul peggio avrei dei dubbi :eek: ).
ScreenWithAnimation.java
package it.tukano.simpleranimation;
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import java.util.*;
public class ScreenWithAnimation extends JPanel {
/** Periodo del ciclo di animazione */
private final long TIMER_PERIOD = 24;
/** Velocità dello sprite in pixel per millisecondo */
private final float SPEED = 0.1f;
/** "Sprite" */
private Rectangle2D.Float fakeSprite =
new Rectangle2D.Float(0, 0, 20, 20);
/** Variabili per il calcolo del tempo intercorso tra due passaggi nel
ciclo di aggiornamento */
private long time0, time1;
/** Compito affidato al timer */
private TimerTask updateTask = new TimerTask() {
public void run() {
long dTime = updateTime();
updateSprite(dTime);
repaint();
}
};
/** Una specie di Thread che esegue (anche) compiti periodici */
private java.util.Timer timer;
/** Avvia l'animazione */
public void start() {
if(timer == null) {
timer = new java.util.Timer();
timer.scheduleAtFixedRate(updateTask, 0, TIMER_PERIOD);
}
}
/** Ferma l'animazione */
public void stop() {
if(timer != null) {
timer.cancel();
timer = null;
}
}
/** Restituisce il dT dall'ultima invocazione di updateTime */
private long updateTime() {
time1 = System.currentTimeMillis();
if(time0 == 0) {
time0 = time1;
}
long dTime = time1 - time0;
time0 = time1;
return dTime;
}
/** Aggiorna la posizione dello sprite */
private void updateSprite(long dTime) {
fakeSprite.setRect(
fakeSprite.getX() + dTime * SPEED,
fakeSprite.getY(),
fakeSprite.getWidth(),
fakeSprite.getHeight());
}
/** Disegna il componente e lo sprite */
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.RED);
((Graphics2D)g).fill(fakeSprite);
}
}
Main.java
package it.tukano.simpleranimation;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Main {
public static void main(String...args) {
new Main().setupAndShow();
}
private Main() {}
private void setupAndShow() {
JFrame frame = new JFrame("Simpler Animation");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
ScreenWithAnimation screen = new ScreenWithAnimation();
screen.setPreferredSize(new Dimension(400, 400));
screen.setBackground(Color.BLACK);
frame.getContentPane().add(screen, BorderLayout.CENTER);
bindWindowListener(frame, screen);
packAndShow(frame, screen);
}
private void bindWindowListener(final JFrame f,
final ScreenWithAnimation s)
{
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
s.stop(); //<<-- qui si ferma l'animazione
f.dispose();
}
});
}
private void packAndShow(final JFrame f, final ScreenWithAnimation s) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
f.pack();
f.setVisible(true);
s.start(); //<<-- qui parte l'animazione
}
});
}
}
Il Main di questo è praticamente uguale al Main dell'altro, per questo non l'ho commentato.
java.util.Timer è un Thread che può eseguire un particolare Runnable (TimerTask) periodicamente.
Nota che nel nostro caso non è necessario appoggiarsi esplicitamente al Thread AWT Event Dispatcher (quello che deve occuparsi di aggiornare l'aspetto di componenti AWT/Swing visibili) poichè ridisegnamo il componente (ScreenWithAnimation qui e Screen là) usando il metodo repaint(). repaint() in realtà non repaint un bel nulla: quello che fa è premere nella coda degli eventi AWT (consumata dall'Event Dispatcher Thread) una richiesta di aggiornamento del componente.
Io credo che dalla seconda versione, pur più breve, non si evinca il fatto che l'animazione sia un procedimento in senso proprio (cioè la composizione di più atti distinti e collegati): è più un bel minestrone. Che sia più o meno facile da comprendere dipende poi dai punti di vista di chi guardi. Insomma, vedi tu :).
Questo mi sembra già meglio :D ora me lo gurado un po' e vedo cosa chiderti :fagiano:
Ah ecco mi è venuta in mente subito una cosa, come mai nel main fai questo:
public class Main {
public static void main(String...args) {
new Main().setupAndShow();
}
private Main() {}
private void setupAndShow(){...}
Si tratta di una questione "tecnica" ma non di tecnica del linguaggio: in questo secondo caso, lo stesso metodo "main" è solo un suggerimento.
Solitamente la prima cosa che faccio nel metodo main di un programma Java è saltar fuori dal contesto statico in cui il main mi costringe.
Lo faccio perchè metodi e campi statici in Java sono in grado di infarfugliare anche le menti più arzille, soprattutto se mescolati a membri di istanza definiti nella stessa unità di compilazione: lì è l'apoteosi della confusione.
Il modificatore private applicato all'unico costruttore grida "questa classe non è la definizione di un oggetto". E' una collezione di procedure. Lo dico perchè tentare di assorbire nell'orientamento agli oggetti (che pure abbonda in creatività) qualcosa a cui nessuno salvo lui stesso può rivolgersi è da mal di testa.
Molto semplicemente, Main è quell'insieme di operazioni che devono essere fatte affinchè l'applicazione parta. Il fatto, imposto col costruttore private, che non possano essere create istanze di Main al di fuori dell'unità di compilazione Main.java è una specie di pugno sul tavolo: non si può e basta! :D.
Esiste la possibilità di applicare meccanismi più confacenti alle due O ma l'argomento era "come muovere qualcosa sullo schermo" e non "Elementi della prospettiva orientata agli oggetti: identità, ambiente, comunicazione".
Ecco, questa è la bella copia di quello a cui ho pensato quando ho scritto Main.java :).
Si può costruire un rattangolo o in generale un poligono conoscendone le coordinate dei vertici? Perchè mi servirebbe poi spostare separatemente questi vertici, non tutta la figura insieme.
Yes.
Puoi usare un GeneralPath come definizione di un poligono generico. Questo dovrebbe essere un rettangolo in posizione (x0, y0) di larghezza (width, height):
GeneralPath path = new GeneralPath();
path.moveTo(x0, y0);
path.lineTo(x0 + width, y0);
path.lineTo(x0 + width, y0 + height);
path.lineTo(x0, y0 + height);
path.closePath();
Lo proietti sullo schermo come ogni altro Shape:
graphics2d.fill(path) oppure graphics2d.draw(path)
Per manipolare i punti devi creare un tuo AffineTransform. Ridefinisci il metodo "transform(float[], int, float[], int, int)" del tuo AffineTransform in modo tale che intacchi i punti che ti interessano. Applichi la trasformazione con un:
path.transform(myAffineTransform);
Ehm mi sa che l'ultima parte me la devi rispiegare :fagiano:
Supponiamo che, dato lo Shape di tipo GeneralPath su definito, tu voglia spostare solo il primo punto. Per farlo devi trasformare la geometria di quel GeneralPath. Non ci sono AffineTransform pronti per fare un'operazione di questo tipo ragion per cui devi crearne uno da zero:
public class FirstPointTranslator extends AffineTransform {
public void transform(float[] src, int srcOff, float[] dst, int dstOff,
int numPts)
{
if(srcOff == 0 && src.length > 1) {
float x = src[0];
float y = src[1];
dst[dstOff] = x + 5;
dst[dstOff + 1] = y + 5;
}
}
}
Ogni volta che applichi questa trasformazione, il primo punto della figura è spostato di 5 pixel verso destra e 5 pixel verso il basso.
FirstPointTranslator fpt = new FirstPointTranslator();
shape.transform(fpt); //hop, primo punto +5,+5
shape.transform(fpt); //hop, primo punto +5,+5
E' un approccio un po' crudo ma è l'unico se si vogliano sfruttare i "precotti" di Sun in materia di grafica vettoriale. In alternativa puoi definire un tuo oggetto Poligono e lì la strada si apre ai più biechi desideri.
Grazie mille credo di aver capito.
Cosa intendi con "puoi definire un tuo oggetto Poligono"?
Ho cambiato la classe ScreenwhitAniamtion.java così
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import java.util.*;
public class ScreenWithAnimation extends JPanel {
/** Periodo del ciclo di animazione */
private final long TIMER_PERIOD = 24;
/** Velocità dello sprite in pixel per millisecondo */
private final float SPEED = 0.1f;
/** "Sprite" */
int[] xPoints={100,150,150,100};
int[] yPoints={1,1,80,80};
private GeneralPath fakeSprite = new GeneralPath(GeneralPath.WIND_EVEN_ODD,xPoints.length);
fakeSprite.moveTo(xPoints[0],yPoints[0]);
for (int index = 1;index < xPoints.length;index++) {
fakeSprite.lineTo(xPoints[index],yPoints[index]);
};
fakeSprite.closePath();
/** Variabili per il calcolo del tempo intercorso tra due passaggi nel
ciclo di aggiornamento */
private long time0, time1;
/** Compito affidato al timer */
private TimerTask updateTask = new TimerTask() {
public void run() {
long dTime = updateTime();
updateSprite(dTime);
repaint();
}
};
/** Una specie di Thread che esegue (anche) compiti periodici */
private java.util.Timer timer;
/** Avvia l'animazione */
public void start() {
if(timer == null) {
timer = new java.util.Timer();
timer.scheduleAtFixedRate(updateTask, 0, TIMER_PERIOD);
}
}
/** Ferma l'animazione */
public void stop() {
if(timer != null) {
timer.cancel();
timer = null;
}
}
/** Restituisce il dT dall'ultima invocazione di updateTime */
private long updateTime() {
time1 = System.currentTimeMillis();
if(time0 == 0) {
time0 = time1;
}
long dTime = time1 - time0;
time0 = time1;
return dTime;
}
/** Aggiorna la posizione dello sprite */
private void updateSprite(long dTime) {
FirstPointTranslator fpt = new FirstPointTranslator();
fakeSprite.transform(fpt);
}
/** Disegna il componente e lo sprite */
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.RED);
((Graphics2D)g).fill(fakeSprite);
}
}
Ma mi da <identifier>expected in corripsondenza di "private GeneralPath fakeSprite = new GeneralPath(GeneralPath.WIND_EVEN_ODD,xPoints.length);
fakeSprite.moveTo(xPoints[0],yPoints[0]);
for (int index = 1;index < xPoints.length;index++) {
fakeSprite.lineTo(xPoints[index],yPoints[index]);
};
fakeSprite.closePath();"
e illegal start of type in corrpisondenza del ciclo for...
Dove sbaglio?
Il corpo di una classe Java può contenere solo dichiarazioni oppure blocchi.
class Bingo {
int x = 30; //ok, dichiarazione con assegnamento contestuale
GeneralPath x; //ok, dichiarazione
x = new GeneralPath(...) //Niet!!! l'assegnamento non è dichiarazione
for(int i = 0; i < 40; i++) { //Nain!!! il ciclo for non è una dichiarazione
{ //Blocco
for(int i = 0; i <... //Ok, un blocco può contenere l'istruzione for
}
public void doSomenthing() { //Ok, dichiarazione di metodo
}
Nel tuo caso abbiamo due tentate invocazioni di metodo (fakeSprite.moveTo e fakeSprite.closePath) e un ciclo for. Puoi usare un costruttore per "completare" la creazione del tuo GeneralPath:
public class ScreenWithAnimation extends JPanel {
GeneralPath fakeSprite = new GeneralPath(...omissis);
public ScreenWithAnimation() {
fakeSprite.moveTo(xPoints[0],yPoints[0]);
for (int index = 1;index < xPoints.length;index++) {
fakeSprite.lineTo(xPoints[index],yPoints[index]);
};
fakeSprite.closePath();
}
}
Grazie mille chiarissimo!
Ma nell'esempio che mi hai fatto tu, inserisci il codice per spostare il quadrato qui:
/** Aggiorna la posizione dello sprite */
private void updateSprite(long dTime) {
fakeSprite.setRect(
fakeSprite.getX() + dTime * SPEED,
fakeSprite.getY(),
fakeSprite.getWidth(),
fakeSprite.getHeight());
}
Quindi in pratica il codice che è all'interno di questo metodo viene ripetuto ad intervalli di 24 ms se ho capito bene giusto?
E' quindi plausibile che pure io devo inserire qui il mio codice per spostare ruotare ecc ecc le mie figure?
Poi un altra cosa, diciamo che ora ho materiale a sufficicenza per cominciare a fare qualcosa di semplice, mi potresti spiegare cosa intendevi con crearti un oggetto Poligono? magari potrebbe semplificarmi la vita o comunque farmi capire meglio :).
Sempre se non ti è troppo disturbo :)
Grazie ancora
Ho fatto le modifiche che mi hai suggerito, ora compila ma quando esegue sulla console mi esce questo...
Exception in thread "Timer-0" java.lang.NullPointerException
at ScreenWithAnimation.updateSprite(ScreenWithAnimation.java:74)
at ScreenWithAnimation.access$100(ScreenWithAnimation.java:6)
at ScreenWithAnimation$1.run(ScreenWithAnimation.java:36)
at java.util.TimerThread.mainLoop(Timer.java:512)
at java.util.TimerThread.run(Timer.java:462)
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at sun.awt.windows.Win32Renderer.doShape(Win32Renderer.java:217)
at sun.awt.windows.Win32Renderer.fill(Win32Renderer.java:260)
at sun.java2d.pipe.ValidatePipe.fill(ValidatePipe.java:142)
at sun.java2d.SunGraphics2D.fill(SunGraphics2D.java:2258)
at ScreenWithAnimation.paintComponent(ScreenWithAnimation.java:81)
at javax.swing.JComponent.paint(JComponent.java:1005)
at javax.swing.JComponent.paintChildren(JComponent.java:842)
at javax.swing.JComponent.paint(JComponent.java:1014)
at javax.swing.JComponent.paintChildren(JComponent.java:842)
at javax.swing.JComponent.paint(JComponent.java:1014)
at javax.swing.JLayeredPane.paint(JLayeredPane.java:559)
at javax.swing.JComponent.paintChildren(JComponent.java:842)
at javax.swing.JComponent.paintWithOffscreenBuffer(JComponent.java:4970)
at javax.swing.JComponent.paintDoubleBuffered(JComponent.java:4916)
at javax.swing.JComponent.paint(JComponent.java:995)
at java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:21)
at sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:60)
at sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:97)
at java.awt.Container.paint(Container.java:1709)
at sun.awt.RepaintArea.paintComponent(RepaintArea.java:248)
at sun.awt.RepaintArea.paint(RepaintArea.java:224)
at sun.awt.windows.WComponentPeer.handleEvent(WComponentPeer.java:254)
at java.awt.Component.dispatchEventImpl(Component.java:4031)
at java.awt.Container.dispatchEventImpl(Container.java:2024)
at java.awt.Window.dispatchEventImpl(Window.java:1774)
at java.awt.Component.dispatchEvent(Component.java:3803)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:463)
at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:242)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:163)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:157)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:149)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:110)
A cosa si riferisce?
Questo è il codice per aggiornare la posizione dello sprite:
/** Aggiorna la posizione dello sprite */
private void updateSprite(long dTime) {
FirstPointTranslator fpt = new FirstPointTranslator();
fakeSprite.transform(fpt);
}
Nella linea 81 di ScreenWithAnimation cosa c'è scritto?
Ho provato e sembra tutto ok. L'interpretazione dei messaggi di errore della piattaforma Java potrebbe occupare da sola più d'un volume. Comunque, un buon inizio per scovare la causa è leggere la traccia dall'alto verso il basso e trovare il primo nome corrispondente ad un tipo da te dichiarato.
Nel nostro caso si arriva a updateSprite.
L'eccezione dice che un riferimento è "null" (e non dovrebbe esserlo). In updateSprite l'unica cosa che possa essere null è "fakeSprite". Occorre a questo punto, codice sorgente alla mano, andare a capire se al nome "fakeSprite" corrisponda un valore diverso da null prima della prima invocazione di "updateSprite" e per ogni invocazione di "updateSprite".
Ti incollo il codice che ho provato io (che è poi quello che hai incollato tu)
package it.tukano.simpleranimation;
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import java.util.*;
public class ScreenWithAnimation2 extends JPanel {
/** Periodo del ciclo di animazione */
private final long TIMER_PERIOD = 24;
/** Velocità dello sprite in pixel per millisecondo */
private final float SPEED = 0.1f;
/** "Sprite" */
int[] xPoints={100,150,150,100};
int[] yPoints={1,1,80,80};
private GeneralPath fakeSprite =
new GeneralPath(GeneralPath.WIND_EVEN_ODD,xPoints.length);
public ScreenWithAnimation2() {
fakeSprite.moveTo(xPoints[0],yPoints[0]);
for (int index = 1;index < xPoints.length;index++) {
fakeSprite.lineTo(xPoints[index],yPoints[index]);
};
fakeSprite.closePath();
}
/** Variabili per il calcolo del tempo intercorso tra due passaggi nel
ciclo di aggiornamento */
private long time0, time1;
/** Compito affidato al timer */
private TimerTask updateTask = new TimerTask() {
public void run() {
long dTime = updateTime();
updateSprite(dTime);
repaint();
}
};
/** Una specie di Thread che esegue (anche) compiti periodici */
private java.util.Timer timer;
/** Avvia l'animazione */
public void start() {
if(timer == null) {
timer = new java.util.Timer();
timer.scheduleAtFixedRate(updateTask, 0, TIMER_PERIOD);
}
}
/** Ferma l'animazione */
public void stop() {
if(timer != null) {
timer.cancel();
timer = null;
}
}
/** Restituisce il dT dall'ultima invocazione di updateTime */
private long updateTime() {
time1 = System.currentTimeMillis();
if(time0 == 0) {
time0 = time1;
}
long dTime = time1 - time0;
time0 = time1;
return dTime;
}
/** Aggiorna la posizione dello sprite */
private void updateSprite(long dTime) {
FirstPointTranslator fpt = new FirstPointTranslator();
fakeSprite.transform(fpt);
}
/** Disegna il componente e lo sprite */
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.RED);
((Graphics2D)g).fill(fakeSprite);
}
}
Funziona come intendevi.
Nella linea 81 di ScreenWithAnimation cosa c'è scritto?
((Graphics2D)g).fill(fakeSprite);
A me non funziona...mi da sempre una serie di errori a console.
Può darsi che ho sbagliatoa definire la classe FirstPointTraslator.
Questa:
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import java.util.*;
public class FirstPointTranslator extends AffineTransform {
public void transform(float[] src, int srcOff, float[] dst, int dstOff,
int numPts)
{
if(srcOff == 0 && src.length > 1) {
float x = src[0];
float y = src[1];
dst[dstOff] = x + 5;
dst[dstOff + 1] = y + 5;
}
}
}
Se FirstPointTranslator fosse coinvolto (anche indirettamente) nell'errore il suo nome apparirebbe nella traccia dell'eccezione. Mi sembra comunque che sia Ok.
Incolla il codice completo della classe ScreenWithAnimation che stai usando e vediamo subito dove stia l'inghippo.
Circa il Poligono, si tratta di creare un che di simile a GeneralPath ma che consenta una manipolazione un po' più intuitiva dei suoi punti.
Io immagino qualcosa a cui io possa dire "hey franceschino, il terzo punto lo spostiamo di 5 pixel in su". In "Javese" (o Javesco) è un metodo:
//una capacità di Polygon
public void movePoint(int pointIndex, int dx, int dy) {...}
Il poligono deve essere disegnato e qui dipende da come la vedi. O il poligono sa come proiettarsi su una superficie:
//in Polygon.java
public void draw(Graphics2D g)
o qualcuno disegna il Poligono
//in PolygonRenderer.java
public void draw(Polygon p)
La definizione dell'atto di disegnare un poligono non cambia ma la struttura è profondamente diversa come diverse sono le conseguenze sul piano del sistema.
MA (c'è sempre un ma) per disegnare concretamente il poligono, il modo più semplice è rivolgersi di nuovo a GeneralPath. L'alternativa richiede la scrittura di un rasterizzatore, che non è voodooo ma visto che ne abbiamo uno pronto... Si spende più memoria.
Al solo fine di sfruttare il rasterizzatore integrato, Polygon avrebbe non un metodo "draw" ma un:
public Shape getShape() {...}
Polygon potrebbe essere una cosa così:
package it.tukano.simpleranimation;
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
public class Polygon {
private GeneralPath shape = new GeneralPath();
private ArrayList<Point> vertices = new ArrayList<Point>();
private boolean closePath;
public Polygon(boolean closed, Point...pts) {
vertices.addAll(Arrays.asList(pts));
closePath = closed;
}
/** Sposta un vertice */
public void moveVertex(int index, int dx, int dy) {
Point p = vertices.get(index);
p.setLocation(p.x + dx, p.y + dy);
/* ...e rigenera tutto il GeneralPath */
shape.reset();
for(int i = 0; i < vertices.size(); i++) {
p = vertices.get(i);
if(i == 0) {
shape.moveTo(p.x, p.y);
} else {
shape.lineTo(p.x, p.y);
}
}
if(closePath) {
shape.closePath();
}
}
public Shape getShape() {
return shape;
}
}
Al che la sua creazione (in ScreenWithAnimation) sarebbe:
private Polygon fakeSprite = new Polygon(true,
new Point(100, 100),
new Point(200, 100),
new Point(200, 200),
new Point(100, 200));
e la trasformazione di uno dei suoi punti (che era poi la destinazione del nostro percorso):
/** Aggiorna la posizione dello sprite */
private void updateSprite(long dTime) {
fakeSprite.moveVertex(2, 5, 5);
}
Per il fatto dell'errore, io ho usato facendo copia-incolla esattamente il tuo ultimo codice dove dici che funziona. Parlo di ScreenWithAnimation2.
Pari pari quello ho usato. :fagiano:
Per il resto, beh che dire, come al solito non posso che ringraziarti!!! :D :)
La stranezza dell'eccezione va risolta perchè siamo programmatori, non stregoni.
La linea 81 di ScreenWithAnimation2 è "g.setColor(Color.RED)". Puoi rilanciare il programma e incollarmi di nuovo l'eccezione generata?
Exception in thread "Timer-0" java.lang.NullPointerException
at ScreenWithAnimation.updateSprite(ScreenWithAnimation.java:74)
at ScreenWithAnimation.access$100(ScreenWithAnimation.java:6)
at ScreenWithAnimation$1.run(ScreenWithAnimation.java:36)
at java.util.TimerThread.mainLoop(Timer.java:512)
at java.util.TimerThread.run(Timer.java:462)
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at sun.awt.windows.Win32Renderer.doShape(Win32Renderer.java:217)
at sun.awt.windows.Win32Renderer.fill(Win32Renderer.java:260)
at sun.java2d.pipe.ValidatePipe.fill(ValidatePipe.java:142)
at sun.java2d.SunGraphics2D.fill(SunGraphics2D.java:2258)
at ScreenWithAnimation.paintComponent(ScreenWithAnimation.java:81)
at javax.swing.JComponent.paint(JComponent.java:1005)
at javax.swing.JComponent.paintChildren(JComponent.java:842)
at javax.swing.JComponent.paint(JComponent.java:1014)
at javax.swing.JComponent.paintChildren(JComponent.java:842)
at javax.swing.JComponent.paint(JComponent.java:1014)
at javax.swing.JLayeredPane.paint(JLayeredPane.java:559)
at javax.swing.JComponent.paintChildren(JComponent.java:842)
at javax.swing.JComponent.paintWithOffscreenBuffer(JComponent.java:4970)
at javax.swing.JComponent.paintDoubleBuffered(JComponent.java:4916)
at javax.swing.JComponent.paint(JComponent.java:995)
at java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:21)
at sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:60)
at sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:97)
at java.awt.Container.paint(Container.java:1709)
at sun.awt.RepaintArea.paintComponent(RepaintArea.java:248)
at sun.awt.RepaintArea.paint(RepaintArea.java:224)
at sun.awt.windows.WComponentPeer.handleEvent(WComponentPeer.java:254)
at java.awt.Component.dispatchEventImpl(Component.java:4031)
at java.awt.Container.dispatchEventImpl(Container.java:2024)
at java.awt.Window.dispatchEventImpl(Window.java:1774)
at java.awt.Component.dispatchEvent(Component.java:3803)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:463)
at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:242)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:163)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:157)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:149)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:110)
Guarda ho risolto, scusa se ti ho fatto perdere tempo ma il problema era che facevo dei casini a compilare il progetto che era tutto un rimischio di quello che c'era prima.
Ora ho ripulito tutto e funziona, come giusto che sia! Grazie e scusa per il tempo perso.
Allora ricapitolando, le mie trasformazione le devo fare modificando il metodo transform.
Ma se io devo fare una transformazione a cui passo un valore, non so tipo una rotazione a cui passo l'angolo di cui deve ruotare, come mi consigli di fare?
Magari implementandolo nell'oggetto stesso poligono.
Così ad esempio io poi faccio qualcosa come poligono.ruota(alpha) e quello mi ruota.
Ci dovrei quasi essere ma mi manca ancora quello spunto per prendere il via e continuare con le mia gambe :)
Per una rotazione puoi usare AffineTransform.getRotateInstance. Come hai già notato, un AffineTransform può manipolare un intero Shape o singoli punti. Nel nostro caso (appoggiandoci al rasterizzatore integrato e volendo avere accesso ad un singolo vertice nel modo già visto) trasformiamo prima i punti della lista "vertices" e poi ricostruiamo lo Shape.
La classe Polygon che ho incollato con troppa fretta conteneva un errore. Lo Shape era inizializzato solo dopo la prima trasformazione, cosa chiaramente sbagliata. Lo incollo corretto e "rotante":
package it.tukano.simpleranimation;
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
public class Polygon {
private GeneralPath shape = new GeneralPath();
private ArrayList<Point> vertices = new ArrayList<Point>();
private boolean closePath;
public Polygon(boolean closed, Point...pts) {
vertices.addAll(Arrays.asList(pts));
closePath = closed;
rebuildShape();
}
/** Ruota il poligono di un angolo φ (gradienti) intorno al
baricentro. */
public void rotate(double φ) {
// Calcola il baricentro
int x = 0;
int y = 0;
for(Point p : vertices) {
x += p.x;
y += p.y;
}
x /= vertices.size();
y /= vertices.size();
// Applica una trasformazione ai vertici
AffineTransform rotate = AffineTransform.getRotateInstance(
Math.toRadians(φ), x, y);
for(Point p : vertices) {
rotate.transform(p, p);
}
// Rigenera la geometria restituita da getShape
shape.transform(rotate);
}
/** Sposta un vertice */
public void moveVertex(int index, int dx, int dy) {
Point p = vertices.get(index);
p.setLocation(p.x + dx, p.y + dy);
rebuildShape();
}
public Shape getShape() {
return shape;
}
private void rebuildShape() {
shape.reset();
for(int i = 0; i < vertices.size(); i++) {
Point p = vertices.get(i);
if(i == 0) {
shape.moveTo(p.x, p.y);
} else {
shape.lineTo(p.x, p.y);
}
}
if(closePath) {
shape.closePath();
}
}
}
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.