domingo, 4 de agosto de 2013

Juego de Ping Pong (Arduino y Processing)

 
En este post les mostrare como hacer un juego de ping pong con Arduino y processing.

Processing lo utilizaremos para la interfaz grafica del juego y Arduino será el controlador por medio de dos potenciómetros y la comunicación mediante Arduino y processing será serialmente.

portada

 

Clase Jugador

Empezaremos por crear una clase en Processing llamda “Jugador” para dibujar los rectángulos (uno para cada jugador) que serán controlados por los potenciómetros. Aquí definimos las propiedades que va tener (largo, ancho, coordenadas, color, puntuación) y las acciones que va realizar (desplazar hacia arriba y abajo). 

El código de la clase “Jugador” se muestra a continuación:

   1:  /*
   2:  *Clase jugador
   3:  *
   4:  *Para crear los cuadrados que se van a manejar por
   5:  *medio de los controles
   6:  *
   7:  */
   8:   
   9:  class Jugador{
  10:   
  11:    int largo;
  12:    int ancho;
  13:    float x,y;
  14:    color c;
  15:    int marcador;
  16:    float velocidad;
  17:   
  18:   //constructor 
  19:   Jugador(float x, float y,color c){
  20:     this.largo = (height-100)/8;
  21:     this.ancho = width/80;
  22:     this.x = x;
  23:     this.c = c;
  24:     this.y = y;
  25:     this.marcador = 0;
  26:     this.velocidad = 7;
  27:   }
  28:   
  29:   //Dibujar el cuadro del jugador
  30:   void display(){
  31:      rectMode(CENTER);
  32:      stroke(10);
  33:      fill(this.c);
  34:      rect(x,y,ancho,largo); 
  35:   }
  36:   
  37:   void Up(){
  38:     if(this.y  > this.largo/2 )
  39:     this.y -=velocidad;
  40:   }
  41:   
  42:   void Down(){
  43:     if(this.y  < (height-100) - this.largo/2 )
  44:     this.y +=velocidad;
  45:   }
  46:     
  47:  }

 


Clase Bola


Después se necesita otra clase para la pelota  y de igual forma describimos sus propiedades ( radio, coordenadas x y, velocidad de la pelota tanto en x como y la cual va a estar variando y el color) y su única acción que va realizar es moverse. 


Las dimensiones tanto de los cuadros como de la pelota se definen en proporción al tamaño de la pantalla, por eso utilizamos las variables del sistema (width y height ).


El método display solo dibuja la pelota usando la figura de un eclipse donde los parámetros que recibe son las coordenadas donde se va a dibujar y la altura y anchura que la definimos como el doble del radio.


Para el método mover se tiene que recibir como parámetro los dos cuadrados para saber las coordenadas  de estos y poder determinar si la pelota tiene que rebotar en alguno de los dos cuadrados (en donde se tendrá que cambiar la dirección de la velocidad de la pelota en el eje x)  o en las paredes que limitan el juego (donde se cambiara la velocidad de la pelota en el eje y).


Para la comparación tanto con los bordes del juego como de los rectángulos respecto a la pelota, se tiene que tener en consideración que las coordenadas de la pelota se toman a partir del centro de esta, por lo tanto, se tiene que sumar el radio de la pelota a su coordenada en el eje x, positivo si es a la derecha y negativo a la izquierda; y en el eje y, positivo si es hacia arriba y negativo si es hacia abajo.  


