UE4 component可视化

本文介绍如何在UE4中创建自定义编辑器组件,包括编辑器模块设置、菜单命令定义、组件类实现及组件可视化器设计。通过实例演示,实现动态调整目标点位置并提供复制、删除等功能。

源码地址

https://download.youkuaiyun.com/download/maxiaosheng521/11584159

最新4.23更新一下 源码中VisualTarget.cpp中

需要额外再添加一个头文件#include "Editor/UnrealEdEngine.h"

否则会报GUnrealED为定义错误

 

 

最终效果 可以通过component中添加target点 动态调整点的位置 也可以在点上右键 进行复制 创建

 

下面的代码可能有省略 以最终链接中的工程为准

1.首先 创建一个editor module 这里不具体描述了创建过程了 参考上一篇教程

额外要注意的是 我们需要添加一些系统模块

		PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore", "EditorStyle", "UnrealEd" });

2.定义HTargetProxy 类 用来保存target点的信息  这里只保存了点的index

struct HTargetingVisProxy : public HComponentVisProxy
{
	DECLARE_HIT_PROXY();
	HTargetingVisProxy(const UActorComponent* InComponent)
		:HComponentVisProxy(InComponent, HPP_Wireframe)
	{}


};

struct HTargetProxy :public HTargetingVisProxy
{
	DECLARE_HIT_PROXY();

	HTargetProxy(const UActorComponent* Incomponent, int32 index)
		:HTargetingVisProxy(Incomponent), TargetIndex(index)
	{

	}
	int32 TargetIndex;
};

3.定义菜单命令

.h文件

class FTargetingVisualizerCommands :public TCommands<FTargetingVisualizerCommands>
{
public:
	FTargetingVisualizerCommands():TCommands<FTargetingVisualizerCommands>("TargetingComponent", FText::FromString(FString(TEXT("Visualizer"))), NAME_None, FEditorStyle::GetStyleSetName())
	{

	}

	virtual void RegisterCommands() override;

	TSharedPtr<FUICommandInfo> Duplicate;

};

.cpp

void FTargetingVisualizerCommands::RegisterCommands()
{
	UI_COMMAND(Duplicate, "Duplicate Target", "Duplicate the CurrentTarget", EUserInterfaceActionType::Button, FInputGesture());
}

4.定义component类

.h文件

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

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "TargetComponent.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class VISUALTARGET_API UTargetComponent : public USceneComponent
{
	GENERATED_BODY()

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

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

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
	void DeleteTarget(int32 deleteIndex); //用于执行删除target点操作

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Target)
		TArray<FVector> Targets; //用于保存target点
	
		

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Target)
		FLinearColor EditorSelectedColor; //选中点的颜色

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Target)
		FLinearColor UnSelectedColor;  //没有选中点的颜色
};


.cpp文件

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


#include "TargetComponent.h"

// Sets default values for this component's properties
UTargetComponent::UTargetComponent()
{
	// 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;

	// ...
	EditorSelectedColor = FLinearColor(1.0f, 0.0f, 0.0f, 1.0f);
	UnSelectedColor = FLinearColor(0.0f, 0.0f, 0.0f, 1.0f);
}


// Called when the game starts
void UTargetComponent::BeginPlay()
{
	Super::BeginPlay();

	// ...
	
}


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

	// ...
}

void UTargetComponent::DeleteTarget(int32 deleteIndex)
{
	Targets.RemoveAt(deleteIndex);
}



5.定义ComponentVisualizer  注释写在代码里了

.h文件
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "InputCoreTypes.h"
#include "Components/ActorComponent.h"
#include "HitProxies.h"

class AActor;
class FCanvas;
class FEditorViewportClient;
class FPrimitiveDrawInterface;
class FSceneView;
class FViewport;
class SWidget;
struct FViewportClick;

struct HComponentVisProxy : public HHitProxy
{
	DECLARE_HIT_PROXY(UNREALED_API);

	HComponentVisProxy(const UActorComponent* InComponent, EHitProxyPriority InPriority = HPP_Wireframe) 
	: HHitProxy(InPriority)
	, Component(InComponent)
	{}

	virtual EMouseCursor::Type GetMouseCursor() override
	{
		return EMouseCursor::Crosshairs;
	}

	TWeakObjectPtr<const UActorComponent> Component;
};


/** Base class for a component visualizer, that draw editor information for a particular component class */
class UNREALED_API FComponentVisualizer : public TSharedFromThis<FComponentVisualizer>
{
public:
	FComponentVisualizer() {}
	virtual ~FComponentVisualizer() {}

