Desenvolvimento de Game em Java

Olá, pessoal! Iniciei um curso de desenvolvimento de game, retomando a realização de um sonho. Estou ainda em 30% do curso. O problema que estou enfrentando refere-se ao clone do Mario. Quando pus a classe menu e configurei, a tela aparece, mas ao dar “Enter”, o jogo para em um bag. Somente “Sair” está funcionando. O problema é descrito como segue:

Exception in thread “Thread-1” java.lang.NullPointerException: Cannot invoke “java.util.List.size()” because “com.estudos.main.Game.enemies” is null

O que fazer?

Link para acesso ao projeto:

https://drive.google.com/file/d/1JxodNGo9_zhBMPtg36t1s-XTtzYhYG3f/view?usp=sharing

Estou utilizando o Eclipse IDE

Cara bota o seu código…a sua variavel “enemies” está vazia por isto deu o erro.

Os erros aparem na classe Game. Vou postar o código completo desta classe.
package com.estudos.main;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import javax.swing.JFrame;

import com.estudos.entities.Enemy;
import com.estudos.entities.Entity;
import com.estudos.entities.Player;
import com.estudos.graficos.Spritesheet;
import com.estudos.graficos.UI;
import com.estudos.world.World;

public class Game extends Canvas implements Runnable, KeyListener, MouseListener, MouseMotionListener {

private static final long serialVersionUID = 1L;
public static JFrame frame;
private Thread thread;
private boolean isRunning = true;
public static final int WIDTH = 360;
public static final int HEIGHT = 160;
public static final int SCALE = 3;

private int CUR_LEVEL = 1, MAX_LEVEL = 3;
private BufferedImage image;
public static List<Entity> entities;
public static List<Enemy> enemies;
public static Spritesheet spritesheet;
public static World world;
public static Player player;
public static Random rand;

public UI ui;
public int xx, yy;
public static String gameState = "MENU";
private boolean showMessageGameOver = true;
private int framesGameOver = 0;
private boolean restartGame = false;
public boolean saveGame;
public Menu menu;

public Game() {

	Sound.musicBackground.loop();
	rand = new Random();
	addKeyListener(this);
	addMouseListener(this);
	addMouseMotionListener(this);
	setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
	initFrame();
	image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);

	// Inicializando objetos.

	spritesheet = new Spritesheet("/spritesheet.png");
	entities = new ArrayList<Entity>();
	player = new Player(WIDTH / 2 - 80, HEIGHT / 2, 16, 16, 1.4, Entity.PLAYER_SPRITE_RIGHT[0]);
	world = new World("/level1.png");
	ui = new UI();

	entities.add(player);
	

	menu = new Menu();
}

public void initFrame() {
	frame = new JFrame("Super Kanga");
	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);
	isRunning = true;
	thread.start();
}

