viernes, 17 de abril de 2009

Animacion

Otro de los temas basicos y mas importantes es la animacion, esto como muchos sabran consiste en cambiar la imagen original de lugar o forma de a pasos, asi da un efecto fluido de movimiento.
En computacion hay un problema ciertamente cuando hacemos eso, si solo nos disponemos a hacer una animacion, y mostrar una imagen despues de otra, por ponerlo simple, maquinas mas potentes lo haran mas rapido que otras menos potentes, ademas, hay veces que queremos que se tome su tiempo para ciertas cosas, poreso esta seccion de animacion va muy entrelazada con Timers.
Timers son por llamarlo asi objetos que nos permiten repetir algo, cada cierto periodo de tiempo.
Los timers que usare en este Knol son muy limitados, de hecho solo usare el timer de swing, pero hay mas, hay uno integrado a Java, y otra forma es usando Threads, y no se si habra mas, no se mucho de Java, de todas formas, aunque no sea el mas eficiente de todos, es el que usaremos por ser el mas simple, las diferencias con respecto a los otros no son muchas, y si logro terminar esto bien, pondre como anexo las otras formas de timers.

Muy bien, vamos a animar, la meta en esta seccion sera mover una imagen cualquiera, a lo largo de nuestra ventana, para esto, modificare las clases que teniamos hasta ahora, asi que si no viste la seccion anterior, te recomiendo ver el codigo completo que esta mostrado al final.
Nosotros ya teniamos la ventana y una imagen, la imagen esta es muy grande para animar, asi que voy a usar una mas pequeña, lo unico que hare es cambiar el archivo "imagen.png" de una imagen de 300x300 a la de una de 30x30.
La clase Main, no sufre cambios esta vez, todos los cambios seran en la clase Board. Es importante entender que es Board, imaginalo como lo que es la ventana, menos los bordes, el menu, etc, todo lo que esta "blanco" por asi decirlo. Por eso es que todos los cambios son ahi, porque la ventana sige siendo de 300x300.
Si quieren pueden cambiarle el titulo, lo dejo a su criterio.

Comenzamos a modificar, importo una nueva clase de awt, Color. Esta clase te permite elejir colores mas facilmente, solo eso, accedes a los colores como "Color.white", por ejemplo.
Ahora vamos a importar las cosas que vamos a usar para el timer, en este caso el de swing, para esto necesitamos estas clases/estructuras.

import java.awt.Toolkit;
import javax.swing.Timer;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent; // Para poder usar actionPerformed, necesitamos este tipo


Toolkit lo usamos para forzar una sincronizacion con el timer, Timer es el timer en si, ActionListener es una estructura, la cual es necesaria para usar el timer, solo tiene una funcion, la funcion actionListener, que tiene como argumento un ActionEvent, poreso lo importamos, necesitamos ese tipo de datos.
La idea de esta animacion sera simplemente una imagen de 30x30 rebotando por la pantalla. Para eso es importante saber el concepto en si, vamos a necesitar una imagen, y la posicion de la imagen, la cual ira cambiando, por lo tanto es dinamica, si cambia tambien tiene que tener una velocidad a la que cambia, en este caso sera constante, y no cambiara. Hagamos estas variables globales.

private Timer timer;
private int x, y, dx, dy;
private static int SPEED = 2;


dx y dy (o como los leo yo, delta x y delta y) seria la aceleracion de la imagen. A mi tampoco me gusta Fisica, y no la tengo como materia para Cincias de la Computacin, asi que siendo feliz en mi ignorancia, voy a usar esa palabra refiriendome a eso, perdonen si esta mal, en fin, esas variables son simplemente para sumarle a x, asi saber para donde va y a que velocidad.
Vamos al constructor de Board, agegamos esto al final del todo

this.setBackground(Color.white);
x = 150;
y = 10;
dx = dy = SPEED;

// Timer
timer = new Timer(5, this); // cada 5ms llama actionPerformed
timer.start();


Primero, cambiamos el color a blanco, para eso importamos la clase Color, solo para acceder al color mas facilmente, luego damos valores cualquiera para x e y, y ponemos a dx y dy iguales a la velocidad que es constante (dy y dx no son constantes ya que cambiaran de sentido).
Vamos al metodo paint, y agregamos esto antes del dispose

Toolkit.getDefaultToolkit().sync(); // fuerza sincronizacion, basicamente


Bastante explicado esta, la documentacion lo explica como "Synchronizes this toolkit's graphics state. Some window systems may do buffering of graphics events[...]".

Finalmente agregamos esta funcion, que es la que el timer llama cada 5 millisegundos, en ella hacemos la logica y hacemos que actualize lo que dibujamos.

public void actionPerformed(ActionEvent e){ // Se ejecuta cada 5ms
x+=dx;
y+=dy;
if(x > 300-image.getWidth(null) || x < 0)
dx *= -1;
if(y > 300-image.getHeight(null) || y < 0)
dy *= -1;
repaint(); // "re-pintamos" el panel
}


