UE C++中编写蓝图异步节点

我们在操作蓝图的过程中经常有类似定时器、异步任务等需求,用默认蓝图节点虽然也能完成,但看起来较为繁琐,其实可以通过继承C++异步蓝图动作类来实现。

1.首先创建一个C++类,继承UBlueprintAsyncActionBase:
在这里插入图片描述
2.编写该异步节点的h文件和cpp文件。

这里定义了一个回调委托AsyncActionCallback,可以在异步节点触发结束时调用。
AsyncAction为蓝图调用的函数体,通常实例化一个新的对象并返回,例如在蓝图中调用了2次AsyncAction异步节点,实际上就会创建2个对象。所以放到Tick事件里触发就会调用若干次异步节点导致编辑器卡死

Activate是异步节点实际处理的逻辑。

.h:

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "MyBlueprintAsyncActionBase.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FAsyncActionCallback);

UCLASS()
class MYPROJECT_API UMyBlueprintAsyncActionBase : public UBlueprintAsyncActionBase
{
	GENERATED_BODY()

public:
	UPROPERTY(BlueprintAssignable)
	FAsyncActionCallback OnFinished;

	UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
	static UMyBlueprintAsyncActionBase* AsyncAction();

	UMyBlueprintAsyncActionBase();

private:

	bool AsyncActionFlag;

	virtual void Activate() override;
};

.cpp:

#include "MyBlueprintAsyncActionBase.h"

UMyBlueprintAsyncActionBase* UMyBlueprintAsyncActionBase::AsyncAction()
{
	UMyBlueprintAsyncActionBase* Ret = NewObject<UMyBlueprintAsyncActionBase>();
	return Ret;
}

UMyBlueprintAsyncActionBase::UMyBlueprintAsyncActionBase()
{
    AsyncActionFlag = false;
}

void UMyBlueprintAsyncActionBase::Activate()
{
	Super::Activate();

    Async(EAsyncExecution::ThreadPool, [&]()
    {
        AsyncActionFlag = true;
        FPlatformProcess::Sleep(5.0f);
        OnFinished.Broadcast();
    });
}

编译后,新建一个Actor蓝图,在BeginPlay事件处调用,OnFinished在异步逻辑结束后触发。
在这里插入图片描述

