Está en la página 1de 28

d

Sistemas Empotrados y de Tiempo Real Curso 2011/2012

PROYECTO FINAL: JUEGO CONTROLADO POR VOLANTE

Marta Aguado Daz Yolanda Garca Snchez Sara Martnez Herrera

NDICE

1. DESCRIPCIN DEL PROYECTO...................................................................1 2. MATERIAL UTILIZADO Y PRESUPUESTO ...................................................2 3. IMPLEMENTACIN ..................................................................................3 4. DIAGRAMAS DEFINITIVOS .......................................................................7 5. HOJAS DE CARACTERSTICAS DE COMPONENTES ....................................9 6. CDIGO.................................................................................................12 7. CONCLUSIN ......................................................................................... 26 - Dificultades surgidas ...................................................................... 26 - Tiempo dedicado al proyecto ......................................................... 26 - Opinin personal ........................................................................... 26

1. DESCRIPCIN DEL PROYECTO


Este proyecto consiste en un mando, en forma de volante, con el que controlar un sencillo videojuego de conduccin. Dicho juego ser semejante a la prueba que hay que superar para el examen psicotcnico del carnet de conducir, pero en este caso con un slo coche que debe avanzar por una pantalla. En realidad, es el entorno del vehculo el que se mueve hacia abajo en la pantalla; el usuario puede girar hacia la derecha o la izquierda con el movimiento del mando. En principio el usuario dispone de un determinado nmero de vidas, que pierde en el caso de chocar contra un obstculo. Cuando pierda todas las vidas, deber reiniciar el juego. A continuacin se presenta un esquema con las pantallas del juego y las posibilidades que tiene el usuario:

2. MATERIAL UTILIZADO Y PRESUPUESTO

Componente

Imagen

Precio

Shaftless Vibration Motor

4,95

Sensor digital tilt x2

7,00

Kit Arduino UNO: cables jumper, protoboard, botones push, resistencias de 10K

50 aprox. (proporcionado)

Material para el volante: funda de volante de coche con soporte y caja

5,00 aprox.

Materiales de unin: cinta, clavos, plastilina, pegamento,...

Se dispona de ellos TOTAL 66,95

3. IMPLEMENTACIN
A nivel externo, este proyecto consiste en un volante al que se ha adherido una caja con todas las partes utilizadas (placa Arduino, placa de conexin y componentes), con la necesaria salida para el puerto USB.

Figura 1: resultado final del volante

Por otro lado, la implementacin hardware est basada en el uso de sensores tilt, componentes que pueden detectar la inclinacin de un objeto. Se consideran equivalentes a botones push pero con un mecanismo de activacin diferentes: son una versin de los interruptores de mercurio, tal que contienen una bola mecnica en el interior que conecta dos pines, pasando de apagado a encendido y viceversa si el sensor alcanza determinado ngulo. Para el volante objeto de nuestro proyecto se han utilizado dos de estos sensores digitales, colocados verticalmente a ambos lados de la mencionada caja. Como se puede observar en el esquema del siguiente apartado, constan de tres conexiones: a un pin digital, a voltaje y a tierra. De esta forma, cuando el volante se gira hacia la derecha, el sensor colocado en ese lado emite una seal digital de activacin, que se leer mediante Arduino y despus se tratar para mover en consecuencia el vehculo que se maneja. 3

Figura 2: sensores tilt

El volante dispone de 3 botones push, montados con su correspondiente resistencia, de 10K Ohmios, utilizados para manejar el juego. Los pulsadores o switches conectan dos puntos de un circuito al ser pulsados. Cuando el pulsador est abierto (sin pulsar) no hay conexin entre las dos patas del pulsador, de forma que el pin est conectado a tierra (a travs de la resistencia pulldown) y se lee LOW (bajo 0). Cuando el botn se cierra (pulsado), se establece la unin entre sus dos extremos, conectando el pin a 5 voltios, por lo que se lee HIGH (alto 1).

Estos botones corresponden a funciones de Play/Pause, Subir y Bajar, de forma que, cuando el jugador est situado en un men, si pulsa los botones de subir o bajar, una flecha se mueve como corresponda para mostrar cul ser la opcin elegida si se pulsa el botn de Play/Pause.

Figura 3: interaccin de los mens

Adems, como elemento de salida se ha incluido un pequeo motor vibrador (motor de DC), conectado a un pin digital y a tierra, de forma que cuando dicho pin se active, se obtiene una vibracin nada despreciable. Se ha utilizado para transmitir una vibracin por el volante cuando se produce una colisin, generando as mayor sensacin de realismo al reproducir un choque.

Figura 4: motor vibrador

Como se ha ido comentando anteriormente, se necesita relacionar todos estos componentes para ofrecer la interfaz de un juego, en el que el volante mueva el vehculo que gua el juego (representado como un crculo), haya interaccin con los mens mediante los botones y las colisiones produzcan la vibracin del volante. 5

Para ello se ha utilizado Processing, un entorno con lenguaje de programacin propio que permite crear imgenes, animaciones e interacciones. Como se puede observar en el apartado 6, de forma anloga a lo que ocurre con Arduino, el cdigo de Processing consta de una funcin setup(), que se ejecuta una vez, y un bucle draw() que se repite constantemente. Adems, se trabaja con coordenadas y pxeles, tal que de forma sencilla se puede declarar, por ejemplo, una elipse, con la sentencia ellipse(x, y, width, weight), configurando la posicin (x,y) y sus dimensiones ancho y alto. Dicha posicin puede cambiar fcilmente a lo largo del tiempo modificando el valor de las variables x e y. Permite adems una fcil conexin con Arduino mediante el puerto serie (abierto con new Serial(this, Serial.list()[0], 9600). Para mover el vehculo, por ejemplo, se leen repetidamente mediante Arduino las seales de los puertos a los que se han conectado los sensores tilt. Si uno de ellos se ha activado (siempre que el otro no lo est, para evitar inconsistencias), se manda por el puerto serie una seal (con Serial.println()). La parte de Processing es capaz de leer esa seal del puerto (con puerto.read()) e incrementar o decrementar la posicin del vehculo. Esta estrategia se ha utilizado tanto con los dos sensores como con los botones, mediante un cdigo numrico: se puede enviar del 1 al 6 (49 a 53 en ASCII, al leer en Processing) y se tratar de los sensores derecho e izquierdo y los botones de Bajar, Subir y Play/Pause respectivamente. El caso del vibrador es muy similar, pero en sentido contrario: es Processing quien detecta una colisin, escribir un 6 en el puerto y Arduino lo leer, activando la seal del pin correspondiente.

Se ha intentado disear un juego agradable y sencillo de comprender. Algunos de los aspectos a destacar son: - Processing permite mostrar y mover imgenes en formato jpg o png (los mens, smbolos de las vidas, detalles de las casas y rboles a los lados de la carretera, imagen de Game Over,). En primer lugar se debe cargar la imagen con loadImage(nombre) y despus simplemente mostrarla donde se desee, proporcionando las coordenadas, con image(nombre, x, y). - Para dar mayor dinamismo y que cada partida sea diferente, la posicin en el eje x de los obstculos y sus colores son aleatorios. - Tambin es posible manejar texto mediante el comando text(). Se debe cargar la fuente que se desee y tambin se puede decidir el color y la posicin. Se ha utilizado en los crditos finales.

- Se ha utilizado la librera para sonido Minim, que permite crear variables de tipo AudioPlayer, que luego se pueden manejar con las funciones play() o pause(). En este juego existe una cancin principal que suena constantemente, y se han intercalado sonidos de colisiones, una explosin cuando se pierden todas las vidas o aplausos al ganar.

4. DIAGRAMAS DEFINITIVOS
A continuacin se presenta el diagrama definitivo del proyecto con todos los componentes, equivalente a la foto.

Figura 5: diagrama de componentes

Figura 6: montaje final de todos los componentes

5. HOJAS DE CARACTERSTICAS DE COMPONENTES


- Motor vibrador

Figura 7: hoja de caractersticas del motor vibrador

- Sensor tilt

10

Figura 8: hoja de caractersticas del sensor tilt

11

6. CDIGO
Arduino:
/* Proyecto Final Sistemas Empotrados: Crash Road. Grado Ingeniera Informtica, URJC. Marta Aguado Daz, Yolanda Garca Snchez, Sara Martnez Herrera. */ const int buttonPin = 2; // Sensor derecho const int buttonPin2 = 3; //Sensor izquierdo const int buttonOK = 6; // botn de OK const int buttonArriba = 5; // botn arriba const int buttonAbajo = 4; // botn abajo const int ledPin = 13; // the number of the LED pin

const int pinVibrador = 11; int int int int int sensor1State=0; sensor2State=0; buttonStateAbajo=0; buttonStateArriba=0; buttonStateOk=0;

void setup() { Serial.begin(9600); pinMode(buttonPin, INPUT); pinMode(buttonPin2,INPUT); pinMode(pinVibrador,OUTPUT); } void loop(){ //Leer la seal de los sensores. Si estn activados, escribir en el puerto //serie un 1 (sensor derecho) o un 2 (sensor izquierdo). sensor1State = digitalRead(buttonPin); if (sensor1State == HIGH && sensor2State == LOW) { Serial.println(1); } else { Serial.println(0); } sensor2State = digitalRead(buttonPin2); if (sensor2State == HIGH && sensor1State == LOW) {

12

Serial.println(2); } else { Serial.println(0); } //Para los botones de Arriba, Abajo y Play/Pause, escribir en el puerto serie 3, 4 y 5. buttonStateAbajo = digitalRead(buttonAbajo); if (buttonStateAbajo == HIGH) { Serial.println(3); } buttonStateArriba = digitalRead(buttonArriba); if (buttonStateArriba == HIGH) { Serial.println(4); } buttonStateOk = digitalRead(buttonOK); if (buttonStateOk == HIGH) { Serial.println(5); //boton OK => 53 }

//Si se ha recibido seal de que hay colisin (6 por el puerto), vibrar un segundo. if(Serial.available()>0){ int rec = 0; rec = Serial.read(); if (rec== 6){ digitalWrite(pinVibrador, HIGH); delay(1000); digitalWrite(pinVibrador, LOW); } } }

13

Processing:
/* Proyecto Final Sistemas Empotrados: Crash Road. Grado Ingeniera Informtica, URJC. Marta Aguado Daz, Yolanda Garca Snchez, Sara Martnez Herrera. */ import processing.serial.*; import ddf.minim.*; int ANCHO = 600; int ALTO = 600; int MAX_CUAD = 40; int vidas = 3; int v = 300; //Posicin del coche. Serial puerto; //Puerto serie para comunicacin arduino-processing.

//Para leer datos del puerto serie int val = 0; int val2 = 0; int val3 = 0; //Obstculos. float[] xs = new float[MAX_CUAD]; int[] ys = new int[MAX_CUAD]; float[] colors = new float[MAX_CUAD*3]; int[] lineas = new int[10]; //Imgenes utilizadas. PImage menu, gameOver, vida, pause, toPlay, meta, casita, arbol, instrucciones; int int int int int int vida1X vida1Y vida2X vida2Y vida3X vida3Y = = = = = = 25; 10; 25; 65; 25; 120;

//Tringulo de men principal. int x1=340; int y1=70; int x2= 340; int y2=40; int x3=300; int y3=55;

14

//Tringulo del men Pause int x11 = 420; int y11 = 55; int x22 = 420; int y22 = 25; int x33 = 380; int y33 = 40; boolean inicio = true; boolean menu1 = true; boolean parar = false; boolean jugar = false; boolean puertoAbierto = false; boolean ganado = false; boolean over = false; boolean repetido = false; boolean instrucc = false; int colisionado = -1; //Posiciones iniciales de crditos y meta. int yWinner = -200; int xWinner = 100; int xCreditos = 130; int yCreditos = -1550; int yMeta = -40; String creditos = " Pulsa para volver a jugar\n\n\n\n\n\n\n\n\n\n\n\n" + " Marta Aguado Daz\n\n" + " Yolanda Garca Snchez\n\n" + " Sara Martnez Herrera\n\n"; //Fuentes para los crditos. PFont fuente, fuenteGrande; //Posiciones iniciales detalles. int xCasa = 29; int yCasa = 200; int xCasa2 = 520; int yCasa2 = -200; int yCasa3 = -900; int yCasa4 = -1650; int yCasa5 = -2300; int yCasa6 = -2500; int yCasa7 = -3100; int xArbol = 20;

15

int int int int int

yArbol = -100; xArbol2 = 520; yArbol2 = -400; yArbol3 = -1300; yArbol4 = -2900;

//Msica y efectos. AudioPlayer cancion, explosion, colision1, colision2, aplausos; Minim minim; void setup() { //Restablecer valores iniciales. vidas = 3; vida1X vida1Y vida2X vida2Y vida3X vida3Y = = = = = = 25; 10; 25; 65; 25; 120;

xCasa = 29; yCasa = 200; xCasa2 yCasa2 yCasa3 yCasa4 yCasa5 yCasa6 yCasa7 = = = = = = = 520; -200; -900; -1650; -2300; -2500; -3100;

xArbol = 20; yArbol = 350; xArbol2 = 520; yArbol2 = -400; yArbol3 = -1300; yArbol4 = -2900; //Msica (librera Minim). minim = new Minim(this); cancion = minim.loadFile ("crash0.mp3"); explosion = minim.loadFile("explo.mp3"); colision1 = minim.loadFile("colision.mp3"); colision2 = minim.loadFile("colision.mp3"); aplausos = minim.loadFile("aplausos.mp3"); size(ANCHO, ALTO);

16

if (!puertoAbierto) { //Solo abrir una vez (por si se vuelve a empezar). String nombrePuerto = Serial.list()[0]; puerto = new Serial(this, nombrePuerto, 9600); puertoAbierto = true; } //Cargar fuentes e imgenes. fuente = loadFont("Fuente.vlw"); fuenteGrande = loadFont("FuenteGrande.vlw"); menu = loadImage("FondoS2.png"); gameOver = loadImage("gameOver.jpg"); vida = loadImage("Vida1.png"); pause = loadImage("FondoPAUSE.png"); toPlay= loadImage("FondoS.png"); meta = loadImage("meta.jpg"); casita = loadImage ("casita.png"); arbol = loadImage("arbol.jpg"); instrucciones = loadImage("instr.jpg");

//Obstculos. for (int i=0;i<MAX_CUAD;i++) { //Coordenadas x aleatorias xs[i] = random(340) + 130; //random(370) devuelve valores entre 0 y 370 => entre 130 y 470 }

for (int j=0;j<MAX_CUAD;j++) {//Coordenadas y fijas (salen poco a poco); ys[j] = -100*j-50; //-50, -150, -250,... } for (int k = 0; k<MAX_CUAD*3;k++) { //R, G y B para cada uno de los cuadrados colors[k] = random(255); } for (int n=0;n<10;n++) { //Coordenada y de las lneas discontinuas. lineas[n] = 70*n +20; //-20, 50, 120,... } cancion.play(); } //setup

17

void draw() { image(toPlay, 0, 0); while (puerto.available () > 0) { val = puerto.read(); } if (val==53) { // pulsado OK menu1=true; inicio=false; } if (!inicio) { if (!repetido) { delay(10); image (menu, 0, 0); //Primer men while (puerto.available () > 0) { val = puerto.read(); } if ( (y3==55) && (val==51)) { //De Jugar a Instrucciones x1=540; y1=115; x2=540; y2=85; x3=500; y3=100; } else if ( (y3==100) && (val==51)) { //De Instrucciones a Salir x1=340; y1=160; x2=340; y2=130; x3=300; y3=145; } else if ((y3==145) && (val==52)) { //De Salir a Instrucciones x1=540; y1=115; x2=540; y2=85; x3=500; y3=100; } else if ((y3==100) && (val==52)) { //De Instrucciones a Jugar x1=340; y1=70;

18

x2=340; y2=40; x3=300; y3=55; } else if ((y3==145) && (val==53)) { //Pulsado Salir exit(); } else if ((y3==100) && (val==53)) { instrucc = true; } else if ( (y3==55) && (val==53)) { //Pulsado Jugar menu1 = false; jugar = true; } strokeWeight(3); triangle(x1, y1, x2, y2, x3, y3); strokeWeight(1); } if (jugar) { //Apartar el tringulo x1=1000; y1=1000; x2=1000; y2=1000; x3=1000; y3=1000; background(78); fill(0, 180, 100); rectMode(CORNER); //Lados de la carretera. rect(-10, -10, 110, 700); rect(500, -10, 100, 700); rectMode(CENTER); fill(255, 255, 255); for (int p = 0;p<10;p++) { //lneas discontinuas. if (lineas[p] == 620) { //Si toca el borde inferior lineas[p] = -10;//Vuelve a aparecer por arriba } }

19

if (!parar) { // Si est en pause, parar juego. for (int m=0; m<10;m++) { //Mover lneas discontinuas. lineas[m] = lineas[m] + 2; } for (int n=0; n<10;n++) { //Pintar lneas discontinuas rect(300, lineas[n], 20, 40); } //Mover casas y rboles. yCasa = yCasa+2; yCasa2 = yCasa2+2; yCasa3 = yCasa3+2; yCasa4 = yCasa4+2; yCasa5 = yCasa5+2; yCasa6 = yCasa6+2; yArbol = yArbol+2; yArbol2 = yArbol2+2; yArbol3 = yArbol3+2; yArbol4 = yArbol4+2; //Mostrar casas, rboles y vidas. image(casita, xCasa, yCasa); image(casita, xCasa2, yCasa2); image(casita, xCasa2, yCasa3); image(casita, xCasa2, yCasa4); image(casita, xCasa2, yCasa5); image(arbol, image(arbol, image(arbol, image(arbol, xArbol, yArbol); xArbol2, yArbol2); xArbol2, yArbol3); xArbol2, yArbol4);

image(vida, vida1X, vida1Y); image(vida, vida2X, vida2Y); image(vida, vida3X, vida3Y); int v2 = 0; // Incremento de la posicin del coche. while (puerto.available () > 0) { val = puerto.read(); } // Leer sensores

if (val == 49) { //Detectado sensor derecho v2 = 10; } else if (val ==50) { v2 = -10; } else v2 = 0; v= v + v2; //Mover coche //Detectado sensor izqdo

20

fill(0, 102, 204); // Color coche if (v<=120) //No sobrepasar bordes de carretera. v=120; if (v>=480) v=480; ellipse(v, 475, 40, 40); //Pintar coche rectMode(CENTER); for (int i =0; i<MAX_CUAD; i++) { //Mover obstculos ys[i]= ys[i]+2; } for (int i =0; i<MAX_CUAD; i++) { //Asignar colores aleatorios y pintar obstculos. fill(colors[i], colors[i+1], colors[i+2]); rect(xs[i], ys[i], 50, 50); } for (int j=0;j<MAX_CUAD;j++) { //Colisiones if (( ((v+20>=xs[j]-25) && (v+20<=xs[j])) || ((v-20<=xs[j]+ 25) && (v-20>=xs[j])) || ((v+20<=xs[j]+25) && (v-20>=xs[j]-25)) || (((xs[j]+25)-(v+20)<25) && (v+20<xs[j]+25) ) || ( ((v-20)-(xs[j]-25)<25) && (v-20>xs[j]-25) ) ) && (ys[j]<=500)&& (ys[j]>=450) && colisionado != j) { colisionado = j; //Solo una colisin por cuadrado. colision(); } } if (ys[MAX_CUAD-1] > 515 && vidas>0) { //ltimo obstculo sobrepasa coche. ganado = true; } } //if (!parar).

if (ganado) { //Se ha acabado ganando. //Mover meta y crditos. yMeta = yMeta + 2; image (meta, 100, yMeta); fill(255, 255, 255); yWinner = yWinner + 2; textFont(fuenteGrande); text (" WINNER", xWinner, yWinner); yCreditos = yCreditos + 2;

21

textFont(fuente); text(creditos, xCreditos, yCreditos); if (yMeta==ALTO) { //Sonido final. aplausos.play(); } }

while (puerto.available () > 0) { val2 = puerto.read(); if (val2==53) { //Pulsado ok parar = true; } }

// Leer sensores

if (parar) { //Mostrando men Pause inicio = false; menu1 = false; image(pause, 0, 0); /* while (puerto.available () > 0) { val3 = puerto.read(); println(val3); } */ //Mover tringulo o actuar segn dato del puerto. if ((y33==40) && (val==51)) { //De Continuar a Volver a Empezar x11 y11 x22 y22 x33 y33 } else if ((y33==85) && (val==51)) { //De Volver a Empezar a Intrucciones x11 y11 x22 y22 x33 y33 } else if ((y33==130) && (val==51)) { //De Instrucciones a Salir = = = = = = 515; 145; 515; 115; 475; 130; = = = = = = 590; 100; 590; 70; 550; 85;

22

x11 y11 x22 y22 x33 y33 }

= = = = = =

320; 190; 320; 160; 280; 175;

else if ((y33==175) && (val==52)) { //De Salir a Instrucciones x11 y11 x22 y22 x33 y33 } else if ((y33==130) && (val==52)) { //De Instrucciones a Volver a Empezar x11 y11 x22 y22 x33 y33 } else if ((y33==85) && (val==52)) { //De Volver a Empezar a Continuar x11 y11 x22 y22 x33 y33 } else if ((y33==175) && (val==53)) { //Salir exit(); } else if ((y33==40)&&(val==53)) { //Continuar image(pause, 1000, 1000); parar = false; } else if ((y33==130) && (val==53)) { //Instrucciones instrucc=true; } else if ((y33==85) && (val==53)) { //Volver a Empezar = = = = = = 420; 55; 420; 25; 380; 40; = = = = = = 590; 100; 590; 70; 550; 85; = = = = = = 515; 145; 515; 115; 475; 130;

23

cancion.pause(); inicio = false; menu1 = false; parar = false; jugar = true; repetido = true; ganado = false; setup(); draw(); } //Pintar tringulo de los mens. fill(255, 255, 255); strokeWeight(3); triangle(x11, y11, x22, y22, x33, y33); strokeWeight(1); } //if parar if (over && !parar) { //En el men gameOver. image(gameOver, -50, 0, 800, 616); cancion.pause(); explosion.play(); if (val==53) { //Si se pulsa ok - volver a men inicial. fill(255, 255, 255); x1=340; y1=70; x2=340; y2=40; x3=300; y3=55; setup(); over = false; menu1 = true; inicio = false; ganado = false; parar = false; } }

24

} //if (jugar) } //if !inicio if (instrucc) { image(instrucciones, 0, 0); while (puerto.available ()>0) { val3 = puerto.read(); if (val3==53) { //Pulsado ok image(instrucciones, 1000, 1000); instrucc = false; } } } } //draw void colision() { if (vidas==3) { //Se tenan todas las vidas. vidas = 2; vida3X=ANCHO*2; vida3Y=ALTO*2; colision1.play(); puerto.write(6); } else if (vidas==2) { vidas = 1; vida2X=ANCHO*2; vida2Y=ALTO*2; colision2.play(); puerto.write(6); } else if (vidas==1) { vidas = 0; vida1X=ANCHO*2; vida1Y=ALTO*2; over = true; puerto.write(6); } }

25

7. CONCLUSIN

- Dificultades surgidas:
En cuanto a las dificultades halladas a lo largo del proyecto, la ms importante ha sido el Bluetooth. Inicialmente el mando deba comunicarse de manera inalmbrica con el ordenador mediante un Shield Bluetooth, pero debido a la falta de cdigo, y a problemas surgidos con las pilas y con el ordenador, se retir del proyecto. Otro de los grandes conflictos, que an no se ha resuelto por completo, es el ruido y los rebotes al pulsar los botones, que dificultan el perfecto funcionamiento de los mens. Se intent implementar un algoritmo anti-rebotes (basado en la tcnica debounce), pero los resultados no eran relevantes as que se ha eliminado para limpieza del cdigo. Tambin hubo dificultades a la hora de lograr que el motor de vibracin se activara al chocar contra los obstculos. No resulto fcil, por otra parte, trabajar con la implementacin del cdigo, especialmente de Processing, ya que se trata de dos entornos completamente nuevos y que suponen una forma de trabajar muy distinta, con el programa principal dentro siempre de un bucle.

- Tiempo dedicado al proyecto:


El proyecto comenz el da 5 de Marzo y ha finalizado el 23 de Abril. Contando con las horas de clase y el tiempo empleado fuera horario lectivo, el total de horas dedicadas se estima que ha sido ms de 50. Hay que tener en cuenta que las dificultades sealadas en el apartado anterior y que los componentes llegaron ms tarde de lo previsto; sin embargo, la motivacin del equipo ha sido alta.

- Opinin personal:
En definitiva, el resultado de este proyecto ha sido satisfactorio, a pesar del problema con la conexin Bluetooth. Es un proyecto bastante completo, que ha permitido trabajar con elementos tanto de entrada como de salida, manejar cdigo y realizar un montaje fsico. Creemos que muestra la capacidad de Arduino para crear proyectos divertidos y funcionales con pocos componentes y un presupuesto asequible. Adems, es fcilmente adaptable a otros juegos de manejo de vehculos, por ejemplo, en carreras y/o juegos en 3D. 26

También podría gustarte