# Performance Problem bei mit Graphics



## nitsche2 (21. Dez 2006)

Moin,
ich bin grade dabei mir ein bisschen Java beizubringen, und probier eine Art GTA 1/2 Verschnitt zu machen, also eine Person die sich mit denn Pfeiltasten bewegen lässt.

Es funktioniert auch ganz gut soweit, das einzige Problem ist das Performance Problem. Es hackt teilweise (bei schnellen Rechnern nicht, aber bei langsamen <2Ghz).

Die ganze Sache funktioniert wie folgt. Es läuft ein Thread das Bild aktualisiert und sich nach 2 ms neu aufruft (ich brauch so wenig, sonst wirkt es nicht richtig flüssig).

In diesem Thread holt er sich die Akuellen Positionsdaten aus der Instanz Figur, die einen Thread Figur_Bewegen hat.

Bin ich auf dem Holzpfad, bzw. sollte man es komplett anders programmieren? Bin ein ziemlicher Neueinsteiger?

Hier das Programm zum Selbst-Testen (einmal auf die Fläche klicken, dann Pfeiltasten)

www.blumenallee.de/java.zip


```
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.image.BufferedImage;
import java.awt.geom.AffineTransform;
import java.io.File;
import javax.imageio.ImageIO;
import java.lang.Thread;

class Figur
{
   public Thread Laufen = null ;
   public Image ImageSpielfigur,ImageSpielfigurSteht;
   public double DoubleGradRad;
   public Figur_Bewegen BewegeVorgang;

  public void TastenAuswertung( int Richtung, boolean TastenGedrueckt)
  {
     switch(Richtung)
     {
        case 39:
        if(TastenGedrueckt==true)
        {
             BewegeVorgang.IntGradRichtung=1;
          }
          else
          {
             BewegeVorgang.IntGradRichtung=0;
          }
          break;

        case 37:
        if(TastenGedrueckt==true)
        {
            BewegeVorgang.IntGradRichtung=-1;
          }
          else
          {
            BewegeVorgang.IntGradRichtung=0;
          }
          break;


         case 38:
         if(TastenGedrueckt==true)
        {
            BewegeVorgang.IntRichtung=-1;
         }else{
            BewegeVorgang.IntRichtung=0;
         }
        break;

        case 40:

        if(TastenGedrueckt==true)
        {
            BewegeVorgang.IntRichtung=1;
         }else{
            BewegeVorgang.IntRichtung=0;
         }

        break;
     }
  }

  class Figur_Bewegen implements Runnable
   {
      public Thread Laufen;
      public int IntRichtung;
      public int IntGrad;
      public double DoubleGradRad;
      public double DoublePosX=500;
      public double DoublePosY=500;
      public int IntGradRichtung=0;
      public int IntGeschwindigkeitGrad=2;
      public int IntGeschwindigkeitPosition=1;

      public void run()
      {

         while(true)
         {
            try
            {


               IntGrad+=IntGeschwindigkeitGrad*IntGradRichtung;
               DoubleGradRad=2 * Math.PI / 360 * IntGrad;
               DoublePosX+=(Math.sin(DoubleGradRad)*IntRichtung*IntGeschwindigkeitPosition);
               DoublePosY+=(Math.cos(DoubleGradRad)*IntRichtung*-1)*IntGeschwindigkeitPosition;
               Laufen.sleep(5);

            }
            catch(Exception e)
            {

            }
         }
      }



      Figur_Bewegen()
    {
       IntGrad=180;
       System.out.println("ja");
       Laufen=new Thread( this );
       Laufen.start();

      }
   }

  Figur()
  {

     ImageSpielfigur = Toolkit.getDefaultToolkit().getImage( "gesicht.gif" );
     ImageSpielfigurSteht = Toolkit.getDefaultToolkit().getImage( "gesicht_steht.gif" );

     BewegeVorgang=new Figur_Bewegen();

  }
}

class Bild extends Canvas implements Runnable
{
   private Image image,ImageOffscreen;
   private Figur spieler;
   public int IntFensterBreite;
   public int IntFensterHoehe;
   private Dimension DimensionOffscreen;
   private Graphics2D Graphics2dOffscreen;
   private Thread th = null ;
   private Figur Spielfigur = null;
   private double IntAbweichungY,IntAbweichungX = 0;

   private int IntAbweichungGrad = 0;
   private Font myFont;


  public void TastenAuswertung( int Richtung, boolean TastenGedrueckt)
  {
     Spielfigur.TastenAuswertung(Richtung, TastenGedrueckt);
    }

   public void run()
   {
   }

  public void paint( Graphics  bild )
  {
      // Bild anlegen

      try
      {
         repaint();
         th.sleep(2);
      }
      catch(Exception e)
      {

      }
  }

  public void update(Graphics g)
  {
      if(Graphics2dOffscreen == null || getSize().width != DimensionOffscreen.width || getSize().height != DimensionOffscreen.height)
      {
         DimensionOffscreen = getSize();
         ImageOffscreen = createImage(DimensionOffscreen.width, DimensionOffscreen.height);
         Graphics2dOffscreen = (Graphics2D) ImageOffscreen.getGraphics();
      }

      Graphics2dOffscreen.clearRect((int) Spielfigur.BewegeVorgang.DoublePosX-IntFensterBreite/2-250, (int) Spielfigur.BewegeVorgang.DoublePosY-IntFensterHoehe/2-250, IntFensterBreite+500, IntFensterHoehe+500);

      // Zeichenvorgang beginnen
       Graphics2dOffscreen.drawImage(image,1,1,this);
       Graphics2dOffscreen.drawImage(image,1,1,this);

     Graphics2dOffscreen.setFont( myFont ); //Schriftart setzen



    // Debug Funktionen
    Graphics2dOffscreen.drawString(""+Spielfigur.BewegeVorgang.IntGrad+ "sin:"+Math.sin(Spielfigur.BewegeVorgang.DoubleGradRad)+", cos:"+Math.cos(Spielfigur.BewegeVorgang.DoubleGradRad),IntFensterBreite-250,IntFensterHoehe-50); //String rendern
    Graphics2dOffscreen.drawString("x"+Spielfigur.BewegeVorgang.DoublePosX+"--y"+Spielfigur.BewegeVorgang.DoublePosY,IntFensterBreite-250,IntFensterHoehe-100); //String rendern
    // Debug Ende


     Graphics2dOffscreen.translate((int) Spielfigur.BewegeVorgang.DoublePosX, Spielfigur.BewegeVorgang.DoublePosY);
     IntAbweichungGrad=0;
     IntAbweichungGrad-=Spielfigur.BewegeVorgang.IntGrad;
     Graphics2dOffscreen.rotate(Spielfigur.BewegeVorgang.DoubleGradRad);


     if(Spielfigur.BewegeVorgang.IntRichtung==0)
     {
         Graphics2dOffscreen.drawImage(Spielfigur.ImageSpielfigurSteht,0,0,this);
      }else{
         Graphics2dOffscreen.drawImage(Spielfigur.ImageSpielfigur,0,0,this);
      }

      // Zurückverschieben
      Graphics2dOffscreen.rotate(2 * Math.PI / 360 * IntAbweichungGrad);
      Graphics2dOffscreen.translate((int)  -Spielfigur.BewegeVorgang.DoublePosX+(IntAbweichungX-Spielfigur.BewegeVorgang.DoublePosX), -Spielfigur.BewegeVorgang.DoublePosY+(IntAbweichungY-Spielfigur.BewegeVorgang.DoublePosY));

   //   davor 500
   //   jetzt -500+(500-497)

      IntAbweichungX=Spielfigur.BewegeVorgang.DoublePosX;
      IntAbweichungY=Spielfigur.BewegeVorgang.DoublePosY;
     paint(g);
     g.drawImage (ImageOffscreen, 0, 0, this);
  }

  Bild()
  {
     // Alles initalisieren

      // Nun die Spielfigur
      Spielfigur=new Figur();

      // Haus
      image = Toolkit.getDefaultToolkit().getImage( "hintergrund.jpg" );

      // Font
      myFont=new Font("Arial", Font.ITALIC|Font.PLAIN, 26);

      // Abweichung
      IntAbweichungX=Spielfigur.BewegeVorgang.DoublePosX;
      IntAbweichungY=Spielfigur.BewegeVorgang.DoublePosY;

       if( th==null)
       {
             th = new Thread(this);
           th.start();
       }
  }
}

public class Spiel extends JFrame
{
   private Bild Bild1;
   public static int IntFensterBreite=1024;
   public static int IntFensterHoehe=800;

   public Spiel ()
   {
      super("Testspiel");


   //   Fenster.getContentPane().setLayout( new BorderLayout(5, 5) );
      setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      setSize( IntFensterBreite, IntFensterHoehe );
      Bild1 = new Bild();
      Bild1.run();


      Bild1.IntFensterBreite=IntFensterBreite;
      Bild1.IntFensterHoehe=IntFensterHoehe;
      getContentPane().add(Bild1);

      Bild1.addKeyListener( new KeyListener(){

        public void keyReleased(KeyEvent k)
        {
              Bild1.TastenAuswertung(  k.getKeyCode(), false);
           }

        public void keyPressed(KeyEvent k)
        {
           Bild1.TastenAuswertung(  k.getKeyCode(), true);
        }

        public void keyTyped(KeyEvent k){}
      });

      setVisible( true );
   }

  public static void main( String[] args )
  {
      Spiel das_Spiel=new Spiel();
      das_Spiel.IntFensterBreite=IntFensterBreite;
      das_Spiel.IntFensterHoehe=IntFensterHoehe;

   }
}
```

