[Pong] III: Atasco temporal

Según mi maestro Jedi: nada es comparable a la salvaje libertad que te proporciona programar sin pantalones. Por ello, todo el código que veréis a continuación ha sido escrito en calzoncillos. Se que este dato no tiene ninguna relevancia, es más, es totalmente innecesario, pero he descubierto que me divierte comenzar mis entradas con cualquier tipo de detalle de dudoso gusto. Bien, podemos continuar.

En la anterior entrada me quedé con una pelota cuadrada que se movía por el frame. El siguiente paso es construir las raquetas que golpearán esta pelota. He tardado bastante en volver a escribir porque me he encontrado algunos problemas que aún no he terminado de solucionar por lo que advierto que este código no debe ser tomado como válido en su totalidad y además aprovecho a hacer un llamamiento a todo programador que pueda encontrar una manera de solucionarlo ^_^

En primer lugar empezaremos por lo fácil. Iremos a nuestra clase Configuracion donde añadimos el tamaño de las raquetas. Como a estas alturas esto ya no necesita ninguna explicación, paso directamente a mostrar el código:

package Configuracion;

/**
 * Configuración del tablero del Pong
 * Incluye los atributos finales X e Y que representarán la longitud del tablero
 * También incluye el tamaño de la pelota y su velocidad de movimiento en milisegundos
 * @author Phil Spectrum
 */
public class Configuracion
{
    private final int X = 500;
    private final int Y = 325;
    private final int LADO_PELOTA = 15;
    private final int ANCHO_RAQUETA = 10;
    private final int LARGO_RAQUETA = 45;
    private int velocidad = 3;

    public Configuracion()
    {
    }

    public int getX()
    {
        return X;
    }

    public int getY()
    {
        return Y;
    }

    public int getLADO_PELOTA()
    {
        return LADO_PELOTA;
    }

    public int getVelocidad()
    {
        return velocidad;
    }

    public void setVelocidad(int velocidad)
    {
        this.velocidad = velocidad;
    }

    public int getANCHO_RAQUETA()
    {
        return ANCHO_RAQUETA;
    }

    public int getLARGO_RAQUETA()
    {
        return LARGO_RAQUETA;
    }
}

A continuación hago una nueva clase en el paquete Grafico. Será la clase Raqueta que utilizo para crear los dos objetos de este tipo con los que golpearemos la pelota.

Al igual que en la clase Pelota, crearemos las variables x e y que utilizaremos en el constructor para definir la posición en la que pondremos la raqueta. Otros dos atributos a los que he llamado arriba y abajo contendrán el código de las teclas que utilizaremos para el movimiento de la raqueta. También como en la clase pelota utilizo un atributo ya que será el número de píxeles que se moverá la raqueta y una llamada a la clase Configuracion con el atributo config.

Creo con ello el constructor y después el método mover() con el que sumo el valor de ya al valor de y poniendo el máximo en los limites del frame (igual que ocurría con el valor en la pelota, aquí hace falta restar 25 píxeles para definir el límite. En el caso he restado 28 porque me parece que quedaba más correcto gráficamente).

He utilizado también un método dibujarRaqueta() que funciona exactamente igual que el de la clase Pelota.

keyReleased() y keyPressed() son dos métodos fantabulosos que indican lo que ocurre cuando se levanta una tecla o se pulsa respectivamente. En este caso simplemente alteraremos el valor de ya en 1 si lo que queremos es que la raqueta baje o -1 si lo que queremos es que suba.

Por último, los getters de x e y que necesitaremos posteriormente y el método getBounds() que devolverá un rectángulo alrededor de la posición que esté ocupando la raqueta, lo utilizaremos también después, cuando comprobemos si la pelota y esta colisionan. Todo junto nos queda así de bonico:

package Grafico;

import Configuracion.Configuracion;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;

/**
 * Clase Raqueta que definirá las pautas para poder crear los dos objetos de
 * este tipo.
 * @author Phil Spectrum
 */
public class Raqueta
{
    private int x;
    private int y;
    private int ya;
    private int arriba;
    private int abajo;
    private Configuracion config;

    /**
     * En su constructor pedimos los valores para los atributos que necesitaremos
     * En el caso de "x", "y", "arriba" y "abajo" serán diferentes para cada jugador
     * @param x la posición x que ocupará la raqueta en el frame
     * @param y la posición y que ocupará la raqueta en el frame
     * @param arriba el código de la tecla que hará que la raqueta se mueva hacia arriba
     * @param abajo el código de la tecla que hará que la raqueta se mueva hacia abajo
     * @param config llamada a la clase Configuración donde se define el tamaño
     */
    public Raqueta(int x, int y, int arriba, int abajo, Configuracion config)
    {
        this.x = x;
        this.y = y;
        this.arriba = arriba;
        this.abajo = abajo;
        this.config = config;
    }

    /**
     * Método que controla el movimiento de la raqueta
     * Permite sumar el valor del atributo "ya" al de "y" siempre y cuando
     * la raqueta no haya pasado de los límites del frame.
     */
    public void mover()
    {
        //Aquí resto 28 pixels igual que los 25 de la clase pelota
        if (y + ya > 0 & y + ya < (config.getY() - 28) - config.getLARGO_RAQUETA())
        {
            y = y + ya;
        }
    }

