Bug no programa

poqr que quando eu fecho meu programa as X da esse erro java.io.IOException: closed
at java.desktop/javax.imageio.stream.ImageInputStreamImpl.checkClosed(ImageInputStreamImpl.java:110)
at java.desktop/javax.imageio.stream.ImageInputStreamImpl.close(ImageInputStreamImpl.java:857)
at java.desktop/javax.imageio.stream.FileCacheImageInputStream.close(FileCacheImageInputStream.java:253)
at java.desktop/javax.imageio.ImageIO.read(ImageIO.java:1473)
at java.desktop/javax.imageio.ImageIO.read(ImageIO.java:1422)
at graficos.ImageLoader.loadImage(ImageLoader.java:14)
at entidades.Entiti.loadImage(Entiti.java:30)
at entidades.Player2.render(Player2.java:46)
at com.cett.Game.render(Game.java:132)
at com.cett.Game.run(Game.java:167)
at java.base/java.lang.Thread.run(Thread.java:833)

/// package com.cett;

public class Game extends Canvas implements Runnable {

private static JFrame frame;
public static final int WIDTH = 1000;
public static final int HEIGHT = 700;
private int FPS;
private Thread thread;
private boolean isRunnable = false;


Player1 p1;
Player2 p2;
private BufferedImage image;

public List<Entiti> entities; 





public void iniciarJogo() {
 

}
public void info() {
	
  
	
}

public Game() {

	initFrame();
	iniciarJogo();
	info();

	
	image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
	entities = new ArrayList<Entiti>();
	p1 = new Player1();
	entities.add(p1);
	
	p2 = new Player2();
	entities.add(p2);
	
}

public void initFrame() {

	frame = new JFrame("Imperio do Caos");
	frame.setPreferredSize(new Dimension(WIDTH, HEIGHT));
	frame.add(this);
	frame.setResizable(false);
	frame.pack();
	frame.setLocationRelativeTo(null);
	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	frame.setVisible(true);

}

public synchronized void start() {

	thread = new Thread(this);
	isRunnable = true;
	thread.start();
}

public synchronized void stop() {

	try {
		thread.join();
	} catch (InterruptedException e) {

		e.printStackTrace();
	}

}

public static void main(String[] args) {

	Game game = new Game();
	game.start();
}

public void tick() {

	for (int i = 0; i < entities.size(); i++) {
		Entiti e = entities.get(i); // pecorre a Lista para achar todos as classes/entidades.
		////////////////// LOGICA DO GAME! //////////////////
		long agora = System.currentTimeMillis();
		///// Testo na tela!!!!
	
	
		////////////////////////////////////
		e.tick(); // Chama o metodo tick de todas as clases/entidaes que estao dentro da Lista!
	}

}

public void render() {
	BufferStrategy bs = this.getBufferStrategy();
	if (bs == null) {
		this.createBufferStrategy(3);
		return;
	}
	//
	Graphics g = image.getGraphics();
	g.setColor(new Color(0, 0, 0));
	g.fillRect(0, 0, WIDTH, HEIGHT);
	// render abaixo

	for (int i = 0; i < entities.size(); i++) {
		Entiti e = entities.get(i);
		e.render(g); // O 'e' que é uma lista de classes/entidades chama
		
	}

///////////////// DIALOGO ////////////////////////

///////////////// DIALOGO ////////////////////////
g = bs.getDrawGraphics(); // A classe Graphics/imagens cria 3 “pré” imagens antes de renderizar!
g.drawImage(image, 0, 0, WIDTH, HEIGHT, null); // Agora Graphics/imagem pinta a(as) imagem que estiver pronra!

	g.setFont(new Font("Arial", Font.BOLD, 14));
	g.setColor(Color.white);
	g.drawString("Fps."+FPS, 900, 650);

	bs.show(); // exibe a(as) imagens!
}

@Override
public void run() {

	// Variaveis q serao necessarias para o calculo dos fps e gerar os ticks do
	// looping.
	long lestTime = System.nanoTime(); // menor tempo do sistema.Ponto de inicio.
	double amountOfticks = 60; // quantidade de ticks desejados
	double ns = 1000000000 / amountOfticks;
	double delta = 0;
	int frames = 0;
	double time = System.currentTimeMillis();

	while (isRunnable) {

		long now = System.nanoTime(); // Pega o tempo de agora e nano segundos!
		delta += (now - lestTime) / ns;
		lestTime = now;
		if (delta >= 1) {
			tick();
			render();
			frames++;
			delta--;

		}
		if (System.currentTimeMillis() - time >= 1000) {
			System.out.println("FPS: " + frames);
			FPS = frames;
			frames = 0;
			time += 1000;
		}

	}

// esse meto nao e necessario, mas e importante pois funciona como uma segurança para parar o looping!
stop();
}

}
///////
package graficos;