El código de la clase “Pelota” se muestra a continuación.


   1:  /*
   2:  *Clase Bola
   3:  *
   4:  *Atributos y funciones de la bola
   5:  *Mover, Cambio de direccion, Velocidad
   6:  *
   7:  */
   8:  class Bola {
   9:    
  10:    float r; // radio
  11:    float x,y;
  12:    float xvelocidad,yvelocidad;
  13:    color c = color(0);
  14:    
  15:    // Constructor
  16:    Bola() {
  17:      r = width/50;
  18:      x = width/2;
  19:      y =(height-100)/4;
  20:      xvelocidad = random( -3,3);
  21:      yvelocidad = random( -3,3);
  22:    }
  23:   
  24:    void mover(Jugador jugador1, Jugador jugador2) {
  25:      x += xvelocidad; // Incrementar x
  26:      y += yvelocidad; // Incrementar y
  27:      
  28:      // Checar bordes horizontales para rebote
  29:      if (x > width-r){
  30:        jugador1.marcador++; this.restar();
  31:      }
  32:      if( x < r) {
  33:         jugador2.marcador++; this.restar();
  34:      }
  35:      
  36:      // Checar los bordes verticales para el rebote
  37:      if (y > (height-100)-r || y < r) {
  38:        yvelocidad *=  -1;
  39:      }
  40:      
  41:     
  42:      if( (( jugador1.x + jugador1.ancho/2 > bola.x - bola.r  && jugador1.y-jugador1.largo/2  < bola.y+bola.r ) &&
  43:      ( (jugador1.x + jugador1.ancho/2 > bola.x-bola.r ) && (jugador1.y+jugador1.largo/2 > bola.y-bola.r ) )) ||
  44:        (( jugador2.x -jugador2.ancho/2 < bola.x + bola.r  && jugador2.y-jugador2.largo/2  < bola.y+bola.r ) &&
  45:      ( (jugador2.x - jugador2.ancho/2 < bola.x+bola.r ) && (jugador2.y+jugador2.largo/2 > bola.y-bola.r ) ))){
  46:        xvelocidad *= -1;
  47:      }
  48:      
  49:    }//fin de mover
  50:   
  51:    // Dibujar la bola
  52:    void display() {
  53:      c = color(0);
  54:      stroke(0);
  55:      fill(c);
  56:      ellipse(x,y,r*2,r*2);
  57:      
  58:    }
  59:    
  60:    void restar(){
  61:      this.x = width/2;
  62:      this.y =(height-100)/4;
  63:      this.xvelocidad = random( -3,3);
  64:      this.yvelocidad = random( -3,3);
  65:    }
  66:      
  67:  }





 


Clase Principal


En cuanto a processing se refiere, solo basta la clase principal donde definiremos la pantalla, la interacción de las dos clases anteriores y la comunicación serial con arduino.


Primero se importa la librería para la comunicación serial. Definimos algunas variables que vamos a usar para la comunicación y después se definen 2 objetos de la clase jugador y 1 de la clase pelota.


En el setup determinamos el tamaño de la pantalla en 500x500 y inicializamos la comunicación serial.


Inicializamos nuestros objetos, recordamos que el constructor de la clase Jugador tiene varios parámetros (anchura, altura y el color).


Utilice un tipo de letra especial (se tiene que tener el archivo .vlw), para esto se uso la variable de tipo “font” y le  asigne un tamaño de letra de 25 (línea 53 y 54).


En el draw modificamos nuestro color de fondo a blanco, y dibujamos los bordes que van a limitar nuestro juego (línea 68),  ya que también habrá una parte para el marcador del juego (línea 70-74). En caso de que se pause, se agregara la leyenda de “pausado”.


En  el serialEvent se realiza la comunicación con Arduino, donde se obtienen los datos de voltaje de los potenciómetros provenientes de la placa arduino y nosotros solo verificamos que esos valores estén en el rango establecido dentro de los bordes del juego. Al final de este método se envía al arduino el caracter “A”   para indicarle que ya terminamos de procesar la información y que solicitamos otra vez los datos actualizados.


En el keyPressed tenemos que al presionar la tecla “p” el juego se pausara y al presionar la tecla “n” el juego se reiniciara.


