Mostrando entradas con la etiqueta Conceptos. Mostrar todas las entradas
Mostrando entradas con la etiqueta Conceptos. Mostrar todas las entradas

jueves, 23 de abril de 2009

Estados de Juego

Muy bien, vamos a empezar con otro concepto, al cual llamo estados de juego.
Todos los juegos tienen diferentes estados, por ejemplo cuando empezamos, es muy seguro que se nos presente el menu de inicio o una animacion, luego pasamos a lo que es el juego, y finalmente los creditos. Tambien hay muchos mas estados, un ejemplo es cuando pausamos el juego o especificamos las opciones.
Hasta ahora veniamos muy directo, solo mostrando los conceptos y como ponerlos en practica, pero si queremos hacer un juego mas serio, vamos a querer que aunquesea tenga un menu, donde podemos elejir opciones, comenzar el juego, creditos, etc, para eso usaremos los estados de juego.
Comenzemos con el esqueleto basico. Las clases Main.java y Board.java ya estan explicadas anteriormente, usaremos la version de Board.java que usa el timer de swing que veniamos usando. Veamos ahora como agregar estados. Primero que nada, agregemos una interface, lo que esta tendra seran variables globales a todas nuestras clases.

package gamestates;

/**
*
* @author fede
*/
public interface Commons {
enum GameState {MenuScreen, Pause, Playing, GameOver};
}


Como veran solo definimos un nuevo tipo llamado GameState con los estados que deseamos, estos pueden ir modificandose mas adelante.

public class Board extends JPanel implements ActionListener, Commons {


Luego modificamos la clase para implementar la interface.
El constructor ahora se vera algo asi

public static GameState currentState;
public Board(){
setDoubleBuffered(true);
setFocusable(true);
addKeyListener(new Listener());
setBackground(Color.white);

timer = new Timer(5, this);
timer.start();
currentState = GameState.MenuScreen;
}


La pantalla inicial sera MenuScreen, es decir, la pantalla del menu.
Fijense que la variable currentState es estatica, esto es importante, para poder modificar el estado desde otras clases.
Estas clases seran una representando cada estado, en este caso haremos una para MenuScreen, otra Playing y otra GameOver.

MenuScreen.java
package gamestates;

import java.awt.Graphics2D;
import java.awt.event.KeyEvent;

/**
*
* @author fedekun
*/
public class MenuScreen implements Commons {
public void draw(Graphics2D g2d){
g2d.drawString("Este es el menu, presiona [ENTER] para jugar", 3, 10);
}

public void keyPressed(KeyEvent e){
if(e.getKeyCode() == KeyEvent.VK_ENTER)
Board.currentState = GameState.Playing;
}
}


Hay 3 partes en donde llamaremos a funciones de esta clase, una es la parte de dibujar, otra la de actualizar y otra la de input. En este caso no necesitamos actualizar, asi que solo dibujamos, y hacemos input.

Playing.java
package gamestates;

import java.awt.Graphics2D;
import java.awt.event.KeyEvent;

/**
*
* @author fedekun
*/
public class Playing implements Commons {
private int x, y, dx, dy, speed;

public Playing(){
x = 3;
y = 10;
dx = dy = 1;
speed = 1;
}

public void draw(Graphics2D g2d){
g2d.drawString("Presiona [Escape] para salir", 3, 10);
g2d.drawString("Estamos jugando!", x, y);
}

public void update(){
if(x > 200 || x < 0)
dx*=-1;
if(y > 280 || y < 0)
dy*=-1;
x+=speed*dx;
y+=speed*dy;
}

public void keyPressed(KeyEvent e){
if(e.getKeyCode() == KeyEvent.VK_ESCAPE)
Board.currentState = GameState.GameOver;
}
}


Aca tenemos las 3 implementaciones, draw, udpdate e input.
Y finalmente...

GameOver.java
package gamestates;

import java.awt.Graphics2D;

/**
*
* @author fedekun
*/
public class GameOver {

public void draw(Graphics2D g2d){
g2d.drawString("GAME OVER", 100, 100);
}

}


Esos son los fundamentos, ahora unimos todo en Board.java

package gamestates;

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

// timer
import javax.swing.Timer;
import java.awt.Toolkit;

// teclas y timer
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

// teclas
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

/**
*
* @author fede
*/
public class Board extends JPanel implements ActionListener, Commons {
private Timer timer;
public static GameState currentState;
private MenuScreen menuScreen;
private Playing playing;
private GameOver gameOver;
public Board(){
setDoubleBuffered(true);
setFocusable(true);
addKeyListener(new Listener());
setBackground(Color.white);

timer = new Timer(5, this);
timer.start();
currentState = GameState.MenuScreen;
menuScreen = new MenuScreen();
playing = new Playing();
gameOver = new GameOver();
}

public void paint(Graphics g){
super.paint(g);
Graphics2D g2d = (Graphics2D)g;

// Dibuja aca
if(currentState == GameState.MenuScreen)
menuScreen.draw(g2d);
if(currentState == GameState.Playing)
playing.draw(g2d);
if(currentState == GameState.GameOver)
gameOver.draw(g2d);

Toolkit.getDefaultToolkit().sync();
g.dispose();
}

public void actionPerformed(ActionEvent e){
// Logica aca
if(currentState == GameState.Playing)
playing.update();

repaint();
}

private class Listener extends KeyAdapter{
@Override
public void keyPressed(KeyEvent e){
// Metodo de los objetos que reciben input
if(currentState == GameState.MenuScreen)
menuScreen.keyPressed(e);
if(currentState == GameState.Playing)
playing.keyPressed(e);
}

@Override
public void keyReleased(KeyEvent e){
// Metodo de los objetos que reciben input
}
}
}


Como veran, en la parte de draw, update e input, hacemos un if para ver en que estado estamos, y segun el estado hacemos lo que debemos hacer.
En un proyecto mas grande esto es muy util, es lo basico de programacion modular, dividir el problema en subproblemas, con orientacion a objetos, es mas facil aun :)
Espero hallan podido entender la idea, cualquier cosa, contactenme.