Hab halt wenig Erfahrung, vielleicht könnt ihr mir ein paar Ansätze geben.

Vielen Dank im Vorraus Smile


----------



## EgonOlsen (21. Dez 2006)

Du könntest von Swings repaint()-Mechanismus auf Active Rendering und BufferStrategy umstellen. Dadurch sollte es flüssiger und auch wesentlich schneller laufen, wenn das nötig ist. Ich habe deinen Code aus Langeweile mal entsprechend umgehackt (nicht schön aber selten...). Timing ist ein einfaches, tickbasiertes drin. Das müsste man evtl. ändern, bzw. anpassen. Egal, hier isses:

P.S.: Du solltest deine Variablen nach SUNs Konventionen benennen, das macht es leserlicher.

P.P.S.: Nimm nicht unbedingt Thread.sleep(); zum Timen, es sei denn, du kannst mit voller CPU-Last nicht leben bei einem Spiel.

P.P.P.S.: Und alles mit Threads zu machen, ist verlockend...aber für Spiele nicht zu empfehlen. Deswegen habe ich das auch ersatzlos aus deinem Code gestrichen.


Edit: Gefixte Version, die eine Macke mit Java5/6 behebt.


```
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;

import javax.swing.JFrame;

class Figur {
    public Image ImageSpielfigur, ImageSpielfigurSteht;
    public Image ImageSpielfigurWork, ImageSpielfigurStehtWork;
    public double DoubleGradRad;

    public int IntRichtung;
    public int IntGrad=180;
    public double DoublePosX = 0;
    public double DoublePosY = 0;
    public int IntGradRichtung = 0;
    public int IntGeschwindigkeitGrad = 2;
    public int IntGeschwindigkeitPosition = 1;

    public void TastenAuswertung(int Richtung, boolean TastenGedrueckt) {
        switch (Richtung) {
        case 39:
            if (TastenGedrueckt) {
                IntGradRichtung = 1;
            } else {
                IntGradRichtung = 0;
            }
            break;

        case 37:
            if (TastenGedrueckt) {
                IntGradRichtung = -1;
            } else {
                IntGradRichtung = 0;
            }
            break;

        case 38:
            if (TastenGedrueckt) {
                IntRichtung = -1;
            } else {
                IntRichtung = 0;
            }
            break;

        case 40:

            if (TastenGedrueckt) {
                IntRichtung = 1;
            } else {
                IntRichtung = 0;
            }
            break;
        }
    }

    public void move(int ticks) {
        for (int i=0; i<ticks; i++) {
            IntGrad += IntGeschwindigkeitGrad * IntGradRichtung;
            DoubleGradRad = 2 * Math.PI / 360 * IntGrad;
            DoublePosX +=-(Math.sin(DoubleGradRad) * IntRichtung * IntGeschwindigkeitPosition);
            DoublePosY += (Math.cos(DoubleGradRad) * IntRichtung) * IntGeschwindigkeitPosition;
        }
    }

    public void draw(Graphics g, int posx, int posy) {
        ((Graphics2D)g).rotate(DoubleGradRad,posx+10,posy+10);       
        if (IntRichtung == 0) {   
            g.drawImage(ImageSpielfigurSteht, posx, posy, null);
        } else {
            g.drawImage(ImageSpielfigur, posx, posy, null);
        }
    }

    Figur() {
        ImageSpielfigur = Toolkit.getDefaultToolkit().getImage("gesicht.gif");
        ImageSpielfigurSteht = Toolkit.getDefaultToolkit().getImage("gesicht_steht.gif");
    }
}

class Background {

    private BufferedImage image=null;

    Background(Component c) {
        Image image2 = Toolkit.getDefaultToolkit().getImage("hintergrund.jpg");
        MediaTracker mt=new MediaTracker(c);
        mt.addImage(image2,0);
        try {
            mt.waitForAll();
        }catch(Exception e){}

        image=((Graphics2D)c.getGraphics()).getDeviceConfiguration().createCompatibleImage(image2.getWidth(null), image2.getHeight(null), Transparency.OPAQUE);
        Graphics2D g1=image.createGraphics();
        g1.drawImage(image2,0,0,null);
    }

    void draw(Graphics g, int posx, int posy) {
        g.clearRect(0, 0, Spiel.IntFensterBreite, Spiel.IntFensterHoehe);
        g.drawImage(image, posx, posy, null);
    }
}

public class Spiel extends JFrame {
    public static int IntFensterBreite = 1024;
    public static int IntFensterHoehe = 800;

    private Background back=null;
    private Figur Spielfigur = null;

    private Font myFont;
    private BufferStrategy strategy=null;

    private int tfps=0;

    public void TastenAuswertung(int Richtung, boolean TastenGedrueckt) {
        Spielfigur.TastenAuswertung(Richtung, TastenGedrueckt);
    }

    public void runMe() {
        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
        try {
            long s=System.currentTimeMillis();
            int fps=0;
            
            Ticker ticker=new Ticker(20);
            
            while (true) {
                int ticks=ticker.getTicks();
                if (ticks>0) {
                    Spielfigur.move(ticks);
                }
                myUpdate();
                fps++;
                if (System.currentTimeMillis()-s>1000) {
                    tfps=fps;
                    fps=0;
                    s=System.currentTimeMillis();
                }
                Thread.yield();
            }
        } catch (Exception e) {e.printStackTrace();}
    }

    public void paint(Graphics bild) {}

    public void update(Graphics g) {}

    public void myUpdate() {
        Graphics g=strategy.getDrawGraphics();


        // Zeichenvorgang beginnen
        back.draw(g, (int)Spielfigur.DoublePosX, (int)Spielfigur.DoublePosY);

        // Debug-Funktionen
        g.setFont(myFont); //Schriftart setzen
        g.drawString(Spielfigur.IntGrad + "sin:" +
                     Math.sin(Spielfigur.DoubleGradRad) + ", cos:" +
                     Math.cos(Spielfigur.DoubleGradRad),
                     IntFensterBreite - 850,
                     IntFensterHoehe - 50); //String rendern
        g.drawString("x" + Spielfigur.DoublePosX +
                     "--y" +
                     Spielfigur.DoublePosY,
                     IntFensterBreite - 850,
                     IntFensterHoehe - 100); //String rendern

        g.drawString("fps: "+tfps,IntFensterBreite-200,50);

        // Debug Ende
        Spielfigur.draw(g, IntFensterBreite>>1, IntFensterHoehe>>1);
        strategy.show();
    }


    public Spiel() {
        super("Testspiel");
        // Font
        myFont = new Font("Arial", Font.ITALIC | Font.PLAIN, 26);

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(IntFensterBreite, IntFensterHoehe);

        this.addKeyListener(new KeyListener() {

            public void keyReleased(KeyEvent k) {
                TastenAuswertung(k.getKeyCode(), false);
            }

            public void keyPressed(KeyEvent k) {
                TastenAuswertung(k.getKeyCode(), true);
            }

            public void keyTyped(KeyEvent k) {}
        });

        this.setVisible(true);
        this.createBufferStrategy(2);
        strategy=this.getBufferStrategy();

        Spielfigur = new Figur();
        back=new Background(this);
    }

    class Ticker {
        
        private int rate;
        private long s;
        
        Ticker(int tickrateMS) {
            rate=tickrateMS;
            s=System.nanoTime();
        }
        
        int getTicks() {
            long i=System.nanoTime();
            if ((i-s)/1000000L>rate) {
                int ticks=(int)(((i-s)/1000000L)/(long)rate);
                s+=rate*ticks*1000000L;
                return ticks;
            }
            return 0;
        }
    }

    public static void main(String[] args) {
        Spiel das_Spiel = new Spiel();
        das_Spiel.runMe();
    }
}
```


