# getSubImage sorgt für starken Performanceeinbruch



## Idiat (4. Jul 2011)

Hallo zusammen,

beim Anzeigen eines Tilesets in einem FullScreen und beim darstellen einer Spielfigur
tritt bei mir das Problem auf, dass sich die Spielfigur nur sehr unflüssig bewegt.

Nach längerem suchen fiel mir dann auf, dass die getSubImage()-Methode für dieses Problem sorgt.

Mein Vorgehen: Ich schneide mit Hilfe des Codes unten 32x32 große Bilder aus einem großten Tileset aus und lade diese in eine ArrayList (tiles).


```
int width=picTileset.getWidth()/32;
int height=picTileset.getHeight()/32;	
for(int x=0;x<width;x++){
	for(int y=0;y<height;y++){
		tile = picTileset.getSubimage(x*32, y*32, 32, 32);
		this.tiles.add(tile);			
	}
}
```
(selber Code ist auch im unteren vorhanden)

Wenn ich die Bilder einzeln in die ArrayList lade und anzeige geht das ganze problemlos,
sobald ich auch nur ein Image mit getSubImage clippe geht es nicht mehr und es muss ja
wohl eine Möglichkeit geben dies über getSubImage zu machen statt die Grafiken
alle einzeln zu laden.

Woran liegt das ganze?

Außerdem fiel mir auf, dass es beim Bewegen der Grafik zur Anschlagverzögerung von Windows
kommt, wenn man eine Taste gedrückt hält. Sprich: Die Grafik bewegt sich erst einmal 
und dann nach längerem Halten durchgehend. Wie kann ich das ganze umgehen ohne
die Verzögerung in Windows herunterzusetzen? Schließlich hab ich das Problem in anderen
2D-Spielen auch nicht.


Hier der gesamte Code, hoffe er ist nicht zu lang.

Main.java

```
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.*;

public class Main implements KeyListener {	
	
    private static DisplayMode modes[] = {
        new DisplayMode(800, 600, 32,0),
        new DisplayMode(800, 600, 24,0),
        new DisplayMode(800, 600, 16,0),
        new DisplayMode(640, 480, 32,0),
        new DisplayMode(640, 480, 24,0),
        new DisplayMode(640, 480, 16,0),
    };
    
    private boolean running;
    protected ScreenManager s;
    
    //tile engine
    ArrayList<BufferedImage> tiles = new ArrayList<BufferedImage>();
    public Image face;
    public BufferedImage tile;
    public BufferedImage test;
    
    int map_width = 32;
    int map_height = 24;
	
    int[][][] map = new int[map_width][map_height][3];
    
    int sx = 32;
    int sy = 32;
    
    String mess = "";
    
    public static void main(String[] args){
        Main c = new Main();
    	c.run();
    }

    //stop method
    public void stop() {
        running = false;
    }
    
    //call init and gameloop
    public void run() {
        try{
            init();     
            loadImages();
            loadMap();
            gameLoop();
 
        }finally{
            s.restoreScreen();
        }
    }
    
    //set to full screen
    public void init() {
        s = new ScreenManager();
        DisplayMode dm = s.findFirstCompatibleMode(modes);
        s.setFullScreen(dm);
        
        Window w = s.getFullScreenWindow();
        w.setFocusTraversalKeysEnabled(false);
        w.addKeyListener(this);
        w.setFont(new Font("Arial", Font.PLAIN, 20));
        w.setBackground(Color.BLACK);
        w.setForeground(Color.WHITE);
        running = true;
 
        mess = "press escape to exit";
    }

    
    //load images
    public void loadImages(){

		try {
			BufferedImage picTileset=ImageIO.read(new File("gfx/Tileset2.png"));	
			int width=picTileset.getWidth()/32;
			int height=picTileset.getHeight()/32;	
			for(int x=0;x<width;x++){
				for(int y=0;y<height;y++){
					tile = picTileset.getSubimage(x*32, y*32, 32, 32);
					this.tiles.add(tile);			
				}
			}	
			
		}catch (IOException error){
			System.err.println("gfx not found");
			error.printStackTrace();
		}  
        face = new ImageIcon("gfx/face.png").getImage();       
    }
    
    public void loadMap(){
    	
		try {
			BufferedInputStream buf=new BufferedInputStream(new FileInputStream("D:\\test.test"));
			ObjectInputStream read = new ObjectInputStream(buf);

			map=(int[][][]) read.readObject();
			String name=(String) read.readObject();
			String fileName=(String) read.readObject();

			read.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
		}
    }
    
    //main gameLoop
    public void gameLoop() {
        long startTime = System.currentTimeMillis();
        long cumTime = startTime;
        
        while(running){
            long timePassed = System.currentTimeMillis() - cumTime;
            cumTime += timePassed;
            
            update(timePassed);
            
            Graphics2D g = s.getGraphics();
            draw(g);
            g.dispose();
            s.update();
            
            try{
                Thread.sleep(20);
            }catch(Exception ex){}
        }
    }
    
    
    //keyPressed
    public void keyPressed(KeyEvent ke){
        
    	int keyCode = ke.getKeyCode();
        if(keyCode == KeyEvent.VK_ESCAPE){
            stop();   
        }else if(keyCode== KeyEvent.VK_F1){
        	
        }else{
            mess = "Pressed : "+KeyEvent.getKeyText(keyCode);
            ke.consume();
        }	
        
    	
        switch (ke.getKeyCode()) {
        case KeyEvent.VK_RIGHT: {
            sx+=2;
        }
        break;
        case KeyEvent.VK_LEFT: {
            sx-=2;
        }
        break;
        case KeyEvent.VK_DOWN: {
            sy+=2;
        }
        break;
        
        case KeyEvent.VK_UP: {
            sy-=2;
        }
        break;
    }
      
    }
    
    //keyReleased
    public void keyReleased(KeyEvent e){
        int keyCode = e.getKeyCode();
        mess = "Released : "+KeyEvent.getKeyText(keyCode);
        e.consume();
    }
    
    //last method from interface
    public void keyTyped(KeyEvent e){
        e.consume();
    }
    
    //update animation
    public void update(long timePassed){
        
    }

    //draws to screen
    public void draw(Graphics2D g){
        Window w = s.getFullScreenWindow();
      
        g.setColor(w.getBackground());
        g.fillRect(0, 0, s.getWidth(), s.getHeight());
        g.setColor(w.getForeground());
  
    	for(int x=0;x < map.length;x++){
			for(int y=0;y < map[0].length;y++){
				for(int z=0; z<=0; z++){
					if(map[x][y][z] != 0){				
						g.drawImage(tiles.get(0), x*32, y*32, null);
					}	
				}
			}
		}	      
        g.drawImage(face, sx, sy, null);
        g.drawString(mess, 30, 30);     
	}
}
```

ScreenManager.java

```
import java.awt.*;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;

public class ScreenManager {

private GraphicsDevice vc;
    
    // give vc access to monitor screen
    public ScreenManager() {
        GraphicsEnvironment e = GraphicsEnvironment.getLocalGraphicsEnvironment();
        vc = e.getDefaultScreenDevice();      
    }
    
    //get all compatible DM
    public DisplayMode[] getCompatibleDisplayModes() {
        return vc.getDisplayModes();
    }

    //compares DM passed in vc DM and see if they match
    public DisplayMode findFirstCompatibleMode(DisplayMode modes[]) {
        DisplayMode goodModes[] = vc.getDisplayModes();
        for(int x=0; x<modes.length; x++) {
            for(int y=0; y<goodModes.length;y++) {
                if(displayModesMatch(modes[x],goodModes[y])) {
                    return modes[x];
                }
            }
        }
        return null;
    }
            
    //get current DM
    public DisplayMode getCurrentDisplayMode() {
        return vc.getDisplayMode();       
    }
    
    //checks if two modes match each other
    public boolean displayModesMatch(DisplayMode m1, DisplayMode m2) {
        if(m1.getWidth() != m2.getWidth() || m1.getHeight() != m2.getHeight()){
            return false;
        }
        if(m1.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI && m2.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI && m1.getBitDepth() != m2.getBitDepth()){
            return false;
        }
        if(m1.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN && m2.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN && m1.getRefreshRate() != m2.getRefreshRate()){
            return false;
        }
        
        return true;
    }
    
    //make frame full screen
    public void setFullScreen(DisplayMode dm) {
        JFrame f = new JFrame();
        f.setUndecorated(true);
        f.setIgnoreRepaint(true);
        f.setResizable(false);
        vc.setFullScreenWindow(f);
        
        if(dm != null && vc.isDisplayChangeSupported()){
            try{
                vc.setDisplayMode(dm);
            }catch(Exception ex){}
        }
        f.createBufferStrategy(2);     
    }   
    
    //set graphics object = to this
    public Graphics2D getGraphics(){
        Window w = vc.getFullScreenWindow();
        if(w != null){
            BufferStrategy s = w.getBufferStrategy();
            return (Graphics2D)s.getDrawGraphics();
        }else{
            return null;
        }
    }
    
    //updates display
    public void update(){
        Window w = vc.getFullScreenWindow();
        if(w != null){
            BufferStrategy s = w.getBufferStrategy();
            if(!s.contentsLost()){
                s.show();
            }
        }
    }
    
    public Window getFullScreenWindow(){
        return vc.getFullScreenWindow();
    }
    
    //get width of window
    public int getWidth(){
        Window w = vc.getFullScreenWindow();
        if(w != null){
            return w.getWidth();
        }else{
            return 0;
        }
    }
    
    //get height of window
    public int getHeight(){
        Window w = vc.getFullScreenWindow();
        if(w != null){
            return w.getHeight();
        }else{
            return 0;
        }
    }
    
    //get out of full screen
    public void restoreScreen(){
        Window w = vc.getFullScreenWindow();
        if(w != null){
            w.dispose();
        }
    }
    
    //create image compatible with monitor
    public BufferedImage createCompatibleImage(int w, int h, int t){
        Window win = getFullScreenWindow();
        if(win != null){
            GraphicsConfiguration gc = win.getGraphicsConfiguration();
            return gc.createCompatibleImage(w,h,t);
        }
        return null;
    }
 
}
```

Beim OOP kam es bis jetzt oft zu Fehlern, deswegen gehe ich hier gerade sparsam mit mehreren Klassen um. Wird aber dann noch geändert, sobald sich die Figur mal flüssig bewegt, ich bitte also
um Verständnis und hoffe der Code ist auch so lesbar.

Viele Grüße,
Idiat


----------



## JTryn (4. Jul 2011)

Obiger User bin ich, hab die Grafiken vergessen.

ImageShack Album - 2 images
(in einem Unterordner 'gfx' speichern)

Falls ihr das ganze ausführen wollt.


----------



## muckelzwerg (4. Jul 2011)

Schau Dir mal an, was getSubImage() macht.
Es gibt ein neues BufferedImage zurück, dafür muss der Platz angelegt werden und die Daten müssen kopiert werden.
Warum machst Du das denn so?
Du kannst doch ruhig einzelne BufferedImages verwenden. Die kannst Du ja am Anfang einmal aus einem großen Bild rausschneiden, wenn Du unbedingt eins haben willst.


Für das Problem mit den Tasten, musst Du anders arbeiten. Nimm mit dem KeyListener nur Ereignisse entgegen und leg sie in einer Queue ab. Die wird dann regelmäßig abgearbeitet. (kann auch schneller sein, als die Bildrate)
Außerdem solltest Du nicht mit "sx + ..." direkt an jedem Ereignis arbeiten, sondern Bewegungszustände ein- und ausschalten, die dann Deine Objekte bewegen.


----------



## JTryn (4. Jul 2011)

Super, vielen Dank! Wenn es schneller als die Bildrate ist, vermute ich, dass man das ganze dann mit einem Timer regeln muss.
Ich versuch das jetzt mal und melde mich, wenn es wieder zu
Problemen kommen sollte, danke schon einmal


----------



## muckelzwerg (4. Jul 2011)

Eigentlich solltest Du eine Spielschleife haben, die mit maximaler Geschwindigkeit (kann immernoch "sleep" enthalten) durchlaufen wird. Und dann entscheidest Du je nach abgelaufener Zeit, was zu tun ist. Also z.B. ob wieder gezeichnet werden soll, oder ob nur die Eingabeereignisse verarbeitet werden ...


----------



## Marco13 (4. Jul 2011)

Schau auch mal unter http://www.java-forum.org/spiele-mu...18-performance-bufferedimages.html#post776199


----------



## Kr0e (4. Jul 2011)

Nochmal eben zu getSubImage(). Auch wenn es in diesem Thread um was anderes geht, wollte ich noch kurz erwaehnen, dass auch bei der Benutzung von "compatible Buffered Images" ggf. Probleme bei dieser Funktion auftreten koennen. Ich selber hatte das Problem auf meinen Rechnern bisher onch nicht, aber als ich mal meine Progamme auf anderen PCs/Plattformen getestet habe, war diese Methode recht lahm. Der Grund war damals alte/falsche TReiber. GErade bei Laptop-ATI Karten kann es passieren, dass OpenGL nicht sonderlich gut implementiert ist...


----------