    /**
     * Método para dibujar la raqueta
     * @param g le envia los gráficos
     */
    public void dibujarRaqueta(Graphics2D g)
    {
        g.setColor(Color.GRAY);
        g.fillRect(x, y, config.getANCHO_RAQUETA(), config.getLARGO_RAQUETA());
    }

    /**
     * Con este método, cuando el usuario levanta la tecla el movimiento se para
     * ya que el atributo "ya" se iguala a 0
     * @param e captura un evento de teclado
     */
    public void keyReleased(KeyEvent e)
    {
        ya = 0;
    }

    /**
     * Con keyPressed indicamos lo que queremos hacer cuando se pulsa una tecla
     * En este caso, las teclas irán definidas por los atributos "arriba" y "abajo"
     */
    public void keyPressed(KeyEvent e)
    {
        if (e.getKeyCode() == arriba)
        {
            ya = - 1;
        }
        else if (e.getKeyCode() == abajo)
        {
            ya = 1;
        }
    }

    public int getX()
    {
        return x;
    }

    public int getY()
    {
        return y;
    }

    /**
     * Con este método crearemos un rectángulo alrededor de la raqueta que podremos usar
     * para comprobar las colisiones
     * @return el rectángulo
     */
    public Rectangle getBounds()
    {
        return new Rectangle(x, y, config.getANCHO_RAQUETA(), config.getLARGO_RAQUETA());
    }

}

La clase Pelota por su parte la mantendré igual, con la salvedad de que añadiré un setter para el atributo moverX que voy a utilizar posteriormente y también otro método getBounds() que será para crear otro rectángulo, esta vez para la pelota, que utilizaremos para ver si colisiona con el que hemos hecho antes para la raqueta. Total, que nos queda esto:

package Grafico;

import Configuracion.Configuracion;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;

/**
 * Clase Pelota
 * Incluye lo necesario para dibujarla y su movimiento en el frame
 * @author Phil Spectrum
 */
public class Pelota
{
    private int x = 100;
    private int y = 100;
    private int moverX = 1;
    private int moverY = 1;
    private Configuracion config;

    public Pelota(Configuracion config)
    {
        this.config = config;
    }

    /**
     * Método que especifica las caracterísitcas de la pelota para dibujarla
     * @param g le enviamos los gráficos a través de este parámetro
     */
    public void dibujarPelota(Graphics2D g)
    {
        g.setColor(Color.GRAY);
        g.fillRect(x, y, config.getLADO_PELOTA(), config.getLADO_PELOTA());
    }

    /**
     * Método mediante el que se mueve la pelota
     * Cuando esta llega a los bordes del frame altera la dirección de las coordenadas
     */
    public void mover()
    {
        if(x + moverX < 0)
        {
            moverX = 1;
        }
        else if (x + moverX > config.getX() - config.getLADO_PELOTA())
        {
            moverX = -1;
        }
        else if(y + moverY < 0)
        {
            moverY = 1;
        }
        //Aquí no se por qué hay que añadirle 25px más, si no se sale del frame
        else if(y + moverY > config.getY() - (config.getLADO_PELOTA()+25))
        {
            moverY = -1;
        }
        x = x + moverX;
        y = y + moverY;
    }

    public Rectangle getBounds()
    {
        return new Rectangle(x, y, config.getLADO_PELOTA(), config.getLADO_PELOTA());
    }

    public void setMoverX(int moverX) {
        this.moverX = moverX;
    }

}

Vamos con la clase gorda, la que dejo para el final, la clase Pong, aquí viene donde la matan…

Bueno, en primer lugar hemos de construir las dos raquetas que queremos que aparezcan en el frame. Para ello tenemos que pasarlas los parámetros que hemos definido antes. Los dos primeros serán los valores x e y que serán las coordenadas que ocuparán en el frame. Para los siguientes, necesitamos el código numérico (¿recordáis lo que os comentaba antes de los atributos arriba y abajo?) de las teclas que queremos usar. He decidido que quiero que una de las raquetas se mueva con las flechas del teclado y la otra con la w y la s. Como no soy tan friki como para conocerme de memoria el código numérico del teclado he recurrido a esta página de la web de Adobe. Que majetes los de Adobe mire usté. Con ello ya puedo crear los dos objetos jugadorA y jugadorB que representaremos con las dos raqueticas.

Estas raqueticas las vamos a añadir en el método paint() al igual que a la pelota, llamando al dibujarRaqueta() que creamos anteriormente y pasandole el parámetro de los gráficos.

El método mover() simplemente engloba las llamadas a los métodos de movimiento de las raquetas (ahora mismo estoy pensando que podría llamar también al de la pelota).

