Spiel Performance erhöhen

Status
Nicht offen für weitere Antworten.

Moonlight1234

Bekanntes Mitglied
Ich bin gerade dabei ein Jump´n Run-Spiel zu programmieren.
Das Spiel soll als Applikation im Vollbild-Modus 1024x768 laufen.

Das vom Spieler gesteuerte animierte Sprite ist soweit fertig, mir allerdings noch zu langsam.
Es wird muß ich dazu sagen vor einer 1024x768 großen Image dargestellt.

Ich verwende für die verscheidenen Bildschirme (Start, PlayScreen) ein JPanel.
Diese werden in einem JFrame dargestellt.

1. Bringt es eine Erhöhung der Geschwindigkeit, wenn ich anstelle von einem JPanel ein JComponent verwende?
Ich spare dadurch nur eine Instanz.

Ich habe irgendwo hier im Forum gelesen das Swing-Komponenten relativ langsam sein sollen.
2. Bringt es eine Geschwindigkeitssteigerung wenn ich anstelle von Swing-Komponenten ein Frame, Panel verwende?

Ich hatte sowas schon mal ausprobiert allerdings bei einem Frame weiße Streifen auf dem Bildschirm.
3. Wie kann ich letzteres Problem beheben?
 
Hi Moonlight! 🙂
Swing ist zwar etwas langsamer, weil es alles manuell zeichnet und nicht die OS-Liebs verwendet, aber wenn du nicht zig JComponents zeichnest wie JButton, JMenu usw. wirst du vermutlich keinen Unterschied spüren.

Wichtig ist erst mal, wie du die Animation bewergstelligst, denn da könnten sich auch Performance-Probleme auftun.

2. Was verwendest du, um das Flimmern zu vermeiden? Double Buffering ist alles andere als perfomant. Besser ist BufferStrategy dass PageFlipping verwendet, wenn es die Grafikkarte unterstützt (was es bestimmt tut!).

Liebe Grüße
Reality
 
Hallo Reality!

Von BufferStrategy würde ich bei meinem Programm noch nicht sprechen. 😀
Außer das Swing von sich aus Double Buffering macht.
Ich dachte DoubleBuffering und Page Flipping wären das gleiche.
Ich habe eine Radeon 9800 pro, glaube schon das die Page Flipping unterstützt.
Ich zeichne einfach mit drawImage den Hintergrund und die einzelnen Images für das Sprite in der paintComponent()-Meth..
Die einzige Performance Steigerung habe dadurch erreicht indem ich mit repaint(x,y,w,h) tatsächlich nur den Bereich aktualisiere welcher durch die Bewegung des Sprites verändert wurde.
Mit Page Flipping meinst du ich habe zwei Bildschirme, einer der angezeigt wird und einer in dem gezeichnet wird und zwischen den beiden Bildschirmen wird hin- und her geschaltet?
Wie setze ich das um?
Erzeuge ich zwei Graphics2D-Objecte? In einem zeichne ich, das andere wird angezeigt? Wie erhalte ich ein Graphics2d_Objekt und wie sorge ich dafür das das jeweilige angezeigt wird?
 
Also ich würde DoubleBuffering im Spiel deaktivieren und BufferStrategy und somit PageFlipping verwenden.

Wie man BufferStrategy verwendet:
http://java.sun.com/j2se/1.4.2/docs/api/java/awt/image/BufferStrategy.html
http://java.sun.com/docs/books/tutorial/extra/fullscreen/bufferstrategy.html

Wie PageFlipping funktioniert:
http://java.sun.com/docs/books/tutorial/extra/fullscreen/doublebuf.html

Zusammengefasst: Zwei Speicher. Einer für die Darstellung (nennen wir es mal Display Buffer) und beim anderen wird im Hintergrund gezeichnet (nennen wir es Back Buffer). Wenn es fertig mit zeichnen ist, wird der Grafikkarte gesagt, dass er auf den anderen Speicher umschalten soll, damit der Inhalt angezeigt wird. Und dann wird der Back Buffer zum Display Buffer und der Display Buffer zum Back Buffer.

Bei Double Buffering wird hin- und herkopiert, was langsamer ist.

Und: Wie machst du die Animationen? Das ist auch wichtig.

Liebe Grüße
Reality
 
Das mit dem Page Flipping probiere ich aus, sofern ich es hinbekomme. :wink:

Die Animationen?

Ich habe die einzelnen Bilder als .png vorliegen.
Mit drawImage zeichne ich die Bilder nacheinander in der paintComponent Methode.

Gibt es auch da etwas besseres?
 
Moonlight1234 hat gesagt.:
Die Animationen?

Ich habe die einzelnen Bilder als .png vorliegen.
Mit drawImage zeichne ich die Bilder nacheinander in der paintComponent Methode.

