Es momento de revisar un poco el código que tenemos escrito y reordenar un poco, hacerlo un poco más «elegante».
Si vemos en el método paint(), tenemos tres bloques de código que son idénticas; es la iteración que recorren las tres listas (box,weapon y fruit) para ir dibujando en la ventana, si recordamos sabemos que las clases Box, Weapon y Fruit implementan la interface Element; es decir que con muy poquitos cambios podemos mejorar.
En la interface Element incorporaremos dos metodos:
public interface Element {
/** resto del codigo **/
public boolean isVisible();
public BufferedImage getImage();
}
Será necesario renombrar los métodos que devuelven BufferedImage de las clases: Box, Weapon, Fruit y tambien Player, puesto que implementa la interface.
En Player debemos implementar isVisible(),solo hacemos que devuelva true:
public boolean isVisible() {
return true;
}
Dentro de RunnerOne definimos un nuevo método:
private void drawList(ArrayList itemList,Graphics g) {
if(!itemList.isEmpty()) {
Iterator it = itemList.iterator();
while(it.hasNext()) {
Element el = (Element)it.next();
if(el.isVisible()) {
g.drawImage(el.getImage(),el.getX(),el.getY(), null);
}
}
}
}
Si vemos el bloque de código que ejecuta, es similar a lo que tenemos escrito en paint(), cambia el hecho que en lugar de castear el objeto a un Box, Fruit o Weapon lo hacemos a Element; el resto es idéntico.
Por último modificamos paint() para invocar al método drawList() por cada una de las listas que tenemos:
public void paint(Graphics g) {
/** resto del codigo **/
drawList(listFruit, g);
drawList(listBox, g);
drawList(listWeapon, g);
/** resto del codigo **/
}
De esta forma hacemos uso de la interfaz para reutilizar codigo.
Nuestro juego ya tiene un fondo, un personaje (que podemos cambiar), frutas para sumar puntos y cajas.
A continuación incorporaremos una nueva acción a nuestro héroe, podrá disparar afiladas sierras para abrirse paso.
Comenzaremos definiendo una clase llamada Weapon que implemente la interfaz de Element:
package gsampallo;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
public class Weapon implements Element {
public int width = 19;
public int height = 19;
private Point position;
public Weapon(Point initialPosition) {
this.position = initialPosition;
loadImages();
}
private BufferedImage imageWeapon;
private void loadImages() {
try {
imageWeapon = ImageIO.read(new File("image/Traps/Saw/On (19x19).png"));
} catch (Exception e) {
System.err.println("No se pudieron cargar imagenes de Box");
System.err.println(e.getMessage());
}
}
Como se implementa la interfaz Element, debemos definir los siguientes métodos:
public int getX() {
return this.position.x;
}
public int getY() {
return this.position.y;
}
public int getWidth() {
return this.width;
}
public int getHeight() {
return this.height;
}
private boolean visible = true;
public boolean isVisible(){
return visible;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
De paso definimos isVisible() para determinar si es visible o no.
También debemos definir el método updateWeapon() para poder actualizar los parámetros y getImage() para obtener la imagen que será dibujada:
private int imageNumber = 0;
public void updateWeapon(boolean move) {
if(imageNumber < (imageWeapon.getWidth()/width)-1) {
imageNumber++;
} else {
imageNumber=0;
}
position.x = position.x + 3;
visible = visible && (position.x < RunnerOne.FRAME_WIDTH);
}
public BufferedImage getImage() {
int x = imageNumber*width;
return imageWeapon.getSubimage(x, 0, this.width,this.height);
}
En esta ocasión el disparo siempre actualizara su posición; en lugar de disminuir en 1 a position.x, que lo acercaría al limite izquierda de la ventana, sumamos 3 de manera que se desplace hacia la derecha a mayor velocidad.
Al igual que con las frutas y las cajas, utilizaremos un ArrayList para mantener la lista de disparos activos; estos a diferencia de las anteriores serán generados por un evento del jugador: cuando presione la tecla SPACE.
Incorporamos en RunnerOne entonces:
private ArrayList<Weapon> listWeapon;
public RunnerOne() {
/** resto del codigo **/
listWeapon = new ArrayList<Weapon>();
}
Definiremos un método llamado shootWeapons() que será invocado cuando se presione la tecla espacio; de esa manera incorporamos:
public void shootWeapons() {
Weapon weapon = new Weapon(new Point(player.getX(),player.getY()+6));
listWeapon.add(weapon);
}
Toma el punto (x,y+6) como punto de partida para dibujar la sierra en el plano; luego lo incorpora a la lista.
En GameKeys.keyPressed( ) incorporamos:
public class GameKeys extends KeyAdapter {
public void keyPressed(KeyEvent e) {
/** resto del codigo **/
} else if(e.getKeyCode() == KeyEvent.VK_SPACE) {
shootWeapons();
}
}
}
Debemos actualizar el método updateGame() para que recorra la lista de weapons y actualice el estado de cada una:
public void updateGame() {
/** resto del codigo **/
if(!listWeapon.isEmpty()) {
Iterator it = listWeapon.iterator();
while(it.hasNext()) {
Weapon weapon = (Weapon)it.next();
weapon.updateWeapon(moved);
if(!weapon.isVisible()) {
it.remove();
}
}
}
}
También debemos actualizar es paint(Graphics g) para que dibuje los disparos:
public void paint(Graphics g) {
/** resto del codigo **/
if(!listWeapon.isEmpty()) {
Iterator it = listWeapon.iterator();
while(it.hasNext()) {
Weapon weapon = (Weapon)it.next();
if(weapon.isVisible()) {
g.drawImage(weapon.getImage(),weapon.getX(),weapon.getY(),null);
}
}
}
}
Si en este punto ejecutamos el juego, al presionar la tecla espacio se efectuaran los disparos:
Debemos determinar el comportamiento que tendrá la sierra ante los objetos con los que colisione, por el momento solo tenemos dos: cajas (de tres tipos) y las frutas; con las frutas no tendrá ningún efecto; pero las cajas, dependiendo su tipo las destruirá.
Debemos modificar el método updateGame(), en el bloque de código que actualiza los weapons, para que recorra la lista de cajas y las actualice según corresponda, para ello debemos hacer algunos cambios:
Recorremos la lista de cajas, si se cumple la condición que que existe una colisión horizontal (por ahora no vamos a complicarnos con el otro eje), entonces indicamos que la caja se rompe, weapon ya no es visible y salimos del bucle.
La ultima caja, la del tipo BOX3, quiero que sea a prueba de disparos; modificaremos el método setBreak() de la clase Box:
public void setBreak() {
isBreak = (this.boxNumber<BOX3);
}
Con este cambio solo se romperán las cajas de tipo BOX1 y BOX2.