public synchronized void stop() {
	isRunning = false;
	try {
		thread.join();
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
}

public static void main(String args[]) {
	Game game = new Game();
	game.start();
}
/*
 * public void tick() { if (gameState == "NORMAL") { this.restartGame = false;
 * for (int i = 0; i < entities.size(); i++) { Entity e = entities.get(i);
 * e.tick();
 * 
 */

public void tick() throws IOException {
	if (gameState == "NORMAL") {
		xx++;

		if (this.saveGame) {
			this.saveGame = false;
			String[] opt1 = { "level" };
			int[] opt2 = { this.CUR_LEVEL };
			Menu.saveGame(opt1, opt2, 10);
			System.out.println("Jogo Salvo!");
		}
		this.restartGame = false;
		for (int i = 0; i < entities.size(); i++) {
			Entity e = entities.get(i);
			e.tick();
		}

		if (enemies.size() == 0) {
			//System.out.println("Próximo level!");
			// Avançar para o próximo level!
			CUR_LEVEL++;
			if (CUR_LEVEL > MAX_LEVEL) {
				CUR_LEVEL = 1;
			}
			String newWorld = "level" + CUR_LEVEL + ".png";
			// System.out.println(newWorld);
			World.restartGame(newWorld);
		}
	} else if (gameState == "GAME_OVER") {
		this.framesGameOver++;
		if (this.framesGameOver == 30) {
			this.framesGameOver = 0;
			if (this.showMessageGameOver)
				this.showMessageGameOver = false;
			else
				this.showMessageGameOver = true;
		}

		if (restartGame) {
			this.restartGame = false;
			Game.gameState = "NORMAL";
			CUR_LEVEL = 1;
			String newWorld = "level" + CUR_LEVEL + ".png";
			// System.out.println(newWorld);
			World.restartGame(newWorld);

		}
	} else if (gameState == "MENU") {
		player.updateCamera();
		menu.tick();
	}
}

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

	/* Renderização do jogo */
	// Graphics2D g2 = (Graphics2D) g;

	world.render(g);
	Collections.sort(entities, Entity.nodeSorter);
	for (int i = 0; i < entities.size(); i++) {
		Entity e = entities.get(i);
		e.render(g);
	}

	/***/

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

	g.setFont(new Font("Open Sans Extrabold", Font.BOLD, 18));
	g.setColor(Color.WHITE);

	if (gameState == "GAME_OVER") {
		Graphics2D g2 = (Graphics2D) g;
		g2.setColor(new Color(0, 0, 0, 100));
		g2.fillRect(0, 0, WIDTH * SCALE, HEIGHT * SCALE);
		g.setFont(new Font("arial", Font.BOLD, 36));
		g.setColor(Color.white);
		g.drawString("Game Over", (WIDTH * SCALE) / 2 - 90, (HEIGHT * SCALE) / 2 - 20);
		g.setFont(new Font("Copperplate Gothic Light", Font.BOLD, 32));
		if (showMessageGameOver)
			g.drawString("Pressione Enter para reiniciar!", (WIDTH * SCALE) / 2 - 230, (HEIGHT * SCALE) / 2 + 40);

	} else if (gameState == "MENU") {
		menu.render(g);

		ui.render(g);
		bs.show();

	}
}

public void run() {
	long lastTime = System.nanoTime();
	double amountOfTicks = 60.0;
	double ns = 1000000000 / amountOfTicks;
	double delta = 0;
	int frames = 0;
	double timer = System.currentTimeMillis();
	requestFocus();
	while (isRunning) {
		long now = System.nanoTime();
		delta += (now - lastTime) / ns;
		lastTime = now;
		if (delta >= 1) {
			try {
				tick();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			render();
			frames++;
			delta--;
		}

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

	}

	stop();
}

@Override
public void keyPressed(KeyEvent e) {

	if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
		player.right = true;

	}

	else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
		player.left = true;

	}

	if (e.getKeyCode() == KeyEvent.VK_SPACE) {
		player.jump = true;
	}

	if (e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_W) {
		player.jump = true;
		if (gameState == "MENU") {
			menu.up = true;
		}
	} else if (e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_S) {
		player.jump = true;
		if (gameState == "MENU") {
			menu.down = true;
		}
	}

	if (e.getKeyCode() == KeyEvent.VK_ENTER) {
		this.restartGame = true;
		if (gameState == "MENU") {
			menu.enter = true;
		}

	}
	if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
		gameState = "MENU";
		Menu.pause = true;
	}

	if (e.getKeyCode() == KeyEvent.VK_SPACE) {
		if (Game.gameState == "NORMAL")
			this.saveGame = true;
	}

}

@Override
public void keyReleased(KeyEvent e) {

	if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
		player.right = false;
	}

	else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
		player.left = false;

	}
}

@Override
public void keyTyped(KeyEvent e) {
	// TODO Auto-generated method stub

}

@Override
public void mouseClicked(MouseEvent arg0) {
	// TODO Auto-generated method stub

}

@Override
public void mouseEntered(MouseEvent arg0) {
	// TODO Auto-generated method stub

}

@Override
public void mouseExited(MouseEvent arg0) {
	// TODO Auto-generated method stub

}

@Override
public void mousePressed(MouseEvent e) {
}

