Light gun – arduino & processing [PT]

Neste miniprojecto iremos desenvolver um jogo e construir de raiz uma light gun semelhante  às popularizadas pelos videojogos de 8 bits da década de 1980. O projecto usa materiais e componentes comuns e disponíveis localmente em qualquer cidade, e às plataformas opensource Arduino e Processing.

Este é um projeto muito divertido de construir e que pode ser facilmente operacionalizado como workshop/oficina de computação física ou numa sala de aula.

Neste vídeo podemos assistir a um grupo de alunos do mestrado MCMM, da Universidade de Aveiro, a fabricarem e a testarem a lighgun.

lightgun

 

Modo de funcionamento

diagrama_geral

A pistola (1) é constituída por uma lente convergente  (pequena lupa ou objectiva de binóculos de brinquedo) que concentra a luz proveniente de um monitor, uma televisão ou de um videoprojetor  num sensor sensível à luz -um fotodíodo comum (idêntido aos dos telecomandos). Além do sensor de luz está também presente um botão de pressão que representa o gatilho.
O arduino (3) gere os dados entre o sensor e o botão e comunica com o processing (4) , no qual corre o jogo.

diagrama_funcionamento2

O principal desafio técnico que este projecto suscita é conseguir obter um desempenho satisfatório na deteção do alvo, dado que não temos uma forma de conhecer diretamente a posição X, Y para onde a pistola aponta no visor (ao contrário do que sucede com os antigo monitores e TVs, os quais sendo baseados no CRT, tornavam o processo de deteção da posição x,y numa tarefa tecnicamente simples e fácil de implementar)

A medição e análise da variação absoluta da luminosidade do sprite face ao fundo simplesmente não funciona devido ao facto da luminosidade média envolvente também ela variar ao longo tempo, com o tipo de visor e também de local para local. Por outro lado o sprite move-se face ao fundo e pode não ser possível estabelecer um limite que em termos de lumonisidade seja possivel discriminá-los.

Para ultrapassar estes problemas limita-se a deteção a um único alvo e implementa-se o seguinte algoritmo.

Quando o jogador clica, o arduino informa o processing e o jogo passa imediamente a exibir um fundo negro. É realizada a medição do nível de luminosidade do ecrã a negro que no fundo se trata da luminosidade ambiente.
Em seguida, sobre o fundo negro, o processing apresenta uma imagem do contorno do sprite preenchida a branco e proceder-se a um nova medição da luminosidade.

diagrama_funcionamento

Se o nível do sprite, subtraído do nível negro, ultrapssar um certo limiar, significa que o sensor está apontar para o alvo. Caso contrário, o valor do sprite é muito próximo, ou mesmo idêntico, ao do nível negro é sinal que o jogador falhou o tiro.

Circuito

O elemento principal deste projecto é a pistola. Sem o cano e a lente convergente o sensor iria colectar  a luz de uma áera muito vasta. Assm, se o jogador se posicionar a uma distância razoável do ecrã (mais de 2 metros), podemos assumir que os raios de luz emitidos pelo monitor chegam à lente com um âmgulo paralelo. A lente convergente concentra no plano da  distância focal (plano de focagem), a luz proveniente de uma área muito reduzida do ecrã. Na zona do gatilho incorporamos um botão de pressão.

diagrama_gun

Entre a pistola e o arduino usamos como extensão um cabo de telefone de 4 fios (atenção alguns têm apenas 2), mas, qualquer outro cabo pode servir para o mesmo efeito.

No lado oposto da extensão, na outra extremidade do cabo junto ao arduino, implementamos um circuito simples de amplificação do sinal que provém do fotodíodo. Este último receptor, geralmente, é montado num simples circuito de divisor de voltagem, (tal como no caso do LDR). Contudo, a diferença de sinal entre o nível negro e o do sprite, uma vez que se trata de luz emitada no espectro visível e o receptor é mais sensível na regiaõ do IF próximo, necessita de maior amplificação para conseguir ser discriminado.

circuito_1circuito_2

O fototransístor seria o componente adequado para este projecto, porém, é mais difícil de adquirir numa loja de electrónica convencional e por razão optámos por construir um fototransístor a partir do componente recpetor fotodíodo. Na verdade, a única diferença entre os dois é a presença de um transístor que amplifica o sinal, como está representado na figura acima ao lado esquerdo.

Do lado direito temos o circuito do botão que é ligado ao arduino no pino 8, o qual, será configurado com uma resistência PULL UP   interna.

circuito_3

A montagem física, do lado do arduino, está representada na imagem acima. O circuito de amplifcação está implementado na breadboard e o sinal chega ao arduino pelo pino adc 0. O botão, por sua vez, pode ser ligado do cabo directamente na linha de pinos do arduino.

 

 

