Space Laser

Autores

  • Laura Ortiz Écija
  • Gonzalo Rico Esteban
  • Gabriel Sánchez Losa

Idea y planteamiento

Partimos de una idea sobre un proyecto ante todo lúdico. Un proyecto que divirtiera y fuera entretenido de ver y ejecutar. Como primera impresión, rondábamos la idea de ingeniar un “Conecta 4”, pero no nos terminaba de convencer por la simplicidad intrínseca del proyecto. Estuvimos dándole vueltas a la posibilidad de proporcionar un giro de originalidad hasta que se nos ocurrió una nueva idea también relacionada con el ocio y mucho más original y divertida. Es así como elegimos recrear un stand de disparos como el de las casetas de feria, con dianas objetivo que se alzasen y bajasen aleatoriamente, pistola láser y un contador de puntos y tiempo.

Casos de uso

El funcionamiento consta de dos partes diferenciadas: la elección del modo de juego y la partida. En cuanto se alimenta el circuito, las dianas se levantan a la vez a modo de menú, listas para regir uno de los tres niveles de dificultad del juego. La primera diana es el nivel fácil, la segunda es el medio y la última el difícil (dificultad inversamente proporcional al tiempo de vida de las dianas, cuanto mayor dificultad, menor tiempo de vida). Al pasar el láser por encima del centro de alguna de las dianas, su respectivo led se enciende para remarcar la diana seleccionada, y si se dispara manteniendo la diana marcada, el juego comienza en cualquiera de los niveles.

Al iniciar la partida, suena un sonido emitido por el zumbador y los tres leds se encienden y se apagan siguiendo un determinado patrón programado. El display también se encenderá, mostrando en la parte derecha un temporizador inicializado en 30 que irá disminuyendo cada segundo, y en la izquierda la puntuación del jugador. Cada punto sólo se contará si se apunta al centro de la diana y se aprieta el botón de la pistola mientras la diana está levantada. Si no se cumplen estas condiciones, no hay punto. Por cada punto obtenido, el zumbador emitirá un sonido. Al finalizar la partida, sonará también un sonido de victoria y el resultado de la puntuación se guardará a modo de récord, que se podrá batir en la siguiente partida.

Materiales empleados

Los materiales empleados se dividen en dos categorías: los del montaje y los de la circuitería.

En cuanto a los primeros, tuvimos la suerte de contar con una tabla de contrachapado a nuestra disposición, por lo que fuimos capaces de dibujar, recortar y perfilar la estructura del stand y de las dianas.

Para los materiales necesarios para la circuitería, además de los básicos (cables, protoboards, Arduino UNO), requerimos de:

  • 3 fotorresistencias que funcionarán como receptor del puntero láser en el centro de las dianas.
  • 3 servomotores que elevarán y bajarán la estructura de madera de la diana.
  • 3 leds, cada uno asignado a una diana, para simbolizar de manera más visual su elección.
  • 1 diodo láser que hará de mirilla de la pistola.
  • 1 botón que simbolizará el gatillo de la pistola.
  • 1 zumbador que emitirá sonidos al iniciar la partida, al finalizarla y al contar punto.
  • 1 display de siete segmentos para visualizar el contador de puntuación y el temporizador.
  • 1 batería de 9V y 2 pilas de 4.5V para proporcionar la alimentación necesaria al circuito

Para los materiales externos dedicados a la maqueta del proyecto (stand, dianas, pistola) utilizamos:

  • Madera de contrachapado para el stand y dianas.
  • Cartón para la pistola.
  • Silicona caliente para unir las piezas.
  • Cinta para mayor sujeción.

Presupuesto total del proyecto

COMPONENTESUNIDADESPRECIO
Arduino UNO1
Batería 9V15,25€
Pilas 4.5V2
Protoboard2
Fotorresistor3
Diodo Láser11,25€
Pulsador1
Zumbador piezzo1
Banco de interruptores1
Servomotores SG90314,70€
Módulo Display 7 segmentos HT16K33 BACKPACK114,34€
Coste total35,54€
MATERIALESUNIDADESPRECIO
Tablas de madera de contrachapado4
Rollos de cartón3
Segueta1
Taladro1
Cinta americana1
Pistola de silicona + silicona1
Coste total0,00€

Esquema en Tinkercad e imágenes

Como se puede observar en la siguiente imagen (esquema de tinkercad que realizamos como simulación del circuito), son necesarios tres pines de salida analógica para los servomotores, cinco pines de entradas analógicas para las fotorresistencias y el display y el resto de pines empleados son digitales o de alimentación y tierra. Cabe destacar la protección de los leds y las fotorresistencias con resistencias de 220 ohmios para evitar posibles desastres y el led rojo de la izquierda simbolizando el diodo láser (tinkercad no tiene ese componente disponible para los esquemas de conexión).

