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.
Unity script
Section titled “Unity script”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); } } }}Path a carpeta de python
Section titled “Path a carpeta de python”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.
path| |-/.venv |- main.py |-...Python Script
Section titled “Python Script”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 UdpCommsfrom ClauLib.clau import Clauimport 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()Referencias
Section titled “Referencias”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í.