本文是该系列的第一部分,介绍了将Emotiv头戴式耳机实现为用Unity制作的视频游戏中的输入设备的过程。
要求
- 电动耳机

- WebSocket插件:用于统一WebGL的简单Web套接字
- JSONObject
- Unity 2017.4.8
预先
首先需要了解一些概念,才能开始使用电动耳机进行开发。
连接到任何Emotiv设备并与之通信的方式是通过一个名为Cortex的API进行,该API建立在JSON和WebSockets上 ,可以从各种编程语言和平台轻松访问。
cortex API的协议由3个构建块组成:
WebSockets:一种通信协议,用于从用户的Web浏览器到服务器的持久性双向连接。
JSON :JavaScript对象表示法的缩写,是一种轻量级的数据交换格式。 人类易于读写,机器也易于解析和生成。
JSON-RPC :这是一个轻量级的远程过程调用(RPC)协议,基本上是给定的结构和规则,用于在许多不同的消息传递环境(如本例中的websocket)上发送请求和接收响应。
请求对象的结构由JSON对象中的以下字段组成:
- jsonrpc:一个字符串,指定JSON-RPC协议的版本。 必须精确为“ 2.0”。
- method:一个字符串,其中包含要调用的方法的名称。
- params:一个结构化值,其中包含在方法调用期间要使用的参数值。 该成员可以省略。
- id:由客户端建立的标识符,必须包含字符串,数字或NULL值(如果包含)。 此ID在响应对象中用于匹配请求。
响应对象的结构由JSON对象中的以下字段组成:
- jsonrpc:一个字符串,指定JSON-RPC协议的版本。 必须精确为“ 2.0”。
- 结果:成功需要此成员。 如果调用该方法时出错,则该成员不得存在。 该成员的值由服务器上调用的方法确定。
- 错误:发生错误时需要此成员。 如果在调用过程中未触发错误,则该成员不得存在。
- id:该成员是必需的。 它必须与请求对象中id成员的值相同。
设置项目
要开始使用cortex API,需要一些插件来简化通信过程。 第一个是WebSocket插件,可以在资产商店中找到它,导入包,以便WebSocket类可用。
项目窗口应如下所示。

接下来,我们需要JSONObject存储库,以zip格式下载并在plugins文件夹中将其解压缩,它应如下所示。

最后,从Cortex-Implementation-For-Unity存储库中获取CortexJsonUtility类,并将其放在Plugins / Cortex文件夹中。

通过WebSocket连接
现在项目中已经存在所需的资源,让我们创建一个名为EmotivTest的脚本,该脚本继承自MonoBehaviour,并将其作为组件添加到GameObject中,称为Emotiv。

使用系统;
使用System.Collections;
使用System.Collections.Generic;
使用UnityEngine;
公共课程EmotivTest:MonoBehaviour
{
}
由于使用WebSocket需要等待响应,因此我们将在协程中拥有与其相关的所有代码。 首先,让我们添加一个“开始”协程。
…
公共课程EmotivTest:MonoBehaviour
{
私有IEnumerator Start()
{
}
}
在这个协程上,我们将创建一个WebSocket类的实例。 WebSocket构造函数要求一个Uri类的实例,在这个Uri实例上,我们传递了动机网址作为参数,该网址始终为:“ wss://emotivcortex.com:54321”。
最后,我们要告诉程序等待,直到websocket完成连接
…
私有IEnumerator Start()
{
WebSocket w =新的WebSocket(新的Uri(“ wss://emotivcortex.com:54321”));
收益回报StartCoroutine(w.Connect());
}
现在,该程序已准备好开始通过WebSocket与Emotiv进行通信。
Emotiv的请求文档
要创建请求JSON对象,我们将使用CortexJsonUtility类,该类包含三个公共静态方法,这些方法可以简化创建对Cortex服务的请求所需的JSON-RPC对象的过程,并从中收集一些信息皮质反应。
要开始与Emotiv设备的连接,我们需要先在cortex服务中登录,为此,我们需要一个登录方法,所有方法的文档都可以在此链接上找到https://emotiv.github.io/cortex -docs /#introduction。
登录方法要求您提供Emotiv Cloud中用户的用户名和密码,以及应用程序的客户端ID和密码。 这些要求在文档页面的表中列出,如下所示:

对于用户名和密码,用户必须在Emotiv网页https://www.emotiv.com/上登录。 对于client_id和client_secret,必须在Emotiv网页https://www.emotiv.com/my-account/cortex-apps/的开发人员仪表板上创建一个应用程序。
创建应用程序后,将显示client_secret,请注意它,因为它将是唯一显示它的时间。 另一方面,可以在cortex-apps链接上找到client_id。

收集完所有参数后,我们可以为login方法创建JSON-RPC对象。
登录
此时,可以直接发送用户名和密码,但是让我们创建一个带有一些输入字段和按钮的窗口,以便用户能够提供用户名和密码,然后单击按钮进行登录。 像这样:

现在,我们需要修改EmotivTest脚本,以便可以访问之前启动的WebSocket连接。
首先,让我们将WebSocket类型变量作为静态变量从Start协程移动到类的开头。
//静态字段static WebSocket w;
此后,需要修改协程,应更改名称,以使其在需要时而不是在场景开始时成为协程启动,并且还需要在此处分配w变量,但不包含定义部分。
私有IEnumerator Initialize()
{
w = new WebSocket(new Uri(“ wss://emotivcortex.com:54321”));
收益回报StartCoroutine(w.Connect());
}
我们还修改了Uri构造函数中的文字参数,并将其替换为常量。
…const字符串EMOTIV_URL =“ wss://emotivcortex.com:54321”;…w =新的WebSocket(新的Uri(EMOTIV_URL));
还可以添加其他常量来保存客户端ID和密码。
const string CLIENT_ID =“ xxx”;
const string CLIENT_SECRET =“ xxx”;
当前,当按下登录按钮时,我们需要一个公共方法来启动协程,因此我们将对其进行定义。
公共无效的Init()
{
StartCoroutine(Initialize());
}
为了最终获得用户的信息,我们需要2个静态变量和2个设置方法,这些方法和输入字段的OnEndEdit事件一起使用。
静态字符串username =“ XXX”;
静态字符串userPassword =“ XXX!”;
公共无效set_Username(string s)
{
用户名= s;
}
公共无效set_UserPassword(字符串s)
{
userPassword = s;
}
该代码应类似于以下代码,但请记住使用您自己的应用程序信息来更改client_id和client_secret中的字符串。
使用系统;
使用System.Collections;
使用System.Collections.Generic;
使用UnityEngine;
公共课程EmotivTest:MonoBehaviour
{
//常数
const string EMOTIV_URL =“ wss://emotivcortex.com:54321”;
const string CLIENT_ID =“ XXX”;
const string CLIENT_SECRET =“ XXX”;
//静态字段static WebSocket w;
静态字符串username =“ XXX”;
静态字符串userPassword =“ XXX”;
公共无效set_Username(string s)
{
用户名= s;
}
公共无效set_UserPassword(字符串s)
{
userPassword = s;
}
公共无效的Init()
{
StartCoroutine(Initialize());
}
私有IEnumerator Initialize()
{
w = new WebSocket(new Uri(EMOTIV_URL));
收益回报StartCoroutine(w.Connect());
}
}
要在输入字段上设置事件,我们将Emotiv GameObject拖到事件字段,然后选择EmotivTest类,最后选择动态字符串组中的UserName或UserPassword。

创建登录JSON-RPC对象
现在该使用CortexJsonUtility类构建请求了。 首先,必须创建一个字典,以保存登录类所需的每个参数。 在初始化协程内部的最后一行之后,添加以下行。
…
字典 loginDictionary = new字典();
现在应添加args。
loginDictionary.Add(“用户名”,用户名);
loginDictionary.Add(“ password”,userPassword);
loginDictionary.Add(“ client_id”,CLIENT_ID);
loginDictionary.Add(“ client_secret”,CLIENT_SECRET);
最后一步是一行代码,该代码创建JSONObject并将其转换为字符串,然后将其发送到websocket。
w.SendString(
CortexJsonUtility.GetMethodJSON
(
“登录”,
1,
loginDictionary
));
GetMethodJSON函数具有3个参数,一个methodName是要调用的Method(这次是登录方法),该响应的methodId以及一个包含要调用的方法的所有参数的字典。
邮件已发送,但是我们能知道它是否收到吗? 好吧,实际上我们可以通过创建另一个协程来做到这一点,看看它。
IEnumerator GetReply()
{
而(真)
{
字符串回复= w.RecvString();
如果(回复!= null)
{
Debug.Log(回复);
}
收益率返回0;
}
}
这是一个无限循环,以字符串形式接收来自websocket的响应,如果响应不为null,则将其打印在控制台上,并且协程等待下一帧。
为了获得登录请求的响应,让我们先启动协程,然后再将字符串发送到websocket
…私有IEnumerator Initialize()
{
w = new WebSocket(new Uri(EMOTIV_URL));
收益回报StartCoroutine(w.Connect());
StartCoroutine(GetReply());
字典 loginDictionary = new字典();
loginDictionary.Add(“用户名”,用户名);
loginDictionary.Add(“ password”,userPassword);
loginDictionary.Add(“ client_id”,CLIENT_ID);
loginDictionary.Add(“ client_secret”,CLIENT_SECRET);
w.SendString(
CortexJsonUtility.GetMethodJSON
(
“登录”,
1,
loginDictionary
));
}
要尝试此操作,请将Init函数分配给登录按钮,输入正确的用户名和密码,并确保客户端ID和密码正确无误,在按下按钮后,您应该会在控制台上看到类似于以下内容的1条消息:
{“ id”:1,“ jsonrpc”:“ 2.0”,“结果”:“用户xxx登录成功”}
该请求也是JSON-RPC格式的字符串,如果它具有名为“ result”的字段,则该请求成功,或者出了点问题并显示了错误。
{“错误”:{“代码”:-32001,“消息”:“用户名或密码不正确。”},“ id”:1,“ jsonrpc”:“ 2.0”}
如果任何参数错误(包括客户端ID或密码),都会显示此错误。 或者也许是错误。
{“错误”:{“代码”:-32032,“消息”:“在使用新帐户登录之前先注销。”},“ id”:1,“ jsonrpc”:“ 2.0”}
如果用户已经登录,则会显示该信息(如果已登录),请使用cortex UI手动注销,或按照与上述相同的登录过程将注销请求发送到cortex。
处理回应
由于指定的ID,可以对来自皮质的特定响应做出反应,响应消息将始终与请求的ID相匹配,在这种情况下,我们可以隐藏登录面板,因为我们不希望用户重新登录,我们可以显示另一个面板,其中包含已记录的用户信息和一个注销按钮。

下一步不是执行操作的适当方法,但可以用于测试目的。
我们将添加2个私有变量来保存我们的画布游戏对象,并能够激活和停用它们,该变量将具有[SerializeField]字段属性,因此我们可以将其添加到检查器中。
…
//私有字段
[SerializeField]
GameObject loginCanvas;
[SerializeField]
GameObject userInfoCanvas;
我们还将需要在类外部进行枚举,因此该方法的ID是按有序方式给出的,现在将具有Login和Logout,但是每次实现一个方法时,都应在此枚举中添加它。
公共枚举MethodsID
{
登录,
登出
}
现在,让我们替换登录方法调用,将( 1
(int)MethodsID.Login
更改为(int)MethodsID.Login
,因此它应如下所示。
w.SendString(
CortexJsonUtility.GetMethodJSON
(
“登录”,
(int)MethodsID.Login,
loginDictionary
));
这是从响应字符串获取信息的时刻,首先我们必须将字符串转换为JSONObject,然后我们能够获取响应的每个字段,我们需要的是一个名为“ result”的字段,如果该字段存在,我们能够从中获取ID。 让我们在if语句内部和调试之后,向已有的GetReply方法中添加一些代码。
…
JSONObject replyObj =新的JSONObject(reply);
//如果有一个名为“结果”的字段,则它是对已发送方法的实际响应
JSONObject resultObject = replyObj.GetField(“ result”);
如果(resultObject)
{
JSONObject idObject = replyObj.GetField(“ id”);
}
现在我们可以创建一个开关来根据id处理代码。
…
如果(resultObject)
{
JSONObject idObject = replyObj.GetField(“ id”);
开关((MethodsID] [int)idObject.n)
{
案例MethodsID.Login:
//处理登录成功
打破;
案例MethodsID.Logout:
//处理注销成功
打破;
}
}
在使用Unity UI时,我们需要将此库添加到EmotivTest脚本中
使用UnityEngine.UI;
要处理登录响应,我们需要一个功能来停用登录画布并激活用户信息画布,然后它将在画布内的文本字段中显示用户名变量内容。
void HandleLoginSuccess(){loginCanvas.SetActive(false); userInfoCanvas.SetActive(true); userInfoCanvas.transform.GetChild(0).GetChild(0).GetComponent ()。text = username;}
最后,应该在MethodsID.Login情况下调用它。
…switch((MethodsID)(int)idObject.n){case MethodsID.Login:HandleLoginSuccess(); break;…
登出
我们添加了2个功能,一个用于发送注销方法请求,另一个用于隐藏用户信息画布并显示登录信息。
公共无效Logout()
{
字典 logoutDictionary = new字典();
logoutDictionary.Add(“用户名”,用户名);
w.SendString(
CortexJsonUtility.GetMethodJSON
(
“登出”,
(int)MethodsID.Logout,
注销字典
));
}
私有void HandleLogoutSuccess()
{
loginCanvas.SetActive(true);
userInfoCanvas.SetActive(false);
}
为此,需要将画布分配给EmotivTest变量,并且“注销”按钮事件需要指向“注销”功能。


测试游戏,现在您可以登录,查看用户名,然后根据需要注销多次。
EmotivTest类应如下所示:
使用系统;
使用System.Collections;
使用System.Collections.Generic;
使用UnityEngine;
使用UnityEngine.UI;
公共枚举MethodsID
{
登录,
登出,
}
公共课程EmotivTest:MonoBehaviour
{
//常数
const string EMOTIV_URL =“ wss://emotivcortex.com:54321”;
const string CLIENT_ID =“ xxx”;
const string CLIENT_SECRET =“ xxx”;
//静态字段
静态WebSocket w;
静态字符串username =“ XXX”;
静态字符串userPassword =“ XXX!”;
公共无效set_Username(string s)
{
用户名= s;
}
公共无效set_UserPassword(字符串s)
{
userPassword = s;
}
//私有字段[SerializeField]
GameObject loginCanvas;
[SerializeField]
GameObject userInfoCanvas;
public void Init()
{
StartCoroutine(Initialize());
}
私有IEnumerator Initialize()
{
w = new WebSocket(new Uri(EMOTIV_URL));
收益回报StartCoroutine(w.Connect());
StartCoroutine(GetReply());
字典 loginDictionary = new字典(); loginDictionary.Add(“用户名”,用户名);
loginDictionary.Add(“ password”,userPassword);
loginDictionary.Add(“ client_id”,CLIENT_ID);
loginDictionary.Add(“ client_secret”,CLIENT_SECRET);
w.SendString(
CortexJsonUtility.GetMethodJSON
(
“登录”,
(int)MethodsID.Login,
loginDictionary
));
}
IEnumerator GetReply()
{
而(真)
{
字符串回复= w.RecvString();
如果(回复!= null)
{
Debug.Log(回复);
JSONObject replyObj =新的JSONObject(reply);
//如果有一个名为“结果”的字段,则它是对已发送方法的实际响应
JSONObject resultObject = replyObj.GetField(“ result”);
如果(resultObject)
{
JSONObject idObject = replyObj.GetField(“ id”);
开关((MethodsID] [int)idObject.n)
{
案例MethodsID.Login:
HandleLoginSuccess();
打破;
案例MethodsID.Logout:
HandleLogoutSuccess();
打破;
}
}
}
收益率返回0;
}
}
void HandleLoginSuccess()
{
loginCanvas.SetActive(false);
userInfoCanvas.SetActive(true);
userInfoCanvas.transform.GetChild(0)
.GetChild(0).GetComponent ()。text =用户名;
}
公共无效Logout()
{
字典 logoutDictionary = new字典();
logoutDictionary.Add(“用户名”,用户名); w.SendString(
CortexJsonUtility.GetMethodJSON
(
“登出”,
(int)MethodsID.Logout,
注销字典
));
}
私有void HandleLogoutSuccess()
{
loginCanvas.SetActive(true);
userInfoCanvas.SetActive(false);
}
}
请遵循下一篇文章第2部分中的过程