En el ejemplo, tenemos una nave espacial y aliens. Podemos mover la nave espacial usando las teclas del cursor. Los misiles que se lanzan con la tecla de la barra espaciadora.
El código que pueden compartir todos los sprites (una nave, un extraterrestre y un misil) se coloca en la clase Sprite.
Sprite.java
package com.codbas; import java.awt.Image; import java.awt.Rectangle; import javax.swing.ImageIcon; public class Sprite { protected int x; protected int y; protected int width; protected int height; protected boolean visible; protected Image image; public Sprite(int x, int y) { this.x = x; this.y = y; visible = true; } protected void getImageDimensions() { width = image.getWidth(null); height = image.getHeight(null); } protected void loadImage(String imageName) { ImageIcon ii = new ImageIcon(imageName); image = ii.getImage(); } public Image getImage() { return image; } public int getX() { return x; } public int getY() { return y; } public boolean isVisible() { return visible; } public void setVisible(Boolean visible) { this.visible = visible; } public Rectangle getBounds() { return new Rectangle(x, y, width, height); } }
El método getBounds() devuelve el rectángulo delimitador de la imagen del sprite. Necesitamos los límites en la detección de colisiones.
public Rectangle getBounds() { return new Rectangle(x, y, width, height); }
Esta clase representa una nave espacial.
SpaceShip.java
package com.codbas; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.List; public class SpaceShip extends Sprite { private int dx; private int dy; private List<Missile> missiles; public SpaceShip(int x, int y) { super(x, y); initCraft(); } private void initCraft() { missiles = new ArrayList<>(); loadImage("src/resources/spaceship.png"); getImageDimensions(); } public void move() { x += dx; y += dy; if (x < 1) { x = 1; } if (y < 1) { y = 1; } } public List<Missile> getMissiles() { return missiles; } public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); if (key == KeyEvent.VK_SPACE) { fire(); } if (key == KeyEvent.VK_LEFT) { dx = -1; } if (key == KeyEvent.VK_RIGHT) { dx = 1; } if (key == KeyEvent.VK_UP) { dy = -1; } if (key == KeyEvent.VK_DOWN) { dy = 1; } } public void fire() { missiles.add(new Missile(x + width, y + height / 2)); } public void keyReleased(KeyEvent e) { int key = e.getKeyCode(); if (key == KeyEvent.VK_LEFT) { dx = 0; } if (key == KeyEvent.VK_RIGHT) { dx = 0; } if (key == KeyEvent.VK_UP) { dy = 0; } if (key == KeyEvent.VK_DOWN) { dy = 0; } } }
Todos los misiles disparados por la nave espacial se almacenan en la lista de misiles.
private List<Missile> missiles;
Cuando disparamos un misil, se agrega un nuevo objeto de Misil a la lista de misiles. Se retiene en la lista hasta que colisiona con un extraterrestre o sale por la ventana.
public void fire() { missiles.add(new Missile(x + width, y + height / 2)); }
Esta es la clase Lienzo
Lienzo.java
package com.codbas; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.List; import javax.swing.JPanel; import javax.swing.Timer; public class Lienzo extends JPanel implements ActionListener { private Timer timer; private SpaceShip spaceship; private List<Alien> aliens; private boolean ingame; private final int ICRAFT_X = 40; private final int ICRAFT_Y = 60; private final int B_WIDTH = 400; private final int B_HEIGHT = 300; private final int DELAY = 15; private final int[][] pos = { {2380, 29}, {2500, 59}, {1380, 89}, {780, 109}, {580, 139}, {680, 239}, {790, 259}, {760, 50}, {790, 150}, {980, 209}, {560, 45}, {510, 70}, {930, 159}, {590, 80}, {530, 60}, {940, 59}, {990, 30}, {920, 200}, {900, 259}, {660, 50}, {540, 90}, {810, 220}, {860, 20}, {740, 180}, {820, 128}, {490, 170}, {700, 30} }; public Lienzo() { initBoard(); } private void initBoard() { addKeyListener(new TAdapter()); setFocusable(true); setBackground(Color.BLACK); ingame = true; setPreferredSize(new Dimension(B_WIDTH, B_HEIGHT)); spaceship = new SpaceShip(ICRAFT_X, ICRAFT_Y); initAliens(); timer = new Timer(DELAY, this); timer.start(); } public void initAliens() { aliens = new ArrayList<>(); for (int[] p : pos) { aliens.add(new Alien(p[0], p[1])); } } @Override public void paintComponent(Graphics g) { super.paintComponent(g); if (ingame) { drawObjects(g); } else { drawGameOver(g); } Toolkit.getDefaultToolkit().sync(); } private void drawObjects(Graphics g) { if (spaceship.isVisible()) { g.drawImage(spaceship.getImage(), spaceship.getX(), spaceship.getY(), this); } List<Missile> ms = spaceship.getMissiles(); for (Missile missile : ms) { if (missile.isVisible()) { g.drawImage(missile.getImage(), missile.getX(), missile.getY(), this); } } for (Alien alien : aliens) { if (alien.isVisible()) { g.drawImage(alien.getImage(), alien.getX(), alien.getY(), this); } } g.setColor(Color.WHITE); g.drawString("Aliens left: " + aliens.size(), 5, 15); } private void drawGameOver(Graphics g) { String msg = "Game Over"; Font small = new Font("Helvetica", Font.BOLD, 14); FontMetrics fm = getFontMetrics(small); g.setColor(Color.white); g.setFont(small); g.drawString(msg, (B_WIDTH - fm.stringWidth(msg)) / 2, B_HEIGHT / 2); } @Override public void actionPerformed(ActionEvent e) { inGame(); updateShip(); updateMissiles(); updateAliens(); checkCollisions(); repaint(); } private void inGame() { if (!ingame) { timer.stop(); } } private void updateShip() { if (spaceship.isVisible()) { spaceship.move(); } } private void updateMissiles() { List<Missile> ms = spaceship.getMissiles(); for (int i = 0; i < ms.size(); i++) { Missile m = ms.get(i); if (m.isVisible()) { m.move(); } else { ms.remove(i); } } } private void updateAliens() { if (aliens.isEmpty()) { ingame = false; return; } for (int i = 0; i < aliens.size(); i++) { Alien a = aliens.get(i); if (a.isVisible()) { a.move(); } else { aliens.remove(i); } } } public void checkCollisions() { Rectangle r3 = spaceship.getBounds(); for (Alien alien : aliens) { Rectangle r2 = alien.getBounds(); if (r3.intersects(r2)) { spaceship.setVisible(false); alien.setVisible(false); ingame = false; } } List<Missile> ms = spaceship.getMissiles(); for (Missile m : ms) { Rectangle r1 = m.getBounds(); for (Alien alien : aliens) { Rectangle r2 = alien.getBounds(); if (r1.intersects(r2)) { m.setVisible(false); alien.setVisible(false); } } } } private class TAdapter extends KeyAdapter { @Override public void keyReleased(KeyEvent e) { spaceship.keyReleased(e); } @Override public void keyPressed(KeyEvent e) { spaceship.keyPressed(e); } } }
Estas son las posiciones iniciales de las naves alienígenas.
private final int[][] pos = { {2380, 29}, {2500, 59}, {1380, 89}, {780, 109}, {580, 139}, {680, 239}, {790, 259}, {760, 50}, {790, 150}, {980, 209}, {560, 45}, {510, 70}, {930, 159}, {590, 80}, {530, 60}, {940, 59}, {990, 30}, {920, 200}, {900, 259}, {660, 50}, {540, 90}, {810, 220}, {860, 20}, {740, 180}, {820, 128}, {490, 170}, {700, 30} };
El método initAliens() crea una lista de objetos alien. Los alien toman sus posiciones iniciales de la matriz pos.
public void initAliens() { aliens = new ArrayList<>(); for (int[] p : pos) { aliens.add(new Alien(p[0], p[1])); } }
Dentro del método paintComponent(), dibujamos sprites del juego o escribimos el mensaje sobre el juego. Esto depende de la variable del juego.
@Override public void paintComponent(Graphics g) { super.paintComponent(g); if (ingame) { drawObjects(g); } else { drawGameOver(g); } Toolkit.getDefaultToolkit().sync(); }
El método drawObjects() dibuja sprites del juego en la ventana. Primero, dibujamos el sprite de la nave.
private void drawObjects(Graphics g) { if (spaceship.isVisible()) { g.drawImage(spaceship.getImage(), spaceship.getX(), spaceship.getY(), this); }
........ }
En este ciclo dibujamos todos los extraterrestres; solo se dibujan si no han sido destruidos previamente. Esto se verifica mediante el método isVisible().
for (Alien alien : aliens) { if (alien.isVisible()) { g.drawImage(alien.getImage(), alien.getX(), alien.getY(), this); } }
En la esquina superior izquierda de la ventana, dibujamos cuántos alienígenas quedan.
g.setColor(Color.WHITE); g.drawString("Aliens left: " + aliens.size(), 5, 15);
El metodo drawGameOver() dibuja un mensaje "GAME OVER" en el medio de la ventana. El mensaje se muestra al final del juego, ya sea cuando destruimos todas las naves alienígenas o cuando chocamos con una de ellas.
private void drawGameOver(Graphics g) { String msg = "Game Over"; Font small = new Font("Helvetica", Font.BOLD, 14); FontMetrics fm = getFontMetrics(small); g.setColor(Color.white); g.setFont(small); g.drawString(msg, (B_WIDTH - fm.stringWidth(msg)) / 2, B_HEIGHT / 2); }
Cada evento de acción representa un ciclo de juego. La lógica del juego se divide en métodos específicos. Por ejemplo, updateMissiles() mueve todos los misiles disponibles.
@Override public void actionPerformed(ActionEvent e) { inGame(); updateShip(); updateMissiles(); updateAliens(); checkCollisions(); repaint(); }
Dentro del método updateAliens(), primero verificamos si quedan objetos extraños en la lista de extraterrestres. El juego termina si la lista está vacía. Si no está vacío, revisamos la lista y movemos todos sus elementos. Los alienígenas destruidos se eliminan de la lista.
private void updateAliens() { if (aliens.isEmpty()) { ingame = false; return; } for (int i = 0; i < aliens.size(); i++) { Alien a = aliens.get(i); if (a.isVisible()) { a.move(); } else { aliens.remove(i); } } }
El método checkCollisions() busca posibles colisiones. Primero, verificamos si el la nave espacial choca con alguno de los objetos extraños. Obtenemos los rectángulos de los objetos con el método getBounds(). El método intersects() verifica si los dos rectángulos se cruzan.
public void checkCollisions() { Rectangle r3 = spaceship.getBounds(); for (Alien alien : aliens) { Rectangle r2 = alien.getBounds(); if (r3.intersects(r2)) { spaceship.setVisible(false); alien.setVisible(false); ingame = false; } } ..... }
Este código verifica las colisiones entre misiles y alienígenas.
List<Missile> ms = spaceship.getMissiles(); for (Missile m : ms) { Rectangle r1 = m.getBounds(); for (Alien alien : aliens) { Rectangle r2 = alien.getBounds(); if (r1.intersects(r2)) { m.setVisible(false); alien.setVisible(false); } } }
Los aliens regresan a la pantalla en el lado derecho después de haber desaparecido a la izquierda.Esto se implementa en la clase Alien.
Alien.java
package com.codbas; public class Alien extends Sprite { private final int INITIAL_X = 400; public Alien(int x, int y) { super(x, y); initAlien(); } private void initAlien() { loadImage("src/resources/alien.png"); getImageDimensions(); } public void move() { if (x < 0) { x = INITIAL_X; } x -= 1; } }
Esta es la clase missile.
Missile.java
package com.codbas; public class Missile extends Sprite { private final int BOARD_WIDTH = 390; private final int MISSILE_SPEED = 2; public Missile(int x, int y) { super(x, y); initMissile(); } private void initMissile() { loadImage("src/resources/missile.png"); getImageDimensions(); } public void move() { x += MISSILE_SPEED; if (x > BOARD_WIDTH) visible = false; } }
Los misiles se mueven en una sola dirección. Desaparecen después de llegar al borde derecho de la ventana. Finalmente, asi se ve el programa: