使用Unity制作3D场景中的运动想象范式
3D技术可以创建出立体的图像和环境,给用户带来更加真实和沉浸式的体验,本文介绍了一种可控的左右手运动的3D场景范式的设计流程,用于被试在3D场景下完成运动想象脑电信号数据的采集。
目录
1.制作动画
[2.1 模型导入与设置](#2.1 模型导入与设置)
[2.2 代码相关](#2.2 代码相关)
2.3完整参数选择设置
1.制作动画
1.1导入模型
制作左右手运动想象的范式,想要在unity中得到会动的场景,当然要制作出有动画的模型。这里使用C4D完成对手掌的建模、动作的制作。关于肢体骨骼的绑定与建立,详细教程在我另一篇博客里有介绍,这里不在过多赘述:
https://wyz99666.blog.csdn.net/article/details/128507248?spm=1001.2014.3001.5502
本文使用的手掌模型这里给出:
链接:https://pan.baidu.com/s/124-XAsYDsobkvbn01bE7tg?pwd=7gok
提取码:7gok
选择文件中下面的.c4d模型,在C4D中打开左手或者右手的手掌原始静止模型,如下图所示:
打开模型后,C4D中右侧布局可以看到手掌的各个关节信息,需要使用这些关节来制作对应的动画。
制作动画需要使用到关键帧,也就是在两个帧中间C4D可以记录模型运动的轨迹或者角度,从而生成动画,当从C4D导出fbx格式或者其他格式的模型时,模型会包含我们设置的动画。接着就可以在unity或者别的场景搭建软件中使用这些动画,然后就可以制作范式啦。
1.2 记录关键帧
如下图所示,红圈圈住的地方有关键帧的字样,点击后C4D会自动记录你对当前模型操作的过程 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/430b4b516ada4a32979d67de08dcd578.png#pic_center =500x)①首先点击红色按钮,点击后展示界面边框会变成红色,比如想要的效果是在0-20帧之间执行手掌的抓取动作,②则将帧数刻度移动到20的地方,③然后选中食指的根关节,并拖动关节某一个轴旋转一定的角度,具体操作如下图①②③所示:
①:
②:
③:
完成这步操作后,手指运动的过程就记录在0-20的关键帧中了,点击播放按钮,食指在0-20帧间就可以运动了。这里具体每个关节执行的步骤不再演示(可以根据自己所需的不同动作自行设置角度),最后得到完整的抓握动作如下图所示:
完成这步操作之后,导出文件为fbx格式就可以得到带有动画的手掌模型了,下一步就可以在unity中使用模型完成范式搭建啦。具体操作如下图
注意在导出设置中最好按下面的选项设置,特别是这个细分面,如果不勾选,在unity中得到的手臂边界会很带有锯齿、比较粗糙。
2.制作范式
2.1 模型导入与设置
在unity中新建一个3D项目,并导入刚刚导出的fbx模型。这里的带有动画的fbx模型不在给出,读者可以自行设计并得到自己的模型。结果如下图,这里我新建了Model文件夹,并导入四个带动画的model,分别是左手抓握、右手抓握、左手静止、右手静止,其中静止的模型也是带有动画的,这个动画中关节移动的角度非常小,所以忽略不计为静止状态,这么设置是为了后续的动作连贯。
将左右手的模型拖入场景中,在Hierarchy中可以看到两个模型。
接着针对每个模型,需要设置一个动画控制器Animator Controller,用来控制模型动画的执行,也就是使得手掌的抓握是可以控制的,如下图我新建了一个Control文件夹并右键新建两个Animator Controller,分别命名为right、left来控制左右手的动画。
双击Animator Controller,进入设置页面,这里我设置了一个参数left_offline用来控制手掌动画的执行,参数在新增的时候选择是bool类型的。
在Base layer栏,将左手静止模型和左手抓握模型中的animator都拖入Base layer栏中,分别命名为左手静止和左手抓握,并在这两个动画直接右键建立连接
建立完连接后,点击连接两个动画的线,如从左手静止到左手抓握,在inspector栏中的Conditions中添加刚刚新增的参数,并设置当left_offline为true时,左手抓握的动作执行。同理,从左手抓握到左手静止的Conditions设置成left_offline为false,具体设置如下图
同理也可以完成对右手控制器的设置。完成以上的设置后,我们通过程序控制模型执行动作的目的已经完成一半了,在动画控制器的帮助下我们已经完成了对模型的设置,也就是当参数left_offline为true时执行抓握动作,当left_offline为false时抓握动作结束,进而完成静止动作,这样就可以实现运动想象中手掌运动的cue效果了。最终我们的设置如下图所示。
将对应的Animator Controller绑定到对应的scene中模型上即可,如下图所示。
2.2 代码相关
在完成场景以及动画的控制设置后,如何通过来控制动画执行呢? 这里使用C#脚本,在Asset文件夹下新建了Script文件,其中首先新建两个脚本,命名为left_offline.cs和right_offline.cs将这两个文件分别拖到场景的左手抓握和右手抓握模型下,如图所示: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/e700017125a6492baed544d1ce501fd5.png#pic_center =500x)这两个文件决定了手掌抓握动作的执行,这里我简单举例,具体实验所需的执行频率设置由另一个文件drop.cs设置决定。 如left_offline.cs:
csharp
using UnityEngine;
using System.Collections;
public class left_offline : MonoBehaviour
{
private Animator Anim;
void Start()
{
Anim = GetComponent<Animator>();
}
public void UpdateAnimation()
{
if (Anim != null)
{
Anim.SetBool("left_offline", true);
StartCoroutine(ResetBoolAfterDelay(2f));
}
else
{
Debug.Log("Animator 未正确引用");
}
}
IEnumerator ResetBoolAfterDelay(float delay)
{
yield return new WaitForSeconds(delay);
Anim.SetBool("left_offline", false);
}
}
在start函数中获取了左手抓握模型的动画对象,并命名为Anim,在UpdateAnimation函数中对参数赋值,如对动画控制器中的left_offline赋值为true,并设置携程函数ResetBoolAfterDelay,当赋值完成2秒后再将参数left_offline设置为false,这个脚本每执行一次update即可完成一次左手动作的cue。右手的控制脚本同理,这里不再赘述,直接给出代码:
csharp
using UnityEngine;
using System.Collections;
public class right_offline : MonoBehaviour
{
private Animator Anim;
void Start()
{
Anim = GetComponent<Animator>();
}
public void UpdateAnimation()
{
if (Anim != null)
{
Anim.SetBool("right_offline", true);
StartCoroutine(ResetBoolAfterDelay(2f));
}
else
{
Debug.Log("Animator 未正确引用");
}
}
IEnumerator ResetBoolAfterDelay(float delay)
{
yield return new WaitForSeconds(delay);
Anim.SetBool("right_offline", false);
}
}
2.3 完整参数选择设置
然而每次运动想象实验可能需要的试次和实验间隔都会不一样,这就需要设置一个开始的参数选择界面,在开始前设置好实验试次以及试次之间的间隔时间,这个时间包括cue、被试运动想象以及休息时间。所以这里需要在unity场景中设置一个选择界面,如下图所示:
这里在场景中新建了一个画布。画布中包括两个下拉框,分别为试次数量和试次间隔时间,通过此模块,用户可以根据自己实验需要设置不同参数,画布中包括的内容如下图:
其中包括两个黑色背景图、两个text(试次数量、试次时间)、两个下拉框、一个确认按钮
那么需要新建一个脚本用于获取确认按钮提交的下拉框内的数据,并执行相应数量和相应时间间隔的左右手抓取动作。这里给出drop.cs脚本如下,只需要将drop.cs拖动到画布的Inspector中即可
csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System;
public class test : MonoBehaviour
{
public Dropdown trialDropdown; // 绑定 Dropdown(Trial)
public Dropdown internalDropdown; // 绑定 Dropdown(Internal)
public Button submitButton; // 绑定 SubmitButton
public bool gameEnded = false;
CanvasGroup canvasGroup;
private left_offline leftOfflineScript;
private right_offline rightOfflineScript;
void Start()
{
// 确保组件已绑定
if (trialDropdown == null || internalDropdown == null || submitButton == null)
{
Debug.LogError("Dropdowns or Button are not assigned in the Inspector.");
return;
}
// 为按钮添加点击事件
submitButton.onClick.AddListener(SubmitValues);
canvasGroup = transform.GetComponent<CanvasGroup>();
// 缓存 left_offline 和 right_offline 实例
leftOfflineScript = FindObjectOfType<left_offline>();
rightOfflineScript = FindObjectOfType<right_offline>();
if (leftOfflineScript == null || rightOfflineScript == null)
{
Debug.LogError("Missing left_offline or right_offline script instances in the scene.");
}
}
void SubmitValues()
{
// 获取 Dropdown 的选定值
string trialValue = trialDropdown.options[trialDropdown.value].text;
string internalValue = internalDropdown.options[internalDropdown.value].text;
// 调试信息
Debug.Log($"Trial Selected: {trialValue}");
Debug.Log($"Internal Selected: {internalValue}");
int num = int.Parse(trialValue);
int Internal = int.Parse(internalValue);
canvasGroup.alpha = 0;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
// 将值发送到后端
StartCoroutine(DoTrials(num, Internal));
}
IEnumerator DoTrials(int num, int interval)
{
for (int i = 0; i < num; i++)
{
int hand = i % 2; // 0 for left, 1 for right
if (hand == 0)
{
if (leftOfflineScript != null)
leftOfflineScript.UpdateAnimation();
}
else
{
if (rightOfflineScript != null)
rightOfflineScript.UpdateAnimation();
}
// 等待指定的间隔
yield return new WaitForSeconds(interval);
}
gameEnded = true;
}
void Update()
{
// 检查游戏是否结束
if (gameEnded)
{
// 结束游戏
Debug.Log("Game Ended.");
Application.Quit();
}
}
}
这里drop.cs脚本给的是交替执行左右手抓取运动,读者可以自行改进改成随机执行,同时,考虑到打标签的需要,读者可以在这个脚本中添加socket通信给你,用于对其余设备通信左右手执行的信息,通知设备开始打标签。
最后只需要将摄像头移动到手臂上方调整角度,范式就搭建完成啦。
最后运行的视角是这样的:
开始界面:
左手运动:
右手运动:
更复杂的实验场景就需要读者自行设计并实现啦。