Gibt es auch da etwas besseres?

Das ist schon klar. Ich dachte eher an die Technik.
Ich habe die Bilder in einer ArrayList gespeichert wo ich sage wie lange sie bei der Animation angezeigt werden sollen und messe z.B. pro Framedurchgang die vergangene Zeit. Und je nach dem wieviel Zeit vergangen ist, holt es den entsprechenden Image aus der ArrayList. Die Technik habe ich aus dem Buch "Developing Games in Java" (Ein sehr sehr gutes Buch!!!).
Zuvor hatte ich Animationen so bewerkstelligt: Bild1 gezeichnet, dann mit Thread.sleep() ein paar Millisekunden Pause gemacht, dann das nächste Bild, wieder ein paar Millisekunden Pause. Der Effekt war, dass es nicht flüssig wiedergegeben wurdet.

Hier die Klasse Animation, falls du es einbauen willst:

Code:
package com.brackeen.javagamebook.graphics;

import java.awt.*;
import java.util.ArrayList;

public class Animation {
  private ArrayList<AnimFrame> frames;
  private int currFrameIndex;
  private long animTime;
  private long totalDuration;

  //Creates a new empty Animation
  public Animation(){
    frames = new ArrayList<AnimFrame>();
    totalDuration = 0;
    start();
  }

  //Adds an image to the animation with the specified duration

  public synchronized void addFrames(Image image, long duration){
    totalDuration += duration;
    frames.add(new AnimFrame(image, totalDuration));
  }

  //Starts this animation over from the beginning

  public synchronized void start(){
    animTime = 0;
    currFrameIndex = 0;
  }

  //Updates this animation´s current image (frame), if necessary
  public synchronized void update(long elapsedTime){
    if(frames.size() > 1){
      animTime += elapsedTime;

      if(animTime >= totalDuration){
        animTime = 0;
        currFrameIndex = 0;
      }

      while(animTime > getFrame(currFrameIndex).endTime){
        currFrameIndex++;
      }
    }
  }

  public synchronized Image getImage(){
    if(frames.size() == 0){
      return null;
    }

    return getFrame(currFrameIndex).image;
  }

  private AnimFrame getFrame(int i){
    return frames.get(i);
  }

  private class AnimFrame{
    Image image;
    long endTime;

    public AnimFrame(Image image, long endTime){
      this.image = image;
      this.endTime = endTime;
    }
  }
}

Nicht ganz einfach, aber dafür sehr effektiv!

Liebe Grüße
Reality
 
Reality hat gesagt.:
Zuvor hatte ich Animationen so bewerkstelligt: Bild1 gezeichnet, dann mit Thread.sleep() ein paar Millisekunden Pause gemacht, dann das nächste Bild, wieder ein paar Millisekunden Pause. Der Effekt war, dass es nicht flüssig wiedergegeben wurdet.

So habe ich es letzten endes auch gemacht, mit dem selben Effekt :?
Reality hat gesagt.:
Hier die Klasse Animation, falls du es einbauen willst:

Nicht ganz einfach, aber dafür sehr effektiv!

Liebe Grüße
Reality

Die Anregung nehme ich gerne an.

Ich hatte auch schon überlegt mir das Buch von Brackeen zu kaufen.
Ich habe mir Chapter 18 runtergeladen um zu sehen wie weit man mit dem Buch kommt.
Leider lief das Beispiel bei mir in der neuen Konfiguration (NetBeans 4.1, Java 1.5) nicht mehr, in der alten ja (andere IDE , Java 1.4) ja.
Liegt glaube ich an den ANT-Skripts.

Kenne mich mit ANT nicht so aus.

Vielen Dank erstmal.

Jetzt habe ich erstmal etwas zu verarbeiten :idea:


Ich habe mir das ganze nochmal angeschaut.
Wenn ich Pageflipping verwenden will, mit welchen Argumenten rufe ich
w.createBufferStrategy(); auf?

Erhalte ich mit
w.createBufferStrategy(3);
automatisch Page-Flipping weil die Buffer-Anzahl höher als 2 ist?

Standartmäßig ist bei mir Page-Flipping im FullScreen-Mode unterstützt
Der Wert für FlipContents ist prior.

Mit

Code:
gd =GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
gc=gd.getDefaultConfiguration();
bufferCap=gc.getBufferCapabilities();
w.createBufferStrategy(3, buffCap);

müßte ich eine BufferStrategy mit PageFlipping erhalten.

Wie setzte ich die Werte für Page-Flipping per Hand:

Code:
BufferCapabilities bufCap=new BufferCapabilities(new ImageCapabilities(true), new ImageCapabilities(true), BufferCapabilities.FlipContents.PRIOR); 
w.createBufferStrategy(3, bufCap);

?

Der Pointer für den Backbuffer, wird der automatisch nach myStrategy.show() auf den anderen Backbuffer gesetzt, also erfolgt die Unschaltung automatisch?
 
Irgendwie hast du das ziemlich kompliziert umgesetzt.
Nimm doch einfach den Code:

Code:
// Create a general double-buffering strategy
 frame.createBufferStrategy(2); //Kannst natürlich auch 3 erstellen, jedoch weiß ich nicht, ob das etwas bringt.
 BufferStrategy strategy = frame.getBufferStrategy();

 // Render loop
 while (!done) {
    Graphics g = strategy.getDrawGraphics();

    // Draw to graphics
    paintComponents(g);

    strategy.show();
 }

Das mit den Back Buffer und dem Display Buffer wird automatisch umgesetzt.

Ich würde dir raten, auch noch unbedingt die Umsetzung der Animation zu verändern, denn das wird dir am Meisten Performance-Gewinn bringen!

Liebe Grüße
Reality
 
Hallo Reality!

Stimmt ich habe wie so oft mal wieder zu kompliziert gedacht.

Laut Beispielcode in der API Class BufferStrategy erhältst du mit

Code:
w.createBufferStrategy(2)

Double Buffering.

Ich hatte daher angenommen das 2 die Anzahl der Buffer mit Frontbuffer ist.
Die sind aber anscheinend davon ausgegangen das Page Flipping nicht unterstützt wird.
Tatsächlich ist 2 die Anzahl der Backbuffer und die beste BufferStrategy, also Page Flipping wenn unterstützt ansonsten Double Buffering wird gewählt.

Das ist mein Problem ich denke oft zu kompliziert.


Die Animationen werde ich auch ändern.

Gruß, Moonlight
 
Hi Moonlight!
Du erhälst nur Double Buffering, wenn deine Grafikkarte Page Flipping nicht unterstützt. BufferStragey kann also beides.

Liebe Grüße
Reality
 
Hy Reality!

Also die Bildschirmdarstellung funktioniert so nicht.

Ich habe das Problem das ich weiße Streifen auf dem Bildschirm habe.

Kann es sein das der KeyListener sich mit der Bildschirmdarstellung ins Gehege kommt?

Gruß, Moonlight
 
Hat sich schon geklärt.
Ich hatte irgendwo noch ein repaint() verwendet.

repaint() verwendet ein PaintEvent(). Anscheinend kommt irgendwie die Synchronisation der Bildschirmdarstellung aus dem Takt wenn ein PaintEvent verwendet wird.

Ich hoffe es gibt keine Probleme mit Threads(), diese sind ja auch eine Art Event.
Ich wollte die Bewegung der Enemys in einem Thread unterbringen.

Die Darstellung ist jedenfalls erheblich besser.
Jetzt werde ich mir deinen Code für die Animationen genauer anschauen.
Dafür muß ich aber erstmal den synchnized-Befehl nachschlagen, der wurde in meinem Lehrbuch leider nicht erklärt (Java 1.4).

Gruß, Moonlight
 
Danke. Das Angebot nehme ich gerne an.

Die Beispiele aus dem hatte ich mir schon angeschaut, allerdings nur die fertigen Programme.
Die Sources sind ohne Buch nicht zu verstehen.
 
Ich erklär mal ein bißchen:
Jedes mal wenn du mit der Methode addFrames ein Bild hinzufügst, erstellt du gleichzeitig ein neues Objekt von der Klasse AnimFrame. AnimFrame speichert einmal das jeweilige Bild ab und wie lange das Bild bei der Animation angezeigt werden soll.

Nun steht ja da:
Code:
  public synchronized void addFrames(Image image, long duration){
    totalDuration += duration;
    frames.add(new AnimFrame(image, totalDuration));
  }

Nehmen wir an, wie erstellen eine Animation und das erste Bild soll 150 ms angezeigt werden, dann ist totalDuration = 150 ms. Soll das zweite Bild 100 ms angezeigt werden, dann ist totalDuration 250 ms.

Es wird also folgendes übergeben: frames.add(zweitesImage, 250 ms);

Jetzt denkst du vielleicht:"Warum wird 250 ms übergeben, wenn es nur 100 ms angezeigt werden soll?"
Ganz einfach: Du misst ja bei jedem Framedurchgang wieviel Zeit vergangen ist und wenn die vergangene Zeit größer als 150 ms ist und kleiner gleich 250 ms ist, dann wird das zweite Bild angezeigt (250 ms - 150 ms = 100 ms).
Wieviel Zeit vergangen sind wird bei der Update-Methode kontrolliert und die Variable currFrameIndex entsprechend erhöht (ist für die Methode getImage() wichtig). Wenn elapsedTime größer als totalDuration (also die komplette Animationsdauer aller Bilder) ist, wird es auf 0 gesetzt und die Animation fängt wieder von vorne an.