Y por ultimo tenemos los métodos pausar donde en caso de que se invoque a este método se ponen las variables de velocidad de nuestros objetos en 0 para que no haya movimiento. El método reiniciar solo vuelve a crear nuestros objetos con todas sus variables con valor inicial.   


   1:  /*


   2:  * Clase principal del programa

   3:  *Juego de pingPong

   4:  *

   5:  *Luis Angel Reyes Cruz

   6:  *

   7:  *Version 1.0

   8:  *

   9:  *Ultima modificacion: 03/01/13

  10:  *

  11:  *Ultimas modificaciones:Distintas velocidades en el rebote

  12:  *     

  13:  *

  14:  *Modificaciones pendientes: Revisar problema con la pelota en el centro del cuadro

  15:  *              

  16:  */

  17:   

  18:  import processing.serial.*;

  19:   

  20:  Serial port;

  21:   

  22:  //variables para la comunicacion serial con Arduino

  23:  String buff = "";

  24:  String temp = "";

  25:   

  26:  float valorTemp = 0;

  27:  int val = 0;

  28:  int SALTOLINEA = 10;//para saber cuando hay un salto de linea

  29:   

  30:  Bola bola;

  31:  Jugador jugador1;

  32:  Jugador jugador2;

  33:  PFont f;

  34:  boolean pausado;

  35:   

  36:  void setup() {

  37:    size(500,500);

  38:    //frameRate(25);

  39:    smooth();

  40:    

  41:    //conexion serial con arduino

  42:    println(Serial.list());

  43:    port = new Serial(this, Serial.list()[0], 9600);

  44:    // lee y almacena un byte dentro del buffer hasta que obtiene un salto de linea(ASCII 10)

  45:    //para saber que la comunicacion tiene exito

  46:    port.bufferUntil('\n');

  47:    

  48:    

  49:    // Inicializamos los objetos

  50:    bola = new Bola();

  51:    jugador1 = new Jugador(4,(height-100)/2,color(255,0,0)); 

  52:    jugador2 = new Jugador(width-4,(height-100)/2,color(0,0,255)); 

  53:    f = loadFont( "AngsanaNew-Italic-48.vlw" );

  54:    textFont(f,25);

  55:    pausado = false;

  56:    

  57:  }

  58:   

  59:  void draw() {

  60:    background(255);

  61:    

  62:    jugador1.display();

  63:    jugador2.display();

  64:    // Mover y mostrar los elementos

  65:    bola.mover(jugador1,jugador2);

  66:    bola.display();

  67:   

  68:    line(0,height-100,width,height-100);

  69:    fill(0);

  70:    textFont(f,30);

  71:    text("MARCADOR",width/2-60,height-80);

  72:    textFont(f,25);

  73:    text("Jugador 1: " + jugador1.marcador ,20,height-50);

  74:    text("Jugador 2: "+ jugador2.marcador ,width-100,height-50);

  75:    

  76:    

  77:    if(pausado==true){

  78:      textFont(f,40);

  79:      text("PAUSA",width/2-50,height/2);

  80:    }

  81:    

  82:  }

  83:   

  84:  /*Funcion para capturar cuando pasa un evento

  85:  en una comunicacion serial y pasamos como parametro

  86:  el puerto serial que ya habiamos configurado en el setup*/

  87:  void serialEvent(Serial port){

  88:    

  89:    // lee el buffer del serial hasta que encuentra un salto de linea

  90:    String bString = port.readStringUntil('\n');

  91:    

  92:    // elimmina en caso de que haya espacios en blanco antes de los bytes

  93:      bString = trim(bString);

  94:   

  95:      // separamos el strings por comas

  96:      // y los convertimos a enteros y los almacenamos en un arreglo

  97:      int sensors[] = int(split(bString, ','));

  98:   

  99:      // imprimimos la salida

 100:      for (int sensorNum = 0; sensorNum < sensors.length; sensorNum++) {

 101:        print("Sensor " + sensorNum + ": " + sensors[sensorNum] + "\t"); 

 102:      }

 103:      

 104:      // salto de linea

 105:      println();

 106:      

 107:      //comprobamos que el arreglo sea mayor a 1

 108:      if (sensors.length > 1) {

 109:        

 110:        /*asignamos los valores correspondietes a cada rectangulo del juego

 111:        y nos aseguramos que no pasen del limite permitido*/

 112:        

 113:        jugador1.y =constrain(sensors[0]+jugador1.largo/2,jugador1.largo/2,400-jugador1.largo/2);

 114:        jugador1.display();

 115:        jugador2.y = constrain(sensors[1]+jugador1.largo/2,jugador1.largo/2,400-jugador1.largo/2);

 116:        jugador2.display();

 117:       

 118:      }

 119:      

 120:      // enviamos una letra cualquiera para pedir otra vez datos

 121:      port.write("A");

 122:      

 123:      

 124:  }//fin de SerialEvent

 125:   

 126:  void keyPressed() {

 127:          if ( keyCode == UP ) {

 128:            //jugador1.Up();jugador1.display(); 

 129:            jugador2.Up();jugador2.display();

 130:          } else if ( keyCode == DOWN ) {

 131:            //jugador1.Down();jugador1.display(); 

 132:            jugador2.Down();jugador2.display(); 

 133:          }else if(keyCode == 80){

 134:           pausar(); 

 135:          }else if(keyCode == 78){

 136:           reiniciar(); 

 137:          }

 138:        

 139:  }

 140:   

 141:   

 142:  void pausar(){

 143:    if(pausado == false){

 144:      bola.xvelocidad=0;bola.yvelocidad=0;

 145:      jugador1.velocidad=0;

 146:      jugador2.velocidad=0;

 147:      pausado = true;}

 148:    else if(pausado == true){

 149:      bola.xvelocidad=random( -3,3);bola.yvelocidad=random( -3,3);

 150:      jugador1.velocidad=7;

 151:      jugador2.velocidad=7;

 152:      pausado = false;}

 153:  }

 154:   

 155:  void reiniciar(){

 156:    bola = new Bola();

 157:    jugador1 = new Jugador(4,(height-100)/2,color(255,0,0)); 

 158:    jugador2 = new Jugador(width-4,(height-100)/2,color(0,0,255)); 

 159:  }




 


