ArduGame: Juego portatil con Arduino, OLED y Acelerómetro

Publicado por en DIY el 04/08/2008
Comentarios:

A todos nos gusta realizar proyectos electrónicos, sin embargo, muchas veces, una vez hechos, quedan en el olvido en algún cajón o incluso peor, pasan a ser placas de desecho en un baul para reaprovechar componentes. ArduGame nace con la idea de un proyecto divertido de realizar y divertido de utilizar, sin necesidad de muchas horas de desarrollo.

Os presentamos el último proyecto de BricoGeek.com: ArduGame. Se trata de un pequeño sistema de videojuego portatil basado en la plataforma Arduino (Atmega168) y que utiliza una pantalla color Micro-OLED de 4D Systems para visualizar los gráficos junto con un pequeño zumbador o "speaker" para emitir sonidos mientras se juega. En éste caso se han aprovechado los botones de una vieja PSP estropeada para hacer el mando.

Éste sistema ofrece infinitas posibilidades para programar nuestros propios juegos, de hecho, hemos programado un pequeño juego (el cual podeis descargar los fuentes gratuitamente) el cual por el momento no tiene nombre definido!

Todo esto está muy bien, pero como no hemos podido resistirnos, también hemos incorporado un pequeño acelerómetro ADXL 2g para jugar con movimiento!! La pelota posee aceleración y podemos seleccionar de qué forma queremos jugar desde el menu principal. Calcula los puntos acumulados y siempre tendremos un tiempo limitado entre cada nivel.

No os perdais el video altamente recomendado a continuación, asi como los detalles del proyecto.

Como siempre, esperamos vuestros comentarios y/o sugerencias ya que sois ya muchos los que colaborais a diario con BricoGeek y sus proyectos!

Actualización:

Por tercera vez, BricoGeek.com aparece en portada de Make Magazine, también en Hacked Gadgets y Daily DIY!

Otros sitios que enlazan el proyecto:
DIY Zedomax
Gadgets Blogs2k

Gracias por el post amiguetes! Esperamos vuestros comentarios!

Click here for english translation


ArduGame - Arduino Portable Gaming System - BricoGeek.com from BricoGeek on Vimeo.

Descripción
Todo el sistema está basado en la placa Arduino, con un microcontrolador ATMega168 corriendo a 16Mhz. Conectado a la placa Arduino se encuentra una pequeña placa para los mandos que fueron recuperados de una PSP estropeada. Practicando unos agujeros y teniendo un poco de paciencia, se puede colocar los botones facilmente.

La pantalla es una Micro-OLED-128-G1 color de 4D Systems, la cual es facilmente programable mediante un protocolo de interfaz série utilizando el UART del Atmega. Dispone de un zócalo para alojar una tarjeta de memoria MicroSD de hasta 2G, la cual permite incluso almacenar imagenes o videos que posteriormente podemos mostras en pantalla enviando unos simples comandos a la pantalla. Esto nos da muchas ventajas a la hora de hacer un juego, ya que no necesitamos desperdiciar el valioso espacio de programa, que es limitado, y asi poder guardar hasta 2Gb de gráficos! Quizas un Street Fighter sería viable... por que no? Abierto queda el debate.

El juego
No hay mucho que decir sobre éste sencillo juego, ya que principalmente es de prueba. Se trata de una pelota azul que deber somerse a las pelotas verdes. En cuanto se come una pelota verde, se incrementas los puntos en función del diámetro de la bola comida. A más tamaño, más puntos. Si tocamos una bola roja, restaremos puntos, y al igual que con las verdes, a mayor diametro, mas puntos perderemos al tocarlas. También si tocamos cualquiera de los bordes de pantalla, no restaremos puntos, pero estaremos perdiendo un valioso tiempo del cual disponemos para pasar al siguiente nivel.
Se calcula cada nivel (posición de las bolas de colores) de modo aleatorio con la función random. Para garantizar que siempre tendremos un número realmente aleatorio, se inicializa el seed (o semilla) mediante una lectura a un puerto ADC sin conectar. Esto nos proporciona un valor variable para la semilla.
Al mover los cursores, aceleramos o frenamos la bola azul, por lo que hay que tener cuidado de no acelerar mucho o perderemos el control!

Sobre el código fuente no hay mucho que decir ya es es bastante sencillo de leer y entender ya que se incluyen algunos comentarios útiles. Podeis descargar el archivo PDE de codigo fuente en los enlaces de abajo.

Conclusión
Me gusta mucho este proyecto, por que una vez tengamos todo el hardware funcional, podemos dedicarle un tiempo a desarrollar más juegos utizando los controles o bien el acelerómetro integrado para jugar con movimiento. De un mismo hardware, podemos pasar horas y horas desarrollando para esta pequeña consola con un aire similar a la ultra conocida Game Boy.

Lista de componentes usados:

Código fuente:

/********************************************
  ArduGame
  Oscar Gonzalez Varela 2008
  www.BricoGeek.com
 ********************************************/
#include <string.h>
#include <Stdio.h> 
#include <math.h>

#include <avr/pgmspace.h>
#include "oled160drv.h"

#define BUTTON_UP       7
#define BUTTON_RIGHT    8
#define BUTTON_DOWN     4
#define BUTTON_LEFT     3

#define BUTTON_A        12
#define BUTTON_B        13

#define SOUND_PIN      5

#define SENSOR_PIN_X       1
#define SENSOR_PIN_Y       0

#define BALL_ACCEL  0.3
#define BALL_DECEL  0.3

char use_adxl=0;
float ball_x = 64;
float ball_y = 64;
int ball_size= 5;
float acc_x = 0;
float acc_y = 0;
long last_time=0;
char game_time=0;
int game_totalpt=0;
char game_level=1;

#define MAX_PUNTOS  10

int mPuntos_val[MAX_PUNTOS];
int mPuntos_x[MAX_PUNTOS];
int mPuntos_y[MAX_PUNTOS];

void setup()                    // run once, when the sketch starts
{
  
   // Set I/O
   pinMode(OLED_RESETPIN, OUTPUT);   
   pinMode(SOUND_PIN, OUTPUT);   
   pinMode(BUTTON_UP, INPUT);
   pinMode(BUTTON_RIGHT, INPUT);
   pinMode(BUTTON_DOWN, INPUT);
   pinMode(BUTTON_LEFT, INPUT);
   pinMode(BUTTON_A, INPUT);
   pinMode(BUTTON_B, INPUT);
   
   pinMode(SENSOR_PIN_X, INPUT);
   pinMode(SENSOR_PIN_Y, INPUT);
   
   randomSeed(analogRead(0));
     
   // Init serial comunication for the OLED display
   Serial.begin(OLED_BAUDRATE);
  
  // Initialise display
  OLED_Init();
  
  //Clear screen
  OLED_Clear();
    
}

void beep (unsigned char speakerPin, int frequencyInHertz, long timeInMilliseconds)     // the sound producing function
{ 	 
          int x; 	 
          long delayAmount = (long)(1000000/frequencyInHertz);
          long loopTime = (long)((timeInMilliseconds*1000)/(delayAmount*2));
          for (x=0;x<loopTime;x++) 	 
          { 	 
              digitalWrite(speakerPin,HIGH);
              delayMicroseconds(delayAmount);
              digitalWrite(speakerPin,LOW);
              delayMicroseconds(delayAmount);
          } 	 
} 

// Usefull function to get distance from two 2D points
int getDistance(int x1, int y1, int x2, int y2)
{
  return sqrt(pow(x2-x1, 2) + pow(y2-y1, 2));
}

void InitGame()
{

  int i;
  
  for (i=0 ; i<MAX_PUNTOS ; i++)
  {
    
      mPuntos_x[i] = 0;
      mPuntos_y[i] = 0;      
      mPuntos_val[i] = 0;    
    
      // Set random values here
      mPuntos_x[i] = random(5, 105);
      mPuntos_y[i] = random(5, 105);      
      mPuntos_val[i] = random(0, 10);
  }
  
  ball_x = 64;
  ball_y = 64;
  ball_size = 5;
  acc_x = 0;
  acc_y = 0;
  
  game_time = 20-(game_level-1);  
  
}