En cuanto a la alimentación, en nuestro circuito final decidimos alimentar una protoboard adicional con una pila de 4’5V, la pistola (diodo láser y botón) también se conectaron a otra pila de 4’5V y la placa de Arduino se alimentó con una batería de 9V, suficiente para el nivel de voltaje recomendado (entre 7 y 12V).


Software

/**
///////////////////////////////////////////
----------PROYECTO SPACE LASER-------------
///////////////////////////////////////////
**/

#include <Servo.h>
#include <Wire.h>
#include <Adafruit_LEDBackpack.h>

Adafruit_7segment matrix = Adafruit_7segment();

Servo servo[3];
 
// ASIGNACION DE PINES

const int button = 2;

const int led1_pin = 8;
const int led2_pin = 12;
const int led3_pin = 13;

const int buzzerPin = 3;

const int ldr1_pin = A0;
const int ldr2_pin = A1;
const int ldr3_pin = A2;


// VARIABLES DE ESTADO DE BOTONES Y SENSORES

int estadoButton = HIGH;

int valor_led[3] = {8,12,13};

int valor_ldr[3] = {0,0,0};


// VARIABLES PARA CONTROLAR EL TIEMPO

//Utilizamos "unsigned long" para variables que manejan el tiempo
//El valor se haría demasiado extenso para almancenar en un entero

unsigned long startTime_servo[3] = {0,0,0};  // will store last time SERVO was updated
unsigned long startTime_servoVida[3] = {0,0,0}; //will store last time SERVOVIDA was updated
unsigned long startTime_game = 0;  // will store last time GAME was updated
unsigned long timer = 0; // will store

// INTERVALOS CONSTANTES

const unsigned long interval_second = 1000;  // interval at which to blink (milliseconds)
const unsigned long interval_servo = 2;  // interval at which to generate motion on the gear (milliseconds)
const unsigned long interval_game = 36000;  // interval at which to finish the game (milliseconds)
unsigned long interval_mode[3] = {4000, 2500, 1000};
//----------------------------------------

// VARIABLES GENERALES

int puntuacion = 0;
int record = 0;

int angle[3] = {0,0,0};
int diana = random(0,3);

int tiempo = 30;

int condicion = 0;
int opcion = 0;

boolean menu = true;
boolean select = false;

boolean partida = false;

boolean acierto = false;
boolean fail = false;

boolean diana_viva[3] = {false, false, false};

/////////////////////////////////////////////////////
void setup() {
/////////////////////////////////////////////////////
  Serial.begin(9600);
  
  // CONFIGURAR PINES COMO ENTRADAS
  pinMode(button, INPUT);
  
  // CONFIGURAR PINES COMO SALIDAS
  pinMode(LED_BUILTIN, OUTPUT);
  
  pinMode(led1_pin, OUTPUT);
  pinMode(led2_pin, OUTPUT);
  pinMode(led3_pin, OUTPUT);
  
  
  servo[0].attach(9);
  servo[1].attach(10);
  servo[2].attach(11);
  servo[0].write(0);
  servo[1].write(0);
  servo[2].write(0);
  
  matrix.begin(0x70);  // Inicializa el display en la dirección 0x70
  matrix.setBrightness(15); // ajusta el brillo del display (0 a 15)
  
  randomSeed(analogRead(A3));  
}

