# Massive FPS-Schwankungen, schlechte Performance



## Faberix (9. Okt 2014)

Hallo,

Ich programmiere gerade ein Jump and Run. Es ist mir aufgefallen, dass es sehr unregelmässig läuft, das heisst, es läuft teilweise wie in Zeitlupe. Weiss jemand, wie man die Performance optimieren kann?

Google hat nichts vernünftiges ausgespuckt und im Forum hab ich auch nichts gefunden.

Ich repräsentiere die Levels mit PNG-Dateien, auf denen die Balken eingezeichnet sind, dann werden die obersten Pixel jedes Balkens ("solide Pixel") in eine Hashmap gepackt, wo sie dann nach Bedarf abgerufen werden.

In der Game-Klasse befinden sich im wesentlichen die repaint-Schleife und ein Thread für die Positionsberechnungen.


Die Game-Klasse:


```
package ch.faberix.jumpandrun;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Panel;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JButton;

import ch.faberix.jumpandrun.listeners.BackToGameListener;
import ch.faberix.jumpandrun.listeners.BackToLevelMenuListener;
import ch.faberix.jumpandrun.listeners.BackToMainMenuListener;
import ch.faberix.jumpandrun.listeners.PauseButtonListener;




public class Game extends Panel implements Runnable {

	
	public boolean finishedLoading = false;
	public boolean isPaused = false;
	public boolean isDead = false;
	
	public int Health = 3;
	
	
	private int strecke = 0, strecke2 = 0, PlayerRelativeHeight = 0, PlayerAbsoluteHeight = 0, PlayerAbsoluteHeight2 = 0;
	private int maxHeight = 0, maxWidth = 0;
	private int imgNum = 0;
	public boolean running = true;
	public final int characterWidth = /*221*/121, characterHeight = /*307*/207;
	public final int characterHeight2 = 190, characterWidth2 = characterWidth + 155;
	
	private int screenHeight = 0;
	private int relativeScreenHeight = 0;
	public final int maxScreenHeight;
	
	public boolean isJumping = false;
	public boolean isFalling = false;
	public int jumpedHeight = 0;
	
	private Level level;
	
	
	private int width;
	private int height;
	
	private int backgroundWidth;
	private int backgroundHeight;
	
	private BufferedImage background;
	private BufferedImage currentScreen;
	private Graphics currentScreenGraphics;
	
	private int jumpHeightInTick = 3;
	
	public Image[] frames = new Image[62];
	public Image jump;
	public Image fall;
	
	private Image levelImage;
	
	private Image currentCharacter;
	
	private Image currentBackground;
	
	private BufferedImage pauseBackground;
	private BufferedImage pauseOverlay;
	
	private BufferedImage deathOverlay;
	
	private GameMain main;
	private Game Instance;
	
	/**true wenn die Berechnung der Position fertig ist*/
	private boolean finished = false;
	
	/**Die Instanz für die Berechnungen*/
	public Game.Game2 game2 = new Game2();
	
	public Panel inGameMenu = new Panel(null);
	public JButton backToGame = new JButton("Zurück zum Spiel");
	public JButton restartLevel = new JButton("Level neustarten");
	public JButton backToLevelMenu = new JButton("Zurück zum Levelmenü");
	public JButton backToMainMenu = new JButton("Zurück zum Hauptmenü");
	
	public JButton pauseButton = new JButton(new ImageIcon(getClass().getResource("assets/pause.png")));
	
	public Game(GameMain main, BufferedImage sourceImage, BufferedImage displayImage) throws IOException {
		this.setLayout(null);
		
		this.main = main;
		Instance = this;
		

		
		
		this.background = ImageIO.read(getClass().getResource("assets/wolken.png"));
		this.backgroundWidth = this.background.getWidth();
		this.backgroundHeight = this.background.getHeight();
		
		
		System.out.println("backgroundWidth: " + backgroundWidth + ", backgroundHeight: " + backgroundHeight);
		
		this.width = backgroundWidth;
		this.height = backgroundHeight;
		
		createPauseOverlay();
		createDeathOverlay();

		backToGame.setBounds(width/2 - 100, height/2 - 150, 200, 50);
		backToGame.addActionListener(new BackToGameListener(main));
		backToGame.setVisible(false);
		
		restartLevel.setBounds(width/2 - 100, height/2 - 80, 200, 50);
		
		backToLevelMenu.setBounds(width/2 - 100, height/2 - 10, 200, 50);
		backToLevelMenu.addActionListener(new BackToLevelMenuListener(this, main));
		
		backToMainMenu.setBounds(width/2 - 100, height/2 + 60, 200, 50);
		backToMainMenu.addActionListener(new BackToMainMenuListener(this, main));

		pauseButton.setBounds(5, 5, 40, 40);
		pauseButton.setOpaque(false);
		pauseButton.setContentAreaFilled(false);
		pauseButton.setBorderPainted(false);
		pauseButton.addActionListener(new PauseButtonListener(main));
		this.add(pauseButton);
		
		currentScreen = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		currentScreenGraphics = currentScreen.createGraphics();
		
		this.height = GameMain.f.getHeight();
		this.width = GameMain.f.getWidth();
		
		for(int i = 0; i < frames.length; i++) {
			String path;
			if(i < 10)
				path = "assets/animation/char/char__00" + i + ".png";
			else
				path = "assets/animation/char/char__0" + i + ".png";
			System.out.println("Loading image: " + path);
			BufferedImage img = null;
			if(path != null)
				img = ImageIO.read(getClass().getResource(path));
			
			frames[i] = img;
		}
		
		level = new Level(sourceImage, this);
		
		levelImage = displayImage;
		
		maxHeight = level.levelHeight;
		maxWidth = level.levelWidth;
		
		Stitcher.stitch(background, maxWidth/background.getWidth() + 1, maxHeight/background.getHeight() + 1);
		
		
		
		background = ImageIO.read(new File("background.jpg"));
		this.backgroundWidth = this.background.getWidth();
		this.backgroundHeight = this.background.getHeight();
		jump = ImageIO.read(getClass().getResource("assets/animation/char/jump/jump.png"));
		fall = ImageIO.read(getClass().getResource("assets/animation/char/fall/fall.png"));
		
		
		
		this.PlayerAbsoluteHeight = level.getStartPos() - 10;
		
		this.screenHeight = PlayerAbsoluteHeight + characterHeight2 - height/2;
		if(screenHeight < 0)
			screenHeight = 0;
		
		PlayerRelativeHeight = PlayerAbsoluteHeight - screenHeight;
		
		maxScreenHeight = level.levelHeight - height;
		
		System.out.println("maxScreenHeight: " + maxScreenHeight);
		
		maxWidth = maxWidth - width + 50;
		
		finishedLoading = true;
	}
	
	public void start() {
		Thread thread = new Thread(this);
		Thread thread2 = new Thread(game2);
		
		thread2.start();
		thread.start();
	}
	
	public void setDead() {
		isDead = true;
		repaint();
		
		add(restartLevel);
		add(backToLevelMenu);
		add(backToMainMenu);
		
		restartLevel.setVisible(true);
		backToLevelMenu.setVisible(true);
		backToMainMenu.setVisible(true);
	}
	
	
	
	
	public void createPauseOverlay() {
		pauseOverlay = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		
		for(int x = 0; x < width; x++) {
			for(int y = 0; y < height; y++) {
				pauseOverlay.setRGB(x, y, 838860800);
			}
		}
	}
	
	public void createDeathOverlay() {
		deathOverlay = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		
		for(int x = 0; x < width; x++) {
			for(int y = 0; y < height; y++) {
				deathOverlay.setRGB(x, y, 2013216832);
			}
		}
	}
	

	@Override
	public void run() {
		while(running) {
			long anfang = System.currentTimeMillis();
			
			while(!finished) {
				System.out.println("waiting");
			}
			
			
			repaint();
			
			
			synchronized(game2) {
				game2.notify();
			}
			
			if(isPaused || isDead) {
				synchronized(this) {
					try {
						this.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			
			try {
				Thread.sleep(8L);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			if(strecke + characterWidth2 >= maxWidth)
				running = false;
			long schluss = System.currentTimeMillis();
			
			System.out.println(schluss - anfang - 8);
		}
		main.setScreen(GameMain.SCREEN_LEVEL_MENU);
		File file = new File("background.jpg");
		file.delete();
	}
	
	protected void offPaint() {
		long mitte1 = System.currentTimeMillis();
		currentScreenGraphics.drawImage(background, 0, 0, width, height, strecke, screenHeight, strecke + width, screenHeight + height, null);
		
		currentScreenGraphics.drawImage(levelImage, 0, 0, width, height, strecke, screenHeight, strecke + width, screenHeight + height, null);
		
		currentScreenGraphics.drawImage(currentCharacter, 50, PlayerRelativeHeight, null);
		long mitte2 = System.currentTimeMillis();
		System.out.println("Mitte: " + (mitte2 - mitte1));
	}
	
	public void update(Graphics g) {
		paint(g);
	}
	
	@Override
	public void paint(Graphics g) {
		
		offPaint();
		
		g.drawImage(currentScreen, 0, 0, null);
		super.paintComponents(g);
		
		if(isPaused){
			g.drawImage(pauseOverlay, 0, 0, null);
			g.setColor(Color.white);
			g.setFont(new Font("sans", Font.PLAIN, 50));
			g.drawString("Pausiert", width/2 - 90, height/2 - 200);
		}
		else if(isDead) {
			g.drawImage(deathOverlay, 0, 0, null);
			g.setColor(Color.white);
			g.setFont(new Font("sans", Font.PLAIN, 50));
			g.drawString("Du bist gestorben", width/2 - 200, height/2 - 200);
		}
		super.paintComponents(g);
	}
	
	
	
	
		
	public class Game2 implements Runnable {
		

		@Override
		public void run() {
			while(running) {
				
				strecke += 2;
				strecke2 += 2;
				
				if(strecke2 >= width) {
					strecke2 = 0;
				}
				
				imgNum++;
				if(imgNum >= 62)
					imgNum = 0;
				
				if(jumpedHeight < 100)
					jumpHeightInTick = 3;
				
				else if(jumpedHeight > 100 && jumpedHeight < 110) {
					jumpHeightInTick = 2;
				}
				
				else if(jumpedHeight < 130 && isFalling)
					jumpHeightInTick = 2;
				
				else if(jumpedHeight > 140 && jumpedHeight < 150)
					jumpHeightInTick = 1;
				
				else if(jumpedHeight > 150) {
					isJumping = false;
					isFalling = true;
				}
				
				
				
				if(!isJumping) {
					
					int i = level.getNextSolid(strecke + 130, PlayerAbsoluteHeight) - characterHeight2;

					System.out.println("screenHeight: " + screenHeight + ", maxScreenHeight: " + maxScreenHeight);
					if(i + characterHeight2 == 0 && screenHeight >= maxScreenHeight - 2) {
						System.out.println("============================DEAD==============================");
						setDead();
					}
					
					if(PlayerAbsoluteHeight < i - 10 || PlayerAbsoluteHeight > i + 10 || i == -characterHeight2)
						isFalling = true;
					else
						isFalling = false;
					
					if(isFalling) {
						PlayerAbsoluteHeight += jumpHeightInTick;
						PlayerRelativeHeight += jumpHeightInTick;
						jumpedHeight -= jumpHeightInTick;
					}else {
						PlayerAbsoluteHeight = i;
						PlayerRelativeHeight = PlayerAbsoluteHeight - screenHeight;
						jumpedHeight = 0;
						jumpHeightInTick = 3;
					}
				}
				else {
					PlayerAbsoluteHeight -= jumpHeightInTick;
					PlayerRelativeHeight -= jumpHeightInTick;
					jumpedHeight += jumpHeightInTick;
				}
				
				
				if(PlayerRelativeHeight <= 50 && screenHeight >= 1) {
					System.out.println("=======================----------------========================");
					if(isJumping && screenHeight >= 3) {
						screenHeight -= 3;
					}
					else {
						screenHeight--;
					}
				}
				
				else if((PlayerRelativeHeight + characterHeight2) >= (height - 100) && screenHeight <= maxScreenHeight - 1) {
					System.out.println("========================+++++++++++++++++++++++========================");
					if(isFalling && screenHeight <= maxScreenHeight - 3) {
						screenHeight += 3;
					}
					else {
						screenHeight++;
					}
				}
				
				
				if(isJumping)
					currentCharacter = jump;
				else if(isFalling)
					currentCharacter = fall;
				else
					currentCharacter = frames[imgNum];
				
				
				finished = true;
				
			synchronized(this) {
				try {
					this.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		
	}
	
	
	}
	
	
}
```

Und die Level-Klasse:


```
package ch.faberix.jumpandrun;

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Repräsentiert ein Level*/
public class Level {

	public BufferedImage sourceImage;
	public int levelWidth, levelHeight;

	private Game game;
	public Map<Integer, Integer>solid = new HashMap<Integer, Integer>();
	
	
	private int CharacterHeight;
	
	private Map<Integer, List<Integer>>solids = new HashMap<Integer, List<Integer>>();
	private Map<Integer, List<Integer>>balksAbove = new HashMap<Integer, List<Integer>>();
	
	
	public Level(BufferedImage img, Game game) {
		sourceImage = img;
		levelWidth = img.getWidth();
		levelHeight = img.getHeight();
		
		this.game = game;
		CharacterHeight = game.characterHeight2;

		
		for(int x = 0; x < levelWidth; x++) {
			int y = getHeighestSolid(x);
			solid.put(x, y);
			System.out.println("solid: x: " + x + ", y: " + y);
		}
		
		int i = solid.get(levelWidth - 1);
		solid.put(levelWidth, i);
		
		for(int x = 0; x < levelWidth; x++) {
			List<Integer> list = getSolids(x);
			solids.put(x, list);
			
			
		}
		
	}

	public boolean isSolid(int x, int y) {
		return sourceImage.getRGB(x, y) == -16777216 ? true : false;
	}
	
	public int getHeighestSolid(int x) {
		for(int i = 0; i < levelHeight; i++) {
			if(isSolid(x, i))
				return i;
		}
		return 0;
	}
	
	
	/**@param x Die x-Spalte
	 * 
	 * @return Eine liste mit allen soliden Pixeln in der Spalte, die Methode füllt auch automatisch die Map mit den Hindernissen über dem Spieler aus.*/
	private List<Integer> getSolids(int x) {
		List<Integer> Solids = new ArrayList<Integer>();
		List<Integer> balksAbove = new ArrayList<Integer>();
		boolean isBalk = false;
		for(int y = 0; y < levelHeight; y++) {
			if(isSolid(x, y)) {
				if(!isBalk) {
					Solids.add(y);
					isBalk = true;
				}
			}
			else {
				if(isBalk) {
					balksAbove.add(y - 1);
					isBalk = false;
				}
			}
		}
		Collections.reverse(balksAbove);
		this.balksAbove.put(x, balksAbove);
		return Solids;
	}
	
	/**@param x Die aktuelle x-Position des Spielers
	 * @param y Die aktuelle y-Position des Spielers
	 * 
	 * @return Der nächste solide Pixel unter dem Spieler (0 falls es keinen gibt)*/
	public int getNextSolid(int x, int y) {
		List<Integer> Solids = solids.get(x);
		int Height = 0;
		
		for(int i = 0; i < Solids.size(); i++) {
			int height = Solids.get(i);
			System.out.println("solid: " + height + ", y: " + y);
			if(height >= y) {
				System.out.println("solid found: " + height);
				return height;
				
			}
		}
		return Height;
	}
	
	
	/**
	 * @param x Die aktuelle x-Position des Spielers
	 * @param y Die aktuelle y-Position des Spielers
	 * 
	 * @return Das nächste Hindernis über dem Spieler (normalerweise Unterseite des nächsten Balkens über ihm, 0 falls es keinen gibt)*/
	public int getNextBalkAbove(int x, int y) {
		List<Integer> balksAbove = this.balksAbove.get(x);
		
		for(int i = 0; i < balksAbove.size(); i++) {
			int currentHeight = balksAbove.get(i);
			if(currentHeight >= y) {
				System.out.println("balk above found: " + currentHeight);
				return currentHeight;
			}
		}
		
		return 0;
	}
	
	
	
	public int getStartPos() {
		
		return getHeighestSolid(50) - CharacterHeight;
	}
	
	
	
}
```


Hier noch ein Levelblid: 

Ich bedanke mich schon im Voraus für die Antworten.

mfg, Fagerix


----------



## Harry Kane (9. Okt 2014)

Sorry, bei solchen Konstrukten wende ich mich mit Grausen ab:

```
public class Game{
    private Game Instance;
    public Game.Game2 game2 = new Game2();//in der Game Klasse  taucht Game2 oder game2 nie wieder auf!
    public Game(){
        Instance = this;
    }
}
```
Suche mal nach Quaxlis Spiele Tutorial.


----------



## Joose (10. Okt 2014)

Dein Problem ist das deine beiden Threads einfach nacheinander arbeiten.

Sprich dein "Game1" Thread stupst (notify) den "Game2" Thread an.
Game1 zeichnet neu, und muss dann warten bis dein Game2 Thread fertig gerechnet hat.

Der Game2 Thread aber braucht nicht immer die gleiche Zeit für seine Berechnungen.
Daher zeichnet dein Game1 Thread auch mal nach 300ms, mal nach 1 Sekunde.

Du musst messen wie lange dein Game1 Thread gebraucht hat und lässt ihn dann mehr oder weniger lang schlafen.
So kannst du konstant zeichnen lassen.

Aber wie HarryKane schon gesagt hat: Such mal nach Quaxlies Spiel Tutorial


----------



## Faberix (10. Okt 2014)

Harry Kane hat gesagt.:


> in der Game Klasse  taucht Game2 oder game2 nie wieder auf!



"game2" benötige ich in anderen Klassen wie der Hauptklasse um auf den Game2-Thread zuzugreifen.

Ich werde mir dieses Tutorial mal anschauen....


----------