void DrawmPuntos()
{
  
   int color=0;
   int i;
  
  for (i=0 ; i<MAX_PUNTOS ; i++)
  {      
      if (mPuntos_val[i] != 0)
      {
        if (mPuntos_val[i] < 5) { color = GetRGB(255, 0, 0); }
        if (mPuntos_val[i] == 5) { color = GetRGB(255, 0, 255); }
        if (mPuntos_val[i] > 5) { color = GetRGB(0, 255, 0); }
      
        OLED_DrawCircle(mPuntos_x[i], mPuntos_y[i], mPuntos_val[i], 0, color);
      }
  }
}

void DrawToolbar()
{
  
   char strText[30];
   
   // Erase first! (black)
   OLED_DrawRectangle(0, 110, 127, 16, 1, GetRGB(64, 64, 64));   
   
   sprintf(strText,"Time:   %d", game_time);     
   OLED_DrawText(0, 14, 0, strText, 0xFFFF);
      
   sprintf(strText,"Points: %d", game_totalpt);     
   OLED_DrawText(0, 15, 0, strText, 0xFFFF);  
}

void DrawMainMenu()
{
  
  char exit_loop=0;
   
  //Clear screen
  OLED_Clear();
  
  OLED_DrawText(1, 3, 2, "ArduGame v1.0", 0xFFFF);
  OLED_DrawText(1, 6, 0, "www.BricoGeek.com", 0xFFFF);  
  
  OLED_DrawText(1, 10, 0, "Play Motion    A", GetRGB(254, 0, 254));  
  OLED_DrawText(1, 11, 0, "Play Joystick  B", GetRGB(254, 254, 0));  
  
  while (exit_loop == 0)
  {
    // Select (A)
    if (digitalRead(BUTTON_A) == HIGH) { use_adxl = 1; while (digitalRead(BUTTON_A) == HIGH) {  }; exit_loop=1; }
    if (digitalRead(BUTTON_B) == HIGH) { use_adxl = 0; while (digitalRead(BUTTON_A) == HIGH) {  }; exit_loop=1; }
  }
  
  // Some sound here!
  beep(SOUND_PIN,12500,7); 
  
}