En ella sumamos a x dx y a y dy, si x se escapa de la pantalla, invertimos dx, para que cambie su sentido horizontal, eso da el efecto de rebote, lo mismo hacemos con el y, y finalmente, "re-pintamos" el panel. Esa funcion la hereadmos cuando heredamos JPanel.
Aca esta la clase Board completa.

package animation;

import java.awt.Image;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
import javax.swing.ImageIcon;
import java.awt.Color;

// Timer Imports
import java.awt.Toolkit;
import javax.swing.Timer;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent; // Para poder usar actionPerformed, necesitamos este tipo

/**
*
* @author fede
*/
public class Board extends JPanel implements ActionListener {
private Image image;
private Timer timer;
private int x, y, dx, dy;
private static int SPEED = 2;

public Board(){
ImageIcon ii = new ImageIcon(this.getClass().getResource("image.png"));
image = ii.getImage();
this.setBackground(Color.white);
x = 150;
y = 10;
dx = dy = SPEED;

// Timer
timer = new Timer(5, this); // cada 5ms llama actionPerformed
timer.start();
}

public void paint(Graphics g){
super.paint(g);

Graphics2D g2d = (Graphics2D)g; // Convertimos a g de Graphics a Graphics2D
g2d.drawImage(image, x, y, this);

// Timer
Toolkit.getDefaultToolkit().sync(); // fuerza sincronizacion, basicamente

g.dispose();
}

public void actionPerformed(ActionEvent e){ // Se ejecuta cada 5ms
x+=dx;
y+=dy;
if(x > 300-image.getWidth(null) || x < 0)
dx *= -1;
if(y > 300-image.getHeight(null) || y < 0)
dy *= -1;
repaint(); // "re-pintamos" el panel
}
}


Si todo sale bien, al correr Main.java, deberian ver una pequeña imagen 30x30 (la cual ustedes deben de poner ahi, cualquier imagen) rebotar en la ventana.



Repito que hay otros Timers, pero solo use el de swing.
Descargar codigo fuente: de 4shared

Cargando Sprites

Bueno, según creo yo, esto es de lo más básico que hay que saber para armar juegos.
Los Sprites, son imagenes que cargamos para nuestro juego, pueden ser pequeñas o grandes, en cualquier formato, a eso me referiré con sprites. Como podrás ver en los juegos, esta lleno de sprites, hay varios métodos para eso, o repitiendo un sprite muchas veces para hacer mapas completos (tiles, lo veremos más adelante) o simplemente cargando una imagen para nuestro personaje principal, o un fondo, en fin, se traduce como "Imagenes".
Si usas NetBeans, vamos a File -> New Project. Elegimos el nombre del proyecto, en este caso yo use
LoadSprites.
NetBeans por default debería crear una clase principal llamada Main, en este caso así se ve la mia

package loadsprites;

/**
*
* @author fede
*/
public class Main {

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
}

}


Muy bien, ahora vamos a importar JFrame, de swing, esto nos permitirá hacer un Frame (ventana).

import javax.swing.JFrame;


Eso va después del package, mas tarde postearé el código completo, por ahora, vamos armandolo. Una vez que importamos el Frame, tenemos que hacer que nuestra clase principal herede de este, asi podemos armar una ventana, logramos eso editando la siguiente línea

public class Main extends JFrame {


Como veran, agregando 'extends' podemos hacer que Main herede de JFrame.
Ahora vamos a hacer un constructor, y en el vamos a especificar algunas cosas de nuestra ventana (Frame)

public Main(){
setTitle("Loading Sprite"); // Titulo
setSize(300,300); // Tamaño
setDefaultCloseOperation(EXIT_ON_CLOSE); // Salir cuando apretamos el boton 'x'
setLocationRelativeTo(null); // Centrar
setVisible(true); // Visible
setResizable(false); // No se puede resizear
}


El constructor es llamado solo una vez, cuando la clase es instanciada, es usado para especificar valores iniciales, como lo hacemos en este caso.
Finalmente en la funcion main, que es la que se va a ejecutar primero de todas las funciones, llamamos a nuestra clase Main.
Este es el codigo completo de Main.java

package loadsprites;

import javax.swing.JFrame;

/**
*
* @author fede
*/
public class Main extends JFrame {

public Main(){
setTitle("Loading Sprite"); // Titulo
setSize(300,300); // Tamaño
setDefaultCloseOperation(EXIT_ON_CLOSE); // Salir cuando apretamos el boton 'x'
setLocationRelativeTo(null); // Centrar
setVisible(true); // Visible
setResizable(false); // No se puede resizear
}

public static void main(String[] args) {
new Main();
}

}


Ahora, si usamos NetBeans, podemos hacer click derecho en el archivo Main.java, y Run. Deberia aparecer nuestra ventana vacia.



Obviamente, como se ve, varia de su sistema operativo y configuracion de apariencia, en mi caso uso Windows XP y un Theme de Windows 7.

Muy bien! Tenemos nuestra ventana vacía, pero falta lo más importante, la imagen, algo importante a resaltar hasta ahora es la clase Main, se repetirá mucho, ya que la ventana en si no varia mucho, lo que modificamos es lo que hay dentro, y eso es en otra clase, que llamaremos Board.

Creamos una nueva clase, llamada Board dentro del mismo package que estemos usando obviamente.

package loadsprites;

import java.awt.Image;
import javax.swing.ImageIcon;

/**
*
* @author fede
*/
public class Board {

}


Pueden ver que importe un par de librerias, Image la usaremos para cargar una imagen, y usar la imagen en si, ImageIcon es para cargar la imagen mas facilmente.

package loadsprites;

import java.awt.Image;
import javax.swing.ImageIcon;

/**
*
* @author fede
*/
public class Board {
Image image;

public Board(){
ImageIcon ii = new ImageIcon(this.getClass().getResource("image.gif"));
image = ii.getImage();
}
}


Ahora debería verse asi, ya tenemos la imagen lista para dibujar, solo falta dibujarla ;)
Para eso vamos a importar las librerias Graphics y Graphics2D de awt, y sobreescribir la función paint de JPanel, para eso, tenemos que heredar de JPanel. Veamos...

