Animationen auf verschieden Geräten

Hallo!

Wie macht man flüssige Animationen mit Swing, die auf verschiedenen Geräten gleich stabil und in etwa auch gleich schnell laufen.
Zurzeit mache ich meine Animationen noch so in etwa so… Würde es reichen wenn ich die Zeit von updateGUI() messe und dann (50-gemessene Zeit) lang
den Thread schlafen legen? Oder gibt es eine andere Methode, damit es auf verschiedenen Geräten gleich schnell läuft.


    public void run() {

        while(true) {
            updateGUI();
            Thread.sleep(50);
        }

     }

}```

Ich wuerde den thread immer gleich lange schlafen lassen, bzw im prinzip ist das ja egal. Einfach mur die animationsveraenderung * die gemessene zeit nehmen, also zB player.moveX(speex * delta)

Ja, das was mymaksimus da so lapidar dahergesagt hat, ist das wichtige. Nicht nur auf verschiedenen Geräten: Schließlich kann man nicht wissen, wie lange die „Spiellogik“ braucht.

Je nachdem, WIE genau das alles sein soll, und abhängig von einigen anderen Faktoren, kann man da ziemlich viel Hinrschmalz reinstecken. Z.B. im Hinblick auf die Frage, ob die Berechnung alle 10ms (oder gar „so oft wie möglich“, ohne delay) erfolgen soll, aber trotzdem nur alle 30ms neu gezeichnet werden soll, oder oder oder. Aber ein übliches Muster ist GROB sowas wie

long previousUpdateNS = System.nanoTime();
while (gameRunning)
{
    long currentTimeNS = System.nanoTime();
    long passedTimeNS = currentTimeNS - previousUpdateNS;
    
    doGameLogic(passedTimeNS);
    updateGUI();
    previousUpdateNS = currentTimeNS;

    Thread.sleep(50);
}

Dabei ist wichtig, dass die „doGameLogic“-Methode den Spielzustand abhängig von der verstrichenen Zeit ändert. Wenn sich ein Objekt 100 Pixel pro Sekunde bewegen soll, würde dort eben stehen

void doGameLogic(long passedTimeNS)
{
    double passedTimeS = passedTimeNS * 1e-9;
    moveObject(passedTimeS * 100); // 100 pixel pro Sekunde
}

DA kommen die verschiedenen Geräte dann ggf. zum Tragen - also ob 100 Pixel die gesamte Bildschirmbreite sind oder nicht :wink:

Ok ich habe jetzt mal eine kleine Test-Version geschrieben, allerdings ist die Animation noch nicht flüssig, wenn ich Thread.sleep(50) setze.
Würde ich jetzt zum Beispiel Thread.sleep(5) setzen, läuft alles flüssig nur der CPU geht dann weit hoch.

1.Wie kann also die Animation bei sagen wir mal Thread.sleep(50) flüssig laufen?

2.Wie kann man den Speed der Animation exponentiell ändern, sodass es auch auf mehreren PCs gleich schnell läuft?
Ich denke mal nicht, dass man den Speed nach jeder Loop ändern kann, da die Animation ja dann durch eine rechenintensiven doLogic() Methode bei verschiedenen Rechnern
unterschiedlich schnell aufgeführt wird.

import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class TestAnimation implements ActionListener {

    private JFrame window;
    private Animation animation;
    
    private ComponentOne compOne;
    private ComponentTwo compTwo;
    private AtomicBoolean doAnimation;

    public TestAnimation() {
        doAnimation = new AtomicBoolean(false);

        animation = new Animation();
        animation.start();

        compOne = new ComponentOne();
        compTwo = new ComponentTwo();

        window = new JFrame();
        window.setSize(500, 300);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setLocationRelativeTo(null);
        window.setLayout(null);

        compOne.setBounds(0, 0, 500, 300);
        compTwo.setBounds(500, 0, 500, 300);

        window.add(compOne);
        window.add(compTwo);

        window.setVisible(true);
    }

    public static void main(String[] args) {
        new TestAnimation();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println(e.getActionCommand());
        if (e.getActionCommand().equals("Do Animation")) {
            animation.setAnimationLeftToRight();
        } else {
            animation.setAnimationRightToLeft();
        }
        doAnimation.set(true);
    }

    private class ComponentOne extends JComponent {

        double animationX = 0;
        
        public ComponentOne() {
            createButton("Do Animation", Color.WHITE, Color.BLACK, this);
        }

        public void paintComponent(Graphics g) {
            g.setColor(new Color(0, 0, 0));
            g.fillRect(0, 0, getWidth(), getHeight());
        }
    }

    private class ComponentTwo extends JComponent {

        double animationX = 0;
        
        public ComponentTwo() {
            createButton("Do Animation2", Color.BLACK, Color.WHITE, this);
        }

        public void paintComponent(Graphics g) {
            g.setColor(new Color(255, 255, 255));
            g.fillRect(0, 0, getWidth(), getHeight());
        }
    }

    public void createButton(String text, Color bg, Color fg, JComponent component) {
        JButton button = new JButton(text);
        button.setBackground(bg);
        button.setForeground(fg);
        button.setFont(new Font("Arial", Font.BOLD, 17));
        button.setBounds(150, 100, 200, 50);
        button.addActionListener(this);
        component.add(button);
    }

    private class Animation extends Thread {

        private boolean leftToRight = true;
        private int speed = 100;

        public void run() {
            long previousUpdate = System.nanoTime();
            while (true) {
                long currentTime = System.nanoTime();
                long passedTime = currentTime - previousUpdate;
                double currentPassedTime = (double) (passedTime * 1e-9);
                if (doAnimation.get()) {
                    doLogic(currentPassedTime);
                    updateGUI();
                }
                
                previousUpdate = System.nanoTime();
                try {
                    Thread.sleep(50);
                } catch (InterruptedException ex) {
                    Logger.getLogger(TestAnimation.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
        
        private void doLogic(double passedTime) {
            if(leftToRight) {
                compOne.animationX -= (speed*passedTime);
                compTwo.animationX -= (speed*passedTime);
                if(compTwo.animationX <= -500) {
                    compTwo.animationX = -500;
                    compOne.animationX = -500;
                    doAnimation.set(false);
                }
            } else {
                compOne.animationX += (speed*passedTime);
                compTwo.animationX += (speed*passedTime);
                if(compOne.animationX >= 0) {
                    compOne.animationX = 0;
                    compOne.animationX = 0;
                    doAnimation.set(false);
                }
            } 
        }
        
        private void updateGUI() {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    compOne.setLocation(0 + (int)compOne.animationX, 0);
                    compTwo.setLocation(500 + (int)compTwo.animationX, 0);
                    window.repaint();
                }
            });
        }

        public void setAnimationLeftToRight() {
            leftToRight = true;
        }

        public void setAnimationRightToLeft() {
            leftToRight = false;
        }
    }
}```

Naja, was erwartest du?

Angenommen ein render vorgang (also einmal repaint() + update und alles was in der loop ist) dauert 0 nanosekunden,
also nichts, und du würdest 50 millisekunden warten, dann hättest du immer noch maximal 20fps. Das ist nicht gerade viel.
Für eine Wiedergabe mit 60fps dürftest du maximal 16.periode6 millisekunden warten, und dann kommt da noch
die render zeit drauf. 10 millisekunden sind eigentlich optimal.

Versteh ich nicht. was willst du erreichen? So wie es jetzt programmiert ist sollte es überall gleich schnell laufen…

Das Problem könnte (ist aber eher unwahscheinlich) ein kleiner aber feiner Fehler sein, den schon Marco13 hier gepostet hat:

while (gameRunning)
{
    long currentTimeNS = System.nanoTime();
    long passedTimeNS = currentTimeNS - previousUpdateNS;

    doGameLogic(passedTimeNS);
    updateGUI();
    previousUpdateNS = currentTimeNS; // hier definitiv nicht System.nanoTime(); !!!
 
    Thread.sleep(50);
}```Zwichen dem 1. und dem 2. System.nanoTime() vergehen definitiv mehrere 100 Nanosekunden und deswegen wird die Differenz zwischen currentTimeNS und previousTimeNS im nächsten Durchgang nicht berücksichtigt maw übersprungen. Die Zeitnahme sollte also nur einmal pro Durchlauf erfolgen.

[QUOTE=Spacerat]Das Problem könnte (ist aber eher unwahscheinlich) ein kleiner aber feiner Fehler sein, den schon Marco13 hier gepostet hat:

while (gameRunning)
{
    long currentTimeNS = System.nanoTime();
    long passedTimeNS = currentTimeNS - previousUpdateNS;

    doGameLogic(passedTimeNS);
    updateGUI();
    previousUpdateNS = currentTimeNS; // hier definitiv nicht System.nanoTime(); !!!
 
    Thread.sleep(50);
}```Zwichen dem 1. und dem 2. System.nanoTime() vergehen definitiv mehrere 100 Nanosekunden und deswegen wird die Differenz zwischen currentTimeNS und previousTimeNS im nächsten Durchgang nicht berücksichtigt maw übersprungen. Die Zeitnahme sollte also nur einmal pro Durchlauf erfolgen.[/QUOTE]

Also bei meinem Computer sehe ich optisch keinen Unterschied.

Dann ist der so gut, das die berechnung fast nichts dauert.
Dann macht das auch keinen Unterschied.

Dann ist der Fehler wohl in updateGUI zu suchen. Zumindest sollten die Abweichungen bei 50000000 Nanosekunden pro Durchgang nicht allzu hoch sein.

Ich verstehe ueberhaupt nicht welcher fehler hier gemeint ist. Der TO meint einfach nur bei 50 ms sleep mehr als 20 fps rausholen zu koennen

ähhhh… oh. Ja, natürlich, I see. Geht natürlich nicht. Aber 20fps ist eigentlich schon recht flüssig. Dann soller es halt mit 40 (25fps) oder 20 (50fps) versuchen.

1s=1000ms
1000ms/fps=sleeptime