You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Godot 4.3 C#:Task.Run中更新Node3D做圆周动画遇UI冻结

问题分析与解决方案

核心问题

你的代码存在三个关键问题导致UI无响应且冻结:

  1. InvokeHelper实现错误:静态类的CallDeferred未关联Godot节点实例,无法将任务正确投递到主线程消息队列。Godot的CallDeferred必须通过Node对象调用才能生效。
  2. 闭包捕获陷阱:for循环中的Deferred回调捕获的是循环变量的引用,最终所有回调都会使用循环最后一次的坐标值。
  3. 线程阻塞与低效调度:使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 06:17:11