Problemas de desempenho em jogo Java desenvolvido no Eclipse

Olá pessoal,

Estou enfrentando alguns problemas de desempenho em um jogo que estou desenvolvendo em Java usando o Eclipse como meu ambiente de desenvolvimento. Gostaria de pedir a ajuda de vocês para entender a causa desse problema e buscar soluções.

Meu software utiliza as bibliotecas padrões do Java e não depende de nenhuma biblioteca adicional. Em relação ao meu nível de experiência, estou estudando Java há cerca de 5 meses e este é o meu primeiro projeto mais complexo.

O problema que estou enfrentando é que, a cada vez que seleciono uma fase no menu do jogo, o desempenho fica mais lento e há uma queda de fps. O jogo utiliza um JFrame e dentro dele um JPanel que contém a lógica e a renderização do jogo. Já verifiquei o código e não encontrei nenhum problema óbvio que possa estar causando essa diminuição progressiva do desempenho.

Além disso, o meu software não depende de nenhum recurso específico do sistema operacional. Estou usando o Eclipse como ambiente de desenvolvimento para escrever, compilar e executar o código.

Acredito que o problema pode estar relacionado a uma possível má otimização do código ou alguns dados podem continuar na memoria mesmo depois de eu fechar a tela do jogo.

Codigo do Painel

` package com.impulsesquare.scenes;

import java.awt.Graphics;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.Timer;

import com.impulsesquare.objects.Cell;
import com.impulsesquare.objects.Player;

public class LoadLevels extends JPanel implements ActionListener{
	private static final long serialVersionUID = 1L;
	//CRIA OBJETOS
	private Player player;
    private Timer timer;
    
    private Image background;
    private List<Cell> map;
    private List<Cell> newMap;
    
    private File directory;
    private String selectedMap;
    
    // VARIAVEIS DE FPS
    private int fpsCount = 0;
    private double fps = 0;
    private long lastTime = System.currentTimeMillis();
    
	@SuppressWarnings("unchecked")
	//CONSTRUTOR
	public LoadLevels() {
		setFocusable(true);
		setDoubleBuffered(true);
		addKeyListener(new TecladoAdapter());
		
		directory = new File(System.getProperty("user.dir"));
		
		//ADICIONA TODOS OS ARQUIVOS .DAT AO ARRAY
		ArrayList<String> maps = new ArrayList<String>();
        for (File file : directory.listFiles()) {
        	try {
        		if (file.getName().endsWith(".dat")) {
                    maps.add(file.getName());
                }
			} catch (Exception e) {e.printStackTrace();}
        }
        
        String[] maps_array = maps.toArray(new String[maps.size()]);
        
        //ESCOLHA DE MAPA
        selectedMap = (String)JOptionPane.showInputDialog( null, "Selecione um mapa para jogar abaixo: ", "Mapas...",
                JOptionPane.QUESTION_MESSAGE, 
                null, 
                maps_array,
                maps_array[0]);
        
        if (selectedMap == null) {
        	return;
        }
        
    	//GUARDA O MAPA ESCOLHIDO EM UMA LISTA
		try
		{
            FileInputStream fis = new FileInputStream(selectedMap);
            ObjectInputStream ois = new ObjectInputStream(fis);
            map = (List<Cell>) ois.readObject();
            ois.close();
            fis.close();
		}
		catch (Exception ex){JOptionPane.showMessageDialog(null, ex.getMessage()); }
		
		//CRIA UM PLAYER E CARREGA AS TEXTURAS
		player = new Player(map);
		player.load();
		
		//PEGA A IMAGEM DE FUNDO
		background = map.get(map.size()-1).getTexture().getImage();
		
		newMap = new ArrayList<>(map);
		
		//REMOVE O PLAYER DO MAPA
		for (int i = 0; i < map.size(); i++) {
			if (map.get(i).getColor() != null && map.get(i).getColor().contains("character")) {
				newMap.get(i).setColor("");
				newMap.get(i).setTexture(new ImageIcon(getClass().getResource("/com/impulsesquare/images/transparent.png")));
				newMap.get(i).setIcon(new ImageIcon(getClass().getResource("/com/impulsesquare/images/transparent.png")));
			}
		}
		//INICIA LOOP
		timer = new Timer(10, this);
		timer.start();
	}

	//FUNCAO QUE DESENHA NA TELA
	public void paint(Graphics g) {
		Graphics2D graficos = (Graphics2D) g;
		
        graficos.drawImage(background, 0, 0, this);
	    
		for (int i = 0; i < newMap.size()-1; i++) {
			graficos.drawImage(newMap.get(i).getTexture().getImage(), newMap.get(i).getX(), newMap.get(i).getY()-1, this);
		}
		
		graficos.drawImage(player.getCharacter_img(), player.getX(), player.getY(), this);
		
		// DESENHA FPS
	    graficos.drawString("FPS: " + String.format("%.2f", fps), 10, 20);
	    
		g.dispose();
	}
	
	//CRIA LEITORES DE TECLADO
	@Override
	public void actionPerformed(ActionEvent e) {
		//CHAMA A FUNCAO DE MOVIMENTO
		player.moviment();
		repaint();
		
		// CALCULAR FPS
	    long currentTime = System.currentTimeMillis();
	    long elapsedTime = currentTime - lastTime;
	    fpsCount++;

	    if (elapsedTime >= 1000) {
	        fps = (double) fpsCount / (elapsedTime / 1000.0);
	        fpsCount = 0;
	        lastTime = currentTime;
	    }
	}
	
	private class TecladoAdapter extends KeyAdapter{
		@Override
		public void keyPressed(KeyEvent e) {
			player.keyPressed(e);
		}
	}

	//RETORNA NOME DO MAPA ESCOLHIDO
	public String getSelectedMap() {
		return selectedMap;
	}
}
 `