package loadsprites;

import java.awt.Image;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
import javax.swing.ImageIcon;

/**
*
* @author fede
*/
public class Board extends JPanel {
private Image image;

public Board(){
ImageIcon ii = new ImageIcon(this.getClass().getResource("image.png"));
image = ii.getImage();
}

public void paint(Graphics g){
Graphics2D g2d = (Graphics2D)g; // Convertimos a g de Graphics a Graphics2D
g2d.drawImage(image, 0, 0, this);
}
}


Parecen muchas librearias, de hecho lo son, pero...es Java, asi es :P
La verdad a mi no me gusta eso tampoco pero no es tan malo, ni son tantas en realidad. Como veran, heredo de JPanel, hago una variable image privada de tipo Image, luego en el constructor se encarga de llenar esa variable con una imagen. El metodo paint de JPanel es el que se encarga de dibujar, y se llama automaticamente, lo sobreescribimos para dibujar lo que queremos, en este caso una imagen.
Es importante que un archivo "imagen.png" este ubicado en el mismo directorio que Main.java y Board.java, de lo contrario, saldra un error.
Esta clase ya esta terminada, ahora solo nos falta una linea en la clase Main, para poder mostrar la imagen

add(new Board());

La agregamos y queda algo asi

package loadsprites;

import javax.swing.JFrame;

/**
*
* @author fede
*/
public class Main extends JFrame {

public Main(){
add(new Board());
setTitle("Loading Sprite"); // Titulo
setSize(300,300); // Tamaño
setDefaultCloseOperation(EXIT_ON_CLOSE); // Salir cuando apretamos el boton 'x'
setLocationRelativeTo(null); // Centrar
setVisible(true); // Visible
setResizable(false); // No se puede resizear
}

public static void main(String[] args) {
new Main();
}

}


Una vez que tenemos estas clases, ya podemos mostrar la imagen! Solo ejecuten Main.java y veran algo asi (Obviamente, depende de su imagen). Es importante resaltar que java solo soporta imagenes formato PNG.



No nos meteremos mucho mas en lo que es manejo de imagenes ni 2D porque esto es lo que necesitamos basicamente, mas adelante si necesitamos rotar una imagen o algo mas especifico, explicare como pero no le veo sentido a explicar mucho mas sobre ese tema.
Descargar codigo fuente: de 4shared

Antes de Comenzar

Descargas
Todo lo que es utilizado acá, es gratis :)
El IDE que yo utilizo es NetBeans, lo pueden descargar de acá. Utilizo la version que solo tiene Java (40mb~).
Lo otro que necesitan es el Java SE SDK, lo pueden descargar de la web de sun.
Una vez que tengan eso, ya pueden empezar a seguir este blog. (Primero instalar el SDK, despues el IDE)

Antes de comenzar
Si hace tiempo no ves nada en Java, y sabes programar, es buena idea hacerle un repaso rápido, con alguna documentación que tengan ustedes o haciendo pequeños programas. Lo más importante que usaremos acá sera Variables, Arrays, Estructuras de control de flujo, y OOP (Programacion Orientada a Objetos)
Si no sabes programar en Java, recomiendo un pequeño libro gratuito online, lo podes ver acá. Es bastante corto, conciso y explica bien los temas que nos conciernen.
También recomiendo usar NetBeans, ya que es el IDE (Integrated Development Envirment) que use para hacer esto y el código fuente incluye el proyecto de NetBeans.