	/** */
	virtual void OnRegister() {}
	/** Draw visualization for the supplied component */
	virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) {}
	/** Draw HUD on viewport for the supplied component */
	virtual void DrawVisualizationHUD(const UActorComponent* Component, const FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) {}
	/** */
	virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) { return false; }
	/** */
	virtual void EndEditing() {}
	/** */
	virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const { return false; }
	/** */
	virtual bool GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const { return false; }
	/** */
	virtual bool HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltalRotate, FVector& DeltaScale) { return false; }
	/** */
	virtual bool HandleInputKey(FEditorViewportClient* ViewportClient,FViewport* Viewport,FKey Key,EInputEvent Event) { return false; }
	/** */
	virtual TSharedPtr<SWidget> GenerateContextMenu() const { return TSharedPtr<SWidget>(); }
	/** */
	virtual bool IsVisualizingArchetype() const { return false; }

	struct FPropertyNameAndIndex
	{
		FPropertyNameAndIndex()
			: Name(NAME_None)
			, Index(INDEX_NONE)
		{}

		explicit FPropertyNameAndIndex(FName InName, int32 InIndex = 0)
			: Name(InName)
			, Index(InIndex)
		{}

		bool IsValid() const { return Name != NAME_None && Index != INDEX_NONE; }

		void Clear()
		{
			Name = NAME_None;
			Index = INDEX_NONE;
		}

		FName Name;
		int32 Index;
	};

	/** Find the name of the property that points to this component */
	static FPropertyNameAndIndex GetComponentPropertyName(const UActorComponent* Component);

	/** Get a component pointer from the property name */
	static UActorComponent* GetComponentFromPropertyName(const AActor* CompOwner, const FPropertyNameAndIndex& Property);

	/** Notify that a component property has been modified */
	static void NotifyPropertyModified(UActorComponent* Component, UProperty* Property);

	/** Notify that many component properties have been modified */
	static void NotifyPropertiesModified(UActorComponent* Component, const TArray<UProperty*>& Properties);
};

struct FCachedComponentVisualizer
{
	TWeakObjectPtr<UActorComponent> Component;
	TSharedPtr<FComponentVisualizer> Visualizer;
	
	FCachedComponentVisualizer(UActorComponent* InComponent, TSharedPtr<FComponentVisualizer>& InVisualizer)
		: Component(InComponent)
		, Visualizer(InVisualizer)
	{}
};
// Fill out your copyright notice in the Description page of Project Settings.


#include "TargetingComponentVisualizer.h"

IMPLEMENT_HIT_PROXY(HTargetingVisProxy, HComponentVisProxy);
IMPLEMENT_HIT_PROXY(HTargetProxy, HTargetingVisProxy);

#define LOCTEXT_NAMESPACE "ComponentVisualizer"

void FTargetingComponentVisualizer::OnRegister()
{
	FTargetingVisualizerCommands::Register(); //创建菜单instance
	TargetingComponentVisualizerActions = MakeShareable(new FUICommandList());

	const auto& Commands = FTargetingVisualizerCommands::Get();
	TargetingComponentVisualizerActions->MapAction(Commands.Duplicate, FExecuteAction::CreateSP(this, &FTargetingComponentVisualizer::OnDuplicateTarget)); //绑定菜单项
}

//实时绘制target点 线
void FTargetingComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
	if (const UTargetComponent* TargetingComponent = Cast<const UTargetComponent>(Component))
	{
		CurrentTargetComponent = const_cast<UTargetComponent*>(TargetingComponent);
		const FLinearColor SelectedColor = TargetingComponent->EditorSelectedColor;
		const FLinearColor UnSelectedColor = TargetingComponent->UnSelectedColor;

		const FVector Location = TargetingComponent->GetComponentLocation();

		for (int i = 0; i < TargetingComponent->Targets.Num(); i++)
		{
			FLinearColor Color = (i == SelectedTargetIndex) ? SelectedColor : UnSelectedColor;

			PDI->SetHitProxy(new HTargetProxy(Component, i));
			PDI->DrawLine(Location, TargetingComponent->Targets[i], Color, SDPG_Foreground);
			PDI->DrawPoint(TargetingComponent->Targets[i], Color, 20.0f, SDPG_Foreground);
			PDI->SetHitProxy(NULL);
		}
	}
}

//处理选中target点
bool FTargetingComponentVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click)
{
	bool bEditing = false;
	if (VisProxy && VisProxy->Component.IsValid())
	{
		bEditing = true;
		if (VisProxy->IsA(HTargetProxy::StaticGetType()))
		{
			HTargetProxy* Proxy = (HTargetProxy*)VisProxy;
			SelectedTargetIndex = Proxy->TargetIndex;
		}
	}
	else
	{
		SelectedTargetIndex = INDEX_NONE;
	}
	
	return bEditing;
}

//实时获得选中的target位置
bool FTargetingComponentVisualizer::GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const
{
	if (IsValid(GetEditedTargetingComponent()) && SelectedTargetIndex != INDEX_NONE)
	{
		OutLocation = GetEditedTargetingComponent()->Targets[SelectedTargetIndex];
		return true;
	}
	return false;
}

//处理在editor中 旋转 缩放 等操作
bool FTargetingComponentVisualizer::HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltalRotate, FVector& DeltaScale)
{
	bool bHandled = false;
	if (IsValid(GetEditedTargetingComponent()) && SelectedTargetIndex != INDEX_NONE)
	{
		GetEditedTargetingComponent()->Targets[SelectedTargetIndex] += DeltaTranslate;
		bHandled = true;
	}
	return bHandled;
}

