Unity制作3D场景的脑电运动想象范式(左右手抓握)

使用Unity制作3D场景中的运动想象范式

3D技术可以创建出立体的图像和环境,给用户带来更加真实和沉浸式的体验,本文介绍了一种可控的左右手运动的3D场景范式的设计流程,用于被试在3D场景下完成运动想象脑电信号数据的采集。

目录
1.制作动画

1.1导入模型

1.2记录关键帧
2.制作范式

[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通信给你,用于对其余设备通信左右手执行的信息,通知设备开始打标签。

最后只需要将摄像头移动到手臂上方调整角度,范式就搭建完成啦。

最后运行的视角是这样的:

开始界面:

左手运动:

右手运动:
更复杂的实验场景就需要读者自行设计并实现啦。

相关推荐
牙膏上的小苏打233315 小时前
Unity Surround开关后导致获取主显示器分辨率错误
unity·主屏幕
Unity大海17 小时前
诠视科技Unity SDK开发环境配置、项目设置、apk打包。
科技·unity·游戏引擎
huoyingcg21 小时前
3D Mapping秀制作:沉浸式光影盛宴 3D mapping show
科技·3d·动画·虚拟现实
浅陌sss1 天前
Unity中 粒子系统使用整理(一)
unity·游戏引擎
维度攻城狮1 天前
实现在Unity3D中仿真汽车,而且还能使用ros2控制
python·unity·docker·汽车·ros2·rviz2
为你写首诗ge1 天前
【Unity网络编程知识】FTP学习
网络·unity
神码编程1 天前
【Unity】 HTFramework框架(六十四)SaveDataRuntime运行时保存组件参数、预制体
unity·编辑器·游戏引擎
菲fay1 天前
Unity 单例模式写法
unity·单例模式
火一线1 天前
【Framework-Client系列】UIGenerate介绍
游戏·unity
ZKY_242 天前
【工具】Json在线解析工具
unity·json