基于Unity3D的康复训练双模式游戏:C#物理交互与数据记录实战
2026/9/16 19:11:32 网站建设 项目流程

简介:基于Unity3D开发的康复训练双模式互动游戏系统,面向中风后遗症患者,通过趣味性游戏实现上肢协调与认知功能锻炼,解决传统康复训练缺乏吸引力的问题。系统包含两种互动模式:上肢协调训练模拟抓取、投掷、敲击等日常动作;认知功能锻炼利用任务与谜题训练记忆力、注意力和解决问题能力。压缩包共646个文件,包含248个meta、131个dll、54个cs、34个png、33个unity、18个mat、13个shader等,整体48.49MB。其中dll为依赖库、cs为C#脚本、unity为场景文件、ttf为字体资源,共同支撑游戏逻辑与物理整合,模拟重力与碰撞反应。已有55人学习/下载。包内附赠资源.docx提供康复训练指导,说明文件.txt详述技术实现与安装配置,核心目录存放主程序与全部资源,方便部署、修改及二次开发。整体方案兼具医疗康复与游戏交互,适合康复医疗人员、Unity开发者及研究者借鉴,提供完整可用的游戏化康复产品示例。

1. 基于Unity3D的康复训练双模式游戏,到底在做什么

中风后遗症患者的上肢功能恢复,核心矛盾是训练量上不去。患者每天要做几百次重复的肩肘腕动作,纯口头指令和徒手训练很难维持注意力,而动作质量又直接影响神经重塑效果。游戏化是解法之一,但普通游戏不一样的是,这套系统必须同时解决三件事:让患者愿意练、让动作够标准、让训练数据能被医生读取。标题里的“双模式互动”指向的正是这个思路——一个模式管上肢协调,一个模式管认知功能,两者共用同一套Unity3D交互框架,用C#脚本完成游戏逻辑和物理交互的整合。

这里要说明一个细节:Unity3D的游戏逻辑默认用C#编写,标题中的“C编程语言”在落地上就是Unity的C#脚本层,并非脱离引擎单独写C语言插件。整篇文章的处理方式也是围绕C#脚本展开,只有需要极致性能的图像处理才考虑用C原生插件,这部分通常用不上。适合读这篇内容的人,是已经会用Unity做一些基础交互、想往医疗康复方向落地的开发者,或者正在评估Unity作为训练系统承载工具的康复工程团队。理解这套系统的关键不在于某个炫技功能,而在于“双模式”的结构怎么设计,以及游戏逻辑在医疗场景里要注意哪些边界问题。

2. 双模式架构:一套Unity工程里管理上肢协调和认知训练

2.1 模式切换的顶层设计

康复训练游戏的标准架构是“模式控制器+任务场景+数据记录”三层。模式控制器负责切换当前训练类型,并在切换时重置场景状态;任务场景里包含具体的交互对象和判定逻辑;数据记录层独立运行,不随游戏场景卸载。Unity3D里实现这种结构最常见的是用一个全局的单例管理器,配合场景加载或物体显隐切换。

常见做法是维护一个名为RehabGameManager的MonoBehaviour单例,它在Awake阶段防止销毁,并提供SwitchMode方法。调用模式切换时,必须先停止当前任务的所有Active协程,否则会出现上轮训练的计时器还在跑、但又叠加了新任务逻辑的竞态问题。

public enum RehabMode { UpperLimbCoordination, CognitiveTraining } public class RehabGameManager : MonoBehaviour { public static RehabGameManager Instance { get; private set; } public RehabMode CurrentMode { get; private set; } = RehabMode.UpperLimbCoordination; private RehabTaskBase currentTaskInstance; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); } public void SwitchMode(RehabMode newMode) { if (newMode == CurrentMode) return; currentTaskInstance?.StopAllCoroutines(); currentTaskInstance?.EndTask(); CurrentMode = newMode; } }

这段代码解决的问题是模式切换时的状态残留。RehabTaskBase是所有具体训练任务的基类,每个任务都必须实现EndTask,统一释放自身的碰撞体、光线、计时器引用。如果不做这一层,Unity场景里多个巨集物体并存的消耗会成为性能隐患。参数上要关注DontDestroyOnLoad的使用位置——放在Awake而不是Start里,确保场景切换时单例不被重建。

2.2 上肢协调模式:光点追踪与物理触发器

上肢协调训练的核心是追踪任务(Tracking Task)。在Unity3D里,常见做法是:在场景中生成若干球形目标点,患者手持一个带标记物的物体(比如装了反光球的控制器),在屏幕上用光点代表手部位置,移动控制器去触碰目标点。目标点按康复训练要求分布在患者上肢活动范围内,每次只亮一个目标,触碰成功后目标推进到下一个。