void loop()                     // run over and over again
{  
  
  int i;
  int x, y;
  char game_over=0;
  int remaining_points=0;
  int last_x, last_y, last_size;
  int val_x=0;
  int val_y=0;  
  
  OLED_Clear();
  DrawMainMenu();
  OLED_Clear();
    
  OLED_DrawCircle(ball_x, ball_y, ball_size, 0, GetRGB(0, 0, 254));
  
  InitGame();
  DrawmPuntos();
  DrawToolbar();
  
  game_level=1;  
  last_time = millis();
  
  while (!game_over)
  {        
    
    // Read acelerometer values (0-1024)
    if (use_adxl == 1)
    {
      val_x = analogRead(SENSOR_PIN_X)-512;
      val_y = analogRead(SENSOR_PIN_Y)-512;    
    }
    
    last_x = ball_x;
    last_y = ball_y;  
    last_size = ball_size;  
    
    if (use_adxl == 0)
    {
      if (digitalRead(BUTTON_A) == HIGH) { ball_size--; }    
      if (digitalRead(BUTTON_B) == HIGH) { ball_size++; }    
      if (digitalRead(BUTTON_UP) == HIGH) { acc_y-=BALL_ACCEL; }    
      if (digitalRead(BUTTON_DOWN) == HIGH) { acc_y+=BALL_DECEL;}    
      if (digitalRead(BUTTON_LEFT) == HIGH) { acc_x-=BALL_ACCEL; }
      if (digitalRead(BUTTON_RIGHT) == HIGH) { acc_x+=BALL_DECEL; }
    }
    else
    {    
      acc_x -= (val_x/130);
      acc_y -= (val_y/130);        
    }
    
    // Increase/decrease acceleration
    ball_x += acc_x;
    ball_y += acc_y;
    
    if (ball_size < 1) { ball_size=1; }
    if (ball_size > 10) { ball_size=10; }    
    
    if ( (ball_x != last_x) || (ball_y != last_y) || (ball_size != last_size) )
    {       
      OLED_DrawCircle(last_x, last_y, last_size, 0, 0);
      OLED_DrawCircle(ball_x, ball_y, ball_size, 0, GetRGB(0, 0, 254));
    }
    
    // Collision detection on corners
    if ( ((ball_x-ball_size) <= 0) || 
         ((ball_x+ball_size) >= 127) ||             
         ((ball_y-ball_size) <= 0) || 
         ((ball_y+ball_size) >= 110) )
    {
        OLED_DrawCircle(ball_x, ball_y, ball_size, 0, GetRGB(254, 254, 0));
        // We die! Oh noooo!!! BUUUUU!!
        beep(SOUND_PIN,2093,15);
        OLED_DrawCircle(ball_x, ball_y, ball_size, 0, 0);
        // Erase and reset de ball        
        ball_x = 64; ball_y = 64;
        acc_x = 0; acc_y = 0;
        OLED_DrawCircle(ball_x, ball_y, ball_size, 0, GetRGB(0, 0, 254));
    }    
    
    // Collision detection on points
    remaining_points=0;
    
    for (i=0 ; i<MAX_PUNTOS ; i++)
    {
      if (mPuntos_val[i] > 0)
      {
        if (getDistance(ball_x, ball_y, mPuntos_x[i], mPuntos_y[i]) <= (ball_size+mPuntos_val[i]))
        {          
            if (mPuntos_val[i] < 5)
            {
                 // Bad point   
               beep(SOUND_PIN,2000,10);  
               game_totalpt -= mPuntos_val[i];
               DrawToolbar();
            }
            else
            {
              // Good ball!
              OLED_DrawCircle(mPuntos_x[i], mPuntos_y[i], mPuntos_val[i], 0, GetRGB(0, 0, 254)); // Make point blue!
              beep(SOUND_PIN,11100,5);
              game_totalpt += mPuntos_val[i];
              DrawToolbar();
            }        
         
           // Erase point
           OLED_DrawCircle(mPuntos_x[i], mPuntos_y[i], mPuntos_val[i], 0, 0);         
           mPuntos_val[i] = 0; // Point is now obsolete
           
           // Redraw all points
           DrawmPuntos();
        }
      }
      // Green remaining points counter
      if (mPuntos_val[i] >= 5) { remaining_points++; }      
     
    }    
    
      // Detect next level
      if (remaining_points == 0)
      {
          char strText[30];
          game_level++; // Increase level!
          sprintf(strText, "LEVEL %d", game_level);
          OLED_DrawText(1, 3, 2, strText, GetRGB(254, 254, 254));
          sprintf(strText, "Total: %d pt", game_totalpt);
          OLED_DrawText(1, 7, 0, strText, GetRGB(254, 0, 0));
          for (i=5000 ; i<20000 ; i+=600)
          {
            beep(SOUND_PIN,i,1);
            //delay(1);
          }
          
          delay(100);
          
          OLED_Clear();
          
          InitGame();
          DrawmPuntos();       
          DrawToolbar();
       
         OLED_DrawCircle(ball_x, ball_y, ball_size, 0, GetRGB(0, 0, 254));   
          
      }    
      
      // Detect Game Over
      if (game_time == 0)
      {
          char strText[30];
          game_level++; // Increase level!
          OLED_DrawText(1, 3, 2, "* GAME OVER *", GetRGB(254, 0, 0));
          delay(1);
          
          for (i=20000 ; i>=5000 ; i-=200)
          {
            beep(SOUND_PIN,i,1);
            //delay(1);
          }          
          
          // Wait for button press
          while (digitalRead(BUTTON_A) == LOW) { }    
          
          game_over=1; // Game over!!!!
      }
      
      // Time counter
      if ((millis()-last_time) > 100)
      {
        last_time = millis();
        game_time--;
        DrawToolbar();
      }
    
  }
}

Si te ha gustado, compártelo con tus amigos!
Google Plus 

Ver comentarios antiguos...