/////////////////////////////////////////////////////
void loop() {
/////////////////////////////////////////////////////

  //TIEMPO
  /*Returns the number of milliseconds passed since the Arduino board began running the current program.*/
  unsigned long currentMillis = millis();

  //LEER EL ESTADO DE LOS PINES
  valor_ldr[0] = analogRead(ldr1_pin);
  valor_ldr[1] = analogRead(ldr2_pin);
  valor_ldr[2] = analogRead(ldr3_pin);

  estadoButton = digitalRead(button);
  delay(15);

  //====================================
  //MENU INICIAL
  //====================================
  if (menu == true) {
    
    // (Step 0) SET-UP DIANAS 
    if (condicion == 0) {
      
      if(currentMillis - timer >= interval_servo){
        timer = currentMillis;

        if(angle[0] >= 90 && angle[1] >= 90 && angle[2] >= 90) {
          condicion = 1;
          sonidoAcierto();
        }
        else {
          angle[0] += 10;
          servo[0].write(angle[0]);
          angle[1] += 10;
          servo[1].write(angle[1]);
          angle[2] += 10;
          servo[2].write(angle[2]);
        }
      }
    }

    /// (Step 1) DIANAS ALZADAS MENU DESPLEGADO
    if (condicion == 1) {

      // ---- INFO EN DISPLAY SOBRE RECORD -----
      if (record < 10) {
        matrix.writeDigitNum(0,record);
      }
      else {
        matrix.writeDigitNum(0, record/10);
        matrix.writeDigitNum(1, record - (record/10 * 10));
      }
      matrix.writeDisplay();


      // ELEGIR MODO DE DIFICULTAD
      if (valor_ldr[2] > 500) {
          digitalWrite(valor_led[2], HIGH);
          if (estadoButton == HIGH && condicion != 2) {
              condicion = 2; //SIGUIENTE FASE
              opcion = 2; //MODO DIFICIL
              sonidoAcierto();
          }
      }
      else { digitalWrite(valor_led[2], LOW); }

      if (valor_ldr[1] > 500) {
          digitalWrite(valor_led[1], HIGH);
          if (estadoButton == HIGH && condicion != 2) {
              condicion = 2; //SIGUIENTE FASE
              opcion = 1; //MODO INTERMEDIO
              sonidoAcierto();
          }
      }
      else { digitalWrite(valor_led[1], LOW); }

      if (valor_ldr[0] > 500) {
        digitalWrite(valor_led[0], HIGH);
        if (estadoButton == HIGH && condicion != 2) {
            condicion = 2; //SIGUIENTE FASE
            opcion = 0; //MODO FACIL
            sonidoAcierto();
        }
      }
      else { digitalWrite(valor_led[0], LOW); }
    }
    
    // (Step 2) OCULTAR DIANAS
    if (condicion == 2) {
    
      if (currentMillis - timer >= interval_servo) {
          timer = currentMillis;

          if (angle[0] <= 0 && angle[1] <= 0 && angle[2] <= 0) {
            condicion = 0;
            select = true;
            menu = false;
          }
          else {
            angle[0] -= 12;
            servo[0].write(angle[0]);
            angle[1] -= 12;
            servo[1].write(angle[1]);
            angle[2] -= 12;
            servo[2].write(angle[2]);
          }
      }
    }
  }

  //====================================
  //FIN DEL JUEGO
  //====================================
  if (partida == true && currentMillis - startTime_game >= interval_game) {

    if (diana_viva[diana] == true || angle[diana] > 0) {

      if (currentMillis - startTime_servo[diana] >= interval_servo){
          startTime_servo[diana] = currentMillis;
          servo[diana].write(angle[diana]);
          angle[diana] -= 10;

          if(angle[diana] <= 0) {
              diana_viva[diana] = false;
          }
      }
    } 
    else {
      diana = random(0,3);
    
      digitalWrite(valor_led[0], HIGH);
      digitalWrite(valor_led[1], HIGH);
      digitalWrite(valor_led[2], HIGH);
    
      sonidoVictoria();

      partida = false;

      menu = true;
      select = false;
      
      startTime_game = currentMillis;

      if (puntuacion > record) record = puntuacion;
    
      digitalWrite(valor_led[0], LOW);
      delay(100);
      digitalWrite(valor_led[1], LOW);
      delay(100);
      digitalWrite(valor_led[2], LOW);
    }
  }

  //====================================
  // INICIO PARTIDA 
  //====================================
  if (partida == false && select == true) {
    partida = true;

    if (opcion == 0) startSpaceOdyssey(); //EMPIEZA MELODÍA INICIO
    
    digitalWrite(valor_led[0], HIGH);
    digitalWrite(valor_led[1], HIGH);
    digitalWrite(valor_led[2], HIGH);
    
    delay(1000);
    
    digitalWrite(valor_led[0], LOW);
    digitalWrite(valor_led[1], LOW);
    digitalWrite(valor_led[2], LOW);
    
    delay(500);
    
    digitalWrite(valor_led[0], HIGH);
    delay(300);
    digitalWrite(valor_led[1], HIGH);
    delay(300);
    digitalWrite(valor_led[2], HIGH);
    delay(300);
    
    digitalWrite(valor_led[0], LOW);
    digitalWrite(valor_led[1], LOW);
    digitalWrite(valor_led[2], LOW);
    delay(300);
    
    startTime_game = currentMillis;
    
    tiempo = 30;
    puntuacion = 0;
  }
  
  // ==================================================
  if (partida == true) { // ====  DENTRO DE LA PARTIDA
  // ==================================================

    // MOSTRAR INFO EN NUESTROS DISPLAYS DE 7 SEGMENTOS
    if(currentMillis - timer >= interval_second) {
        timer = currentMillis;

        matrix.print(tiempo);
        
        if (puntuacion < 10) {
          matrix.writeDigitNum(0,puntuacion);
        }
        else {
          matrix.writeDigitNum(0, puntuacion/10);
          matrix.writeDigitNum(1, puntuacion - (puntuacion/10 * 10));
        }
        matrix.writeDisplay();
        tiempo--;
    }
    
    // ALZAR DIANA RANDOM ------------------------------
    if (diana_viva[diana] == false) {
      
      if (valor_led[diana] != HIGH) digitalWrite(valor_led[diana], HIGH);
      
      if(currentMillis - startTime_servo[diana] >= interval_servo){
        startTime_servo[diana] = currentMillis;

        if(angle[diana] >= 90) {
          diana_viva[diana] = true;
          startTime_servoVida[diana] = currentMillis;
        }
        else {
          angle[diana] += 10;
          servo[diana].write(angle[diana]);
        }
      }
    }

    // ¿SE OPRIMIO EL BOTON DE ENCENDIDO? ----------------
    if (estadoButton == HIGH && valor_ldr[diana] > 500 && diana_viva[diana] == true) 
    {
      acierto = true;
    }

    //¡ACIERTO! DIANA VUELVE A POSICION INICIAL Y SUMAMOS PUNTO  
    if (acierto == true) {
      if (currentMillis - startTime_servo[diana] >= interval_servo){
        startTime_servo[diana] = currentMillis;
            
        if(angle[diana] <= 0) { // Diana estabilizada
              
          acierto = false;
          puntuacion++; // Sumamos punto
              
          // ----- Diana tumbada -------
          diana_viva[diana] = false; 
              
          sonidoAcierto();

          digitalWrite(valor_led[diana], LOW);
              
          /* NUEVO VALOR DE DIANA INDICARA CUAL SERA LA PROX DIANA A LEVANTARSE */
          diana = random(0,3); 
        }
        else {
          angle[diana] -= 10;
          servo[diana].write(angle[diana]);
        }
      }
    }

    // TIEMPO DE VIDA DE DIANA SUPERADO -----------------
    if (currentMillis - startTime_servoVida[diana] >= interval_mode[opcion] && diana_viva[diana] == true) 
    { // Segun que modo hayamos seleccionado el rango de tiempo del intervalo variará
      fail = true;
    }

    // FALLO, LA DIANA VOLVERA A BAJAR
    if (fail == true) {
      if (currentMillis - startTime_servo[diana] >= interval_servo){
        startTime_servo[diana] = currentMillis;
          
        if(angle[diana] <= 0) { // Diana estabilizada
            
          acierto = false; //No se contabiliza punto
          fail = false;
            
          // ----- Diana tumbada --------
          diana_viva[diana] = false; 
            
          digitalWrite(valor_led[diana], LOW);
            
          /* NUEVO VALOR DE DIANA INDICARA CUAL SERA LA PROX DIANA A LEVANTARSE */
          diana = random(0,3);
        }
        else {
          angle[diana] -= 12;
          servo[diana].write(angle[diana]);
        }
      }
    }
  } 

  // FINAL DENTRO DE PARTIDA ==================
  delay(5);
}