----------



## 0xdeadbeef (21. Dez 2006)

Wobei man durchaus (einigermaßen) flüssig animierte Spiele per Thread.Sleep() timen kann. 
Allerdings ist ein sleep(2) absolut sinnlos und kontraproduktiv.
Zum einen legst das das Programm bestenfalls (dazu später) für 2ms schlafen - was keinesfalls gleichbedeutend ist mit einem Refresh alle 2ms. Außerdem würde ein 2ms-Refresh eine Wiederholfrequenz von 500Hz enntsprechen, was kein noch so guter Montitor schafft. Die heute überall verbreiteten LCDs haben eh normalerweise feste 60Hz, was ca. 16.7ms pro Bild entspricht.

Last but not least ist in keiner Weise garantiert, daß der Thread bei sleep(2) auch wirklich nur für 2ms schlafengelegt wird. Die meisten Betriebssysteme können minimal 10ms. Schlimmstenfalls bringen Zeiten unter 10-20ms (je nach System) sogar Windows XP so aus dem Tritt, daß sich die Systemzeit verstellt, nicht nur für das Timing des Spiels katatrophal ist.

Mal so als Beispiel meine Hauptschleife in Lemmini:


```
final static int timePerFrame = 30*1000;        // in micros - this is the timing everything else is based on
final static int resyncFrameTime = 5*30*1000;   // in micros - resync if time difference greater than that 

public void run() {
	MicrosecondTimer timerRepaint = new MicrosecondTimer();
	try {
		while (true) {				
			redraw();
			try {
				// time until next frame
				long diff = timePerFrame - timerRepaint.delta();
				if (diff > resyncFrameTime) 
					timerRepaint.update(); // resync to time base							
				if (diff > 20*1000) // smaller value may cause system time drift on windows systems
					Thread.sleep(20);
			} catch (InterruptedException ex) {}
		}
	} catch (Exception ex) {
		...
	} catch (Error ex) { 
		...
	}
}
```