import java.util.ArrayList;

public class BancoImagens {

public ArrayList<String> guardarCaminhoImagens;

public BancoImagens(){
	
	guardarCaminhoImagens= new ArrayList<>();
	guardarCaminhoImagens.add("/arqueiro.png");
	guardarCaminhoImagens.add("/guerreiro.png");
	guardarCaminhoImagens.add("/mago.png");


}

}
/////
package graficos;

public class ImageLoader {

public static BufferedImage loadImage(String path) {
	
    try {
        return ImageIO.read(ImageLoader.class.getResource(path));
    } catch (IOException e) {
        e.printStackTrace();
        
       
        return null; // Tratar erros de carregamento de imagem
    }
}

}
/////
package entidades;

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Random;

import graficos.ImageLoader;

public class Entiti {

protected double x;
protected double y;
protected int width;
protected int height;

// String CaminhoImagem;
Random r = new Random();
BufferedImage  imagem; // Carregar a imagem aqui

protected int dinheiro;
protected String nome;
protected int exp;
protected int lv;
protected int mana;
protected int vida;
protected int ataque;
protected int defesa;

protected BufferedImage loadImage(String path) {
	return ImageLoader.loadImage(path);

}

public Entiti(int x, int y, int width, int height, String CaminhoImagem) {

	this.x = x;
	this.y = y;
	this.width = width;
	this.height = height;
	// this.CaminhoImagem=CaminhoImagem;
	
}

public int getX() {
	return (int) this.x;
}

public int getY() {
	return (int) this.y;
}

public void tick() {

}

public void render(Graphics g) {
	// E importante instanciar a image aqui, a principio como vazio
	// para nao dar erro de "InputStream ou ImageInputStream" ao fechar o jogo!
	imagem = loadImage(null);
	
	g.drawImage(imagem, this.getX(), this.getY(), null);

}

}
package entidades;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;

import Cartas.Guerreiro;
import graficos.BancoImagens;

public class Player1 extends Entiti {
// imagens
static String CaminhoImagem;
BancoImagens bi = new BancoImagens();
// Atributos do Jogador!
/*
protected int lv=0;
protected int exp=0;

protected String nome;
protected int vida;
protected int mana;

protected int delta_At_Min;
protected int delta_At_Max;
protected int at;

protected int delta_Df_Min;
protected int delta_Df_Max;
protected int df;

protected int delta_Ag_Max;
*/

public Player1() {
	super(240, 2, 250, 400, CaminhoImagem);
	
	
}

public void card() {
	
	if(vida<=0) {
	// imagem
	int sort = r.nextInt(bi.guardarCaminhoImagens.size());
	CaminhoImagem = bi.guardarCaminhoImagens.get(sort);
	System.out.println("RANDOM =========== " + sort);
	//
	
	Guerreiro guerreiro = new Guerreiro();

	if (sort == 0) {
		nome = guerreiro.card_nome;
		vida = 100;
	} else if (sort == 1) {
		nome = "Guerreiro";
		vida = 100;
	} else if (sort == 2) {
		nome = "mago";
		vida = 100;
	}

}

}

public void tick() {
	card();
}

public void render(Graphics g) {
	imagem = loadImage(CaminhoImagem);
	g.drawImage(imagem, this.getX(), this.getY(), null);

	g.setFont(new Font("Arial", Font.BOLD, 14));
	g.setColor(Color.green);
	g.drawString("Player 1: " + nome, 240, 420);
	g.drawString("Vida: " + vida, 240, 440);

}

}