//////////////////////////////////////////////////////

void sonidoAcierto(){
  tone(buzzerPin, 400);
  delay(50);
  tone(buzzerPin, 500);
  delay(50);
  tone(buzzerPin, 600);
  delay(50);
  tone(buzzerPin, 700);
  delay(100);
  noTone(buzzerPin);
  delay(50);
}

void sonidoVictoria(){
  tone(buzzerPin, 659); // E5
  delay(200);
  tone(buzzerPin, 784); // G5
  delay(200);
  tone(buzzerPin, 880); // A5
  delay(200);
  tone(buzzerPin, 1047); // C6
  delay(400);
  tone(buzzerPin, 880); // A5
  delay(200);
  tone(buzzerPin, 784); // G5
  delay(200);
  tone(buzzerPin, 659); // E5
  delay(200);
  tone(buzzerPin, 523); // C5
  delay(800);
  noTone(buzzerPin);
}

void startSpaceOdyssey(){
  tone(buzzerPin, 1175); // D6
  delay(200);
  tone(buzzerPin, 988); // D#6/Eb6
  delay(200);
  tone(buzzerPin, 880); // A5
  delay(200);
  tone(buzzerPin, 783); // G5
  delay(400);
  tone(buzzerPin, 880); // A5
  delay(200);
  tone(buzzerPin, 988); // D#6/Eb6
  delay(200);
  tone(buzzerPin, 1175); // D6
  delay(200);
  tone(buzzerPin, 1319); // E6
  delay(800);
  noTone(buzzerPin);
}