Dabei ist MicrosecondTimer eine eigene Klasse zur Zeitbestimmung, die man dann entsprechend ersetzen muß. Wichtig ist, daß ich mir ausrechne, ob ich 20ms Zeit habe bis zum nächsten Frame (timePerFrame entspricht hier 30ms). Falls ja, lege ich das Spiel für 20ms schlafen. Falls nein, warte ich halt "hart" per Schleife.
Das mit dem Resync ist erstmal nicht so wichtig - das soll dazu dienen, Frames zu überspringen, falls das Spiel auf langsamen Rechnern oder wegen anderer High-Prio-Taks immer mehr vom gewollten Timing abweicht.


----------



## Nitsche2 (21. Dez 2006)

Moin,
erstmal vielen Dank euch Beiden. Besonders danke ich Egon, hab das gerade einmal kompiliert, und ich muss sagen ich bin begeistert. Jetzt les ich mir das erstmal durch und versuch das zu kapieren.

Wie gesagt ich bin ein ziemlicher Java Einsteiger. 

Danke nochmals


----------



## Nitsche2 (21. Dez 2006)

Das Timing Problem versteh ich erst jetzt. Ich glaub ich probier mal Lemminis Methode, ich kenn nichts anderes als sleep (im Augenblick)


----------



## EgonOlsen (22. Dez 2006)

Ich habe ein einfaches, tickbasiertes Timing für die Spielfigur ergänzt. Dafür ist Java5 erforderlich.


----------



## Nitsche2 (22. Dez 2006)

Hallo,
So ähnlich hab ichs auch gemacht. Aber mit Millisekunden, und noch nicht 100% funktionsfähig, da ich noch nicht soviel Zeit hatte bisher.

Das mit denn NanoSekunden sieht sehr gut aus. Ich glaub ich machs mir einfach und setze einfach Java 5 vorraus. Hab jetzt keine Lust aufwendig die Abweichungen zu  berechnen, ähnlich Lemmini.

Vielen Dank nochmal, ich lass mir das jetzt mal auf der Zunge zergehen. Hab noch nicht alles kapiert, was du mir da optimiert hast, aber ich lerne ja noch


----------