Isso aqui está errado

Além do mais, você não deveria ler imagens no método render, as imagens devem ser carregadas na inicialização

No render deve só apresentar as imagens previamente carregadas

mas se eu retirar imagem = loadImage(null); do render e por fora, nao carregar a imagem. essas parada de sintaxe q tenho q entender, meu curso e muito ruim so aprendo o que vejo não tem tutor para explicara teoria ou tirar duvidas.

AAAAA cara fiz uma modificações com certeza não ta bem arrumadinho mas da sua opinião acho que reduzi a bagunça e a maluquice, agora esta uns 10% melhor “talvez” , bom ta renderizando sem erro ate agora…tirei a classe ImageLoader e mudeis uns pontos. Ignora os erros de português tenho TDH e isso ferra meinha atenção ao digitar escrevo bem mas no teclado sai maluquice.

package com.cett;
public class Game extends Canvas implements Runnable {

private static JFrame frame;
public static final int WIDTH = 1000;
public static final int HEIGHT = 700;
private int FPS;
private Thread thread;
private boolean isRunnable = false;


Player1 p1;
Player2 p2;
private BufferedImage image;

public List<Entiti> entities; 


public void iniciarJogo() {
 

}
public void info() {
	
  
	
}

public Game() {

	initFrame();
	iniciarJogo();
	info();

	image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
	//imageCard = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
	
	entities = new ArrayList<Entiti>();

	p1 = new Player1();
	entities.add(p1);
	
	p2 = new Player2();
	entities.add(p2);
	
}
public synchronized void start() {

	thread = new Thread(this);
	isRunnable = true;
	thread.start();
}

public synchronized void stop() {

	try {
		thread.join();
	} catch (InterruptedException e) {

		e.printStackTrace();
	}

}

public static void main(String[] args) {

	Game game = new Game();
	game.start();
}

public void tick() {

	for (int i = 0; i < entities.size(); i++) {
		Entiti e = entities.get(i);
		////////////////// LOGICA DO GAME! //////////////////
		long agora = System.currentTimeMillis();
		///// Testo na tela!!!!
		e.tick(); 
	}

}

public void render() {
	BufferStrategy bs = this.getBufferStrategy();
	if (bs == null) {
		this.createBufferStrategy(3);
		return;
	}
	//
	Graphics g = image.getGraphics();
	g.setColor(new Color(0, 0, 0));
	g.fillRect(0, 0, WIDTH, HEIGHT);
	// render abaixo

	for (int i = 0; i < entities.size(); i++) {
		Entiti e = entities.get(i);
		e.render(g); 
		
	}

	g = bs.getDrawGraphics();
	g.drawImage(image, 0, 0, WIDTH, HEIGHT, null); 

	g.setFont(new Font("Arial", Font.BOLD, 14));
	g.setColor(Color.white);
	if(FPS>=90 || FPS<=20 && FPS>39) {
		g.setColor(Color.ORANGE);
	}
	else if(FPS>=70 || FPS<=40) {
		g.setColor(Color.RED);
	}
	g.drawString("Fps. "+FPS, 910, 650);

	bs.show(); // exibe a(as) imagens!
}

@Override
public void run() {

	long lestTime = System.nanoTime(); 
	double amountOfticks = 60; // quantidade de ticks desejados
	double ns = 1000000000 / amountOfticks;
	double delta = 0;
	int frames = 0;
	double time = System.currentTimeMillis();

	while (isRunnable) {

		long now = System.nanoTime(); 
		delta += (now - lestTime) / ns;
		lestTime = now;
		if (delta >= 1) {
			tick();
			render();
			frames++;
			delta--;

		}
		if (System.currentTimeMillis() - time >= 1000) {
			System.out.println("FPS: " + frames);
			FPS = frames;
			frames = 0;
			time += 1000;
		}

	}

	stop();
}

}
/////////////////////////
package entidades;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