Es necesario también detectar la colisión entre la pelota y raquetas. Para ello, desde el actionPerformed() del timer crearemos tres objetos nuevos que serán los tres rectángulos que se forman de las dos raquetas y la pelota con el getBounds(). Comprobamos entonces si alguno de los de las raquetas choca (intersects) con el de la pelota, en cuyo caso cambiamos el valor del atributo moverX de la misma por el valor contrario al que tenga (si choca con la raqueta derecha le daremos valor -1, si choca con la izquierda 1).

Bien, ya lo tenemos todo, ahora falta la parte jodida, el punto en el que la he cagado miserablemente pero del que de momento no encuentro solución: la lectura del teclado para el movimiento de las palas.

Lo que por el momento he hecho es añadir al constructor de Pong un listener de teclado desde el que llamo a los keyPressed() y keyReleased() de las raquetas. Esto en principio sería lo correcto si se tratase de un juego para un único jugador, es decir: que solo hubiese una raqueta. El problema es que al haber dos, el evento de una de ellas pisa al de la otra, lo que se traduce en que cuando uno de los jugadores está moviendo su raqueta, el otro no puede mover la suya. A lo mejor esto es una nueva versión mierder del Pong, pero no es mi intención así que trataré de solucionarlo de una forma u otra.

package Grafico;

import Configuracion.Configuracion;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

/**
 * Clase Pong que incluye el método main desde donde se inicia el programa
 * La clase implementa ActionListener para trabajar con sus métodos
 * @author Phil Spectrum
 */
public class Pong extends JPanel implements ActionListener
{
    private Configuracion config = new Configuracion();
    private Pelota pelota = new Pelota(config);
    private Raqueta jugadorA = new Raqueta(25, 125, 87, 83, config);
    private Raqueta jugadorB = new Raqueta(460, 125, 38, 40, config);
    private Timer timer;

    //En el constructor iniciamos el Timer y el Listener del teclado
    public Pong()
    {
        timer = new Timer(config.getVelocidad(), this);
        timer.start();
        addKeyListener(new KeyListener()
        {
                @Override
                public void keyTyped(KeyEvent e)
                {
                }

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

                @Override
                public void keyPressed(KeyEvent e)
                {
                        jugadorA.keyPressed(e);
                        jugadorB.keyPressed(e);
                }
            });
        setFocusable(true);
    }

    /**
     * Método que llama al método mover() de Raqueta
     */
    private void mover()
    {
        jugadorA.mover();
        jugadorB.mover();
    }

    //Llamamos al método paint para dibujar los gráficos en el frame
    @Override
    public void paint(Graphics g)
    {
        super.paint(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        pelota.dibujarPelota(g2d);
        jugadorA.dibujarRaqueta(g2d);
        jugadorB.dibujarRaqueta(g2d);
    }

    public static void main(String[] args)
    {
        JFrame frame = new JFrame("Pong!!");
        Configuracion config = new Configuracion();
        Pong pong = new Pong();
        pong.setBackground(Color.black);
        frame.add(pong);
        frame.setSize(config.getX(), config.getY());
        frame.setLocation(500, 150);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
    }

    /**
     * Aquí especificamos lo que va a hacer el Timer
     * En este caso, llama el método mover() de la clase Pelota y repinta la pantalla
     * También llama al método mover() de la misma clase Pong
     * @param e le enviamos un evento, en este caso el Timer
     */
    @Override
    public void actionPerformed(ActionEvent e)
    {
        if (e.getSource() == timer)
        {
            Rectangle rA = jugadorA.getBounds();
            Rectangle rB = jugadorB.getBounds();
            Rectangle rP = pelota.getBounds();
            if (rA.intersects(rP))
            {
                pelota.setMoverX(1);
            }
            else if (rB.intersects(rP))
            {
                pelota.setMoverX(-1);
            }
            pelota.mover();
            this.mover();
            this.repaint();
        }
    }
}

Posibles soluciones:

Cutrada padre: hacer que una de las raquetas se mueva con el teclado y la otra con el ratón. No mola, simplemente porque no es lo que buscaba.

– Ideaquenosecomollevaracabo: crear dos hilos independientes para cada raqueta que puedan funcionar sin pisar el evento el uno al otro.

Ahí lo dejo, seguiré investigando mientras preparo tortilla de patatas.

tortilla de patatas

Anuncios
Esta entrada fue publicada en Happy Coding y etiquetada , , , , , . Guarda el enlace permanente.

5 respuestas a [Pong] III: Atasco temporal

  1. Juan Cabrerizo dijo:

    Yo voto por dos hilos, uno que sólo escuche si las teclas pulsadas son ws y el otro que este atento a las flechas

    • La cuestión es ¿cómo se haría de esa manera? he estado buscando información sobre el tema de hilos, pero no logro entender más allá de lo teórico como aplicarlo. O es algo especialmente complejo o simplemente no he dado con un sitio que explique exactamente lo que busco 🙂

      Saludos!

  2. Edu dijo:

    Yo también pensaba que la única solución pasaba por crear un hilo por raqueta… Pero no es necesario… En cuanto el “notario” sr. Incera de fe del ganador de la tortilla pondrá el código con la solucuón que le he propuesto.

  3. Pingback: [Pong] IV: Tortipapas de poder +1 | The Phil Spectrum Project

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s