Codigo do Player

package com.impulsesquare.objects;

import java.awt.Dimension;

import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.swing.ImageIcon;

public class Player{
	private Dimension size;
	private int x, y; //POSICAO DO PLAYER
	private int original_x, original_y;//POSICAO INICIAL DO PLAYER
	private int dx, dy; //DIRECAO DO PLAYER
	private String color;//COR DO PLAYER
	private String original_color;//COR DO PLAYER
	private Image character_img; //IMAGEM DO PLAYER
	private Image original_character_img; //IMAGEM INICIAL DO PLAYER
	private List<Cell> blocks; //MAPA DE BLOCOS
	private File file_animations = new File(getClass().getResource("/com/impulsesquare/animations").getFile());
	private List<Image> list_img_explosion = new ArrayList<>();
	private List<ImageIcon> list_img_portal = new ArrayList<>();
	private int index_portal = 10000; 
	private boolean close_portal = false;
	
	//RECEBE O MAPA
	public Player(List<Cell> blocks) {
		super();
		this.blocks = blocks;
	}
	
	//CARREGA IMAGEM E DEFINE TAMANHO DO PLAYER
	public void load(){
		try {
			for (int i = 0; i < blocks.size(); i++) {
				if (blocks.get(i).getColor().contains("character")) {
					color = blocks.get(i).getColor().replace("character-", "");
					original_color = color;
					character_img = blocks.get(i).getTexture().getImage();
					original_character_img = character_img;
					size = new Dimension(character_img.getWidth(null), character_img.getHeight(null));
					x = blocks.get(i).getLocation().x+7;
					y = blocks.get(i).getLocation().y+7;
					original_x = x;
					original_y = y;
					break;
				}
			}
		} catch (Exception e) {}
		loadAnimations();
		portal();
	}
	
	//VARIAVEIS DE CONTROLE
	private boolean isMoving;
	private int speed = 20;
	
	// VERIFICA SE O JOGADOR PODE SE MOVER NAS DIREÇÕES X E Y E MOVE ELE
	public void moviment() {
		
	    // CRIA UM RETÂNGULO QUE REPRESENTA A PROXIMA POSIÇÃO DO JOGADOR
	    Rectangle playerRect = new Rectangle(getX() + dx, getY() + dy, size.width, size.height);
	    
	    Cell block;
	    Rectangle blockRect;
	    String blockTextureName;
	    boolean isTransparent;
	    boolean isBrick;
	    boolean isColorChange;
	    Rectangle intersection;
	    int playerX;
	    int playerY;
	    
	    // PERCORRE TODOS OS BLOCOS DO JOGO
	    for (int i = 0; i < blocks.size()-1; i++) {
	    	// OBTÉM O RETÂNGULO QUE REPRESENTA A POSIÇÃO DO BLOCO ATUAL
	        block = blocks.get(i);
	        
	        blockTextureName = new File(block.getTexture().getDescription()).getName();
	        isTransparent = blockTextureName.equals("transparent.png");
	        isBrick = blockTextureName.contains("bricks.png");
	        isColorChange = blockTextureName.contains("change");
	        
	        // OBTÉM O RETÂNGULO QUE REPRESENTA O BLOCO
	        blockRect = new Rectangle(block.getLocation().x, block.getLocation().y, block.getSize().width, block.getSize().height);
	        
	        //DIMINUI HITBOX DOS BLOCOS DE CIMA
	        if (i <= 17) {
	        	blockRect = new Rectangle(block.getLocation().x, block.getLocation().y-4, block.getSize().width, block.getSize().height);
			}
	        
	        //VERIFICA COLISAO
	        if (!isTransparent//IGNORA BLOCOS TRANSPARENTES OU TIJOLOS
	        		&& !isBrick
	        		&& playerRect.intersects(blockRect)) {
	        	
	        	if (new File(block.getTexture().getDescription()).getName().contains("portal")) {
					close_portal = true;
					close_portal_animation();
					return;
				}
	        	
	        	if (isColorChange) {
	    			String color_change = blockTextureName.replace("change_", "").replace(".png", "");
					character_img = new ImageIcon(getClass().getResource("/com/impulsesquare/textures/character-"
					+color_change + ".png")).getImage(); //MUDA A IMAGEM DO PLAYER PARA A COR QUE TOCOU
					color = color_change;//MUDA A COR DO PLAYER PARA A COR QUE TOCOU
				}
	        	else {
	        		// CRIA UM NOVO RETÂNGULO QUE REPRESENTA A INTERSECÇÃO ENTRE O JOGADOR E O BLOCO
	        		intersection = playerRect.intersection(blockRect);
	        		playerX = playerRect.x;
	        		playerY = playerRect.y;

	        		if (intersection.width < intersection.height) {//SE TOCOU NA HORIZONTAL
	        		    x = (playerX < blockRect.x) ? blockRect.x - size.width : blockRect.x + size.width + 13; //OPERADOR TENARIO
	        		} else {
	        		    y = (playerY < blockRect.y) ? blockRect.y - size.height : blockRect.y + size.height + 13;
	        		}

	        		isMoving = false;
	        		dx = 0;
	        		dy = 0;
	        	}
	        	
	    		if (!block.getColor().contains(color.replace("character-", ""))) {//VERIFICA COLISAO COM COR DIFERENTE DO PLAYER
					deadAnimation();
				}
	        }
	    }
	    x += dx;
        y += dy;
	}
	
	private void loadAnimations() {
		ImageIcon imageicon;
        for (File file : file_animations.listFiles()) {
        	try {
        		if (file.getName().contains("explosion")) {
                	imageicon = new ImageIcon(getClass().getResource("/com/impulsesquare/animations/"+file.getName()));
        			imageicon.setImage(imageicon.getImage().getScaledInstance(43, 43, Image.SCALE_SMOOTH));
                	list_img_explosion.add(imageicon.getImage());
                }
                if (file.getName().contains("open") || file.getName().contains("Portal_purple.png")) {
                	imageicon = new ImageIcon(getClass().getResource("/com/impulsesquare/animations/"+file.getName()));
                	imageicon.setImage(imageicon.getImage().getScaledInstance(43, 43, Image.SCALE_SMOOTH));
                	list_img_portal.add(imageicon);
                }
			} catch (Exception e) {e.printStackTrace();}
        }
        
        for (int i = 0; i < blocks.size()-1; i++) {
        	try {
        		if (new File(blocks.get(i).getTexture().getDescription()).getName().contains("portal.png")) {
    				index_portal = i;
    				break;
    			}
			} catch (Exception e) {e.printStackTrace();}
		}
	}
	
	private void deadAnimation() {
	    new Thread(() -> {
	    	isMoving = true;
	    	for (int i = 0; i < list_img_explosion.size(); i++) {
	            try {
	                Thread.sleep(7); //ESPERA 7 MILISEGUNDOS
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	            }
	            character_img = list_img_explosion.get(i);
	            try {
	                Thread.sleep(7);
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	            }
			}
	    	character_img = new ImageIcon(getClass().getResource("/com/impulsesquare/images/transparent.png")).getImage();
	    	try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
	    	//RESETA O PLAYER
	    	character_img = original_character_img;
	    	color = original_color;
	    	x = original_x;
	    	y = original_y;
	    	isMoving = false;
	    }).start();
	}
	
	private void portal(){
		new Thread(() -> {
			while (!close_portal) {
				for (int i = 0; i < list_img_portal.size(); i++) {
		            try {
		                Thread.sleep(50); //ESPERA 50 MILISEGUNDOS
		            } catch (InterruptedException e) {e.printStackTrace();}
		            
		            blocks.get(index_portal).setTexture(list_img_portal.get(i));
		            
		            try {
		                Thread.sleep(50);
		            } catch (InterruptedException e) {e.printStackTrace();}
				}
			}
	    }).start();
	}
	
	private void close_portal_animation(){
		new Thread(() -> {
			try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
			character_img = new ImageIcon(getClass().getResource("/com/impulsesquare/images/transparent.png")).getImage();
	    }).start();
	}
	
	//EVENTO DE TECLA PRESSIONADA
	public void keyPressed(KeyEvent key) {
		if (!isMoving) {
			int code = key.getKeyCode();
			if (code == KeyEvent.VK_UP || code == KeyEvent.VK_W) {
				dy = -speed;
			}
			if (code == KeyEvent.VK_DOWN || code == KeyEvent.VK_S) {
				dy = speed;
			}
			if (code == KeyEvent.VK_LEFT || code == KeyEvent.VK_A) {
				dx = -speed;
			}
			if (code == KeyEvent.VK_RIGHT || code == KeyEvent.VK_D) {
				dx= speed;
			}
			isMoving = true;
		}
	}

	public Image getCharacter_img() {
		return character_img;
	}
	public int getX() {
		return x;
	}
	public int getY() {
		return y;
	}
}

Codigo do Jframe

package com.impulsesquare.scenes;

import java.awt.Image;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFrame;

public class StartLevel extends JFrame{
	private static final long serialVersionUID = 1L;

	public StartLevel() {
		LoadLevels loader = new LoadLevels();
		if (loader.getSelectedMap() == null) {
			dispose();
		}
		else{
			add(loader);
			setTitle(loader.getSelectedMap().replace(".dat", ""));
			setSize(781, 536);
			setDefaultCloseOperation(DISPOSE_ON_CLOSE);
			setLocationRelativeTo(null);
			setResizable(false);
			setVisible(true);
			try {
				Image iconeTitulo = ImageIO.read(getClass().getResource("/com/impulsesquare/images/logo.png"));
				setIconImage(iconeTitulo);
			} catch (IOException e) {e.printStackTrace();}
		}
	}
}

se quiserem o codigo completo está no meu github: GitHub - lucasrealdev/Impulse-Square: Um pequeno jogo no 2d feito com java. O jogo consiste em um personagem principal que precisa percorrer um determinado caminho para concluir a fase.

Agradeço antecipadamente qualquer ajuda, sugestões ou insights que possam me ajudar a resolver esse problema de desempenho em meu jogo Java.

Obrigado!

Recomendo usar um ‘Profiler’, o que já vem no Eclipse ou então o “Visual VM”. Lá você pode encontrar seu gargalo, que parece ser no processamento.

Usei o Profiler e abri varios mapas diferente e nos testes o processador nao passa de 2% de utilizacao, mas percebi que o tamanho da memoria e o uso da memoria aumentam drasticamente ao carregar um mapa nao carregado anteriormente porém se eu carregar um mapa ja carregado o tamanho da memoria se mantém e o uso tambem, de qualquer jeito o jogo trava

Imagem

eu descobri que o evento de verificar click não estava fechando quando eu fechava a tela e como ele é atualizado todo instante estava pesando muito no jogo. Agora consegui resolver, obrigado

Legal hein. Resolvido :+1: