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

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

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

开始界面:

左手运动:

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

相关推荐
starsongda23 分钟前
VR线上展厅如何重塑展览展示新生态,引领科技潮流?
科技·3d·vr
mirrornan3 小时前
3D可视化产品定制:引领多行业个性化浪潮
3d·3d模型·3d可视化
卧式纯绿6 小时前
自动驾驶3D目标检测综述(六)
人工智能·算法·目标检测·计算机视觉·3d·目标跟踪·自动驾驶
虾球xz6 小时前
游戏引擎学习第73天
学习·游戏引擎
奔跑的犀牛先生16 小时前
unity学习7:unity的3D项目的基本操作: 坐标系
unity
Tiandaren16 小时前
医学图像分析工具02:3D Slicer || 医学影像可视化与分析工具 支持第三方插件
c++·人工智能·python·深度学习·3d·开源
道可云16 小时前
道可云人工智能&元宇宙每日资讯|崂山区政务服务虚拟大厅启用
人工智能·3d·ar·政务
weixin_4480653118 小时前
Unity学习笔记(六)使用状态机重构角色移动、跳跃、冲刺
笔记·学习·unity
mm_exploration18 小时前
halcon三维点云数据处理(六)find_box_3d
前端·数据库·3d
benben04418 小时前
Unity3D仿星露谷物语开发16之角色拾取道具
游戏·unity·游戏引擎