Skip to content

Conectar unity con C.L.A.U.

Para implementar el controlador en unity lo más sencillo es establecer un canal de comunicación entre python y unity usando sockets. Para ello te puedes ayudar de este script para unity y las funciones de servidor dentro de la librería.

Primero debes definir las variables que quieres capturar en tu comunicación. Para ello creas un script que yo llamé ClauInput.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ClauInput : MonoBehaviour
{
public static float xClau = 0f;
public static float yClau = 0f;
public static int bt1Clau = 0;
public static int bt2Clau = 0;
public static int shClau = 0;
}

Luego debes crear un objeto en tu escenario encargado de la comunicación. A este objeto le llamo ControlManager y tiene añadido el siguiente script UdpSocket:

using UnityEngine;
using System.Collections;
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Diagnostics;
using System.IO;
public class UdpSocket : MonoBehaviour
{
Process pythonProcess; // Se encarga de comprobar el estado del servidor de python
public bool debug = true;
public string pathWindows;
public string pathLinux;
[HideInInspector] public bool isTxStarted = false;
[SerializeField] string IP = "127.0.0.1"; // local host
[SerializeField] int rxPort = 8000; // port to receive data from Python on
[SerializeField] int txPort = 8001; // port to send data to Python on
int i = 0; // DELETE THIS: Added to show sending data from Unity to Python via UDP
// Create necessary UdpClient objects
UdpClient client;
IPEndPoint remoteEndPoint;
Thread receiveThread; // Receiving Thread
IEnumerator SendDataCoroutine() // DELETE THIS: Added to show sending data from Unity to Python via UDP
{
while (true)
{
SendData("Sent from Unity: " + i.ToString());
i++;
yield return new WaitForSeconds(1f);
}
}
public void SendData(string message) // Use to send data to Python
{
try
{
byte[] data = Encoding.UTF8.GetBytes(message);
client.Send(data, data.Length, remoteEndPoint);
}
catch (Exception err)
{
print(err.ToString());
}
}
//Iniciar la función de python
void StartPythonServer()
{
try
{
string serverPath = "";
if (!debug)
{
serverPath = pathWindows;
}
else{
serverPath = pathLinux;
}
if (string.IsNullOrEmpty(serverPath))
{
UnityEngine.Debug.LogError("Server path not set!");
return;
}
string pythonExecutable;
if (!debug)
{
pythonExecutable = Path.Combine(serverPath, ".venv", "Scripts", "python.exe");
}
else{
pythonExecutable = Path.Combine(serverPath, ".venv", "bin", "python");
}
if (!File.Exists(pythonExecutable))
{
UnityEngine.Debug.LogError("Python executable not found: " + pythonExecutable);
return;
}
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = pythonExecutable,
Arguments = "main.py",
WorkingDirectory = serverPath,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
pythonProcess = new Process();
pythonProcess.StartInfo = startInfo;
pythonProcess.OutputDataReceived += (sender, args) =>
{
if (!string.IsNullOrEmpty(args.Data))
UnityEngine.Debug.Log("[PY] " + args.Data);
};
pythonProcess.ErrorDataReceived += (sender, args) =>
{
if (!string.IsNullOrEmpty(args.Data))
UnityEngine.Debug.LogError("[PY ERROR] " + args.Data);
};
pythonProcess.Start();
pythonProcess.BeginOutputReadLine();
pythonProcess.BeginErrorReadLine();
UnityEngine.Debug.Log("Python server started");
}
catch (System.Exception e)
{
UnityEngine.Debug.LogError("Error starting Python: " + e.Message);
}
}
void Awake()
{
//Iniciar el programa de python
StartPythonServer();
// Create remote endpoint (to Matlab)
remoteEndPoint = new IPEndPoint(IPAddress.Parse(IP), txPort);
// Create local client
client = new UdpClient(rxPort);
// local endpoint define (where messages are received)
// Create a new thread for reception of incoming messages
receiveThread = new Thread(new ThreadStart(ReceiveData));
receiveThread.IsBackground = true;
receiveThread.Start();
// Initialize (seen in comments window)
print("UDP Comms Initialised");
StartCoroutine(SendDataCoroutine()); // DELETE THIS: Added to show sending data from Unity to Python via UDP
}
// Receive data, update packets received
private void ReceiveData()
{
while (true)
{
try
{
IPEndPoint anyIP = new IPEndPoint(IPAddress.Any, 0);
byte[] data = client.Receive(ref anyIP);
string text = Encoding.UTF8.GetString(data);
print(">> " + text);
ProcessInput(text);
}
catch (Exception err)
{
print(err.ToString());
}
}
}
private void ProcessInput(string input)
{
if (!isTxStarted)
isTxStarted = true;
// Esperamos formato: "1,0,0,0,0"
string[] values = input.Split(',');
if (values.Length != 5)
return;
try
{
ClauInput.xClau = float.Parse(values[0]);
ClauInput.yClau = float.Parse(values[1]);
ClauInput.bt1Clau = int.Parse(values[2]);
ClauInput.bt2Clau = int.Parse(values[3]);
ClauInput.shClau = int.Parse(values[4]);
}
catch (Exception e)
{
UnityEngine.Debug.Log("Parse error: " + e.Message);
}
}
//Prevent crashes - close clients and threads properly!
void OnDisable()
{
if (receiveThread != null)
receiveThread.Abort();
client.Close();
if (pythonProcess != null && !pythonProcess.HasExited)
{
try
{
pythonProcess.Kill();
pythonProcess.Dispose();
UnityEngine.Debug.Log("Python server stopped");
}
catch (System.Exception e)
{
UnityEngine.Debug.LogError("Error stopping Python: " + e.Message);
}
}
}
}