import graficos.BancoImagens;

public class Entiti {

protected double x;
protected double y;
protected int width;
protected int height;

Random r = new Random();

BancoImagens bi = new BancoImagens();
protected static String imagemSorteada;
protected ImageIcon imagemIcon=new ImageIcon();

BufferedImage buferedImage;

protected int dinheiro;
protected int exp=0;
protected int lv=1;

protected String nome;
protected int mana;
protected int vida;

public int delta_At_Min;
public int delta_At_Max;
public int at;

public int delta_Df_Min;
public int delta_Df_Max;
public int df;

public int delta_Ag_Max;



public Entiti(int x, int y, int width, int height) {

	this.x = x;
	this.y = y;
	this.width = width;
	this.height = height;

	
}

public int getX() {
	return (int) this.x;
}

public int getY() {
	return (int) this.y;
}

public void tick() {

}


public void imgcardBuferedImage(String peth) {
	
	try {
		buferedImage = ImageIO.read(getClass().getResource(peth));
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	
}

public void render(Graphics g) {

	Image cartasImg =imagemIcon.getImage();
	g.drawImage(cartasImg, this.getX(), this.getY(), null);

}

public BufferedImage getBuferedImage(int x, int y, int width, int height) {
	return buferedImage.getSubimage(x, y, width, height);
}

}
////////////////////
package entidades;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.io.IOException;
import java.util.zip.InflaterOutputStream;

import Cartas.Arqueiro;
import Cartas.Guerreiro;
import Cartas.Mago;
import graficos.BancoImagens;

public class Player1 extends Entiti {
// imagens
//static String CaminhoImagem;

int sort;

int massaTodal;

public Player1() {
	super(240, 2, 250, 400); // estring
	
	
}

public void card() {
	
	if(vida<=0) {
	// imagem
	int sort = r.nextInt(bi.guardarCaminhoImagens.size());
	imagemSorteada = bi.guardarCaminhoImagens.get(sort);
	System.out.println("RANDOM =========== " + sort);
	// # IMPORTANTE !
	// AQUI EU INVOCO O METODO ONDE VAI SER DESENHADO O GRAFICO COMO UM PAPEL]
	// QUE E O METODO BufferedImage!!
	imgcardBuferedImage(imagemSorteada);
	//
	Guerreiro guerreiro = new Guerreiro();
	Mago mago = new Mago();
	Arqueiro arqueiro = new Arqueiro();

	if (sort == 0) {
	
		nome = guerreiro.card_nome;
		vida = guerreiro.card_vida;
		mana = guerreiro.card_mana;
		delta_At_Min = guerreiro.card_delta_At_Min;
		delta_At_Max = guerreiro.card_delta_At_Max;
		at = guerreiro.card_at; 
		delta_Df_Min =  guerreiro.card_delta_Df_Min;
		delta_Df_Max = guerreiro.card_delta_Df_Max;
		df = guerreiro.card_df;
		delta_Ag_Max =guerreiro.card_delta_Ag_Max;
		
		
	} else if (sort == 1) {
		
		nome = mago.card_nome;
		vida = mago.card_vida;
		mana = mago.card_mana;
		delta_At_Min = mago.card_delta_At_Min;
		delta_At_Max = mago.card_delta_At_Max;
		at = mago.card_at; 
		delta_Df_Min =  mago.card_delta_Df_Min;
		delta_Df_Max = mago.card_delta_Df_Max;
		df = mago.card_df;
		delta_Ag_Max =mago.card_delta_Ag_Max;
		
	} else if (sort == 2) {
		
		nome = arqueiro.card_nome;
		vida = arqueiro.card_vida;
		mana = arqueiro.card_mana;
		delta_At_Min = arqueiro.card_delta_At_Min;
		delta_At_Max = arqueiro.card_delta_At_Max;
		at = arqueiro.card_at; 
		delta_Df_Min =  arqueiro.card_delta_Df_Min;
		delta_Df_Max = arqueiro.card_delta_Df_Max;
		df = arqueiro.card_df;
		delta_Ag_Max =arqueiro.card_delta_Ag_Max;
	}

}

	 massaTodal =vida+mana+delta_At_Max-delta_At_Min+delta_Df_Max-delta_Df_Min+delta_Ag_Max;
	
}

public void tick() {
	
	card();
}




public void render(Graphics g) {
	
	//Image cartasImg =imagemIcon.getImage();
	 
	    g.drawImage(buferedImage, this.getX(), this.getY(), null);

	    g.setFont(new Font("Montserrat", Font.BOLD, 14));
	    g.setColor(new Color(0,150,200));

	    // Define um retângulo que engloba todo o texto
	    Rectangle areaTexto = new Rectangle(240, 420, 250, 200); // Ajuste o tamanho conforme necessário

	    // Salva a cor atual
	    Color originalColor = g.getColor();

	    // Muda a cor do retângulo
	    g.setColor(new Color(10,10,40)); // Cor do retângulo
	    g.fillRect(areaTexto.x, areaTexto.y, areaTexto.width, areaTexto.height); // Desenha o retângulo

	    // Restaura a cor original antes de renderizar o texto
	    g.setColor(originalColor);

	    // Renderiza o texto dentro do retângulo
	    drawTextInRectangle(g, areaTexto, "Player 1:     LV: " + lv + "     EXP: " + exp +
	    		"\n\nCarta:   " + nome + 
	    		"\nVida:   " + vida+
	    		"\nMana:   "+mana+
	    		"\nAt:   "+delta_At_Min+" / "+delta_At_Max+
	    		"\nDf:   "+delta_Df_Min+" / "+delta_Df_Max+
	    		"\nAg:   "+delta_Ag_Max+
	    		"\n\nMassa:     "+massaTodal);
}


// Método para renderizar o texto dentro de um retângulo
private void drawTextInRectangle(Graphics g, Rectangle rect, String text) {
	 // Define o limite de clipping para o retângulo do texto
    g.setClip(rect);

    // Separa as linhas de texto usando "\n"
    String[] lines = text.split("\n");

    // Calcula o tamanho do texto dentro do retângulo
    FontMetrics metrics = g.getFontMetrics();
    int lineHeight = metrics.getHeight();

    // Calcula a posição inicial para alinhar o texto à parte superior verticalmente no retângulo
    int y = rect.y + metrics.getAscent(); // Alinhamento na parte superior do retângulo

    // Desenha cada linha de texto alinhada à esquerda dentro do retângulo
    for (String line : lines) {
        // Ajusta a posição X para alinhar à esquerda do retângulo
        int x = rect.x;
        g.drawString(line, x, y);
        y += lineHeight; // Avança para a próxima linha
    }

    // Limpa o limite de clipping após renderizar o texto
    g.setClip(null);
}

////////
package graficos;

import java.util.ArrayList;

public class BancoImagens {

public ArrayList<String> guardarCaminhoImagens;

public BancoImagens(){
	
	guardarCaminhoImagens= new ArrayList<>();
	
	guardarCaminhoImagens.add("/guerreiro.png");
	guardarCaminhoImagens.add("/mago.png");
	guardarCaminhoImagens.add("/arqueiro.png");
	


}

}
////////