目标点触发依靠物理触发器实现,碰撞体尺寸直接影响训练难度,这是康复场景里必须暴露给治疗师调的核心参数。

public class TargetPoint : MonoBehaviour { public float targetRadius = 0.25f; // 目标点的物理触发半径 public float movementSmoothFactor = 8f; // 目标点位置插值系数 private bool isActive = false; public void Activate(Vector3 position) { isActive = true; transform.position = position; gameObject.SetActive(true); } public void Deactivate() { isActive = false; gameObject.SetActive(false); } private void OnTriggerStay(Collider other) { if (!isActive) return; if (!other.CompareTag("PatientHand")) return; RehabGameManager.Instance.ReportTaskSuccess(this); Deactivate(); } }

注意目标点首先要漂移,所以用OnTriggerStay而不是OnTriggerEnter。原因在于患者手部移动是连续的,尤其是痉挛发作时手会抖动,一个短暂的接触就判定成功会让训练失去意义。targetRadius和光点显示半径可以独立设置:显示半径可以做得比物理半径小一些,患者视觉上觉得目标还没到,但实际上触发范囲更大,给训练者留出操作容差。

2.3 认知功能培养锻炼:事件触发与反应计时

认知训练侧重的是注意力和工作记忆。常见做法是在Unity场景中随机显示图案或数字,要求患者做出特定反应——比如看到“红色方块”就移动手部到目标区,否则不动作。整个过程要记录反应时间和正确率,这些数据最后汇总到数据层。

事件触发不能用Update里每帧判断按键的方式,因为康复训练场景存在误触问题。正确方式是采用事件回调机制:UI按钮或碰撞触发事件,逻辑层订阅事件,时间戳在事件回调里取。

public class CognitiveTaskManager : MonoBehaviour { public float stimulusInterval = 3.5f; private System.DateTime lastReactionTime; private int correctReactionCount = 0; private int totalReactionCount = 0; private IEnumerator TrainingFlow() { while (true) { yield return new WaitForSeconds(stimulusInterval); ShowStimulus(); } } private void ShowStimulus() { // 生成本次刺激类型,例如显示红色方块或蓝色圆形 var currentStimulus = GenerateRandomStimulus(); TotalReactionCount++; StimulusDisplay.Instance.Render(currentStimulus); } private void OnReactionReceived(GameObject targetObject, Vector3 handPos) { var reactionTime = System.DateTime.Now - lastReactionTime; // 判定是否对当前刺激做出了正确的反应 if (ReactionValidator.Validate(currentStimulus, targetObject)) { CorrectReactionCount++; } TrainingDataRecorder.Instance.AppendCognitionRecord(reactionTime, correctReactionCount, totalReactionCount); } }

认知训练的节奏控制要想清楚:stimulusInterval应该在训练开始前由治疗师按患者认知评估结果设置,不能用固定值。反应时间窗口通常设置在2-5秒之间,超过5秒就记一次“未反应”,防止有的患者把等待当成正确策略——这在临床上叫“非反应策略”,是认知训练里重点监控的偏差行为。

3. C#游戏逻辑整合物理:追踪稳定性和碰撞防误触

3.1 从物理设备到手部光点的数据流

Unity3D里做手部追踪,数据源无非两种:外部硬件(如固定式光学传感器)或Unity支持的设备SDK。无论哪种硬件,进入Unity后的处理路径是一致的:原始位置数据 → 过滤/插值 → 映射到游戏坐标 → 驱动代表手部位置的光点物体。这个链路里的关键不是获取数据,而是平滑处理。

直接使用硬件原始位置会让光点在屏幕上抖得像抽搐,因为患者的肢体本身有不自主抖动。所以C#脚本里最常见的手段是加一阶低通滤波,也叫指数平滑。

public class HandPositionSmoothing : MonoBehaviour { public Transform handTarget; // 由硬件数据驱动位置的空物体 public float smoothingFactor = 0.25f; // 0到1之间,越小越平滑 private Vector3 smoothedPosition; private void FixedUpdate() { if (handTarget == null) return; if (smoothedPosition == Vector3.zero) { smoothedPosition = handTarget.position; } else { smoothedPosition = Vector3.Lerp(smoothedPosition, handTarget.position, smoothingFactor); } transform.position = smoothedPosition; } }

smoothingFactor的值不能盲目调低。经验值在0.2-0.4之间:低于0.2会让光点反应明显迟钝,患者手已经移动到目标点,光点还没跟上,产生“脱钩感”;高于0.4则颤抖抑制不足。这个参数要在评估阶段让康复师实操确定,写进患者档案。

3.2 目标点周边的物理防误触设计

康复训练场景里,“误触”不仅是手抖造成的,还有患者依靠身体摆动、用胳膊肘碰等方式“作弊”。物理层的设计要同时应对这两种情况。

首先是交互层的面积匹配。前面已经提到显示半径和物理触发半径分离,这是一重防误触。第二层是碰撞体的层级管理:目标点碰撞体放在单独Layer,患者手部物体放在另一Layer,两者之间的碰撞检测用Layer矩阵控制。不要把碰撞检测做成全局的,否则场景里其他物体(比如桌子、兴趣装饰)会干扰判定。

第三层是时间阈值。用OnTriggerStay配合stayDurationRequired参数,要求手部持续停留在目标点范围内达到一定时间才算命中。这个参数对痉挛型患者尤其关键——痉挛时手会突然弹开,如果要求连续停留0.3秒,就能过滤掉这类无效通过。

public class SustainedTriggerZone : MonoBehaviour { public float requiredHoldTime = 0.3f; private float stayTimer = 0f; private bool isInside = false; private void OnTriggerEnter(Collider other) { if (other.CompareTag("PatientHand")) { isInside = true; stayTimer = 0f; } } private void OnTriggerStay(Collider other) { if (other.CompareTag("PatientHand")) { stayTimer += Time.deltaTime; if (stayTimer >= requiredHoldTime) { TriggerSuccess(); isInside = false; } } } private void OnTriggerExit(Collider other) { if (other.CompareTag("PatientHand")) { isInside = false; stayTimer = 0f; } } }

这个实现里有个细节:stayTimer必须在OnTriggerEnter时重置,而不是累计整个训练周期。否则患者来回出入目标区,时间会累积到一次成功判定。另外,TriggerSuccess要发出一个事件,数据层根据事件时间戳记录一条训练行为,而不是由碰撞体自己去写文件——保持游戏逻辑和数据记录的解耦。

3.3 物理材质和刚体配置的医疗场景适配

Unity的物理引擎默认参数是为游戏场景设计的,拿到康复训练里必须重调。最常见的问题是刚体的maxAngularVelocitycollisionDetectionMode

患者手部的刚体建议将Collision Detection设为Continuous,因为患者动作有时很快(比如注意力突然不集中时甩手),默认的Discrete检测会漏掉高速碰撞,目标点直接被穿过去。

物理材质方面,手部和目标点的摩擦力要设极低(0.1以下),弹性设0。原因很简单:康复训练不需要模拟现实的材料物理,需要的是稳定、可预期的碰撞响应。如果加入反弹效果,目标点被碰飞后,患者无法判断是自己的动作偏差还是系统物理模拟导致的,这在康复场景里是严重的信息误导。所以统一用无弹性的物理材质。

4. 康复强度控制与训练数据记录的落地实现

4.1 训练回合与休息的自动管理

康复游戏和普通游戏最大的区别之一,是必须把休息时间写进系统逻辑里。中风患者的肌力恢复呈U型曲线,连续训练超过一定时间后,动作质量下降、代偿增加,甚至可能诱发关节损伤。Unity项目实施时,要在训练流程里做回合制管理:一个训练回合包含“任务时段”和“休息时段”,时长都由治疗师配置。

public class TrainingSessionConfigProvider : MonoBehaviour { [Header("Session Setup")] public int targetSuccessCount = 20; [Header("Rest Configuration")] public float workDurationPerRound = 60f; public float restDurationPerRound = 30f; public int maxRoundsPerSession = 4; }

休息时段的界面要显示倒计时并让患者回到中性位置。常见做法是同时缩手部光点透明度,提醒患者“现在不需要动”。数据记录模块要在休息时段也工作,记录患者手部的活动幅度——如果休息时手仍然频繁大幅移动,康复师可以判断为肌张力异常或焦虑状态,对这个参数唯一合理的解释是:它不是游戏功能,是临床观察功能。

4.2 训练行为的结构化数据记录

治疗师需要看到的数据不是“玩了多少关”,而是:单位时间内成功触碰目标点的次数、平均触碰时间、错误触碰次数、目标点分布覆盖区域、休息时段活动幅度。这些字段要结构化存储,常见实现是每个训练任务产出JSON行,训练结束后汇总为一个完整记录。

[System.Serializable] public class TrainingRecordEntry { public string patientID; public string modeType; // UpperLimb / Cognitive public string taskName; public float attemptTimestamp; public float responseTimeSeconds; public bool isSuccess; public Vector3 targetWorldPosition; public Vector3 handWorldPosition; public string note; } public class TrainingDataRecorder : MonoBehaviour { private List<TrainingRecordEntry> sessionEntries = new List<TrainingRecordEntry>(); public void AppendEntry(TrainingRecordEntry entry) { sessionEntries.Add(entry); } public void SaveSessionLog(string filePath) { var jsonArray = JsonHelper.ToJsonArray(sessionEntries.ToArray()); System.IO.File.WriteAllText(filePath, jsonArray); } }

注意这里用List而不是每次直接写文件。康复训练中每秒可能产生多条碰撞判定,频繁IO会影响游戏线程性能。内存里累积、训练结束后一次性写入,是常见做法。JsonHelper是Unity没有原生支持直接序列化数组,需要自己做一个包装类的通用方案。

数据落盘路径要注意Unity的沙盒限制,编辑器环境下存在Application.dataPath,打包后必须用Application.persistentDataPath,不然在Windows上会写不进Program Files目录。

4.3 训练难度的自适应调整逻辑

双模式互动系统的另一个落地点是难度自适应。Unity3D里的实现通常不在游戏运行中直接调参数,而是收集一个训练阶段的数据后自动调整下一阶段参数。

比如对上肢协调模式来说,将光点速度、目标点半径作为变量。如果一个训练阶段里成功率超过85%,下一阶段缩小目标半径或增大目标点移动距离;成功率低于50%,系统自动降低速度要求并提示治疗师介入。这个逻辑用C#写起来很直接:

public class AdaptiveDifficultyController : MonoBehaviour { public float successRateThresholdHigh = 0.85f; public float successRateThresholdLow = 0.5f; private TrainingStatistics currentStats; public void AdjustParametersBasedOnStats(TrainingStatistics stats) { if (stats.SuccessRate >= successRateThresholdHigh) { TargetPoint.targetRadius *= 0.9f; TaskSpeedModifier *= 1.1f; } else if (stats.SuccessRate <= successRateThresholdLow) { TargetPoint.targetRadius *= 1.15f; TaskSpeedModifier *= 0.9f; } } }

自适应参数要有边界,比如目标半径最小0.12米,最大0.4米,速度上限不能超过患者主动做动作的生理极限——这个边界值在多数项目里由康复师定义。代码里通过[Header]暴露给Unity Inspector界面,让治疗师在无代码环境下就能调整参数体系。

5. 康复训练场景的3个必查问题:校准、抖动、坐姿范围

5.1 屏幕坐标系和现实动作空间的校准

很多Unity开发者做康复项目时第一个踩坑点就是理想空间和屏幕空间不匹配。屏幕上显示光点移动距离是20厘米,但患者实际抬手动了40厘米——这种不适配会直接造成训练目标设计失效。

校准流程很简单:让患者把标记物放到身体前方的一个已知位置(比如肩峰前下方20cm处),Unity里记录该位置的屏幕坐标,然后让患者手自然下垂,记录另一个点。两点连线换算成屏幕上的像素距离,即得到动作映射比例。每次训练前建议做一次快速校准,同一个患者一天之内做一次就够了,因为设备安放位置一般不变。

5.2 手抖的滤波参数选择:从信号本身出发

上一章讲过指数平滑系数在0.2-0.4之间,但具体怎么选,不能拍脑袋。在训练开始前,让患者保持手部静止5秒,记录光点位置的浮动幅度。如果浮动超过目标点显示半径的三分之一,就需要往0.2方向调整滤波系数;如果浮动很小,可以往0.4方向调,让光点更跟手。

这个方法背后的逻辑是:滤波系数不是游戏手感参数,而是医疗信号处理参数。处理的对象本身包含病理性噪声,平滑的目标是保留主动控制信号,同时压制不自主抖动。比起盲目追求“不抖”,先量化抖动再选择滤波类型,是更稳妥的做法。

5.3 坐姿限制和活动范围的可视化反馈

康复训练中,患者坐在轮椅上完成动作。Unity场景里设置一个半透明的“可活动范围”区域——用光柱或圆环在地面上标示出患者手臂自然活动范围。患者必须移动到范围内才能触发目标点,超出范围只在视觉上给提示,不做碰撞惩罚。

代码上只需要用Box Collider标记范围区域,实现一个边沿高亮效果即可。这个功能看起来简单,实际对临床治疗是重要的支撑:它防止患者在训练中不自觉前倾躯干去够触目标,这种代偿姿势是康复师最不希望看到的。

范围参数需要在场景中以病患个体为单位进行微调,在 Inspector 中可以直接拖动手柄调整包围盒尺寸,所见即所得,不需要编写额外编辑器脚本。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询