Problemas surgidos y soluciones

Problema encontrado con la sincronización

La función delay() detiene la ejecución del código por completo durante el tiempo que se especifique. Esto producía un retardo tremendo a la hora de registrar el tiempo transcurrido de forma que dilataba su valor. Lo mismo ocurría con la acción de pulsar el botón. Debía mantenerse hasta 3 segundos para que el valor del LED al que apuntaba consiguiera activarse. Todo esto ocurría por culpa de la rotación de los motores, ya que para que las dianas pasaran a levantarse el valor del ángulo del servomotor debía variar entre 0 y 90 grados. No iba a ser posible por tanto realizar una acción mientras un led se encendía o una diana se accionaba.

Para solucionar esto se optó por utilizar la función millis(). El funcionamiento de ésta es relativamente sencillo. En el momento en el que se ejecute el sketch cargado en placa un contador empieza a guardar los milisegundos transcurridos. De esta manera, nos permite saber el tiempo que nosotros determinemos a partir de realizar una operación y comparando con el intervalo de tiempo que queramos (1 seg. 30 seg. o incluso décimas de segundo). El resultado de esta función se guardará en la variable “currentMillis”. Guardaremos también en cada iteración el valor que tenga el botón (HIGH-LOW) y los sensores de luz.

Problema encontrado con la alimentación

La razón por la que algunos de los componentes tengan que gozar de alimentación propia es porque no llegaba la que proporcionaba el regulador de Arduino para todos y porque alguno de los componentes requería cuidados especiales.

En el caso de los servomotores, las tensiones caían en el resto de los componentes y a los motores les faltaba fuerza para levantar la diana.

En caso de la pistola, el botón interfería con las demás tensiones. Se intentó conectar a la misma protoboard que el resto de los componentes del circuito (display, leds, LDR, buzzer) y el láser disminuía la intensidad cuando el botón era pulsado, por lo que la solución fue darles alimentación propia.

Respecto al display, en un principio quisimos darle alimentación propia como a los servomotores y a la pistola, sin embargo, probáramos lo que probáramos, el display no funcionaba. Al final decidimos probar a dejarlo conectado junto con el resto de los componentes alimentándose del regulador de 5V de Arduino y descubrimos que de esta manera sí que funcionaba “perfectamente” (pues si un cable se atrevía a no hacer buena conexión, el display se encargaba de “estropear” el resto del circuito, actuaba como un pulsador, por lo que tuvimos muchísimo cuidado con todas las conexiones).

Problema encontrado con el display LCD

Nuestra idea principal fue integrar una pantalla LCD para mostrar el menú de los juegos y puntuaciones, pero esta requería muchísimos pines, por lo que investigamos la posibilidad de emplear desplazadores para ampliar la capacidad de pines de Arduino. Desgraciadamente, no llegamos a buen puerto por ese camino, pues no logramos hacer que el desplazador funcionara, así que abortamos la idea y lo solucionamos creando el menú por selección de dianas que tenemos actualmente.


Posibles mejoras

Mientras trabajábamos en el proyecto, nos surgieron ideas para mejorar la idea general y hacerla más atractiva y compleja.

  • Pantalla LCD.
    • Añadir una pantalla LCD para la elección de modos de juego o dificultad y para ver información relevante de la partida como récord de puntos, sustituyendo el mecanismo de menú-diana que hemos establecido nosotros.
  • Nuevos modos.
    • En lugar de modos de dificultad, podría haberse creado varios juegos diferentes.
      • Pistola con munición determinada, por lo que sería un desafío de absoluta puntería. Una vez agotadas las balas, termina la partida.
      • Dianas con vida. Disparar varias veces a la misma diana hasta que se “rompa”.
  • Pistola bluetooth.
    • Con un módulo bluetooth, hacer la pistola inalámbrica e independiente del resto del circuito. Mejoraría aspectos como la posibilidad de desconexión de cables por algún tirón sin mala intención.
  • Dos jugadores.
    • La posibilidad multijugador abarcaría un espectro de ideas muy grande. Se podría replicar el mismo stand, pero contrapuesto, y que los dos jugadores se mirasen cara a cara mientras aciertan o fallan disparos. Se podría incluso implementar algún modo de juego especial para dos jugadores, con movimientos inesperados de dianas en un campo o en otro, o penalizaciones de restricción de balas hacia el jugador contrario si aciertas una diana en un determinado momento.
  • Atractivo general.
    • Mejorar la temática del proyecto, como teníamos pensado inicialmente, y decorarlo todo con aspecto espacial.

Video explicativo

Anexo con imágenes del proceso

También te podría gustar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *