Volante bluetooth – lego nxt + unity3d
Ter idéias boas para desenvolver coisas com o lego nxt é muito difícil, um dia jogando Dirt 2 no Xbox com o volante wireless tive a idéia de fazer um volante desse com o lego, isso ficou na minha cabeça por um tempo, com esse feriado de páscoa, resolvi por em prática já que não tinha nada para fazer mesmo.
O negócio funciona assim, o lego manda para um servidor socket escrito em java os dados de rotação e de dois botões(acelerador e freio, os dois juntos da a marcha ré), esse servidor manda para o unity3D uma string com as informações concatenadas, o unity processa a string e separa as informações.
Utilizei um jogo de carro de corrida já pronto, disponível em : JCar Demo , Todos os créditos para o autor, apenas modifiquei um script que controla o carro para adaptar na minha lógica.
O vídeo abaixo demonstra o funcionamento.
Para essa brincadeira foi desenvolvido 3 códigos.
O primeiro é para o cérebro do Lego, que apenas pega os dados de rotação de um motor e os estados de 2 sensores do toque, concatena em uma string separando por “/” as informações, exemplo:
“-20/true/false”, que seria -20 graus , acelerador apertado e freio não apertado.
import lejos.nxt.*;
import lejos.nxt.comm.*;
import java.io.*;
public class VolanteLego {
public static void main(String [] args) throws Exception
{
String connected = "Conectando";
String waiting = "Aguardando...";
String closing = "Fechando...";
String texto;
//Tira a força do Motor para poder girar manualmente
Motor.A.setPower(0);
//Pega os sensores
TouchSensor acelerador = new TouchSensor(SensorPort.S1);
TouchSensor freio = new TouchSensor(SensorPort.S2);
LCD.drawString(waiting,0,0);
LCD.refresh();
//fica aguardando o pc conectar
BTConnection btc = Bluetooth.waitForConnection();
LCD.clear();
LCD.drawString(connected,0,0);
LCD.refresh();
//cria o canal de saida
DataOutputStream dos = btc.openDataOutputStream();
while (!Button.ENTER.isPressed()) {
LCD.clear();
//monta uma string com os dados, eles serão filtrados no unity
texto = Motor.A.getTachoCount() + "/" + acelerador.isPressed() + "/" + freio.isPressed();
//apenas para Debug
LCD.drawString(texto, 0, 0);
LCD.refresh();
//envia os daos e atualiza o canal
// \n necessario para falar que acabou a string
dos.writeBytes(texto + "\n");
dos.flush();
}
//fecha e limpa tudo depois que terminou
dos.close();
Thread.sleep(100);
LCD.clear();
LCD.drawString(closing,0,0);
LCD.refresh();
btc.close();
LCD.clear();
}
}
O segundo código é um servidor em java, ele se conecta com o lego, pega os dados e transmite via socket, para ser capturado no unity3D.
import lejos.pc.comm.*;
import java.io.*;
import java.net.*;
public class VolantePC {
// variaveis para UDP socket
static DatagramSocket udpSocket;
static int porta = 23175;
static InetAddress localhost;
static DataOutputStream dos ;
static DataInputStream dis ;
//string que será compartilhada
static String bufferStr="";
public static void main(String[] args) {
// Conecta o nxt
NXTConnector conn = new NXTConnector();
boolean connected = conn.connectTo("btspp://");
if (!connected) {
System.err.println("Falhou a conexao com o NXT");
System.exit(1);
}
//canais de entrada e saida
dos = conn.getDataOut();
dis = conn.getDataIn();
// cria o socket para enviar
try
{
localhost = InetAddress.getLocalHost();
udpSocket = new DatagramSocket();
}
catch(SocketException e)
{
System.out.println("Não abriu o socket");
}
catch(UnknownHostException e)
{
System.out.println("Não achou o localhost!");
}
// Inicia uma nova thread para enviar os dados para o pc
System.out.println("Transmitindo dados UDP na porta "+porta);
(new Thread(new EnviarDados(udpSocket))).start();
//loop de leitura dos dados do lego
while(true)
{
try {
bufferStr = dis.readLine() + "\n";
} catch (IOException ioe) {
System.out.println("IO Exception lendo os bytes:");
System.out.println(ioe.getMessage());
break;
}
}
}
// thread para enviar os dados
public static class EnviarDados implements Runnable
{
DatagramSocket datagrama;
public EnviarDados(DatagramSocket datagrama)
{
this.datagrama = datagrama;
}
public void run()
{
// buffer para os dados de saida
DatagramPacket pacote;
byte[] pacoteBuffer;
try
{
// le os dados para o buffer
while(true)
{
// converte para byte array para enviar o pacote
pacoteBuffer = stringToByteArray(bufferStr);
pacote = new DatagramPacket(pacoteBuffer, pacoteBuffer.length, VolantePC.localhost, VolantePC.porta);
datagrama.send(pacote);
System.out.println(bufferStr);
Thread.sleep(33);
}
}catch(IOException e){
System.out.println("erro");
}catch(InterruptedException e) {
e.printStackTrace();
}
}
//le a string e converte para um array de bytes
byte[] stringToByteArray(String str)
{
int length = str.length();
byte[] byteArray = new byte[length];
for(int i=0; i<length; i = i + 1)
{
byteArray[i] = (byte)(str.charAt(i) & 0xff);
}
return byteArray;
}
}
}
O terceiro é um javascript que se conecta com o servidor socket, filtra a string com os dados e fornece funções para passar os dados para o jogo.
#pragma strict
import System.Net.Sockets;
import System.Net;
import System.Text;
// qual porta o socket usará
var porta:int = 23175;
//string recebida no socket
private var dados:String = "";
// buffer dos dados pegos
private var buffer:byte[];
// Referencia para o socket UDP
private var cliente:UdpClient;
private var source:IPEndPoint;
private var rotacao:String;
private var aceleracao:String;
private var freio:String;
//inicia o socket
function Awake()
{
IniciarSocket();
}
//le os dados
function Update()
{
buffer = cliente.Receive(source);
dados = Encoding.ASCII.GetString(buffer);
dados = dados.Replace("\n", "");//limpa o finalizador da string
//filtra os dados
rotacao = dados.Substring(0,dados.IndexOf("/"));
dados = dados.Substring(dados.IndexOf("/")+1);
aceleracao = dados.Substring(0,dados.IndexOf("/"));
freio = dados.Substring(dados.IndexOf("/")+1);
}
public function getRotation():float{
//transforma os dados em um range de -1 até 1, depois inverte o sinal
return parseFloat(Mathf.Clamp(parseInt(rotacao), -90, 90)/100.0f)*-1.0f;
}
function getAcceleration():float{
//se só acelerar vai para frente, se aceletar e freiar da ré , ou desacelera
return aceleracao=="true"?(freio=="true"?-1:1):0;
}
function getBrake():boolean{
//só freiará se apenas o freio estiver apertado
return (freio=="true" && aceleracao=="false");
}
/**
* Inicia a comunicação com o servidor
*/
function IniciarSocket()
{
try
{
cliente = new UdpClient(porta);
source = new IPEndPoint(IPAddress.Any, 0);
}
catch(e:System.Exception)
{
Debug.Log(e.ToString());
}
}
Mais um código em C# foi modificado no JCar, para ler as informações desse último código, ele apenas pega os dados e utiliza no jogo.
Para você refazer esse negócio na sua casa, coloquei os códigos para download, apenas tenho umas observações.
O código javascript deve ficar dentro da pasta plugin, pois o unity tem uma frescura no compilador que atrapalha a referência na chamada de um código javascript por um script em c#, colocando dentro da pasta chamada plugins ele é compilado antes, não causando mais problemas.
Passos para refazer:
- Baixar o demo do JCar que está no link acima e substituir o arquivo JControlledCar.cs.
- Copiar a pasta Plugins que contem o arquivo ClienteSocket.js na pasta Assets.
- Dentro do unity3D, colocar o ClienteSocket.js no gameobject Car.
- O JCar usa o torque do motor em 560, acho muito, então diminui para 400, para ficar mais fácil de controlar.
Depois disso é só executar os programas na sequência – Lego , servidor , jogo no Unity e pronto.
PS: Se alguém tiver uma idéia boa de algum projeto bacana que seria legal desenvolver com o lego e quiser compartilhar, posta nos comentários.
DOWNLOADS:
Código fonte
eu quero ele rodando no play 3 p que eu possa jogar dirt com volatinho do lego!:D
Vixi , ai sim hein, quem sabe em uma próxima versão,