Arduino


Para la parte de arduino, tenemos que definir los pines en los cuales se van a conectar nuestros potenciómetros.


En el setup lo único que tenemos que hacer es inicializar nuestra comunicación serial.


En el loop  vamos a leer un dato desde el puerto serial, el cual nos lo manda processing para solicitar información. Posteriormente leemos los valores de los potenciómetros mediante el método analogRead() el cual regresa un valor comprendido entre 0 y 1023 correspondiente al voltaje entre 0 y 5, nosotros tenemos que hacer que ese valor de entre 0 y 1023 corresponda a el rango de los bordes de nuestro juego, para esto usamos el metodo map() donde le decimos que el valor del potenciómetro esta en un rango de 0-1023 y que nos de su correspondiente en un rango de 0-400 el cual es el rango de nuestro juego.


Por ultimo enviamos serialmente los valores de nuestros potenciómetros separados por una coma para que processing los pueda manipular.


Conectamos dos potenciómetros a la placa arduino en los pines definidos anteriormente.   

/*
*Programa para controlar el juego de pingpong mediate
*2 potenciometros comunicándose serialmente con processing
*
*Luis Angel Reyes Cruz
*03/01/13
*/

// Envío de Entrada analógica 0 al puerto serie
int potPin1 = A2; // Designa el numero de entrada analógica
int potPin2 = A3; // Designa el numero de entrada analógica

//Variables para almacenar el valor del potenciómetro
int val1 = 0;
int val2 = 0;
int valPush = 0;

int Ibyte = 0; //variable para recibir el dato serial

void setup(){
Serial.begin(9600); //Configura la velocidad de transmisión con el puerto

while (!Serial) {
; // esperar mientras comunica el puerto serial
//solo se necesita en Leonardo
}

}


void loop(){

// si recibimos un byte valido
if (Serial.available() > 0) {

Ibyte = Serial.read();

val1 = map(analogRead(potPin1),0,1023,0,400); // Obtenemos el valor del potenciometro
val2 = map(analogRead(potPin2),0,1023,0,400);
//y ajustamos el valor de acuerdo al tamaño de la ventana
//El potenciometro maneja cantidades de 0 a 1023 y lo dividimos entre 4

Serial.print(val1); //enviamos el valor del potenciometro
Serial.print(","); //Enviamos un el carácter de salto de linea en ASCII

Serial.print(val2); //enviamos el valor del potenciometro

// delay(75); // espera 75 ms
}

}
}




Con todo lo anterior debemos de tener el juego funcionando, donde se pusieron varias cosas en practicas, como la creación de figuras en processing y una interfaz sencilla, el uso de teclas para realizar alguna acción y la mas importante, la comunicación serial en processing para la interacción con arduino.