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

sábado, 18 de abril de 2009

Esqueleto Basico

Bueno, este es el esqueleto basico que seguiran la mayoria de los juegos, no voy a explicar mucho, solo sera dar el codigo, ya que esta explicado en Conceptos.

Main.java
package packagenamehere;

import javax.swing.JFrame;

/**
*
* @author fede
*/
public class Main extends JFrame {
public Main(){
add(new Board());
setTitle("Skeleton");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(300,300);
setLocationRelativeTo(null);
setVisible(true);
setResizable(false);
}

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

}


Board.java
package packagenamehere;

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 {
private Timer timer;
public Board(){
setDoubleBuffered(true);
setFocusable(true);
addKeyListener(new Listener());
setBackground(Color.white);

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

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

// Dibuja aca

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

public void actionPerformed(ActionEvent e){
// Logica aca

repaint();
}

private class Listener extends KeyAdapter{
@Override
public void keyPressed(KeyEvent e){
// Metodo de los objetos que reciben input
}

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


Si leyeron el post de Animacion, veran que digo que hay mas timers, este utiliza threads, y es una forma mas efectiva aunque un poco mas complicada de hacerlo.

Board.java
package packagenamehere;

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

// timer
import java.awt.Toolkit;

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

/**
*
* @author fede
*/
public class Board extends JPanel implements Runnable {
private Thread thread;
private final int DELAY = 50;
private GameState gameState;
private MenuScreen menuScreen;

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

@Override
public void addNotify(){
super.addNotify();
thread = new Thread(this);
thread.start();
}

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

// Dibuja aca

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

public void cycle(){
// Logica aca
}

@Override
public void run(){
long beforeTime, timeDiff, sleep;

beforeTime = System.currentTimeMillis();

while(true){
cycle();
repaint();

timeDiff = System.currentTimeMillis() - beforeTime;
sleep = DELAY - timeDiff;

if (sleep < 0)
sleep = 2;

try {
Thread.sleep(sleep);
} catch (InterruptedException e) {
System.out.println("interrupted");
}

beforeTime = System.currentTimeMillis();
}
}

private class Listener extends KeyAdapter{
@Override
public void keyPressed(KeyEvent e){
// Metodo de los objetos que reciben input
}

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

Space Invaders

Otro juego popular "ochentoso". Vamos a clonarlo, consistira basicamente en una nave que podemos controlar, la cual se mueve solo horizontalmente, y muchas naves enemigas que van bajando y moviendose, si destruimos todas antes que llegen al piso, ganamos, de lo contrario, perdemos x)
En este caso las naves enemigas seran llamados Aliens. Para descargar las imagenes que vamos a utilizar, clickea aca.
Muy bien, empezamos como veniamos hasta ahora, creamos un nuevo proyecto en netbeans, en este caso lo llame Space Invaders, y el package spaceinvaders. En la clase principal heredamos JFrame y hacemos la ventana.

package spaceinvaders;

import javax.swing.JFrame;

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

  public Main(){
      add(new Board());
      setTitle("JSpace Invaders");
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setSize(300,300);
      setLocationRelativeTo(null);
      setVisible(true);
      setResizable(false);
  }

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

}


Nada nuevo hasta ahora, y de hecho no deberia haber nada nuevo en las otras clases tampoco, solo logica.
Veamos ahora la clase Ship. Esta clase es nuestra nave principal, la cual movemos y es capaz de disparar.
Realmente no tiene cosas nuevas ninguna de estas clases, pero si no sabes ArrayList por ejemplo, recomendaria leer la documentacion a parte.
Aca tenemos la clase Ship.

package spaceinvaders;

import javax.swing.ImageIcon;

import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.Rectangle;

import java.util.ArrayList;

/**
*
* @author fede
*/
public class Ship {
  private Image image;
  private int x,y,dx;
  private final int SPEED = 2;
  private ArrayList lasers;
  private boolean shot;
  public Ship(){
      ImageIcon ii = new ImageIcon(this.getClass().getResource("images/ship.png"));
      image = ii.getImage();
      y = 250;
      x = 150-image.getWidth(null)/2;
      dx = 0;
      lasers = new ArrayList();
      shot = true;
  }

  public int getX(){
      return x;
  }

  public int getY(){
      return y;
  }

  public Image getImage(){
      return image;
  }

  public ArrayList getLasers(){
      return lasers;
  }

  public void logic(){ // Toda la logica la pondre aca
      if((x>0 && dx<0)>0))
          x += dx;
  }

  public void keyPressed(KeyEvent e){
      int key = e.getKeyCode();

      if(key == KeyEvent.VK_RIGHT)
          dx = SPEED;
      if(key == KeyEvent.VK_LEFT)
          dx = SPEED * -1;
      if(key == KeyEvent.VK_SPACE && shot)
      {
          lasers.add(new Laser(x + image.getWidth(null)/2, y));
          shot = false;
      }
  }

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

      if(key == KeyEvent.VK_LEFT && dx < dx =" 0;" key ="="> 0)
          dx = 0;
      if(key == KeyEvent.VK_SPACE)
          shot = true;
  }

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


Como veran, la mayoria son variables, algunos sets y gets, logica y teclas, no es mucho realmente, lo que resaltaria de aca es cuando apretamos la barra espaciadora, vemos que agregamos un nuevo objeto Laser (que sera otra clase) a una ArrayList, la idea es luego devolver esa lista y asi manipular todos los lasers en un bucle.
Ahora, veamos la clase Laser, esta es bastante simple, solo se necesita la imagen, y algo de logica

package spaceinvaders;

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

/**
*
* @author fede
*/
public class Laser {
  protected Image image;
  protected int x,y;
  private final int SPEED = 5;
  private boolean visible;

  public Laser(int x, int y){
      ImageIcon ii = new ImageIcon(this.getClass().getResource("images/laser.png"));
      image = ii.getImage();
      visible = true;
      this.x = x;
      this.y = y;
  }

  public int getX(){
      return x;
  }

  public int getY(){
      return y;
  }

  public Image getImage(){
      return image;
  }


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

  public boolean isVisible(){
      return visible;
  }

  public void setVisible(boolean visible){
      this.visible = visible;
  }

  public void update(){
      y -= SPEED;
      if(y<0) visible =" false;">


Vemos que en el constructor ponemos las coordenadas iniciales, es importante ya que queremos los lasers en las mismas coordenadas que nuestra nave. Como veran solo tenemos cinco variables, image, x, y, SPEED y visible.
Son bastante descriptivas por si mismas, visible quizas sea mas complicada que las demas ahora, simplemente dice si debemos dibujar y considerar este objeto para colision, cuando esta como no visible, es como si no existiera y luego es borrado.
Los aliens de nuestro juego tambien pueden tirar bombas, son muy similares a los lasers, la unica diferencia es que caen, envez de subir, ya que los aliens estan arriba y van bajando, dejan caer bombas. La clase Bomb especifica estas bombas, ya que son tan similares a los lasers, heredamos de esta clase y cambiamos solo lo necesario.

package spaceinvaders;

/**
*
* @author fede
*/
public class Bomb extends Laser {
  public Bomb(int x, int y){
      super(x, y);
  }

  @Override
  public void update(){
      y++;
  }
}


Ahora, la clase Alien, despues de la principal (Board) creo que es la mas complicada, asi que intentare explicarla mas a fondo.

package spaceinvaders;

import java.awt.Image;
import java.awt.Rectangle;
import javax.swing.ImageIcon;
import java.util.Random;
import java.util.ArrayList;

/**
 *
 * @author fede
 */
public class Alien {
    private Image image;
    private int x, y, speed, direction, movedX, movedY, wentDown, bombChance;
    private final int RANGE;
    private boolean visible, goDown;
    private Random random;
    private ArrayList bombs;
    public Alien(int x, int y){
        ImageIcon ii = new ImageIcon(this.getClass().getResource("images/alien.png"));
        image = ii.getImage();
        this.x = x;
        this.y = y;
        speed = 1;
        RANGE = 100;
        movedX = 0;
        direction = 1; // 1 = derecha, -1 = izquierda
        visible = true;
        goDown = false;
        movedY = 0;
        wentDown = 0;
        random = new Random();
        bombChance = 700; // 1 in 40
        bombs = new ArrayList();
    }

    public int getX(){
        return x;
    }

    public int getY(){
        return y;
    }

    public void setX(int x){
        this.x = x;
    }

    public void setY(int y){
        this.y = y;
    }

    public Image getImage(){
        return image;
    }

    public int getSpeed(){
        return speed;
    }

    public void setSpeed(int speed){
        this.speed = speed;
    }

    public void setVisible(boolean visible){
        this.visible = visible;
    }
    
    public boolean isVisible(){
        return visible;
    }

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

    public void update(){
        if(movedX>RANGE){
            movedX = 0;
            goDown = true;
            direction*=-1;
        }

        if(goDown){
            y++;
            movedY++;
            if(movedY > image.getHeight(null))
            {
                goDown = false;
                movedY = 0;
                wentDown++;
                if(wentDown%2==0)
                    speed++;
            }
        } else {
            x += speed * direction;
            movedX+= speed;
        }

        if(random.nextInt()%bombChance==1 && y < 150)
            bombs.add(new Bomb(x, y));
    }

    public ArrayList getBombs(){
        return bombs;
    }
}


A primera vista, notamos muchas variables, la mayoria son faciles de deducir que hacen pero las demas las ire explicando a medida que explique que hacen los aliens.
Para poder explicar esta clase es necesario que entiendas como se mueven los aliens. Los aliens tienen un rango, se mueven hacia la derecha ese rango, luego bajan, y se mueven hacia la izquierda, el mismo rango, y repiten. Ese movimiento lo hacemos en el update, para eso necesitamos muchas variables, creo la mayoria son de esa logica. Otra cosa que hacemos es un random para tirar las bombas, constantemente genera numeros aleatorios hasta que tira una bomba cuando es igual a uno que pidamos dentro del rango. Lo puse en 700, es decir, tenes una chance en 700 de que tire la bomba, pero con todos los aliens y la velocidad que se actualiza, es bastante seguido. Tiene un metodo getBombs, similar al de ship para los lasers, y el resto son puros sets y gets.

Finalmente, llegamos a la clase Board, la que junta todas las demas clases, aca esta

package spaceinvaders;

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;

import java.util.ArrayList;

import java.awt.Font;

/**
 *
 * @author fede
 */
public class Board extends JPanel implements ActionListener {
    private Timer timer;
    private Ship ship;
    private Alien alien[][];
    private final int ALIENFILES, ALIENROWS, ALIEN_STARTX, ALIEN_STARTY, ALIEN_PADDING;
    private Font font;
    private String msg;
    private int aliensLeft;
    private boolean gameEnded;
    public Board(){
        setDoubleBuffered(true);
        setBackground(Color.white);
        setFocusable(true);
        addKeyListener(new Listener());
        
        ship = new Ship();
        
        // Aliens
        ALIENFILES = 6;
        ALIENROWS = 3;
        ALIEN_STARTX = 20;
        ALIEN_STARTY = 20;
        ALIEN_PADDING = 3;
        alien = new Alien[ALIENFILES][ALIENROWS];
        for(int i = 0; i < ALIENFILES; i++)
            for(int j = 0; j < ALIENROWS; j++){
                alien[i][j] = new Alien(ALIEN_STARTX, ALIEN_STARTY);
                alien[i][j].setX(ALIEN_STARTX + i*alien[i][j].getImage().getWidth(null) + i*ALIEN_PADDING);
                alien[i][j].setY(ALIEN_STARTY + j*alien[i][j].getImage().getHeight(null) + j*ALIEN_PADDING);
            }

        aliensLeft = ALIENFILES * ALIENROWS;
        font = new Font("Verdana", Font.PLAIN, 12);
        msg = "Aliens left: " + aliensLeft;

        gameEnded = false;

        timer = new Timer(15, this);
        timer.start();
    }

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

        // Draw
        g2d.drawImage(ship.getImage(), ship.getX(), ship.getY(), this);

        // Draw Lasers
        ArrayList lasers = ship.getLasers();
        for(int i = 0; i < lasers.size(); i++){
            Laser l = lasers.get(i);
            g2d.drawImage(l.getImage(), l.getX(), l.getY(), this);
        }

        // Draw aliens
        for(int i = 0; i < ALIENFILES; i++)
            for(int j = 0; j < ALIENROWS; j++){
                if(alien[i][j].isVisible())
                    g2d.drawImage(alien[i][j].getImage(), alien[i][j].getX(),
                            alien[i][j].getY(), this);
                ArrayList bombs = alien[i][j].getBombs();
                for(int k=0; k < bombs.size(); k++){
                    Bomb bomb = bombs.get(k);
                    g2d.drawImage(bomb.getImage(), bomb.getX(), bomb.getY(), this);
                }
            }

        
        // Draw text
        g2d.setColor(Color.black);
        g2d.setFont(font);
        g2d.drawString(msg, 3, 12);

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

    public void actionPerformed(ActionEvent e){
        // Updates
        ship.logic();
        
        // Lasers
        ArrayList lasers = ship.getLasers();
        for(int i = 0; i < lasers.size(); i++){
            Laser l = lasers.get(i);
            if(l.isVisible())
                l.update();
            else
                lasers.remove(i);
        }

        // Aliens
        for(int i = 0; i < ALIENFILES; i++){
            for(int j = 0; j < ALIENROWS; j++){
                alien[i][j].update();
                if(alien[i][j].getY() >= 250)
                    gameOver(0); // 0 = lose, 1 = win

                // Aliens Bombs
                ArrayList bombs = alien[i][j].getBombs();
                for(int k=0; k < bombs.size(); k++){
                    Bomb bomb = bombs.get(k);
                    if(bomb.isVisible())
                        bomb.update();
                    else
                        bombs.remove(k);

                    if(bomb.getBounds().intersects(ship.getBounds()))
                    {
                        bomb.setVisible(false);
                        gameOver(0);
                    }
                }

                //Hit Test
                for(int li = 0; li < lasers.size(); li++){
                    Laser l = lasers.get(li);
                    if(l.getBounds().intersects(alien[i][j].getBounds()) && l.isVisible() && alien[i][j].isVisible()){
                        alien[i][j].setVisible(false);
                        l.setVisible(false);
                        aliensLeft--;
                        if(aliensLeft <= 0)
                            gameOver(1);
                    }
                }
            }
        }

        if(!gameEnded)
            msg = "Aliens left: " + aliensLeft;

        repaint();
    }

    private class Listener extends KeyAdapter{
        @Override
        public void keyPressed(KeyEvent e){
            ship.keyPressed(e);
            if(e.getKeyCode() == KeyEvent.VK_ENTER){
                if(gameEnded){
                    for(int i = 0; i < ALIENFILES; i++)
                        for(int j = 0; j < ALIENROWS; j++){
                            alien[i][j] = null;
                            alien[i][j] = new Alien(ALIEN_STARTX, ALIEN_STARTY);
                            alien[i][j].setX(ALIEN_STARTX + i*alien[i][j].getImage().getWidth(null) + i*ALIEN_PADDING);
                            alien[i][j].setY(ALIEN_STARTY + j*alien[i][j].getImage().getHeight(null) + j*ALIEN_PADDING);
                        }
                    gameEnded = false;
                    aliensLeft = ALIENFILES * ALIENROWS;
                    timer.start();
                }
            }
        }

        @Override
        public void keyReleased(KeyEvent e){
            ship.keyReleased(e);
        }
    }

    public void gameOver(int status){
        gameEnded = true;
        if(status == 0)
            msg = "You lose!";
        else
            msg = "You won!";
        msg += " - Press [ENTER] to play again.";
        timer.stop();
    }
}


Bueno, la clase mas larga, Board, los conceptos estan todos explicados creo yo, si hay algo sin explicar por favor contactenme asi lo arreglo.

Ciertamente hay cosas que los pueden confundir pero seria puro Java. Uso una array bidimensional para los aliens, asi que especifico la cantidad de filas y columnas que quiero. Los bucles mas complicados serian los de deteccion de colision, que tenemos que comparar todos los lasers con todos los aliens, quizas sea eso lo que mas confunda pero es bastante simple, solo prestar atencion a como se hace. Recomiendo ver el pong, y todos los conceptos, si practicaron con eso, no deberia haber mayores inconvenientes con esto.

Para cualquier duda o sugerencia contactenme!

Descargar: de mediafire

viernes, 17 de abril de 2009

Pong

Muy bien! Suficiente teoria, es hora de hacer algo, como primer juego, me gusta hacer un Pong, me parece el mas simple de todos, y engloba bien hit test, input y animacion, que es lo que vimos hasta ahora.
Creamos un nuevo proyecto, yo lo llame Pong, y el package "pong".
Veamos la clas Main, la "principal" del proyecto, esta solo tendra la ventana y creara un nuevo Board.

package pong;

import javax.swing.JFrame;

/**
*
* @author fede
*/
public class Main extends JFrame {
public Main(){
add(new Board());
setTitle("Basic JPong");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(300,280);
setLocationRelativeTo(null);
setVisible(true);
setResizable(false);
}

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


Como veran es bastante parecido a lo que veniamos haciendo, solo cambie el tamaño y el titulo.
Veamos ahora la clase Player, esa clase es la encargada de las "barritas" que seran los jugadores, en este caso usaremos dos, para hacerlo de dos jugadores, aunque podrias hacer otra clase para el jugador dos, que sea inteligencia artificial y juege solo, asi seria de un jugador. En este ejemplo lo haremos de dos jugadores.
Primero, las librerias que importamos, ellas son

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

// Para tener las constantes de las teclas y el tipo KeyEvent, despues se manda por argumento bien
import java.awt.event.KeyEvent;


Ahora hacemos las variables que usaremos (esto yo lo hize antes, por eso se todas las variables que usare, por lo general esto lo modifico mucho, al igual que los 'imports')

private Image image;
private int x,y,dy,upKey,downKey,width,height,score;

private final int SPEED = 4;


En el constructor iniciamos los valores necesarios, y luego es mayormente get's y set's. Aca les dejo la clase Player

package pong;

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

import java.awt.event.KeyEvent;

/**
*
* @author fede
*/
public class Player {
private Image image;
private int x,y,dy,upKey,downKey,width,height,score;

private final int SPEED = 4;

public Player(int x, int y, String img, int upKey, int downKey){
ImageIcon ii = new ImageIcon(this.getClass().getResource(img));
image = ii.getImage();
this.x = x; // Es irrelevante para getear, solo se mueve arriba y abajo la barra
this.y = y;
dy = 0;
this.upKey = upKey;
this.downKey = downKey;
width = image.getWidth(null);
height = image.getHeight(null);
score = 0;
}

public Rectangle getBounds(){
return new Rectangle(x,y,width,height);
}

public Image getImage(){
return image;
}

public int getY(){
return y;
}

public int getX(){
return x;
}

public int getScore(){
return score;
}

public void setScore(int score){
this.score = score;
}

public void move(){
if((y > 0 && dy <> 0))
y += dy;
}

public void keyPressed(KeyEvent e){
int key = e.getKeyCode();

if(key == upKey)
dy = SPEED * -1;
else if(key == downKey)
dy = SPEED;
}

public void keyReleased(KeyEvent e){
dy = 0;
}
}


La funcion keyPressed se llamara cuando en board se presione alguna tecla, y mandamos la tecla por argumento, asi podemos evaluar bien que hacer con ella en nuestra clase, lo imsmo para keyReleased. La funcion move se llama en el update (en este caso, cada 5ms) y en ella actualizamos la posicion del sprite.
Ahora Ball.java. Esta clase es bastante parecida a la anterior por lo que no explicare mucho.

package pong;

import java.awt.Image;
import java.awt.Rectangle;

import javax.swing.ImageIcon;


/**
*
* @author fede
*/
public class Ball {
private Image image;
private int x,y,dx,dy,width,height;
private final int SPEED = 2;
public Ball(){
ImageIcon ii = new ImageIcon(this.getClass().getResource("ball.png"));
image = ii.getImage();
x = y = 30;
dy = dx = SPEED;
width = image.getWidth(null);
height = image.getHeight(null);
}

public Rectangle getBounds(){
return new Rectangle(x,y,width,height);
}

public Image getImage(){
return image;
}

public int getX(){
return x;
}

public int getY(){
return y;
}

public int getDy(){
return dy;
}

public int getDx(){
return dx;
}

public void setDx(int dx){
this.dx = dx;
}

public void setDy(int dy){
this.dy = dy;
}

public void setPosition(int x, int y, boolean invert){
this.x = x;
this.y = y;
if(invert){
dy*=-1;
dx*=-1;
}
}

public void move(){
y+=dy;
x+=dx;
}
}


Como veran solo importamos lo necesario para usar la imagen y rectangles. La mayoria de las funciones son get's y set's, setPosition se usa para resetear facimente la posicion de la pelota, move actualiza la posicion, igual que en Player, el resto es solo iniciar variables en el constructor.
Finalmente veremos la clase Board. Es muy importante que hallan seguido el blog hasta ahora ya que utiliza todo lo que vimos hasta ahora.

package pong;

import javax.swing.JPanel;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.Rectangle; // Para collision detection

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

// Para leer las teclas
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; // interface con un metodo, actionPerformed
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

// Para escribir
import java.awt.Font;
import java.awt.FontMetrics;

/**
*
* @author fede
*/
public class Board extends JPanel implements ActionListener {
private Player player1, player2;
private Timer timer;
private Ball ball;
private Font small;
private String msg;

private static final int MAX_SCORE = 10;

public Board(){
setDoubleBuffered(true);
setBackground(Color.white);
setFocusable(true);
addKeyListener(new TAdapter());

player1 = new Player(3,100,"bar.png", KeyEvent.VK_UP, KeyEvent.VK_DOWN);
player2 = new Player(285,100, "bar.png", KeyEvent.VK_W, KeyEvent.VK_S);
ball = new Ball();

// font
small = new Font("Verdana", Font.PLAIN, 12);
FontMetrics mtr = this.getFontMetrics(small);

// Timer
timer = new Timer(5, this);
timer.start();
}

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

Graphics2D g2d = (Graphics2D)g;

// Paint
g2d.drawImage(player1.getImage(), player1.getX(), player1.getY(), this);
g2d.drawImage(player2.getImage(), player2.getX(), player2.getY(), this);
g2d.drawImage(ball.getImage(), ball.getX(), ball.getY(), this);

// Write
g2d.setColor(Color.black);
g2d.setFont(small);
if(isGameOver()==0)
msg = "P1: "+player1.getScore()+" - P2: "+player2.getScore();
g2d.drawString(msg, 10, 13);

// Timer
Toolkit.getDefaultToolkit().sync();

g.dispose();
}

public void actionPerformed(ActionEvent e){ // Esto updatea el timer
// Logic
player1.move();
player2.move();
ball.move();

// Collision Detection
Rectangle br = ball.getBounds();
Rectangle p1r = player1.getBounds();
Rectangle p2r = player2.getBounds();

if(ball.getX() <>290 || br.intersects(p1r) || br.intersects(p2r))
ball.setDx(ball.getDx()*-1);
if(ball.getY() <>250)
ball.setDy(ball.getDy()*-1);

// Aver score
if(ball.getX() <= 0){
player2.setScore(player2.getScore()+1);
ball.setPosition(150,100, true);
}
if(ball.getX() >= 285){
player1.setScore(player1.getScore()+1);
ball.setPosition(150,100, true);
}
if(isGameOver()>0){
msg = "Player " + isGameOver() + " wins - Press any key to play again";
ball.setDx(0);
ball.setDy(0);
}

repaint();
}

private int isGameOver(){
if(player1.getScore() >= MAX_SCORE || player2.getScore() >= MAX_SCORE){
if(player1.getScore()>player2.getScore())
return 1;
else
return 2;
}
else
return 0;
}

private class TAdapter extends KeyAdapter {
public void keyPressed(KeyEvent e){
player1.keyPressed(e);
player2.keyPressed(e);
if(isGameOver()>0){
player1.setScore(0);
player2.setScore(0);
ball = new Ball();
}
}
public void keyReleased(KeyEvent e){
player1.keyReleased(e);
player2.keyReleased(e);
}
}
}


Primero que nada vemos que usamos muchas librerias, de las cuales todas estan "explicadas" y usadas anteriormente, excepto las que usamos para escribir, ellas son

import java.awt.Font;
import java.awt.FontMetrics;

Para escribir, en el draw simplemente hacemos

g2d.setColor(Color.black);
g2d.setFont(small);
if(isGameOver()==0)
msg = "P1: "+player1.getScore()+" - P2: "+player2.getScore();
g2d.drawString(msg, 10, 13);


setColor es en el color que vamos a escribir, en este caso como el fondo es blanco, usaremos negro. setFont usa una font que definimos en el constructor, finalmente drawString escribe la string msg en las coordenadas 10, 13.
Para hacer una font, en el constructor pusimos esto

small = new Font("Verdana", Font.PLAIN, 12);
FontMetrics mtr = this.getFontMetrics(small);


Luego tenemos nuestro metodo paint, ahi dibujamos los players, la pelota y escribimos.
En actionPerformed hacemos las actualizaciones move() de los players y de la pelota, ademas nos fijamos por colisiones y por si el juego acabo.
Finalmente en keyAdapter, mandamos las teclas apretadas a que se procesen en los objetos player.
Si venian siguiendo el blog hasta ahora, espero este claro todo lo que hize, cualquier duda pueden contactarme :)



Eso fue Pong.
Codigo Fuente: de 4shared

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

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.

Sobre Java Pragmatico

Antes que nada, Hola! Soy Federico Ramirez, pero muchos me conoceran como fedekun.
Para mas informacion sobre mi pueden ver mi perfil aca...
Voy a comentarles un poco el motivo de este blog, es mas que nada para ayudar a esos programadores que saben algo de programacion o java y quieren empezar a hacer juegos con java.
Por el momento me enfoco en juegos para el escritorio, para celulares si todo sale bien, seguira en un futuro.

Porque Java Pragmatico? Es todo un tecnisismo supongo...
Pragmatismo: Doctrina que adopta como criterio de verdad la utilidad práctica, identificando lo verdadero con lo útil.
Como veran, la idea es la practicidad, y evaluar los resultados y en funcion de eso, ya que "[...] básicamente se puede decir que, ya que el pragmatismo se basa en establecer un significado a las cosas a través de las consecuencias, se basa en juicios a posterioridad y evita todo prejuicio."

El contenido se dividira (por ahora) en Conceptos y Aplicacion. En conceptos van todas las ideas y explicaciones, en Aplicaciones los juegos armados y semi-explicados.

Espero les sea util!