5 min read

Programando Pong con Arduino

Programando Pong con Arduino

Parte 1.

Hoy reviviremos un clásico de los videojuegos Pong, el Juego que empezó con una de las industrias más grandes de la actualidad.

Este proyecto está divido en dos partes, por un lado crearemos la interfaz usando processing y el hardware lo haremos con arduino.


Lo primero que haremos será definir los elementos que componen este juego. Por una parte tenemos la pelota (Ball) y por otro lado cada jugador tiene una paleta (Pad) que usará para golpear la pelota cada vez que se le acerque.
Nuestro último componente es el tablero de juego (Board), este se encargaría de validar cuando un jugador ha ganado y llevar también el puntaje.

Empecemos entonces definiendo el objeto Ball.

  
class Ball {
  PVector position;
  PVector velocity;
  int r;
  
  Ball(){
    position = new PVector(width/2, height/2);
    r=10;
    float [] randomVelocity = randomVelocityValues();
    velocity = new PVector(randomVelocity[0], randomVelocity[1]);
  }
  
  void move(){
    position.add(velocity);
  }
    
  float[] randomVelocityValues(){
    float x,y;
    if(random(2) < 1) {
      x = (random(1) + 3) * -1;
    } else {
      x = (random(1) + 3);
    }
    if(random(2) < 1) {
       y = (random(1) + 2) * -1; 
    } else {
       y = (random(1) + 2);
    }
    float[] out = {x, y};
    return out;
  }
  void display(){
    ellipse(position.x, position.y, r, r);
  } 
}

En la primera parte del código lo que hacemos es definir qué propiedades tiene la pelota, cuál es su posición inicial y con que velocidad se moverá, además de definir su tamaño. Podemos ver también que hay un factor aleatorio al momento de definir su velocidad, lo que nos permite que su velocidad no siempre sea constante, y por otra parte que su dirección sea aleatoria, a veces puede dirigirse hacia la derecha o a la izquierda, hacia arriba o hacia abajo.

Junto con la instrucción move haremos que nuestra pelota se empiece a mover y junto a la función display veremos el resultado en la pantalla.

Ahora en nuestro sketch principal vamos a crear una instancia de la pelota y procederemos a ver el resultado

  
Ball ball;

void setup() {
  size(600, 400);
  ball = new Ball();
}

void draw() {
  background(0);
  ball.display();
  ball.move();
} 

0:00
/0:08

video de una pelota moviendose en la pantalla de processing

De momento solo tenemos la pelota que va de un lado a otro con un arranque aleatorio, ahora crearemos el objeto Pad que usará el jugador para interactuar con la pelota

  
class Pad {
  PVector position;
  int padHeight;
  int padWidth;
  float center;
  
  Pad(int a, int b, int c,int d){
    position = new PVector(a,b);
    padWidth = c;
    padHeight = d;
    center = padHeight /2;
  }
  
  void display() {
    rect(position.x, position.y - center, padWidth, padHeight);
  }
  void updatePosition(float y) {
    if(y + center < height && y > center ) {
      position.y = y;
    }
  }
  float getCurrentPosition() {
    return position.y;
  }
}

Aquí hemos definido la paleta como un rectángulo, junto a su definición tenemos dos funciones updatePosition y getCurrentPosition. estas dos funciones nos serán útiles para que podamos actualizar la posición de este elemento, si integramos la paleta al sketch principal tendremos lo siguiente:

  
Ball ball;
Pad pad;

void setup() {
  size(600, 400);
  ball = new Ball();
  pad = new Pad(0, height/2, 4, height/4 );
}

void draw() {
  background(0);
  ball.display();
  ball.move();
  pad.display();
}

pelota y paleta en la pantalla de processing

De los tres elementos que hemos definido al inicio nos hace falta el tablero. En el siguiente bloque definiremos el objeto Board

  
class Board {
  Board(){
  }
  void display(){
    rect(0, 0, width, 2);
    rect(0, height - 2, width, 2);
  }
  void detectCollision(Ball ball){
    float ballLeftEdge = ball.position.x - ball.r;
    float ballRightEdge = ball.position.x + ball.r;
    float ballTopEdge = ball.position.y - ball.r;
    float ballBottomEdge = ball.position.y + ball.r;
    if(ball.directionY() == "UP" & ballBottomEdge > height){
      ball.bounceY();
    }
    if(ball.directionY() == "DOWN" & ballTopEdge < 0){
      ball.bounceY();
    }
    if(ballRightEdge < 0) {
      ball.restore();
    }
    if(ballLeftEdge > width) {
      ball.restore();
    }
  }
}

Aquí estamos dibujando un par de "muros" en la parte superior e inferior de la pantalla y hemos incluido una función llamada detectCollision, esta función la usaremos para aplicar ciertas reglas en el Juego, si la "pelota" golpea un muro esta rebotará, pero si la pelota se va fuera de la pantalla, reiniciamos la posición de la "pelota".

Un detalle importante a notar es que la función "detectCollision" usa unas funciones que no hemos definido antes en la "pelota", así que vamos a agregar esas nuevas funciones en el Objeto Ball.

  
  void bounceY() {
    velocity.y *= -1;
  }
    
  void bounceX() {
    velocity.x *= -1;
  }
    
  void restore() {
    position.x = width/2;
    position.y = height/2;
    float [] randomVelocity = randomVelocityValues();
    velocity.x = randomVelocity[0];
    velocity.y =randomVelocity[1];
  }
    
  String directionY(){
    if(velocity.y > 0){
      return "UP";
    } else {
      return "DOWN";
    }
  }
    
  String directionX(){
    if(velocity.x > 0){
      return "RIGHT";
    } else {
      return "LEFT";
    }
  }

La función bounce es bastante simple, para cambiar la dirección lo que hacemos es multiplicar el componente X o Y por -1 invirtiendo su sentido.

Restore lo que hará será ubicar la "pelota" en su posición original y asignarle una velocidad aleatoria, lo que hará que hace que la "pelota" vuelva a moverse en una dirección aleatoria.

Finalmente la función direction, calcula en que dirección se mueve la pelota.

Ahora juntemos lo que tenemos hasta el momento en el sketch principal y veamos el resultado.

0:00
/0:10

pelota rebotando en el tablero y reiniciándose cuando sale del area

Poco a poco vamos teniendo algo cercano a un videojuego. pero aun tenemos que hacer algunas mejoras. primero vamos a agregar una lógica dentro de la paleta para que pueda rebotar la pelota si se llegan a chocar, además agregaremos un segundo jugador. Pero para no complicarnos implementando un bot que controle el segundo jugador dejaremos el segundo jugador fijo con una altura igual al de la ventana del sketch.

Lógica para detectar colisiones en la paleta


  void detectCollision(Ball ball){
    float ballCenter = ball.position.x;
    float ballLeftEdge = ballCenter - ball.r;
    float ballRightEdge = ballCenter + ball.r;
    
    float topEdge = position.y - center;
    float bottomEdge = position.y + center;
    float leftEdge = position.x;
    float rightEdge = position.x + padWidth;
  
    boolean leftCondition = ballCenter > rightEdge &&  ballLeftEdge < rightEdge; 
    boolean rightCondition = ballCenter < leftEdge &&  ballRightEdge > leftEdge;
    boolean topBottomCondition = topEdge < ball.position.y && bottomEdge > ball.position.y;
    
    if((leftCondition && topBottomCondition) || (rightCondition && topBottomCondition) ){
      ball.bounceX();
    }
  }

Ahora agregaresmos los nuevos cambios en el sketch principal


Ball ball;
Pad pad1;
Pad pad2;
Board board;

void setup() {
  size(600, 400);
  ball = new Ball();
  pad1 = new Pad(0, height/2, 4, height/4 );
  pad2 = new Pad(width - 4, height/2, 4, height );
  board = new Board();
}

void draw() {
  background(0);
  ball.display();
  ball.move();
  pad1.display();
  pad2.display();
  pad1.detectCollision(ball);
  pad2.detectCollision(ball);
  board.display();
  board.detectCollision(ball);
}

void keyPressed() {
  if (keyCode == 40) {
    pad1.updatePosition(pad1.position.y + 15);
  }
  if (keyCode == 38) {
    pad1.updatePosition(pad1.position.y - 15);
  }
}

0:00
/0:20

Bien. ya con estos últimos cambios tenemos todo listo. Una cosa a destacar es que hemos añadido el control del el jugador uno a travez del teclado del computador, esto lo vemos definido dentro de la función KeyPressed, si el programa detecta que se ha presionado la tecla hacia arriba, actualiza el valor de la paleta del jugador uno para moverlo a la posicion correspondiente.

Con esto lo que nos queda pendiente es la integración con Arduino, aquí usaremos un encoder rotativo para detectar los cambios y enviárselos al programa para que actualice correctamente los valores de las paletas, esto lo veremos en el próximo blog.
Muchas gracias por haber llegado hasta este punto y nos vemos en la próxima.