View Full Version : [Java]Problema animazione 2D
neutronirt
28-09-2011, 00:14
Ciao a tutti ragazzi, il mio problema non è prettamente dovuto al codice, ma a come l'applicazione funziona esclusivamente sul MIO pc.
Sostanzialmente da qualche giorno l'applicazione 2d su cui sto lavorando va molto più velocemente di quanto dovrebbe. Mi spiego meglio.
Quando muovo lo sprite lungo lo schermo, un pixel ogni millisecondo circa, il tutto viene fatto molto più velocemente del dovuto, ma ripeto, solo sul mio pc.
Ho pensato che questo potesse essere dovuto alla JRE, ma non ne sono sicuro e non so cosa pensare, spero qualcuno posssa aiutarmi.
Saluti
banryu79
28-09-2011, 09:42
Quando muovo lo sprite lungo lo schermo, un pixel ogni millisecondo circa, il tutto viene fatto molto più velocemente del dovuto, ma ripeto, solo sul mio pc.
A livello di codice, come fai a ottenere le spostamento di 1 pixel/millisec?
Ho pensato che questo potesse essere dovuto alla JRE, ma non ne sono sicuro e non so cosa pensare, spero qualcuno posssa aiutarmi.
Senza vedere il codice è impossibile dirti qualcosa di concreto e specifico...
neutronirt
28-09-2011, 12:10
Per spostare di un pixel al ms uso un timer che richiama il metodo actionperformed del jpanel che implementa ActionListener.
Ogni millisecondo viene richiamato il metodo actionPerformed che sposta lo sprite. Sicuramente il problema non è dovuto al codice, lo dico perchè aprendo due volte l'eseguibile dell'applicazione(quindi con 2 app aperte) la velocità di entrambe si dimezza...
Segue il codice del jpanel:
public class Board extends JPanel implements ActionListener {
private Timer timer;
private Sprite craft;
public Board() {
addKeyListener(new TAdapter());
setFocusable(true);
setBackground(Color.BLACK);
setDoubleBuffered(true);
craft = new Sprite();
//IL TIMER RICHIAMA IL METODO ACTION PERFORMED OGNI VOLTA
timer = new Timer(1, this);
timer.start();
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D)g;
g2d.drawImage(craft.getImage(), craft.getX(), craft.getY(), this);
ArrayList<Missile> ms = craft.getMissiles();
for (int i = 0; i < ms.size(); i++ ) {
Missile m = (Missile) ms.get(i);
g2d.drawImage(m.getImage(), m.getX(), m.getY(), this);
}
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
public void actionPerformed(ActionEvent e) {
ArrayList ms = craft.getMissiles();
for (int i = 0; i < ms.size(); i++) {
Missile m = (Missile) ms.get(i);
if (m.isVisible())
m.move();
else ms.remove(i);
}
craft.move();
repaint();
}
private class TAdapter extends KeyAdapter {
public void keyReleased(KeyEvent e) {
craft.keyReleased(e);
}
public void keyPressed(KeyEvent e) {
craft.keyPressed(e);
}
}
}
neutronirt
28-09-2011, 12:36
Credo che il problema si manifesti solo quando internet è aperto, non so perchè, ma è così, me ne sono appena accorto.
banryu79
28-09-2011, 12:56
Credo che il problema si manifesti solo quando internet è aperto, non so perchè, ma è così, me ne sono appena accorto.
Puoi postare la versione del JDK che stai utilizzando?
neutronirt
28-09-2011, 13:03
1.6.0, grazie mille dell'interessamento comunque
banryu79
28-09-2011, 15:05
Sostanzialmente da qualche giorno l'applicazione 2d su cui sto lavorando va molto più velocemente di quanto dovrebbe.
Ciao.
Posto che il codice da te pubblicato è ok, nel senso che non fa nulla di "strambo", ci sono un paio di considerazioni da fare, relativamente a quello che vuoi ottenere.
Stai implementando una sorta di gioco 2D, appoggiandoti a Swing, almeno stando a quanto fin qui visto. Quindi vuoi fare grafica 2D performante e controllare il frame rate con il quale girerà il gioco. Roba tutto sommato normale.
Le due considerazioni, sulle quali non mi dilungo per motivi di tempo, sono le seguenti:
1) per farla breve, per le immagini da caricare, affinchè siano performanti e accellerate, non basta caricarle semplicemente con i soliti metodi, bisogna anche convertirle in un formato compatibile con il display (GraphicConfiguration del monitor in uso).
2) per la grafica acellerata in Java non basta fare l'override del metodo paint di un JComponent, è invece neccessario usare un BufferStrategy ricavato da un Canvas (AWT) o un JFrame (Swing).
3) Bisogna controllare direttamente il frame rate del rendering loop di un gioco (essendo in Java non hai nessuna garanzia circa il sistema sottostante e le sue politiche di scheduling per i thread, nonchè la granularità del timer di sistema).
Ti posto due link utili:
- tutrial per giochi 2D in Java, che tocca tutti gli aspetti qui citati e va anche oltre; (http://www.kevinglass.com/index.php?page=tut2djava)
- un forum specializzato su giochi e grafica ad alte prestazioni in Java, una risorsa eccellente; (http://www.java-gaming.org/index.php?action=resources)
Il metodo da te usato per aggiornare la posizione delle sprite (1pixel/ms) non è deterministico perchè non hai nessuna garanzia che il timer lanci effettivamente 1 evento ogni 1 millisecondo. Intanto dipende dal sistema operativo su cui il tuo gioco si trova a girare: se sei sotto Windows, ad esempio, essendo che Timer internamente fa chiamate a System.currentTimeMillis() il risultato è che le pause da 1 millisecondo sono in realtà pause da (circa) 15-16 millisecondi perchè questa è la granularità del timer di sistema in Windows a cui si appoggia quella chiamata.
Il seguente programma lo dimostra in via sperimentale (il mio sistema è un Windows XP):
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import javax.swing.Timer;
/**
* Class TimerOneMillisec...
* @author Francesco Baro
*/
public class TimerOneMillisec implements ActionListener {
public static void main(String[] args) {
TimerOneMillisec tom = new TimerOneMillisec();
tom.start();
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
System.out.println("@" + Time.now() +
" (nanos)> main thread has been interrupted");
}
tom.stop();
tom.printResult();
}
TimerOneMillisec() {
timer = new Timer(1, this);
}
@Override public void actionPerformed(ActionEvent e) {
buffer.append(Time.now()).append(" ");
}
void start() {
startTime = Time.now();
timer.start();
}
void stop() {
timer.stop();
endTime = Time.now();
}
Iterable<String> processResults() {
List<String> result = new LinkedList<String>();
long oldTime = startTime;
String results = buffer.toString().trim();
StringTokenizer st = new StringTokenizer(results);
while (st.hasMoreTokens()) {
long time = Long.parseLong(st.nextToken());
result.add(String.valueOf(time - oldTime));
oldTime = time;
}
return result;
}
void printResult() {
long t1 = Time.toMillis(startTime);
long t2 = Time.toMillis(endTime);
System.out.println("Started at (millis): " + t1);
System.out.println("Ended at (millis): " + t2);
System.out.println("Total elapsed (millis): " + (t2 - t1));
System.out.println("Partials (nanos):");
Iterable<String> lines = processResults();
for (String line : lines) System.out.println(line);
}
private final StringBuilder buffer = new StringBuilder();
private final Timer timer;
private long startTime, endTime;
private static class Time {
// construction forbidden
private Time() {}
static long now() {
return System.nanoTime();
}
static long elapsedFrom(long nanos) {
return System.nanoTime() - nanos;
}
static long toMillis(long nanos) {
return nanos / 1000000;
}
}
}
Una sua tipica esecuzione da me ha prodotto questo risultato:
run:
Started at (millis): 21608361
Ended at (millis): 21609362
Total elapsed (millis): 1001
Partials (nanos):
61758408
1160483
11890617
15492472
15683278
15578796
15611202
15591088
15619862
15627964
15653386
15603659
15617348
15638859
15626567
15622936
15684116
15620980
15608408
15588294
15639977
15762338
15491634
15752840
15585780
15580751
15605335
15607850
15606453
15646681
15632434
15572370
15655062
15638859
15650313
15580193
15669030
15619025
15579075
15637183
15614554
15624891
15648357
15611482
16157360
15127901
15622376
15630479
15599748
15635506
15609526
15627126
15737754
15500015
15651710
15607850
15620141
15632155
15638579
15621539
15606732
15642770
BUILD SUCCESSFUL (total time: 2 seconds)
Ma poi anche se il timer effettivamente pubblicasse un action event ogni millisecondo nella coda degli eventi awt, è possibile che più richieste di repaint() in successione vengano fuse/unite in un'unica rischiesta, e l'effetto risultante è quello di vedere un aggiornamento della posizione delle sprite "a singhiozzo", perdendo gli "step" intermedi.
Non so se questo meccanismo sia la causa che produce gli effetti che hai verificato quando lanci due istanze del tuo gioco (che girano sì in due istanze diverse della jvm, ma la scheda grafica, le risorse video e il window manager del so sono sempre singoli)...
Il primo post (quello con i tutorial), può aiutarti davvero molto, è una lettura che ti consiglio caldamente.
neutronirt
28-09-2011, 15:34
Caro banryu, ti ringrazio infinitamente per l'immenso aiuto che mi hai dato. Sto iniziando ora, infatti, con la programmazione 2d in java e questa è la primissima applicazione con grafica accelerata che implemento (per quanto riguarda le swing sono piuttosto ferrato, ma non per ciò che concerne la grafica accelerata).
Credo proprio che il problema dell'app sia dovuto a questa mancanza di controllo del tempo.
Comunque sia ora leggerò accuratamente le guide che mi hai sottoposto, da tempo ne cercavo di valide e finalmente posso appoggiarmi a qualcosa di utile!
banryu79
28-09-2011, 15:52
Sì, ho visto che nel codice esegui una chiamata a Toolkit.synch(), il che *in teoria* dovrebbe scongiurare l'accumulo di eventi grafici nella coda degli eventi. Dico in teoria, perchè leggendo in giro pare che, come sempre, dipenda dall'implementazione sottostante.
@EDIT: ti segnalo anche un vecchio thread su questo forum, si sa mai che possa contenere info & link utili (ad esempio se cerchi sprite e asset grafici vari da usare nei tuoi giochi, butta un occhio): http://www.hwupgrade.it/forum/showthread.php?t=1817420
Un timer di 1 ms ha poco senso, non è che da te va più veloce, è probabile che in altri pc vada più piano.
Comunque, se vuoi abbozzare un motore di rendering 2d, non servono timer, detta molto alla buona ti basta un ciclo infinito, calcolare il tempo(delta t) che intercorre tra due esecuzioni del ciclo e in base a quello spostare la roba.
Quello è l'unico modo che ti assicura che gli oggetti si spostino tutti alla stessa velocità in tutti i pc che eseguono il programma.
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.