Godot 4.3 C#:Task.Run中更新Node3D做圆周动画遇UI冻结
问题分析与解决方案
核心问题
你的代码存在三个关键问题导致UI无响应且冻结:
- InvokeHelper实现错误:静态类的
CallDeferred未关联Godot节点实例,无法将任务正确投递到主线程消息队列。Godot的CallDeferred必须通过Node对象调用才能生效。 - 闭包捕获陷阱:for循环中的Deferred回调捕获的是循环变量的引用,最终所有回调都会使用循环最后一次的坐标值。
- 线程阻塞与低效调度:使用
Thread.Sleep(50)阻塞后台线程,加上大量Deferred调用挤占主线程资源,导致UI卡顿。
修正方案一:修复线程异步调用逻辑
1. 修正InvokeHelper扩展方法
将其改为Node的扩展方法,确保绑定到Godot节点实例:
using Godot; using System; public static class InvokeHelper { /// <summary> /// 主线程调用委托,等价于WinForms的this.Invoke /// </summary> public static void CallDeferred(this Node node, Action action) { node.CallDeferred(Callable.From(action)); } }
2. 重写圆周移动方法
使用async/await替代Task.Run+Thread.Sleep,解决闭包捕获问题,并确保主线程更新:
public partial class Scene1 : Node3D { // 假设cube是已绑定的Node3D节点 [Export] private Node3D cube; public override void _Ready(){}; public override void _Process(double delta){}; void Button_Move_in_Circle_onClick() => MoveCube_inCirclularPath(); private async void MoveCube_inCirclularPath() { // 初始位置直接在主线程设置(点击事件本身就在主线程) cube.Position = new Vector3(2, 2, cube.Position.Z); for (int i = 0; i < 360; i += 5) { // 非阻塞延迟,避免线程阻塞 await Task.Delay(50); var pt = GetCircleCoordinates_FromAngle(i, 4); // 复制到局部变量,避免闭包捕获引用的问题 var currentPt = pt; // 通过当前节点调用CallDeferred,确保在主线程更新节点 this.CallDeferred(() => { cube.Position = new Vector3((float)currentPt.x, 2, (float)currentPt.y); }); } } public static (double x, double y) GetCircleCoordinates_FromAngle(double angleDegrees, double distance) { double angleRadians = angleDegrees * Math.PI / 180.0; double x = distance * Math.Cos(angleRadians); double y = distance * Math.Sin(angleRadians); return (x, y); } }
修正方案二:使用Godot原生动画机制(推荐)
Godot内置的Tween系统更适合处理动画,无需手动管理线程,时间控制更精确,且完全在主线程运行:
public partial class Scene1 : Node3D { [Export] private Node3D cube; void Button_Move_in_Circle_onClick() => MoveCube_inCirclularPath(); private void MoveCube_inCirclularPath() { var tween = CreateTween(); tween.SetDuration(3.6f); // 严格匹配预期的3.6秒时长 tween.SetLoops(1); // 仅播放一次 // 初始位置设置 cube.Position = new Vector3(2, 2, 0); // 用TweenMethod驱动动画进度 tween.TweenMethod(Callable.From((double progress) => { // progress范围0~1,对应0~360度 double angle = progress * 360; var pt = GetCircleCoordinates_FromAngle(angle, 4); cube.Position = new Vector3((float)pt.x, 2, (float)pt.y); }), 0.0, 1.0); } public static (double x, double y) GetCircleCoordinates_FromAngle(double angleDegrees, double distance) { double angleRadians = angleDegrees * Math.PI / 180.0; double x = distance * Math.Cos(angleRadians); double y = distance * Math.Sin(angleRadians); return (x, y); } }
内容的提问来源于stack exchange,提问作者bh_earth0
相关产品推荐
相关产品推荐

