• 【UE】动画蓝图参数屏幕调试工具


    动画蓝图参数屏幕调试工具

    这里用到的动画蓝图基类为自定义的UBaseAnimInstance

    引脚 类型 默认值 / 用法
    Key FName 手动填写参数名称,不要加冒号
    Value String 变量、转换结果、拼接结果或字符串常量均可
    Key Color Linear Color 白色,包含自动添加的冒号
    Value Color Linear Color 红色
    Location Offset Vector (0,-200,100)

    使用效果

    动画蓝图调用:

    1789017342449

    角色蓝图调用:

    1789017311268

    效果(人物左侧显示)):

    1789017386936

    迁移到自己的项目

    将下面的声明及实现复制到目标 UAnimInstance 派生类中,替换类名和自身头文件名。前置声明放在类外,头文件 region 放在类内,generated.h 保持为最后一个 include。若已有生命周期函数,合并清理调用,不要重复定义。

    BaseAnimInstance.h

    class UCanvas;
    class APlayerController;
    
    #pragma region DrawDebugMessages
    public:
    	virtual void NativeUninitializeAnimation() override;
    	virtual void BeginDestroy() override;
    
    	// 手动输入 Key 和 Value;独立颜色、共享位置偏移和自动分行。
    	UFUNCTION(BlueprintCallable, Category = "DebugDraw")
    	void DrawDebugKeyValueMessages(
    		FName Key,
    		const FString& Value,
    		FLinearColor KeyColor = FLinearColor::White,
    		FLinearColor ValueColor = FLinearColor::Red,
    		FVector LocationOffset = FVector(0, -200, 100));
    
    private:
    	struct FDebugKeyValueMessage
    	{
    		FString KeyText;
    		FString ValueText;
    		FLinearColor KeyColor;
    		FLinearColor ValueColor;
    		FVector WorldLocation;
    	};
    
    	TArray DebugKeyValueMessages;
    	uint64 DebugKeyValueFrame = MAX_uint64;
    	FDelegateHandle DebugKeyValueDrawHandle;
    	void DrawDebugKeyValueMessages(UCanvas* Canvas, APlayerController* PlayerController);
    	void ClearDebugKeyValueMessages();
    #pragma endregion DrawDebugMessages
    

    BaseAnimInstance.cpp

    #include "BaseAnimInstance.h"
    #include "CanvasItem.h"
    #include "Debug/DebugDrawService.h"
    #include "Engine/Canvas.h"
    #include "Engine/Engine.h"
    #include "GameFramework/PlayerController.h"
    #include "SceneInterface.h"
    #include "SceneView.h"
    #include "Components/SkeletalMeshComponent.h"
    #include "GameFramework/Pawn.h"
    
    #pragma region DrawDebugMessages
    
    void UBaseAnimInstance::DrawDebugKeyValueMessages(
    	const FName Key,
    	const FString& Value,
    	const FLinearColor KeyColor,
    	const FLinearColor ValueColor,
    	const FVector LocationOffset)
    {
    #if ENABLE_ANIM_DRAW_DEBUG
    	if (!ensureMsgf(IsInGameThread(), TEXT("Draw Debug Key Value must run on the game thread.")))
    	{
    		return;
    	}
    	const USkeletalMeshComponent* Mesh = GetSkelMeshComponent();
    	const AActor* Owner = Mesh ? Mesh->GetOwner() : nullptr;
    	if (!Owner)
    	{
    		return;
    	}
    
    	FVector DrawLocationOffset = LocationOffset;
    	if (const APawn* PawnOwner = TryGetPawnOwner())
    	{
    		if (const APlayerController* PlayerController =
    			Cast(PawnOwner->GetController()))
    		{
    			FVector ViewLocation;
    			FRotator ViewRotation;
    			PlayerController->GetPlayerViewPoint(ViewLocation, ViewRotation);
    
    			// XY 跟随完整相机旋转:X 沿视线前后,Y 沿相机左右。
    			// Z 单独叠加,继续保持世界向上。
    			DrawLocationOffset = ViewRotation.RotateVector(
    				FVector(LocationOffset.X, LocationOffset.Y, 0.0f))
    				+ FVector(0.0f, 0.0f, LocationOffset.Z);
    		}
    	}
    
    	if (DebugKeyValueFrame != GFrameCounter)
    	{
    		DebugKeyValueMessages.Reset();
    		DebugKeyValueFrame = GFrameCounter;
    	}
    	DebugKeyValueMessages.Add({Key.ToString() + TEXT(":"), Value,
    		KeyColor, ValueColor, Owner->GetActorLocation() + DrawLocationOffset});
    	if (!DebugKeyValueDrawHandle.IsValid())
    	{
    		DebugKeyValueDrawHandle = UDebugDrawService::Register(TEXT("Game"),
    			FDebugDrawDelegate::CreateUObject(this, &UBaseAnimInstance::DrawDebugKeyValueMessages));
    	}
    #endif
    }
    
    void UBaseAnimInstance::DrawDebugKeyValueMessages(UCanvas* Canvas, APlayerController* PlayerController)
    {
    #if ENABLE_ANIM_DRAW_DEBUG
    	// Debug draw can be called for other editor/PIE worlds. Never leak text into those views.
    	if (!Canvas || !Canvas->Canvas || !Canvas->SceneView || !GEngine
    		|| !Canvas->SceneView->Family || !Canvas->SceneView->Family->Scene
    		|| Canvas->SceneView->Family->Scene->GetWorld() != GetWorld()
    		|| DebugKeyValueFrame == MAX_uint64 || GFrameCounter - DebugKeyValueFrame > 1)
    	{
    		return;
    	}
    	UFont* Font = GEngine->GetSmallFont();
    	if (!Font)
    	{
    		return;
    	}
    	// Reset for each viewport draw; stack this instance's messages in submission order.
    	float LineOffsetY = 0.0f;
    	constexpr float LineSpacing = 4.0f;
    	for (const FDebugKeyValueMessage& Message : DebugKeyValueMessages)
    	{
    		if (Canvas->SceneView->WorldToScreen(Message.WorldLocation).W <= 0.0f)
    		{
    			continue;
    		}
    		const FVector ScreenLocation = Canvas->Project(Message.WorldLocation);
    		const FVector2D TextPosition(ScreenLocation.X, ScreenLocation.Y + LineOffsetY);
    		float KeyWidth = 0.0f;
    		float KeyHeight = 0.0f;
    		Canvas->StrLen(Font, Message.KeyText, KeyWidth, KeyHeight);
    		FCanvasTextItem KeyItem(TextPosition, FText::FromString(Message.KeyText), Font, Message.KeyColor);
    		Canvas->DrawItem(KeyItem);
    		// Canvas does not reliably expand tabs; use an explicit screen-space gap.
    		constexpr float KeyValueGap = 24.0f;
    		FCanvasTextItem ValueItem(TextPosition + FVector2D(KeyWidth + KeyValueGap, 0.0f),
    			FText::FromString(Message.ValueText), Font, Message.ValueColor);
    		Canvas->DrawItem(ValueItem);
    		LineOffsetY += KeyHeight + LineSpacing;
    	}
    #endif
    }
    
    void UBaseAnimInstance::ClearDebugKeyValueMessages()
    {
    	if (DebugKeyValueDrawHandle.IsValid())
    	{
    		UDebugDrawService::Unregister(DebugKeyValueDrawHandle);
    		DebugKeyValueDrawHandle.Reset();
    	}
    	DebugKeyValueMessages.Reset();
    	DebugKeyValueFrame = MAX_uint64;
    }
    
    void UBaseAnimInstance::BeginDestroy()
    {
    	ClearDebugKeyValueMessages();
    	Super::BeginDestroy();
    }
    
    void UBaseAnimInstance::NativeUninitializeAnimation()
    {
    	ClearDebugKeyValueMessages();
    	Super::NativeUninitializeAnimation();
    }
    
    #pragma endregion DrawDebugMessages
    

    编译与使用

    目标运行时模块 Build.cs 确保依赖 Core、CoreUObject、Engine。

    关闭编辑器,完整编译 Development Editor,再重启并刷新蓝图节点。动画蓝图父类应为目标动画实例类;不要在线程安全动画更新中调用此函数。

    多参数自动分行

    注意:要让多条参数自动排列成同一列,各个节点的 LocationOffset(X、Y、Z)必须保持一致。不要为每一行单独修改偏移,行距由工具自动累加;偏移不一致会导致文字错位或重叠。

    同一帧连续调用多个节点,并使用相同的 LocationOffset。消息按调用顺序向屏幕下方排列,不需要逐条修改世界 Z。

    每绘制一条消息,累计屏幕偏移 LineOffsetY += KeyHeight + LineSpacing。LineSpacing 默认为 4 个画布像素,修改此常量可以调整额外行距;Key 与 Value 之间仍保持额外 24 像素间距。

    行偏移在每次视口绘制开始时重置,不会跨帧累计。镜头后方被跳过的消息不占行。同一个动画实例的消息共享这一行偏移;如果传入不同的 LocationOffset,还会在各自锚点上叠加累计行偏移。

    这是多条单行消息的自动分行,不是按屏幕宽度自动折行;Key 和 Value 建议均使用单行文本。需要持续显示时,每帧调用这些节点。

    角色蓝图调用时

    通过目标动画实例引用调用:

    目标 Skeletal Mesh Component
        → Get Anim Instance
        → Cast To BaseAnimInstance
        → Draw Debug Key Value Messages(Target 接转换结果)
    

    检查引用和 Cast 是否有效。文字跟随目标动画实例网格所属 Actor,而非另一个发起调用的 Actor。

本文作者:EanoJiang

本文链接:https://www.cnblogs.com/eanojiang/p/22919927

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

  • 相关阅读:
    Prometheus+Grafana实现监控报警
    (一)shell编程
    completablefuture使用案例代码
    艾美捷QuickTiter 逆转录病毒定量试剂盒的制备方案
    机器学习中岭回归、LASSO回归和弹性网络与损失函数
    功能测试 之 单模块测试----轮播图、登录、注册
    C/CPP基础练习题多维数组,矩阵转置,杨辉三角详解
    何为OOM(Out of Memory)?
    R语言数据探索与分析-碳排放分析预测
    数仓开发和中间表的区别是什么?
  • 原文地址:https://www.cnblogs.com/eanojiang/p/22919927