<think>我们正在解决Unreal Engine蓝图中自定义异步节点的回调函数绑定不执行的问题。根据用户需求,我们需要参考之前关于蓝图系统的内容(引用[1]和[2]),但用户的问题集中在异步节点的回调绑定不执行上。 可能的原因和解决方案: 1. 回调函数未正确绑定:确保在异步节点完成后调用的函数已正确绑定到异步节点的输出引脚。 2. 执行顺序问题:异步操作可能尚未完成,但回调已经被调用,或者异步操作完成时回调已经失效(例如,持有回调的对象被销毁)。 3. 蓝图节点未正确实现:自定义异步节点需要正确使用LatentInfo来管理延迟执行,否则可能导致回调无法触发。 4. 回调函数在蓝图中的可见性:确保回调函数是公共的(Public)或在同一蓝图内可访问。 5. 游戏线程问题:异步操作的回调必须在游戏线程中执行,如果回调被其他线程调用,可能导致不执行。 具体步骤: 步骤1:检查回调绑定 - 在自定义异步节点蓝图脚本中,确保在异步操作完成后调用了正确的输出引脚(例如,OnSuccess或OnFailure)。 - 在调用该节点蓝图中,检查是否将回调函数连接到了相应的输出引脚。 步骤2:检查异步节点的实现 - 自定义异步节点通常需要继承自`BlueprintAsyncActionBase`,并重写`Activate`函数和注册回调。 - 在异步操作完成时,必须调用`OnSuccess.Broadcast()`或类似函数来触发回调。 步骤3:检查对象生命周期 - 如果回调函数所属的对象(如Actor)在异步操作完成前被销毁,回调将无法执行。使用`IsValid`检查对象是否存在。 步骤4:使用蓝图接口(如引用[2]所述) - 如果回调需要在不同对象间传递,考虑使用蓝图接口(Blueprint Interface)来定义回调函数,确保调用的对象实现了该接口。 步骤5:调试输出 - 在异步节点的各个关键点添加调试输出(如PrintString),确保节点按预期激活和完成。 示例代码(自定义异步节点类): 以下是一个简单的自定义异步节点类的框架,用于触发一个延迟后回调的节点: ```cpp // 头文件 #pragma once #include "CoreMinimal.h" #include "Kismet/BlueprintAsyncActionBase.h" #include "MyAsyncAction.generated.h" DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMyAsyncOutputPin); UCLASS() class MYPROJECT_API UMyAsyncAction : public UBlueprintAsyncActionBase { GENERATED_BODY() public: UFUNCTION(BlueprintCallable, Category="MyAsync", meta=(BlueprintInternalUseOnly = "true", WorldContext="WorldContextObject")) static UMyAsyncAction* MyAsyncAction(UObject* WorldContextObject, float DelayTime); virtual void Activate() override; UPROPERTY(BlueprintAssignable) FMyAsyncOutputPin OnSuccess; private: float Delay; UObject* WorldContext; FTimerHandle TimerHandle; }; ``` ```cpp // 源文件 #include "MyAsyncAction.h" UMyAsyncAction* UMyAsyncAction::MyAsyncAction(UObject* WorldContextObject, float DelayTime) { UMyAsyncAction* Action = NewObject<UMyAsyncAction>(); Action->WorldContext = WorldContextObject; Action->Delay = DelayTime; return Action; } void UMyAsyncAction::Activate() { // 设置一个定时器,在延迟后触发回调 if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::LogAndReturnNull)) { FTimerDelegate TimerDelegate; TimerDelegate.BindLambda([this]() { // 在触发回调前检查对象是否有效(避免在回调执行前节点被销毁) if (IsValid(this)) { OnSuccess.Broadcast(); } }); World->GetTimerManager().SetTimer(TimerHandle, TimerDelegate, Delay, false); } else { // 如果获取不到World,立即触发失败?或者可以广播一个失败事件 // 注意:这里我们没有定义失败事件,所以可以选择log错误 UE_LOG(LogTemp, Error, TEXT("Failed to get world context")); } } ``` 在蓝图中使用: 1. 创建该节点蓝图调用。 2. 将节点的OnSuccess引脚连接到你的回调函数。 如果回调仍然不执行,请检查: - 定时器是否正常设置(通过调试输出)。 - 确保WorldContextObject有效(通常传入self)。 步骤6:使用蓝图接口(如引用[2]所述)进行回调 如果回调需要在不同对象上执行,可以定义一个蓝图接口,并在异步节点完成时调用该接口的方法。 例如: 1. 创建一个蓝图接口(如BPI_AsyncCallback),其中包含一个函数OnAsyncComplete。 2. 在异步节点中,将回调对象(通过WorldContextObject传入)转换为该接口,并调用接口函数。 在异步节点中广播时,可以这样写: ```cpp // 在头文件中修改委托,增加一个参数(回调对象) DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMyAsyncOutputPin, UObject*, CallbackObject); // 在广播时 OnSuccess.Broadcast(CallbackObject); ``` 然后在蓝图中,将回调对象(实现了接口的对象)传入异步节点,并在OnSuccess事件中,使用“调用接口函数”节点调用接口函数。 总结: 回调不执行通常是由于绑定问题、对象生命周期问题或节点实现问题导致。通过上述步骤逐一排查,应该可以解决问题。 相关问题: 1. 如何在Unreal Engine中创建自定义的异步蓝图节点? 2. 蓝图接口(Blueprint Interface)的使用场景有哪些? 3. 如何避免在异步操作中因对象销毁导致的崩溃? 4. Unreal Engine中的委托(Delegate)和事件(Event)有什么区别? 5. 如何在蓝图中调试异步节点?</think>### Unreal Engine 蓝图自定义异步节点回调函数绑定问题解决方案 在蓝图中自定义异步节点时,回调函数绑定不执行是常见问题,以下是系统化的排查和解决方案: --- #### **核心问题原因** 1. **回调委托未正确初始化** - 未在异步节点中创建动态多播委托 - 委托变量未标记`BlueprintAssignable`属性[^2] 2. **执行流中断** - 异步操作完成后未触发`Broadcast()` - 蓝图执行引脚未正确连接 3. **对象生命周期问题** - 持有回调的对象在异步操作完成前被销毁 - WorldContext对象无效[^1] --- #### **解决方案步骤** **1. 正确声明委托 (C++部分)** ```cpp // MyAsyncNode.h DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnAsyncComplete); UCLASS() class UMyAsyncNode : public UBlueprintAsyncActionBase { GENERATED_BODY() public: UPROPERTY(BlueprintAssignable) // 关键属性 FOnAsyncComplete OnSuccess; // 成功回调 UPROPERTY(BlueprintAssignable) FOnAsyncComplete OnFailure; // 失败回调 UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly, WorldContext="WorldContextObject")) static UMyAsyncNode* ExecuteAsync(UObject* WorldContextObject); virtual void Activate() override; }; ``` **2. 确保广播执行 (C++实现)** ```cpp void UMyAsyncNode::Activate() { // 模拟异步操作 FTimerHandle Timer; GetWorld()->GetTimerManager().SetTimer(Timer, [this]() { if(IsValid(this)) // 生命周期检查 { OnSuccess.Broadcast(); // 必须调用广播 } }, 2.0f, false); // 2秒后触发 } ``` **3. 蓝图正确绑定 (关键步骤)** 1. 在事件图表中调用自定义异步节点 2. **右键点击节点的输出引脚 → 分配函数** ```mermaid graph LR A[自定义异步节点] --> B[OnSuccess] B --> C[创建自定义事件] C --> D[实现回调逻辑] ``` **4. 生命周期保护技巧** ```cpp // 在异步操作前检查有效性 if(!IsValid(this) || !IsValid(WorldContextObject)) { OnFailure.Broadcast(); return; } ``` --- #### **常见问题排查表** | 现象 | 检查点 | 修复方案 | |------|--------|----------| | 回调完全不触发 | 1. 是否调用了Broadcast()<br>2. 委托是否标记BlueprintAssignable | 添加广播调用<br>增加UPROPERTY配置 | | 仅首次触发 | 1. 节点实例是否复用<br>2. 定时器是否重复触发 | 每次使用新建节点<br>清除旧定时器 | | 打包后失效 | 1. 是否使用Development构建<br>2. 编辑器专用API | 检查#if WITH_EDITOR<br>使用运行时API | | 部分蓝图生效 | 1. 回调函数访问权限<br>2. 蓝图接口实现 | 设置函数为Public<br>检查接口继承[^2] | --- #### **完整示例流程** 1. **创建蓝图接口**(如`BPI_AsyncCallback`) ```mermaid classDiagram class BPI_AsyncCallback { <<BlueprintInterface>> +OnAsyncSuccess() +OnAsyncFailure() } MyActor --|> BPI_AsyncCallback MyActor : +OnAsyncSuccess() ``` 2. **异步节点中调用接口** ```cpp // 广播时检查接口实现 if (TargetObject->GetClass()->ImplementsInterface(UBPI_AsyncCallback::StaticClass())) { IBPI_AsyncCallback::Execute_OnAsyncSuccess(TargetObject); } ``` 3. **蓝图绑定验证** - 在回调函数中添加`Print String`调试 - 使用`IsValid`节点检查对象引用 --- #### **最佳实践** 1. **使用延迟代理**:通过`FTimerDelegate`确保回调在游戏线程执行 2. **添加取消机制**: ```cpp virtual void Cancel() override { GetWorld()->GetTimerManager().ClearAllTimersForObject(this); } ``` 3. **调试输出**: ```cpp UE_LOG(LogTemp, Warning, TEXT("Broadcasting to %d listeners"), OnSuccess.GetNumDelegate()); ``` --- ### 相关问题 1. 如何在异步节点中传递自定义参数到回调函数? 2. 蓝图接口(Blueprint Interface)和事件调度器(Event Dispatcher)在回调场景中的优劣比较? 3. 如何实现可取消的异步蓝图操作? 4. 异步操作中如何处理网络同步问题? 5. 如何通过蓝图调试工具追踪未执行的异步回调?[^1][^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值