Codigo fuente: de 4shared

viernes, 17 de abril de 2009

Deteccion de Colision

Collision Detection, Hit Test, etc, basicamente, saber cuando se tocan dos sprites. Esto es topico de discusion algunas veces, de los metodos mas usados es usando rectangulos y ver cuando se tocan, en Java esto es facil, gracias a la clase Rectangle, de awt. He viso otros metodos que tienen hasta precision de pixel, pero no en Java, y creo que para este tutorial y las necesidades que tendremos, esto sera suficiente.
Lo que haremos para detectar colision es encerrar el sprite en un rectangulo que lo cubra "perfectamente", y vemos cuando colisiona con otro rectangulo, que sera el que encierra el sprite con el que queremos ver si hay colision o no.
Para esto, primero tenemos que tener como minimo dos sprites, asi que crearemos otro sprite fijo, y haremos que algo ocurra cuando nuestro sprite (al cual movemos con nuestro teclado) lo toque.
Vamos a crear una nueva clase, renombre el package a "hittest", llamada "Coin" (el sprite que mostraremos sera una moneda) asi que la nueva clase se ve algo asi

package hittest;

import java.awt.Image;

import javax.swing.ImageIcon;

/**
*
* @author fede
*/
public class Coin {
Image image;
public Coin(){
ImageIcon ii = new ImageIcon(this.getClass().getResource("coin.png"));
image = ii.getImage();
}
}


Veran que ya importe Image e ImageIcon y carge la imagen, como veniamos haciendo, ahora vamos a definir la posicion de la imagen para despues dibujarla, simplemente creamos dos variables privadas, x e y, y en el constructor les asignamos cualquier valor dentro de la ventana. Hacemos los respectivos get's.

public class Coin {
private Image image;
private int x,y;
public Coin(){
ImageIcon ii = new ImageIcon(this.getClass().getResource("coin.png"));
image = ii.getImage();
x = 10;
y = 40;
}

public int getX(){
return x;
}

public int getY(){
return y;
}

public Image getImage(){
return image;
}
}


Ahora vamos a agregar una funcion que me devuelve un rectangulo que "envuelve" completamente nuestro sprite, para eso, importamos "java.awt.Rectangle" y hacemos la siguiente funcion

public Rectangle getBounds(){
return new Rectangle(x, y, image.getWidth(null), image.getHeight(null));
}


Volvamos ahora a la clase Board, a agregar nuestro nuevo sprite. Simplemente hacemos un objeto privado, luego hacemos una instancia de este objeto en el constructor, y en la parte de dibujar, lo agregamos.



Como podran ver, ya esta dibujado, ahora vamos a agregar una funcion para que nuestro sprite anterior (al cual debimos haber hecho en otra clase, pero preferi en la misma por simplicidad) nos devuelva un rectangulo que lo encierre, es practicamente igual a la funcion en Coin, pero para poder hacerla en Board, tambien tenemos que importar java.awt.Rectangle.
Una vez que hallamos hecho la funcion, vamos a la parte de la logica (actionPerformed) y agregamos la condicion

Rectangle rectanglePlayer = getBounds();
Rectangle rectangleCoin = coin.getBounds();
if(rectangleCoin.intersects(rectanglePlayer))
x = y = 200;


Como veran, es bastante simple, basta con ver si se intersectan, y luego especificar que sucede, en este caso, el personaje se mueve a las coordenadas 200, 200 como muestra la siguiente imagen.



Sprites mas complejos pueden constrar de varios rectangulos, definidos en su clase, y la comparacion se volveria mas minuciosa, pero eso se lo dejo a su imaginacion y complejidad de juego que tengan en mente hacer, para los ejemplos de este blog esto sera suficiente.
Descargar codigo fuente: de 4shared

Recibiendo Input

Otra de las cosas fundamentales en la programacion de juegos, es recibir input del teclado, es decir, saber que teclas se apretan, y cuales se sueltan, para poder asi mover los sprites a voluntad.
Una vez que comenzamos a recibir input tenemos que tener algo muy importante en cuenta, es mas comodo y por decirlo asi un estandar, y mejor la performance, dividir el juego en logica y dibujar. El dibujo se ejecuta las veces que decimos que se actualize la pantalla, y la logica lo hace mas rapido aun, asi aunque el juego sea muy lento o ande mal el input llegara bien y podremos animar los dibujos.
En nuestro ejemplo anterior, se movia automaticamente, ahora vamos a hacer, que lo movamos nosotros, usando el teclado, en este caso, las flechas, pero puede ser cualquier boton.
Primero que nada las clases que debemos importar, esas son KeyAdapter y KeyEvent, de awt.
Esas clases combinadas nos permiten leer las teclas que se han apretado, o soltado.
Agregamos los imports


import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;


En el constructor tenemos que hacer que la ventana sea focus-able, es decir que pueda estar en primer plano y asi recibir el input, esto es muy importante ya que sin esto no funciona, y es solo una linea de codigo, lo otro que vamos a agregar en el constructor es un KeyListener, esto "escucha" constantemente nuevas entradas (inputs).

this.setFocusable(true);
this.addKeyListener(new Adapter());


Otra cosa que podemos agregar es DoubleBuffering, esta opcion recomiendo ponerla siempre, no cuesta nada y ayuda mucho cuando el juego va creciendo

this.setDoubleBuffered(true);


Basicamente, dibuja primero en memoria, y luego todo junto en pantalla de una sola vez, es mucho mejor asi.
Otra cosa que tenemos que actualizar son los dx y dy, en el anterior valian la velocidad, es decir empezaban valiendo 2, pero nosotros queremos que se mueva por las teclas, asi que los vamos a poner igual a 0, y lo cambiamos cuando apretamos las teclas, que en este caso seran las flechas.

dx = dy = 0;


Ahora, dentro de la clase Board, hacemos otra clase, Adapter, que hereda de KeyAdapter.

private class Adapter extends KeyAdapter{
@Override
public void keyPressed(KeyEvent e){
int key = e.getKeyCode();

if(key == KeyEvent.VK_UP)
dy = SPEED * -1;
if(key == KeyEvent.VK_DOWN)
dy = SPEED;
if(key == KeyEvent.VK_RIGHT)
dx = SPEED;
if(key == KeyEvent.VK_LEFT)
dx = SPEED * -1;
}

@Override
public void keyReleased(KeyEvent e){
int key = e.getKeyCode();

if(key == KeyEvent.VK_UP)
dy = 0;
if(key == KeyEvent.VK_DOWN)
dy = 0;
if(key == KeyEvent.VK_RIGHT)
dx = 0;
if(key == KeyEvent.VK_LEFT)
dx = 0;
}
}


Puden ver los @Override, indican simplemente que la funcion de abajo esta siendo sobre-escrita de la clase que heredamos. Las funciones en si son bastante faciles de entender, keyPressed es cuando se presiona una tecla, y keyReleased cuando se suelta, ambas requieren como argumento un KeyEvent, ahi es donde podemos ver que tecla se apreto, ese argumento contiene la tecla, luego simplemente comparamos cual fue y cambiamos dx y dy.
Ahora, editamos la funcion actionPerformed

public void actionPerformed(ActionEvent e){ // Se ejecuta cada 5ms
if((x>0 && dx<0)>0))
x += dx;
if((y>0 && dy<0)>0))
y += dy;
repaint(); // "re-pintamos" el panel
}


Lo que hace eso es pura logica, la anterior no funcionaba, aca decimos, si la posicion x del sprite es mayor que 0 y lo quiero mover hacia la izuiqerda, o la posicion x es menor que el limite de la pantalla y lo quiero mover a la derecha, lo muevo x)
Lo importante para resaltar sin embargo aca, es la clase Adapter y como la agregamos en el constructor.
Una vez que hallamos hecho todos los cambios, ya podemos mover el sprite con las flechas de nuestro teclado.
Aca les dejo Board.java

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

// Para leer teclas
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

/**
*
* @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);
this.setFocusable(true);
this.addKeyListener(new Adapter());
this.setDoubleBuffered(true); // Dibujo en memoria antes que en pantalla
x = 150;
y = 10;
dx = dy = 0;

// 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
if((x>0 && dx<0)>0))
x += dx;
if((y>0 && dy<0)>0))
y += dy;
repaint(); // "re-pintamos" el panel
}

private class Adapter extends KeyAdapter{
@Override
public void keyPressed(KeyEvent e){
int key = e.getKeyCode();

if(key == KeyEvent.VK_UP)
dy = SPEED * -1;
if(key == KeyEvent.VK_DOWN)
dy = SPEED;
if(key == KeyEvent.VK_RIGHT)
dx = SPEED;
if(key == KeyEvent.VK_LEFT)
dx = SPEED * -1;
}

@Override
public void keyReleased(KeyEvent e){
int key = e.getKeyCode();

if(key == KeyEvent.VK_UP)
dy = 0;
if(key == KeyEvent.VK_DOWN)
dy = 0;
if(key == KeyEvent.VK_RIGHT)
dx = 0;
if(key == KeyEvent.VK_LEFT)
dx = 0;
}
}
}


La imagen en este caso, sera exactamente la misma que la anterior, deberian probarlo para ver la diferencia x)



Bueno, moverlo es lo de menos, lo mas importante de esta parte es lo de leer las teclas, ya lo demas es simple logica, espero se halla entendido bien esa parte.

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