@Override
public void mouseReleased(MouseEvent arg0) {
	// TODO Auto-generated method stub

}

@Override
public void mouseDragged(MouseEvent arg0) {
	// TODO Auto-generated method stub

}

@Override
public void mouseMoved(MouseEvent e) {

}

}

Os erros aparecem em: at com.estudos.main.
Game.tick(Game.java:137)
at com.estudos.main.Game.run(Game.java:236)

Você não inicializou a lista enemies.
enemies = new ArrayList<>();

Olá! Valeu, RoinujNosde.
Coloquei agora. O erro desapareceu, mas a imagem não inicia o jogo. Ela fica congelada.

Aí já teria que fazer um debug.
Você suspeita de algum método?

Creio que seja com relação ao “inimigo” .Não estou conseguindo fazer o inimigo aparecer na tela, mesmo antes de colocar a tela de menu.

Fiz este comando para o inimigo, conforme a posição na spritesheet, na classe World:

}else if(pixelAtual == 0xFFFF0000) {
					//Inimigo
					Enemy enemy = new Enemy(xx*16,yy*16,16,16,1.4,Game.spritesheet.getSprite(128,0,16,16));
					Game.entities.add(enemy);

Ficou assim na classe World:
package com.estudos.world;

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;

import javax.imageio.ImageIO;

import com.estudos.entities.Enemy;
import com.estudos.entities.Entity;
import com.estudos.entities.Moeda;
import com.estudos.entities.Player;
import com.estudos.graficos.Spritesheet;
import com.estudos.main.Game;

public class World {

public static Tile[] tiles;
public static int WIDTH,HEIGHT;
public static final int TILE_SIZE = 16;




public World(String path){
	try {
		BufferedImage map = ImageIO.read(getClass().getResource(path));
		int[] pixels = new int[map.getWidth() * map.getHeight()];
		WIDTH = map.getWidth();
		HEIGHT = map.getHeight();
		tiles = new Tile[map.getWidth() * map.getHeight()];
		map.getRGB(0, 0, map.getWidth(), map.getHeight(),pixels, 0, map.getWidth());
		for(int xx = 0; xx < map.getWidth(); xx++){
			for(int yy = 0; yy < map.getHeight(); yy++){
				int pixelAtual = pixels[xx + (yy * map.getWidth())];
				tiles[xx + (yy * WIDTH)] = new FloorTile(xx*16,yy*16,Tile.TILE_FLOOR);
				if(pixelAtual == 0xFF000000) {
					tiles[xx + (yy * WIDTH)] = new FloorTile(xx*16,yy*16,Tile.TILE_FLOOR);
				} else if (pixelAtual == 0xFFD9F0FF) {
					tiles[xx + (yy * WIDTH)] = new FloorTile(xx * 16, yy * 16, Entity.NUVEM);		
				}else if(pixelAtual == 0xFFFFFFFF) {
					tiles[xx + (yy * WIDTH)] = new WallTile(xx*16,yy*16,Tile.TILE_WALL);
					if(yy-1 >= 0 && pixels[xx+( (yy-1) * map.getWidth())] == 0xFFFFFFFF) {
						tiles[xx + (yy * WIDTH)] = new WallTile(xx*16,yy*16,Game.spritesheet.getSprite(32, 0, 16, 16));
					}
				}else if(pixelAtual == 0xFF0026FF) {
					//Jogador
					Game.player.setX(xx*16);
					Game.player.setY(yy*16);		
				}else if(pixelAtual == 0xFFFF0000) {
					//Inimigo
					Enemy enemy = new Enemy(xx*16,yy*16,16,16,1.4,Game.spritesheet.getSprite(128,0,16,16));
					Game.entities.add(enemy);
				
				}else if(pixelAtual == 0xFFFFC311) {
					//Moeda
					Moeda moeda = new Moeda(xx*16,yy*16,16,16,1,Game.spritesheet.getSprite(0,16,16,16));
					Game.entities.add(moeda);
					Player.maxCoins++;
				
				}
			}
		}
	} catch (IOException e) {
		e.printStackTrace();
	}
}

public static boolean isFree(int xnext,int ynext){
	
	int x1 = xnext / TILE_SIZE;
	int y1 = ynext / TILE_SIZE;
	
	int x2 = (xnext+TILE_SIZE-1) / TILE_SIZE;
	int y2 = ynext / TILE_SIZE;
	
	int x3 = xnext / TILE_SIZE;
	int y3 = (ynext+TILE_SIZE-1) / TILE_SIZE;
	
	int x4 = (xnext+TILE_SIZE-1) / TILE_SIZE;
	int y4 = (ynext+TILE_SIZE-1) / TILE_SIZE;
	
	return !((tiles[x1 + (y1*World.WIDTH)] instanceof WallTile) ||
			(tiles[x2 + (y2*World.WIDTH)] instanceof WallTile) ||
			(tiles[x3 + (y3*World.WIDTH)] instanceof WallTile) ||
			(tiles[x4 + (y4*World.WIDTH)] instanceof WallTile));
}

public static void restartGame(String level){
	//TODO: Aplicar método para reiniciar o jogo corretamente.
	
	
	Game.entities.clear();
	Game.entities.clear();
	Game.entities = new ArrayList<Entity>();
	
	Game.spritesheet = new Spritesheet("/spritesheet.png");
	Game.entities.add(Game.player);
	Game.world = new World("/"+level);
	return;
}

public void render(Graphics g){
	int xstart = Camera.x >> 4;
	int ystart = Camera.y >> 4;
	
	int xfinal = xstart + (Game.WIDTH >> 4);
	int yfinal = ystart + (Game.HEIGHT >> 4);
	
	for(int xx = xstart; xx <= xfinal; xx++) {
		for(int yy = ystart; yy <= yfinal; yy++) {
			if(xx < 0 || yy < 0 || xx >= WIDTH || yy >= HEIGHT)
				continue;
			Tile tile = tiles[xx + (yy*WIDTH)];
			tile.render(g);
		}
	}
}

}

Olá! O problema é na renderização:

public void render(Graphics g) {

	if (right)
		sprite = Entity.ENEMY_RIGHT;
	else if (left)
		sprite = Entity.ENEMY_LEFT;

	super.render(g);

}

}

Quando retiro, o enemy do jogo surge.

Esse método é de qual classe?

da classe Enemy.

Mas também dei uma melhorada no sprite dentro da spritesheet.

Manda o código da classe Entity e da Enemy.

Agora estou com problemas no som do Background em loop. Está sem bug, mas o som não roda…apenas o das moedas e o choque entre o jogador e o enemy.

public Game() {

	Sound.musicBackground.loop();
	rand = new Random();
	
	addKeyListener(this);
	addMouseListener(this);
	addMouseMotionListener(this);
	setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
	initFrame();
	image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);

	// Inicializando objetos.

	spritesheet = new Spritesheet("/spritesheet.png");
	
	entities = new ArrayList<Entity>();
	
	player = new Player(WIDTH / 2 - 120, HEIGHT / 2, 16, 16, 1, Entity.PLAYER_SPRITE_RIGHT[0]);
	
	
	world = new World("/level1.png");
	ui = new UI();
	
	
	entities.add(player);
	
}

Classe Entity completo:

package com.estudos.entities;

import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.Comparator;
import java.util.List;
import java.util.Random;

import com.estudos.main.Game;
import com.estudos.world.Camera;
import com.estudos.world.Node;
import com.estudos.world.Vector2i;
import com.estudos.world.World;

public class Entity {

//public static final BufferedImage MOEDA_SPRITE = null;

public static BufferedImage NUVEM = Game.spritesheet.getSprite(48, 0, 16, 16);

public static BufferedImage[] PLAYER_SPRITE_RIGHT  = {Game.spritesheet.getSprite(4*16, 0,16,16), Game.spritesheet.getSprite(5*16, 0,16,16)};	
public static BufferedImage [] PLAYER_SPRITE_LEFT  = {Game.spritesheet.getSprite(6*16, 0,16,16), Game.spritesheet.getSprite(7*16, 0,16,16)};

public static BufferedImage ENEMY_RIGHT  = Game.spritesheet.getSprite(128, 0, 16,16);
public static BufferedImage ENEMY_LEFT  = Game.spritesheet.getSprite(144, 0, 16,16);

public static BufferedImage MOEDA_EN = Game.spritesheet.getSprite(16,0,16,16);



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

public int depth;

protected List<Node> path;

public boolean debug = false;

protected BufferedImage sprite;

public static Random rand = new Random();

public Entity(double x,double y,int width,int height,double speed,BufferedImage sprite){
	this.x = x;
	this.y = y;
	this.speed = speed;
	this.width = width;
	this.height = height;
	this.sprite = sprite;
}

public static Comparator<Entity> nodeSorter = new Comparator<Entity>() {
	
	@Override
	public int compare(Entity n0,Entity n1) {
		if(n1.depth < n0.depth)
			return +1;
		if(n1.depth > n0.depth)
			return -1;
		return 0;
	}
	
};


public void updateCamera() {
	Camera.x = Camera.clamp(this.getX() - (Game.WIDTH/2),0,World.WIDTH*16 - Game.WIDTH);
	Camera.y = Camera.clamp(this.getY() - (Game.HEIGHT/2),0,World.HEIGHT*16 - Game.HEIGHT);
}

public void setX(int newX) {
	this.x = newX;
}

public void setY(int newY) {
	this.y = newY;
}

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

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

public int getWidth() {
	return this.width;
}

public int getHeight() {
	return this.height;
}

public void tick(){}

public double calculateDistance(int x1,int y1,int x2,int y2) {
	return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}


public void followPath(List<Node> path) {
	if(path != null) {
		if(path.size() > 0) {
			Vector2i target = path.get(path.size() - 1).tile;
			//xprev = x;
			//yprev = y;
			if(x < target.x * 16) {
				x++;
			}else if(x > target.x * 16) {
				x--;
			}
			
			if(y < target.y * 16) {
				y++;
			}else if(y > target.y * 16) {
				y--;
			}
			
			if(x == target.x * 16 && y == target.y * 16) {
				path.remove(path.size() - 1);
			}
			
		}
	}
}

public static boolean isColidding(Entity e1,Entity e2){
	Rectangle e1Mask = new Rectangle(e1.getX(),e1.getY(),e1.getWidth(),e1.getHeight());
	Rectangle e2Mask = new Rectangle(e2.getX(),e2.getY(),e2.getWidth(),e2.getHeight());
	
	return e1Mask.intersects(e2Mask);
}

public void render(Graphics g) {
	g.drawImage(sprite,this.getX() - Camera.x,this.getY() - Camera.y,null);
	//g.setColor(Color.red);
	//g.fillRect(this.getX() + maskx - Camera.x,this.getY() + masky - Camera.y,mwidth,mheight);
}

}

e

Enemy completo:

package com.estudos.entities;

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

import com.estudos.world.World;

public class Enemy extends Entity {

public boolean right = true, left = false;

public int vida = 3;

public Enemy(double x, double y, int width, int height, double speed, BufferedImage sprite) {
	super(x, y, width, height, speed, sprite);
}

public void tick() {
	if (World.isFree((int) x, (int) (y + 1))) {
		y += 1;
	} else {

		if (right) {
			if (World.isFree((int) (x + speed), (int) y)) {
				x += speed;
				if (World.isFree((int) (x + 16), (int) y + 1)) {
					right = false;
					left = true;
				}
			} else {
				right = false;
				left = true;
			}
		}

		if (left) {
			if (World.isFree((int) (x - speed), (int) y)) {
				x -= speed;
				if (World.isFree((int) (x - 16), (int) y + 1)) {
					right = true;
					left = false;
				}
			} else {
				right = true;
				left = false;
			}
		}
	}

}

public void render(Graphics g) {
	
	

	if (right)
		sprite = Entity.ENEMY_RIGHT;
	else if (left)
		sprite = Entity.ENEMY_LEFT;
	
	

	super.render(g);

}

}