En este artículo se explicará como desarrollar un videojuego sencillo utilizando el API de Java Swing, este artículo asume que tienes conocimiento básicos de java y conocimientos sobre Programación Orientada a Objetos. Para el desarrollo se utilizará el IDE Netbeans 7.3.
Lo primero que tenemos que hacer es crear un proyecto dentro de nuestro entorno de desarrollo, para eso seleccionamos la opción "New Proyect" de NetBeans como se muestra en la siguiente imagen.
Seleccionaremos la opción "Java --> Java Application" y hacemos click en "Next".
Colocamos el nombre del proyecto y des habilitamos el checkbox "Create Main Class" y hacemos click en "Finish".
Lo siguiente que haremos será ir a la ruta en la cual se creo el proyecto, esto es en la carpeta "Documents\NetBeansProjects\GameGeeksJavaMexico" y crearemos una carpeta llamada "images", en esta carpeta colocaremos las imágenes que van a aparecer en nuestro juego, se recomienda que estén en formato ".png", un buen sitio donde pueden encontrar algunas imágenes para hacer su juego es en la página http://www.iconarchive.com/ .
A continuación dentro de NetBeans crearemos 3 paquetes uno llamado modelo, util y vista. Dentro de estos paquetes colocaremos todas las clases que necesitaremos a lo largo del juego.
Dentro del paquete modelo voy a crear una clase abstracta llamada Sprite.
package modelo;
import java.awt.Graphics;
public abstract class Sprite {
protected int x;
protected int y;
protected int width;
protected int heigth;
public Sprite() {
}
public Sprite(int x, int y, int width, int heigth) {
this.x = x;
this.y = y;
this.width = width;
this.heigth = heigth;
}
public abstract void draw(Graphics g);
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeigth() {
return heigth;
}
public void setHeigth(int heigth) {
this.heigth = heigth;
}
@Override
public String toString() {
return "Sprite{" + "x=" + x + ", y=" + y + ", width=" + width + ", heigth=" + heigth + '}';
}
}
La clase Sprite dentro de nuestra aplicación nos servirá para representar a los objetos "Dibujables" dentro de nuestra aplicación, por este motivo la clase Sprite sabe que se tiene que dibujar pero no sabe como, esto dependerá de que tipo de objeto sea, por esto el método draw es un método abstracto y la clase esta marcada como abstracta.
A continuación crearemos una enumeración dentro del paquete model llamada TipoDeMalo.
A continuación crearemos una enumeración dentro del paquete model llamada TipoDeMalo.
package modelo;
public enum TipoDeMalo {
PULPO("images/zombie.png", 2), DIABLO("images/zombie2.png", 8), ZOMBIE("images/zombie3.png", 5);
private String path;
private int velocity;
private TipoDeMalo(String path, int velocity) {
this.path = path;
this.velocity = velocity;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public int getVelocity() {
return velocity;
}
public void setVelocity(int velocity) {
this.velocity = velocity;
}
}
Los valores de la enumeración son PULPO,DIABLO y ZOMBIE, por cada uno se tiene su imagen a cargar y un número que representa una velocidad, esto es porque los malos van a ir cayendo a velocidades diferentes dependiendo de que tipo de malo sean.
De igual modo dentro del paquete modelo crearemos una clase llamada Malo.
package modelo;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.ImageIcon;
public class Malo extends Sprite {
private TipoDeMalo zombieType;
private static final TipoDeMalo ZT[]={TipoDeMalo.DIABLO, TipoDeMalo.PULPO, TipoDeMalo.ZOMBIE};
public Malo(int x, int y, int width, int heigth) {
super(x, y, width, heigth);
zombieType=ZT[((int)(Math.random()*3))];
}
public Malo() {
}
@Override
public void draw(Graphics g) {
Image image = new ImageIcon(zombieType.getPath()).getImage();
g.drawImage(image, x, y, width,heigth,null);
y+=zombieType.getVelocity();
}
public TipoDeMalo getTipoDeMalo() {
return zombieType;
}
public void setTipoDeMalo(TipoDeMalo zombieType) {
this.zombieType = zombieType;
}
}
Como se puede observar la clase Malo hereda de la clase Sprite, por tal motivo debe implementar el método draw en el cual se debe implementar lo que queremos que se haga cuando se dibuje, en este caso el comportamiento deseado es que se aumente la posición en "y" dependiendo de la velocidad para así ver como se desplaza hacia abajo el personaje. Otra cosa importante es que se tiene un atributo de tipo TipoDeMalo para así poder asignar de manera aleatoria que tipo de malo queremos que se genere, en este ejemplo pueden ser DIABLO, PULPO o ZOMBIE.
Ahora vamos a crear a un héroe para nuestro juego, para esto crearemos otra clase llamada Heroe dentro del paquete modelo, esta clase también se podrá dibujar en la pantalla, por tal motivo debe heredar de la clase Sprite.
package modelo;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.ImageIcon;
public class Heroe extends Sprite {
public Heroe() {
}
public Heroe(int x, int y, int width, int heigth) {
super(x, y, width, heigth);
}
@Override
public void draw(Graphics g) {
Image image = new ImageIcon("images/baby.png").getImage();
g.drawImage(image, x, y, width, heigth, null);
}
}
Como se puede observar el método dibujar de la clase Heroe solo se encarga de dibujar la imagen del héroe ya que los movimientos de las coordenadas "x" y "y" se realizarán en el momento que se genere un evento, esto se explicará con más detalle más adelante.
Para terminar de hacer nuestras clases de modelo, crearemos una clase llamada Bala.
package modelo;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.ImageIcon;
public class Bala extends Sprite {
public static final int SIZE = 30;
public Bala() {
}
public Bala(int x, int y, int width, int heigth) {
super(x, y, width, heigth);
}
@Override
public void draw(Graphics g) {
Image image = new ImageIcon("images/bean.png").getImage();
g.drawImage(image, x, y, width, heigth, null);
y -= 30;
}
}
Como las balas también se van a dibujar dentro de nuestro escenario también deben de heredar de nuestra clase Sprite, como se puede observar el método dibujar solo reduce la posición en "y" esto es porque las balas van hacia arriba, "también se pudo crear una enumeración con tipos de balas para dependiendo del tipo dispare más rápido".
Con estas sencillas clases de modelo que creamos ya tenemos un buen avance para nuestro juego, ahora lo que resta es crear un manejador de eventos y una pantalla en la cual se van a dibujar nuestros malos nuestras balas y nuestro héroe. Primero crearemos nuestra clase ManejadorDeEventos en el paquete util.
package util;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.List;
import modelo.Bala;
import modelo.Sprite;
public class ManejadorDeEventos extends KeyAdapter implements KeyListener {
private Sprite hero;
private List<Sprite> sprites;
public ManejadorDeEventos(Sprite hero, List<Sprite> sprites) {
this.hero = hero;
this.sprites = sprites;
}
@Override
public void keyPressed(KeyEvent e) {
super.keyPressed(e);
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
hero.setX(hero.getX() - 50);
break;
case KeyEvent.VK_RIGHT:
hero.setX(hero.getX() + 50);
break;
case KeyEvent.VK_UP:
hero.setY(hero.getY() - 50);
break;
case KeyEvent.VK_DOWN:
hero.setY(hero.getY() + 50);
break;
case KeyEvent.VK_SPACE:
Sprite s = new Bala(hero.getX(), hero.getY(), Bala.SIZE, Bala.SIZE);
sprites.add(s);
break;
}
}
}
Esta clase será la encargada de manejar los eventos en el juego, por esto necesita dos atributos, una referencia hacia el héroe del juego y una lista de objetos dibujables en el juego. Como se puede observar estamos heredando de la clase KeyAdapter e implementando la interfaz KeyListener, esto para manejar eventos que provienen desde el teclado, dentro del método keyPressed que es el que se ejecutará en el momento en el que se oprima una tecla estamos validando qué tecla se oprimió, en caso de que sea la flecha izquierda se le restarán 50 a la posición del héroe en el eje "x", en caso de que sea a la derecha se sumará 50 a su posición en "x",en caso de que se oprima la flecha hacia arriba se restará 50 a la posición en "y", en caso de que se oprima la flecha hacia abajo se sumará 50 en la posición en el eje "y" y por último se valida si se oprimió la tecla de espacio se creará una nueva bala con la posición en "x" y en "y" del héroe y con el tamaño definido en la constante SIZE de la clase Bala.
Ahora vamos a ver como quedaría la clase Lienzo que es en la cual se dibujarán los objetos, esta debe ir en el paquete vista.
package vista;
import java.awt.Graphics;
import java.awt.Image;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import modelo.Heroe;
import modelo.Malo;
import modelo.Sprite;
import util.ManejadorDeEventos;
public class Lienzo extends JPanel {
public static final int ANCHO = 370;
public static final int ALTO = 700;
private List<Sprite> sprites;
private Sprite hero;
public Lienzo() {
init();
}
private void init() {
sprites = new ArrayList<Sprite>();
hero = new Heroe(100, 600, 50, 50);
sprites.add(hero);
ManejadorDeEventos me = new ManejadorDeEventos(hero, sprites);
addKeyListener(me);
setFocusable(true);
}
public void agregarMalo() {
int arr[] = {0, 100, 200, 300};
Sprite s3 = new Malo(arr[((int) (Math.random() * arr.length))], 0, 50, 50);
sprites.add(s3);
}
@Override
public void paint(Graphics g) {
super.paint(g);
Image image = new ImageIcon("images/background-game.jpg").getImage();
g.drawImage(image, 0, 0, 410, 700, this);
for (Sprite sprite : sprites) {
sprite.draw(g);
}
}
public void play() throws Exception {
long init = System.currentTimeMillis() + 800;
while (true) {
Thread.sleep(80);
Long time = System.currentTimeMillis();
if (time > init) {
init = System.currentTimeMillis() + 800;
agregarMalo();
}
repaint();
}
}
}
La clase Lienzo es de las clases más importantes dentro del juego, ya que este es el lugar en el cual se van a pintar nuestros personajes, como se puede ver esta clase hereda de la clase JPanel. Dentro de esta clase tenemos como atributos una lista de sprites que es la lista de todos los objetos que se redibujarán y un héroe para nuestro juego, estos atributos son inicializados en el método init, a demás el método init se encarga de asignar el manejador de eventos que creamos y decirle cual es la lista y cual es el héroe de nuestro juego.
El juego inicia llamando al método play, dentro de este método tenemos un ciclo infinito y un pequeño retardo, cada cierto tiempo (800 milisegundos) se va a mandar llamar al método agregarMalo() el cual agrega un malo aleatorio en una posición aleatoria del campo y después redibuja el escenario. En el método paint colocamos el fondo de nuestro juego y redibujamos todos los sprites que están involucrados en el juego, el método paint se invocará de forma implícita cada vez que mandemos llamar al métod repaint().
Por último y para probar nuestro juego vamos a crear una clase llamada Juego.
package vista;
import javax.swing.JFrame;
public class Juego {
public static void main(String[] args) throws Exception {
JFrame jf=new JFrame();
Lienzo l=new Lienzo();
jf.add(l);
jf.setSize(Lienzo.ANCHO,Lienzo.ALTO);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
l.play();
}
}
Dentro de la clase Juego simplemente crearemos un JFrame y un Lienzo, el lienzo lo agregaremos al frame lo haremos visible y llamaremos al método play, veamos como se ve el juego en ejecución.
Como pueden ver si se siguen conceptos de Programación Orientada a Objetos se puede realizar un juego y cualquier otra aplicación de forma muy sencilla, si tienen alguna duda sobre el código no duden en preguntar en la sección de comentarios y esperen los siguientes post's sobre diversos frameworks y herramientas de java.
Coloco adjunto el proyecto en el siguiente archivo GameGeeksJavaMexico.rar
Gracias y espero sea de su utilidad.
--------->>>>>>>>>>>>Oracle Certified Java Programer
--------->>>>>>>>>>>>Oracle Certified Web Components Developer
--------->>>>>>>>>>>>IBM Certified Academic Assosiate DB2.
--------->>>>>>>Twitter: @raidentrance
--------->>>>>>>Contacto:raidentrance@gmail.com
Buenisimooo!!!
ResponderEliminarMuchas gracias :) estén al pendiente porque en estos días subiré como manejar colisiones, sacar puntuaciones y detalles para hacerlo más completo, saludos
EliminarExelente ejemplo y muy sencillo, ahora nos toca capturar colisiones, y porque no puntuaciones :), saludos.
ResponderEliminar