En el script he añadido la funcionalidad de que Unity inicie el proceso de python, por lo que debes añadir el path a la carpeta donde esté el archivo .venv y main.py.

Terminal window
path|
|-/.venv
|- main.py
|-...

Para el proyecto de python debes crear un archivo main.py que tenga la siguiente estructura:

# ==================================================
# Este programa funciona como un servidor de datos que
# recopila los valores del sensor CLAU y los envia a unity.
# Este código se encarga de establecer un socket de comunicación donde
# los datos se envian como un string.
#
# Este código se encarga de traducir el cuaternion a valores de joystick virtual para disminuir la carga de unity
#
# Formato de datos: {x:1, y:1, btn1: 0, btn2: 0, shake: 1}
#
# ==================================================#
from ClauLib.udpComms import UdpComms
from ClauLib.clau import Clau
import logging
def main():
logging.info("Iniciando")
# Create UDP socket to use for sending (and receiving)
sock = UdpComms(udpIP="127.0.0.1", portTX=8000, portRX=8001, enableRX=False, suppressWarnings=True)
logging.info("Socket creado")
clau_obj = Clau(port="COM11", n_data=10) # Objeto clau estandar
# Calibración del controlador CLAU
clau_obj.calibrate()
while True:
# Collect data
data = clau_obj.collect_data()
data = "a"
if not data:
continue
jx, jy = clau_obj.get_virtual_joystick()
shake = clau_obj.get_shake()["shakeStatus"]
send = f"{jx},{jy},{0},{0},{int(shake)}"
sock.SendData(send) # Send this string to other application}
#data = sock.ReadReceivedData() # read data
#if data != None: # if NEW data has been received since last ReadReceivedData function call
# print(data) # print new received data
if __name__ == "__main__":
main()

Estos scripts están basados en otro repositorio de github al que le realicé cambios menores para que fuese compatible con linux y no ocurrieran problemas de bloqueo. El autor original es Elashry y puedes contactarlo, según su repositorio, por youssef.elashry@gmail.com. puedes ver el repositorio de github aquí.