Lista de Materiais e equipamentos

materiais

A lista de material usado neste miniproejcto é a seguinte.

  • Arduino
  • Breadboard
  • Fotodíodo 850 nm, vulgarmente conhecidos por LED receptor infra-vermelhos. Outros fotodíodos marcados com comprimentos de onda superiores simplesmente não funcionam porque não apresentam sensibilidade à luz visível.
  • Botão de pressão
  • Transístor BC548 (este componente não é crítico, pode ser subsituído pelo BC547 ou qualquer outro  de baixa potência)
  • Resistência 220R (novamente, o valor da restência não é crítico, podendo ser qualquer um na gama dos 220 até cerca de 1k )
  • Cabo/ extensão de telefone com 4 fios.
  • lupa ou pequena obejctiva (de uns binóculos de brinquedo) entre 2 a 5 cm de diâmetro. Não deve possuir uma ampliaçaõ muito elevada, caso contrário , a distância focal é muito reduzida.

Equipamento

  • Tesoura e Xacto
  • Cartolina negra
  • fita isoladora negra
  • Alicate de corte
  • Papelão (de caixote) ou outros materiais para construir o exterior da pistola.

 

 

Montagem da lightgun

O componente principal é o tubo. Ele deverá ser negro (cartolina negra) para evitar a difusão de luz no interior do tubo e assim aumentar o contraste do sinal.

O fotodíodo deverá estar posicionado precisamente no plano da distância focal (quando foca no infinito).

Comecemos por medir com a ajuda de uma régua, uma folha branca e um ponto de luz, a distância focal da lente.

build_gun2

Foque o ponto de luz e, com a régua, meça a distância da folha à lente. Esse valor designaremos por df .

Para determinar o perímetro do tubo medimos o diâmetro da lente, multiplicamos por 3,14 e adicionamos mais um ou dois cm para dar solidez. Esse valor desginaremos por p.

Corte um rectângulo a dimensão dos lados p e df como no figura 1 da iamgem em baixo.

Devemos enrolar a cartolina (2) de modo a ficar com o diâmetro ligeiramente mais pequeno que o da lente impedindo que esta última entre para dentro do tubo (3).

build_gun1

Corte um círculo com o diâmetro igual ao da lente e, no centro, faça dois furos para as pernas do fotodíodo. A cabeça deste componente tem de, obrigatoriamente ficar a apontar na direção da lente. Dobre 90º ambas pernas de modo a fixar o componente  ao círculo de cartolina. Com fita isoladora, reforce a a fixação.

20160305_193329

20160305_193351

Numa extremidade fixe com fita isoladora ao tubo a rodela do fotodíodo(2)  e a lente na outra (1).

build_gun5build_gun6

Envolva o tubo num novo mais comprido. Não se esqueça de perfurar ou cortar o tubo exterior de modo às pernas do fotodíodo ficarem salientes.

Por fim solde ou fixe com fita isoladora  as pernas do fotodíodo e o botão de pressão ao cabo extensor.

 

 

O Jogo

Para demonstrar a lightgun foi implementado no Processing um jogo simples do tipo arcade. O objectivo é defender a lua de um ataque de naves extra-terrestes. Aplicação é personalizavel e mudando os conteúdos  podemos facilmente adpatá-la a qualquer outro tema.

jogo

 

 

Arduino

arduinoheader

int tique=0;

void setup() {
pinMode(8,INPUT_PULLUP);
Serial.begin(115200);
}

void loop() {
if (Serial.available() > 0) {
int b = Serial.read();
int v=0;
for (int i=0;i<5;i++){
v+=analogRead(0);
}
v=v/5;
Serial.println(v);
}

if (tique>0) tique--;
if (digitalRead(8)==0 && tique==0){
Serial.println("F"); //FOGO
tique=500;
}

delay(1);

}

 

Processing

processing_header

import ddf.minim.*;
import ddf.minim.analysis.*;
import ddf.minim.effects.*;
import ddf.minim.signals.*;
import ddf.minim.spi.*;
import ddf.minim.ugens.*;

/*
MV 2016
*/

import processing.serial.*;
Minim minim;
AudioPlayer alien;
AudioPlayer sbang;
AudioPlayer click;

final int _NORMAL=0;
final int _DISPARO_START=1;
final int _DISPARO_BLACK=2;
final int _DISPARO_SPRITE=3;
final int _DISPARO_END=4;
final int _BANG=5;
final int _NOVANAVE=6;
final int _ESPERA=7;
final int _GAMEOVER=8;
final int _GAMEOVER_ESPERA=9;
final int _NEWGAME=10;
final int _NEWGAME_ESPERA=11;

int estado=_NEWGAME;
PImage spr;
PImage bang;
PImage fill;
PImage fundo;
int spx;
int spy;
Serial arduino;

int nivelBlack;
int nivelSprite;
int limiar=15;
boolean bangflag=false;

int pontos;
int scale;
int velocidade;
int tempo=0;

void setup() {
size(1280, 720);
frameRate(30);
fundo = loadImage("background.jpg");
arduino = new Serial(this, Serial.list()[0], 115200);
arduino.bufferUntil(10);
minim = new Minim(this);
alien = minim.loadFile("alien.mp3");
sbang = minim.loadFile("sbang.mp3");
click = minim.loadFile("click.mp3");
image(fundo, 0, 0);
}

void draw() {

tempo++;
switch (estado) {

case _NORMAL:
image(fundo, 0, 0);
printPoints();
spy=spy+velocidade;
image(spr, spx, spy);
if (spy>550) estado=_GAMEOVER;
break;

case _DISPARO_START:
click.rewind();click.play();
background(0, 0, 0);
estado=_DISPARO_BLACK;
break;

case _DISPARO_BLACK:
background(0, 0, 0);
delay(50);
arduino.write('R');
delay(10);
break;

case _DISPARO_SPRITE:
background(0, 0, 0);
fill(255, 255, 255);
image(fill, spx, spy);
estado=_DISPARO_END;
break;

case _DISPARO_END:
background(0, 0, 0);
fill(255, 255, 255);
image(fill, spx, spy);
//rect(spx, spy, spr.width, spr.height);
delay(50);
arduino.write('R');
delay(10);
break;

case _BANG:
sbang.rewind();sbang.play();
image(fundo, 0, 0);
printPoints();
image(bang, spx, spy);
if (!bangflag){
delay(500);
pontos+=velocidade*10 + velocidade*100/tempo;
velocidade=velocidade +2;
tempo=0;
estado=_NOVANAVE;
}
bangflag=false;
break;

case _NOVANAVE:
image(fundo, 0, 0);
printPoints();
loadSprites();
spy=0;
spx=int(random(1280-spr.width));
estado=_ESPERA;
break;

case _ESPERA:
image(fundo, 0, 0);
printPoints();
delay(1000+int(random(4000)));
alien.rewind();alien.play();
estado=_NORMAL;
break;

case _GAMEOVER:
image(fundo, 0, 0);
image(spr, spx, spy);
printGameOver();
printPoints();
estado=_GAMEOVER_ESPERA;
break;

case _GAMEOVER_ESPERA:
image(fundo, 0, 0);
image(spr, spx, spy);
printGameOver();
printPoints();
delay(5000);
estado=_NEWGAME;
break;

case _NEWGAME:
image(fundo, 0, 0);
printNewGame();
estado=_NEWGAME_ESPERA;
break;

case _NEWGAME_ESPERA:
image(fundo, 0, 0);
printNewGame();
velocidade=2;
pontos=0;
delay(3000);
estado=_NOVANAVE;
break;

}

}

void serialEvent(Serial p) {
String inString = p.readString();

if (inString.charAt(0)=='F' && estado==_NORMAL) {
estado=_DISPARO_START;
return;
}

if (estado==_DISPARO_BLACK) {
nivelBlack=1024-int(inString.trim());
println("black=" + nivelBlack);
estado=_DISPARO_SPRITE;
return;
}

if (estado==_DISPARO_END) {
nivelSprite=1024-int(inString.trim());
println("sprite=" + nivelSprite);
if ((nivelSprite-nivelBlack)>limiar) {
println("EM CHEIO!!!");
bangflag=true;
estado=_BANG;
}
else {
println("FALHOU!!!");
estado=_NORMAL;
}

}
}

void keyPressed() {
if (key == ' ') estado=_DISPARO_START;
}

void loadSprites(){
spr = loadImage("sprite.gif");
fill = loadImage("fill.gif");
bang = loadImage("bang.gif");
float sorte=0.5 + random(50)/100;
int w=int(spr.width*sorte);
int h=int(spr.height*sorte);
spr.resize(w,h);
fill.resize(w,h);
bang.resize(w,h);
}

void printPoints(){
textSize(30);
fill(255, 200, 0);
text("score " + pontos, 60, 60);
}

void printGameOver(){
textSize(100);
fill(255, 0, 0);
text("GAME OVER", 300, 400);
}

void printNewGame(){
textSize(100);
fill(255, 125, 0);
text("GET READY", 300, 400);
}

 

 

Ficheiros do projecto

Pasta de projecto do Processing e código fonte do arduino – download.

To be continued

 

 

 

 

 

 

Anúncios