Unity 创建Tobii数据服务器
前言
遇到了一个眼动仪的项目,但是我没空做,给了个会cocos creator的人做,他只能用websocket或者http拿数据,捣鼓了一天.Net,很遗憾失败了,退而求其次,用Unity读到数据,并且开了个Http的服务器。
Tips:文末有工程截图
读取Tobii数据
官网连接: https://developer.tobii.com/product-integration/stream-engine/getting-started/
在.Net中用多线程拿数据会有点小问题,以后有空再说
下面是Unity代码,Start
中找到设备并连接Update
持续读取数据
using System;
using System.Collections;
using System.Collections.Generic;
using Tobii.StreamEngine;
using UnityEngine;
public class Yandongyi : MonoBehaviour
{
public static Vector2 GazePoint=Vector2.zero;
private static void OnGazePoint(ref tobii_gaze_point_t gazePoint, IntPtr userData)
{
// Check that the data is valid before using it
if (gazePoint.validity == tobii_validity_t.TOBII_VALIDITY_VALID)
{
//Debug.Log($"Gaze point: {gazePoint.position.x}, {gazePoint.position.y}");
GazePoint.x = gazePoint.position.x;
GazePoint.y = gazePoint.position.y;
}
}
// Create API context创建API上下文
IntPtr apiContext;
// Connect to the first tracker found 连接到找到的第一个跟踪器
IntPtr deviceContext;
tobii_error_t result;
// Enumerate devices to find connected eye trackers 枚举设备查找连接的眼跟踪器
List<string> urls;
void Start()
{
result = Interop.tobii_api_create(out apiContext, null);
Debug.Assert(result == tobii_error_t.TOBII_ERROR_NO_ERROR);
result = Interop.tobii_enumerate_local_device_urls(apiContext, out urls);
Debug.Assert(result == tobii_error_t.TOBII_ERROR_NO_ERROR);
if (urls.Count == 0)
{
Console.WriteLine("Error: No device found");
return;
}
result = Interop.tobii_device_create(apiContext, urls[0], Interop.tobii_field_of_use_t.TOBII_FIELD_OF_USE_INTERACTIVE, out deviceContext);
Debug.Assert(result == tobii_error_t.TOBII_ERROR_NO_ERROR);
// Subscribe to gaze data 订阅凝视数据
result = Interop.tobii_gaze_point_subscribe(deviceContext, OnGazePoint);
Debug.Assert(result == tobii_error_t.TOBII_ERROR_NO_ERROR);
This sample will collect 1000 gaze points 此样品将收集1000个凝视点
//for (int i = 0; i < 1000; i++)
//{
// // Optionally block this thread until data is available. Especially useful if running in a separate thread.可选地阻止此线程,直到数据可用。如果在单独的线程中运行,则特别有用。
// Interop.tobii_wait_for_callbacks(new[] { deviceContext });
// Debug.Assert(result == tobii_error_t.TOBII_ERROR_NO_ERROR || result == tobii_error_t.TOBII_ERROR_TIMED_OUT);
// // Process callbacks on this thread if data is available 如果数据可用,则此线程上的处理回调
// Interop.tobii_device_process_callbacks(deviceContext);
// Debug.Assert(result == tobii_error_t.TOBII_ERROR_NO_ERROR);
//}
}
// Update is called once per frame
void Update()
{
if (deviceContext!=null)
{
// Optionally block this thread until data is available. Especially useful if running in a separate thread.
//可选地阻止此线程,直到数据可用。如果在单独的线程中运行,则特别有用。
Interop.tobii_wait_for_callbacks(new[] { deviceContext });
Debug.Assert(result == tobii_error_t.TOBII_ERROR_NO_ERROR || result == tobii_error_t.TOBII_ERROR_TIMED_OUT);
// Process callbacks on this thread if data is available
//如果数据可用,则此线程上的处理回调
Interop.tobii_device_process_callbacks(deviceContext);
Debug.Assert(result == tobii_error_t.TOBII_ERROR_NO_ERROR);
}
}
private void OnDestroy()
{
Cleanup 清理
result = Interop.tobii_gaze_point_unsubscribe(deviceContext);
Debug.Assert(result == tobii_error_t.TOBII_ERROR_NO_ERROR);
result = Interop.tobii_device_destroy(deviceContext);
Debug.Assert(result == tobii_error_t.TOBII_ERROR_NO_ERROR);
result = Interop.tobii_api_destroy(apiContext);
Debug.Assert(result == tobii_error_t.TOBII_ERROR_NO_ERROR);
}
}
开启Http服务器
下面是Unity开启HTTP服务器的方法
private void HttpReceiveFunction()
{
try
{
httpListener = new HttpListener();
httpListener.Prefixes.Add("http://+:8866/");
httpListener.Start();
//异步监听客户端请求,当客户端的网络请求到来时会自动执行Result委托
//该委托没有返回值,有一个IAsyncResult接口的参数,可通过该参数获取context对象
httpListener.BeginGetContext(Result, null);
Debug.Log($"服务端初始化完毕http://127.0.0.1:8866/,正在等待客户端请求,时间:{DateTime.Now.ToString()}\r\n");
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
/// <summary>
/// 当接收到请求后程序流会走到这里
/// </summary>
/// <param name="ar"></param>
private void Result(IAsyncResult ar)
{
if (!opening)
{
return;
}
//继续异步监听
httpListener.BeginGetContext(Result, null);
var guid = Guid.NewGuid().ToString();
Console.ForegroundColor = ConsoleColor.White;
//获得context对象
HttpListenerContext context = httpListener.EndGetContext(ar);
HttpListenerRequest request = context.Request;
HttpListenerResponse response = context.Response;
Console.WriteLine($"New Request:{guid},时间:{DateTime.Now.ToString()},内容:{context.Request.Url}");
如果是js的ajax请求,还可以设置跨域的ip地址与参数
//context.Response.AppendHeader("Access-Control-Allow-Origin", "*");//后台跨域请求,通常设置为配置文件
//context.Response.AppendHeader("Access-Control-Allow-Headers", "ID,PW");//后台跨域参数设置,通常设置为配置文件
//context.Response.AppendHeader("Access-Control-Allow-Method", "post");//后台跨域请求设置,通常设置为配置文件
context.Response.ContentType = "text/plain;charset=UTF-8";//告诉客户端返回的ContentType类型为纯文本格式,编码为UTF-8
context.Response.AddHeader("Content-type", "text/plain");//添加响应头信息
context.Response.ContentEncoding = Encoding.UTF8;
string returnObj = null;//定义返回客户端的信息
switch (request.HttpMethod)
{
case "POST":
{
//处理客户端发送的请求并返回处理信息
returnObj = HandleRequest(request, response);
}
break;
case "GET":
{
//处理客户端发送的请求并返回处理信息
returnObj = HandleRequest(request, response);
}
break;
default:
{
returnObj = "null";
}
break;
}
var returnByteArr = Encoding.UTF8.GetBytes(returnObj);//设置客户端返回信息的编码
response.AddHeader("Content-type", "text/html;charset=UTF-8");
response.AddHeader("Access-Control-Allow-Origin", "*");
try
{
using (var stream = response.OutputStream)
{
//把处理信息返回到客户端
stream.Write(returnByteArr, 0, returnByteArr.Length);
}
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"网络蹦了:{ex.ToString()}");
}
//Console.WriteLine($"请求处理完成:{guid},时间:{ DateTime.Now.ToString()}\r\n");
}
/// <summary>
/// 处理客户端发送的请求并返回处理信息
/// </summary>
/// <param name="request"></param>
/// <param name="response"></param>
/// <returns></returns>
private string HandleRequest(HttpListenerRequest request, HttpListenerResponse response)
{
string data = null;
try
{
var byteList = new List<byte>();
var byteArr = new byte[2048];
int readLen = 0;
int len = 0;
//接收客户端传过来的数据并转成字符串类型
do
{
readLen = request.InputStream.Read(byteArr, 0, byteArr.Length);
len += readLen;
byteList.AddRange(byteArr);
} while (readLen != 0);
data = Encoding.UTF8.GetString(byteList.ToArray(), 0, len);
//获取得到数据data可以进行其他操作
//Console.WriteLine("客户端发来的是" + data+request.UserAgent);
Console.WriteLine(Yandongyi.GazePoint.x + "-" + Yandongyi.GazePoint.y);
return Yandongyi.GazePoint.x + "," + Yandongyi.GazePoint.y;
}
catch (Exception ex)
{
response.StatusDescription = "404";
response.StatusCode = 404;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"在接收数据时发生错误:{ex.ToString()}");
return null;
//return $"在接收数据时发生错误:{ex.ToString()}";//把服务端错误信息直接返回可能会导致信息不安全,此处仅供参考
}
response.StatusDescription = "200";//获取或设置返回给客户端的 HTTP 状态代码的文本说明。
response.StatusCode = 200;// 获取或设置返回给客户端的 HTTP 状态代码。
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"接收数据完成:{data.Trim()},时间:{DateTime.Now.ToString()}");
return $"接收数据完成";
}
开启服务器并获取数据完整源码(需结合读取Tobii数据)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Text;
using UnityEngine;
public class YandongyiServer : MonoBehaviour
{
public GameObject MyCube;
public Material redMat;
public Material greenMat;
private HttpListener httpListener;
private bool opening = false;
// Start is called before the first frame update
void Start()
{
// 设置分辨率和是否全屏
Screen.SetResolution(1024, 768, false);
MyCube.GetComponent<MeshRenderer>().material = redMat;
}
private void Update()
{
if (MyCube!=null)
{
MyCube.transform.position = new Vector3(Yandongyi.GazePoint.x*10-5,-(Yandongyi.GazePoint.y*6-3));
}
}
public void StartHttpServer()
{
if (!opening)
{
opening = true;
HttpReceiveFunction();
MyCube.GetComponent<MeshRenderer>().material = greenMat;
}
}
public void CloseHttpServer()
{
if (opening)
{
opening = false;
if (httpListener.IsListening)
{
httpListener.Stop();
httpListener = null;
}
MyCube.GetComponent<MeshRenderer>().material = redMat;
}
}
private void HttpReceiveFunction()
{
try
{
httpListener = new HttpListener();
httpListener.Prefixes.Add("http://+:8866/");
httpListener.Start();
//异步监听客户端请求,当客户端的网络请求到来时会自动执行Result委托
//该委托没有返回值,有一个IAsyncResult接口的参数,可通过该参数获取context对象
httpListener.BeginGetContext(Result, null);
Debug.Log($"服务端初始化完毕http://127.0.0.1:8866/,正在等待客户端请求,时间:{DateTime.Now.ToString()}\r\n");
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
/// <summary>
/// 当接收到请求后程序流会走到这里
/// </summary>
/// <param name="ar"></param>
private void Result(IAsyncResult ar)
{
if (!opening)
{
return;
}
//继续异步监听
httpListener.BeginGetContext(Result, null);
var guid = Guid.NewGuid().ToString();
Console.ForegroundColor = ConsoleColor.White;
//获得context对象
HttpListenerContext context = httpListener.EndGetContext(ar);
HttpListenerRequest request = context.Request;
HttpListenerResponse response = context.Response;
Console.WriteLine($"New Request:{guid},时间:{DateTime.Now.ToString()},内容:{context.Request.Url}");
如果是js的ajax请求,还可以设置跨域的ip地址与参数
//context.Response.AppendHeader("Access-Control-Allow-Origin", "*");//后台跨域请求,通常设置为配置文件
//context.Response.AppendHeader("Access-Control-Allow-Headers", "ID,PW");//后台跨域参数设置,通常设置为配置文件
//context.Response.AppendHeader("Access-Control-Allow-Method", "post");//后台跨域请求设置,通常设置为配置文件
context.Response.ContentType = "text/plain;charset=UTF-8";//告诉客户端返回的ContentType类型为纯文本格式,编码为UTF-8
context.Response.AddHeader("Content-type", "text/plain");//添加响应头信息
context.Response.ContentEncoding = Encoding.UTF8;
string returnObj = null;//定义返回客户端的信息
switch (request.HttpMethod)
{
case "POST":
{
//处理客户端发送的请求并返回处理信息
returnObj = HandleRequest(request, response);
}
break;
case "GET":
{
//处理客户端发送的请求并返回处理信息
returnObj = HandleRequest(request, response);
}
break;
default:
{
returnObj = "null";
}
break;
}
var returnByteArr = Encoding.UTF8.GetBytes(returnObj);//设置客户端返回信息的编码
response.AddHeader("Content-type", "text/html;charset=UTF-8");
response.AddHeader("Access-Control-Allow-Origin", "*");
try
{
using (var stream = response.OutputStream)
{
//把处理信息返回到客户端
stream.Write(returnByteArr, 0, returnByteArr.Length);
}
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"网络蹦了:{ex.ToString()}");
}
//Console.WriteLine($"请求处理完成:{guid},时间:{ DateTime.Now.ToString()}\r\n");
}
/// <summary>
/// 处理客户端发送的请求并返回处理信息
/// </summary>
/// <param name="request"></param>
/// <param name="response"></param>
/// <returns></returns>
private string HandleRequest(HttpListenerRequest request, HttpListenerResponse response)
{
string data = null;
try
{
var byteList = new List<byte>();
var byteArr = new byte[2048];
int readLen = 0;
int len = 0;
//接收客户端传过来的数据并转成字符串类型
do
{
readLen = request.InputStream.Read(byteArr, 0, byteArr.Length);
len += readLen;
byteList.AddRange(byteArr);
} while (readLen != 0);
data = Encoding.UTF8.GetString(byteList.ToArray(), 0, len);
//获取得到数据data可以进行其他操作
//Console.WriteLine("客户端发来的是" + data+request.UserAgent);
Console.WriteLine(Yandongyi.GazePoint.x + "-" + Yandongyi.GazePoint.y);
return Yandongyi.GazePoint.x + "," + Yandongyi.GazePoint.y;
}
catch (Exception ex)
{
response.StatusDescription = "404";
response.StatusCode = 404;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"在接收数据时发生错误:{ex.ToString()}");
return null;
//return $"在接收数据时发生错误:{ex.ToString()}";//把服务端错误信息直接返回可能会导致信息不安全,此处仅供参考
}
response.StatusDescription = "200";//获取或设置返回给客户端的 HTTP 状态代码的文本说明。
response.StatusCode = 200;// 获取或设置返回给客户端的 HTTP 状态代码。
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"接收数据完成:{data.Trim()},时间:{DateTime.Now.ToString()}");
return $"接收数据完成";
}
private void OnDestroy()
{
CloseHttpServer();
}
}
修正
收到消息的时候用这个读取解析
/// <summary>
/// 处理客户端发送的请求并返回处理信息
/// </summary>
/// <param name="request"></param>
/// <param name="response"></param>
/// <returns></returns>
private string HandleRequest(HttpListenerRequest request, HttpListenerResponse response)
{
string data = null;
try
{
var byteList = new List<byte>();
int readLength = 1024;
byte[] byteArr = new byte[readLength];
int totalBytesRead = 0;
// 接收客户端传过来的数据并转成字符串类型
while (totalBytesRead < request.ContentLength64)
{
int bytesRead = request.InputStream.Read(byteArr, 0, readLength);
if (bytesRead == 0)
{
break; // 如果读取到流的末尾,跳出循环
}
totalBytesRead += bytesRead;
byteList.AddRange(byteArr.Take(bytesRead)); // 只添加实际读取的部分到 byteList
Debug("Sl正在接受数据当前接受长度为--->" + totalBytesRead + ", 应该接收长度为" + request.ContentLength64);
}
// 将接收到的字节数据转换为字符串
data = Encoding.UTF8.GetString(byteList.ToArray());
// 处理请求并返回处理结果
string returnObj = "";
CombatResultMsg responsemsg = mainGameSceneLite.GetCombatSimulateResult(data);
if (responsemsg.success)
{
returnObj = JsonConvert.SerializeObject(responsemsg);
}
else
{
response.StatusDescription = "200,发生错误";
response.StatusCode = 200;
}
return returnObj;
}
catch (Exception ex)
{
response.StatusDescription = "404";
response.StatusCode = 404;
Error($"在接收数据时发生错误:{ex.ToString()}");
return null;
}
}