Featured image of post UE ComponentVisualizer

UE ComponentVisualizer

利用 ComponentVisualizer 可视化在编辑器视口内画出相机脚本的运行轨迹。

1. 创建一个 Module,修改 Build.cs 和 .cpp 文件如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
.Build.cs

using UnrealBuildTool;

public class UnLuaTestEditor : ModuleRules
{
 public UnLuaTestEditor(ReadOnlyTargetRules Target) : base(Target)
 {
  PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
  // ComponentViz needed for the objects we're visualizing
  PublicDependencyModuleNames.AddRange(new string[] {  "Core", "Engine", 
  "CoreUObject", "UnluaTest" });
        
  // Needed for our editor logic
  // 这里加入 UnrealEd 模块
  PrivateDependencyModuleNames.AddRange(new string[] { "UnrealEd" });	
 }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include "UnLuaTestEditor.h"
#include "UnrealEd.h"
#include "LineVisualizer.h"
#include "UnLuaTest/LineDraw.h"

// Actually registers the module
IMPLEMENT_GAME_MODULE(FUnLuaTestEditorModule, UnLuaTestEditor);

void FUnLuaTestEditorModule::StartupModule()
{
 UE_LOG(LogTemp, Warning, TEXT("StartupModule"));
 if (GUnrealEd)
 {
  UE_LOG(LogTemp, Warning, TEXT("OnRegeister"));
  TSharedPtr<FComponentVisualizer> Viz = MakeShareable(new LineVisualizer());	

 // ULineDraw 为 自定义 ActorComponent, 这里对其注册 ComponentVisualizer
  GUnrealEd->RegisterComponentVisualizer(ULineDraw::StaticClass()->GetFName(), 
  Viz);
  Viz->OnRegister();
 }
 else
 {
  UE_LOG(LogTemp, Warning, TEXT("Regeister failed"));
 }
}

void FUnLuaTestEditorModule::ShutdownModule()
{
 if (GUnrealEd)
 {
  GUnrealEd->UnregisterComponentVisualizer(ULineDraw::StaticClass()->GetFName());
 }
}

2. ActorComponent

在编辑器默认 Module 下创建一 ActorComponent, ActorComponent 内主要存储画线所需要的信息,如点的位置,以及一些增加易用性的函数等

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "PointActor.h"
#include "Components/ActorComponent.h"
#include "Curves/CurveVector.h"
#include "LineDraw.generated.h"

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class UNLUATEST_API ULineDraw : public UActorComponent
{
 GENERATED_BODY()

public:	
 // Sets default values for this component's properties
 ULineDraw();

protected:
 // Called when the game starts
 virtual void BeginPlay() override;

public:	
 // Called every frame
 virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

    /** Spawn Actor 的种类 */
 UPROPERTY()
 TSubclassOf<APointActor> ActorToSpawn;

    /** 点的集合 */
 UPROPERTY(EditAnywhere, BlueprintReadWrite)
 TArray<APointActor*> PointActors;

    /** 画线信息来源的 Curve, 增加的点的值最终也是更新到 Curve 上 */
 UPROPERTY(EditAnywhere)
 UCurveVector* Points;

    /** 是否以 Component Owner 的坐标为起点 */
 UPROPERTY(EditAnywhere)
 bool bDrawFromActor;

    /** 添加一个点 */
 UFUNCTION(CallInEditor, Category="Custom Point")
 void AddPoints();

    /** 销毁所有的点,重置 Curve 数据 */
 UFUNCTION(CallInEditor, Category="Custom Point")
 void ClearAllPoints();
 
private:
    /**AddPoint() 生成 Point 对应 Curve 上的默认 Key 值
 * 每次调用 +2
 */
 float PointDefaultTime = 0;
};

////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////

.cpp
    
// Fill out your copyright notice in the Description page of Project Settings.

#include "LineDraw.h"
#include "GetMeshSizeInterface.h"

// Sets default values for this component's properties
ULineDraw::ULineDraw()
{
 // Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
 // off to improve performance if you don't need them.
 PrimaryComponentTick.bCanEverTick = true;
 bDrawFromActor = true;
 // ...
}

// Called when the game starts
void ULineDraw::BeginPlay()
{
 Super::BeginPlay();
 // ...
}

// Called every frame
void ULineDraw::TickComponent(float DeltaTime, ELevelTick TickType, 
FActorComponentTickFunction* ThisTickFunction)
{
 Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
 // ...
}

void ULineDraw::AddPoints()
{
 if (ActorToSpawn)
 {
  UWorld* MyWorld = GetWorld();

  if (MyWorld)
  {
   APointActor* Point = MyWorld->SpawnActor<APointActor>(ActorToSpawn, 
   this->GetOwner()->GetActorLocation(), FRotator(0.f));
   PointActors.Add(Point);
   Point->AttachToActor(this->GetOwner(), 
   FAttachmentTransformRules::KeepWorldTransform);
   Point->Time = PointDefaultTime;
   PointDefaultTime += 2;
  }
 }
}

void ULineDraw::ClearAllPoints()
{
 for (APointActor* point : PointActors)
 {
  point->Destroy();
 }
 PointActors.Reset();
 Points->ResetCurve();
}

这里因为功能需求,我自己实现了个类似于 SplineComponent 的功能,可以在编辑器内设置线上的点。这里直接用一个自定义的 Actor : “APointActor”, 作为点,如果直接 Spawn 默认的 Actor 的话生成的 Actor 是不带 SceneComponent 的,也就是没有 Transform 属性,毕竟 UE Actor 的属性什么的都是通过挂载 Component 实现的,隔壁 Unity 好歹出生自带 Transform, UE 是真的啥也不带


3. FComponentVisualizer

终于到了实现画线的部分,这里在自定义的 Module 下新建一空的 C++ 类, 然后让其继承自 FComponentVisualizer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "ComponentVisualizer.h"


/**
 * 
 */
class UNLUATESTEDITOR_API LineVisualizer : public FComponentVisualizer 
{
virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override;
};

只需要重写 DrawVisualization() 这一个函数就可以了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Fill out your copyright notice in the Description page of Project Settings.


#include "LineVisualizer.h"
#include "SceneManagement.h"
#include "UnLuaTest/LineDraw.h"

void LineVisualizer::DrawVisualization(const UActorComponent* Component, 
const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
 // UE_LOG(LogTemp, Warning, TEXT("Drawing"));
 const ULineDraw* LineDrawer = Cast<const ULineDraw>(Component);
 
 // 获取组件所在 Actor 的坐标
 FVector ActorLocation= LineDrawer->GetOwner()->GetTargetLocation();
 
 if (LineDrawer->Points != nullptr)
 {
  float Time = 0;
  float MaxTime;
  UCurveVector* Curve = LineDrawer->Points;

  // 将 PointActor 点的信息写入到 Curve 里面
  for (int i = 0; i < LineDrawer->PointActors.Num(); i++)
  {
   APointActor* Point = LineDrawer->PointActors[i];
   FVector Val = Point->GetActorLocation() - LineDrawer->GetOwner()->
   GetActorLocation();

   FKeyHandle Key = Curve->FloatCurves[0].UpdateOrAddKey(Point->Time, Val.X);
   Curve->FloatCurves[0].SetKeyInterpMode(Key, ERichCurveInterpMode::RCIM_Cubic);
   
   Key = Curve->FloatCurves[1].UpdateOrAddKey(Point->Time, Val.Y);
   Curve->FloatCurves[1].SetKeyInterpMode(Key, ERichCurveInterpMode::RCIM_Cubic);
   
   Key = Curve->FloatCurves[2].UpdateOrAddKey(Point->Time, Val.Z);
   Curve->FloatCurves[2].SetKeyInterpMode(Key, ERichCurveInterpMode::RCIM_Cubic);
  }
  
  /** 获取 Curve 所有值的时间区间 */
  Curve->GetTimeRange(Time, MaxTime);
  
  /** 画出整个 Curve 曲线 */
  while (Time < MaxTime)
  {
   FVector Start = LineDrawer->Points->GetVectorValue(Time) + ActorLocation;
   FVector End = LineDrawer->Points->GetVectorValue(Time + 0.1) + ActorLocation;
   
   PDI->DrawLine(Start, End, FLinearColor::Red, SDPG_World, 2.0f);
   
   Time += 0.1;
  }
 }
}

上述代码参杂了太多的我个人项目所需的代码逻辑,其实核心代码就是简简单单的一行

PDI->DrawLine(Start, End, FLinearColor::Red, SDPG_World, 2.0f);

我们看下 DrawLine() 的声明

1
2
3
4
5
6
7
8
9
 virtual void DrawLine(
  const FVector& Start,
  const FVector& End,
  const FLinearColor& Color,
  uint8 DepthPriorityGroup,
  float Thickness = 0.0f,
  float DepthBias = 0.0f,
  bool bScreenSpace = false
  ) = 0;

很清晰了,就是这些参数。

其实原本是准备用 MeshComponent 来画线的,顺便学习下怎么自定义一个 Mesh,但属实能力不够只能先用 Visualizer 凑合凑合,有时间搞定了 Mesh 到时候再发一篇。

参考链接: https://sondreutheim.com/post/ue4_component_visualizers
参考项目: https://github.com/sutheim/UE4-ComponentVisualizer-Example

Licensed under CC BY-NC-SA 4.0
最后更新于 Dec 21, 2024 20:33 +0800
comments powered by Disqus
本博客已稳定运行
使用 Hugo 构建
主题 StackJimmy 设计