//处理在editor中快捷键操作(这里只添加了删除键的响应)
bool FTargetingComponentVisualizer::HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event)
{
	bool bHandled = false;
	if (Key == EKeys::Delete)
	{
		if (IsValid(GetEditedTargetingComponent()) && SelectedTargetIndex != INDEX_NONE)
		{
			GetEditedTargetingComponent()->DeleteTarget(SelectedTargetIndex);
			SelectedTargetIndex--;
			SelectedTargetIndex = FMath::Max<int32>(SelectedTargetIndex, 0);
			bHandled = true;
		}
	}
	
	return bHandled;
}

//右键创建菜单
TSharedPtr<SWidget> FTargetingComponentVisualizer::GenerateContextMenu() const
{
	FMenuBuilder MenuBuilder(true, TargetingComponentVisualizerActions);
	{
		MenuBuilder.BeginSection("Target Actions");
		{
			MenuBuilder.AddMenuEntry(FTargetingVisualizerCommands::Get().Duplicate);
		}
		MenuBuilder.EndSection();
	}

	TSharedPtr<SWidget> MenuBuilderWidget = MenuBuilder.MakeWidget();
	return MenuBuilderWidget;
}

UTargetComponent* FTargetingComponentVisualizer::GetEditedTargetingComponent() const
{
	
	return CurrentTargetComponent;
}

//菜单项复制功能回调
void FTargetingComponentVisualizer::OnDuplicateTarget()
{
	GetEditedTargetingComponent()->Targets.Add(GetEditedTargetingComponent()->Targets[SelectedTargetIndex]);
}

void FTargetingVisualizerCommands::RegisterCommands()
{
	UI_COMMAND(Duplicate, "Duplicate Target", "Duplicate the CurrentTarget", EUserInterfaceActionType::Button, FInputGesture());
}

#undef LOCTEXT_NAMESPACE 

 

在 Unreal Engine 4 (UE4) 中实现空调出风口风效的可视化,通常需要结合粒子系统(Particle System)和材质系统(Material System),以及可能的 Niagara 系统来完成。以下是一个详细的技术实现方案: ### ### 使用粒子系统模拟风效 UE4 的 Cascade 粒子编辑器可以用来创建简单的气流效果,例如从空调出风口吹出的空气流动。 - 创建一个粒子发射器,并将其设置为持续发射模式。 - 调整粒子的速度方向,使其沿着空调出风口的方向进行喷射。 - 可以使用透明的粒子贴图(如模糊的圆形纹理)来模拟气流的视觉表现[^1]。 - 启用“Additive”混合模式或“Translucent”材质混合模式,以便获得更自然的视觉效果。 ```cpp // 示例:在蓝图中动态控制粒子系统的播放与停止 // 假设你已经有一个名为 "AirflowParticles" 的粒子组件 AirflowParticles->Activate(); ``` ### ### 使用 Niagara 系统实现高级风效 Niagara 是 UE4 中新一代的粒子系统,提供了更强大的 GPU 加速能力和模块化设计。 - 创建一个新的 Niagara 系统,选择“Emitter Type: GPU Simulated”或“CPU Simulated”,根据性能需求决定。 - 在 Emitter 模块中设置粒子的生命周期、初始速度、方向等参数。 - 使用“Dynamic Parameter”模块绑定到蓝图变量,从而可以通过游戏逻辑控制风力强度或方向。 - 添加“Color Over Life”或“Size Over Life”模块来增强视觉动态变化。 ```cpp // 示例:通过蓝图控制 Niagara 风效强度 // 获取 Niagara 组件并调用 SetVariableFloat 方法 NiagaraComponent->SetVariableFloat(FName("WindStrength"), WindPower); ``` ### ### 材质系统与后期处理 为了增强风效的真实感,可以在材质层面做一些调整。 - 创建一个半透明材质,使用噪声纹理(Noise Texture)作为遮罩,模拟空气扰动效果。 - 应用“Refraction”折射属性,使风经过的区域产生轻微的光线扭曲。 - 结合后期处理体积(Post Process Volume),添加运动模糊(Motion Blur)或景深(Depth of Field)来强调风的流动感。 ```hlsl // 示例:材质蓝图中的 HLSL 表达式片段 // 使用 Time 节点驱动噪声函数,制造动态扰动效果 float2 Disturbance = Texture2DSample(NoiseTexture, UV).rg * Time * 0.5; FinalColor = Texture2DSample(MainTexture, UV + Disturbance); ``` ### ### 动态交互与物理影响(可选) 如果希望风效对场景中的物体产生物理影响,例如吹动物体或布料,可以使用以下方法: - 在风效附近添加“Radial Force Component”,设定合适的范围和力度。 - 将其附加到空调出风口的位置,并在风效激活时同步触发该力场。 - 对于布料对象,确保启用了 Chaos 物理系统或 APEX Cloth 支持。 ```cpp // 示例:在蓝图中触发风力作用 RadialForceComponent->FireImpulse(); ``` ###
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值