Liebe Grüße
Reality
 
Hy Reality!

Als ich habe die Animation mittlerweile eingebaut.

Aber bei mir wird immer nur das erste Frame angezeigt.
Und ich finde den Fehler einfach nicht.
Der update()-Methode wird doch die Zeit übergeben, die seit dem letzten Aufruf von update() vergangen ist?

Ich habe das ganze jetzt in meine drawSprite()-Methode eingebaut:

Code:
   public void drawSprite(Graphics graphics){
  
        long elapsedTime;
        elapsedTime=System.currentTimeMillis()-timeLastDraw;
        timeLastDraw=System.currentTimeMillis();
        if (xDirection==-1){
            lastXDirection=xDirection;
            animationLeft.update(elapsedTime);
            currentFrame=animationLeft.getImage();
            
        }else if (xDirection==1){
            lastXDirection=xDirection;
            animationRight.update(elapsedTime);
            currentFrame=animationRight.getImage();
        }
        if (lastXDirection==-1)
            graphics.drawImage(currentFrame,x,y,component);
        if (lastXDirection==1)
            graphics.drawImage(currentFrame,x,y,component);
    }

Die Frames füge ich so hinzu:

Code:
       for (int i=0; i<numbers_of_left_moves;i++){
            String path=general_path+"/"+name+"/left/"+i+".png";
            Image frame=inout.loadBufferedImage(path);
            animationLeft.addFrames(frame, timeStepMillis);
        }
        
        for (int i=0; i<numbers_of_right_moves;i++){
            String path=general_path+"/"+name+"/right/"+i+".png";
            Image frame=inout.loadBufferedImage(path);
            animationRight.addFrames(frame, timeStepMillis);
        }

timeStepMillis habe ich bei allen Frames auf 100 gesetzt.

Gruß, Moonlight
 
Hat sich schon erledigt war mal wieder mein Fehler!

Ich sehe das diese Codezeilen sehr professionell sind.
Die Aufgabe wurde in kleinste Bestandteile zerlegt (bis zu einem einzelnen Frame) .
Ich versuche immer noch zu viel auf einmal, als ganzes zu lösen.

Schneller ist es bei mir nicht. Die Geschwindigkeit ist in etwa gleich. Vorteil dieser Methode, neben dem sehr übersichtlichen Aufbau, ist das für jedes Frame eine Anzeigedauer definiert wird.
Dadurch kann man auch, natürlich noch Anpassung der berechneten x, y-Werte, Beschleunigungen darstellen.
Nachteil ist das wenn die Anzeigedauer für ein Frame zu niedrig gewählt wird, einige Frames nicht anzeigt werden, bzw. das nur das erste Frame sichtbar ist. Das gilt besonders für langsame PC. Das ist meiner zwar nicht, aber aus kompatibililtätsgründen zu solchen PC´s werde ich das ganze so umprogrammieren das auf jeden Fall ein einzelnes Frame angezeigt wird auch wenn die duration für ein Frame überschritten wird.
Außerdem ist das ganze in der Größe flexibel.

Die Idee ist jedenfalls gut. Vor allen Dingen mit ArrayList() habe ich vorher noch nie gearbeitet. Ich habe auch irgendwo gelesen das diese sehr viel schneller als Vectoren sind.

Gruß, Moonlight
 
Moonlight1234 hat gesagt.:
Die Idee ist jedenfalls gut. Vor allen Dingen mit ArrayList() habe ich vorher noch nie gearbeitet. Ich habe auch irgendwo gelesen das diese sehr viel schneller als Vectoren sind.
Ja, Vectoren sind etwas langsamer, da sie threadsicher arbeiten.

Gut, dass das jetzt bei dir geklappt hat. 🙂
Würde mich freuen, wenn du mal dein Spiel hier postest, wenn du fertig bist.

Liebe Grüße
Reality
 
Das Rollenspiel hätte mich schon interessiert, muß ich mich nochmal bei Lycos anmelden.
Ich dachte allerdings immer man kann auf Homepages auch ohne Anmeldung zugreifen.

Gruß, Moonlight
 
Moonlight1234 hat gesagt.:
Das Rollenspiel hätte mich schon interessiert, muß ich mich nochmal bei Lycos anmelden.
Ich dachte allerdings immer man kann auf Homepages auch ohne Anmeldung zugreifen.
Kopier den Link in die Adresszeile deines Browsers, dann geht es. Lycos "mag" keine Direktlinks auf die Datei von externen Referern.
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben