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!