虚幻引擎_创建组件

章节介绍:

我们需要用一个组件控制场景中物体的移动;

步骤1: 创建组件类

创建角色组件类, 继承自:Actor Component类, 命名为Mover

步骤2: 使用组件类

使用组件有3种主要方法:
1. 直接选中场景中的物体后, 就可以点击Add搜索添加Mover组件


2. 在蓝图中也可添加:

C++ "看不见" 它, 如果你想在 C++ 里调用这个组件的功能,你不能直接调用。

3.在其他的C++类代码中调用, 在蓝图中只需挂载就行

cpp 复制代码
#include "Mover.h"

class AXXX : public AActor
{
    UPROPERTY(EditAnywhere)
    UMover* MoverComp;
};

我是不是还需要再定义一个TSubclassOf<UMover> MoverClass来存放类型, 不然无法构造组件?

不需要!

因为在 C++ 的构造函数中创建组件时,我们通常是在**编译时(Compile Time)**就已经确定了要用哪个 C++ 类, 这个东西是需要写死的

请看这行代码的奥妙:

cpp 复制代码
// 尖括号 <UMover> 就是在告诉编译器类型
MoverComp = CreateDefaultSubobject<UMover>(TEXT("Mover"));
  • <UMover> (模板参数):这直接告诉编译器:"我要造一个 UMover 类型的对象"。编译器看到这一行,就会去分配 UMover 类所需的内存大小

  • 不需要变量存类型:因为类型已经"写死"在尖括号里了


什么时候才需要"定义一个变量来存放类型"?

只有当你希望**"让策划在编辑器里选择要用哪个子类"**的时候,才需要定义类型变量(TSubclassOf)。

这种情况通常发生在:

  1. 生成 Actor (SpawnActor):比如发射子弹。子弹有普通的、火焰的、冰冻的,你不知道要生成哪种,所以留一个 TSubclassOf<ABullet> 让策划选。

  2. 创建 UI (CreateWidget):比如显示血条。C++ 不知道你的 UI 蓝图长什么样,所以留一个变量让策划选。

对比一下你就明白了

场景 A:创建组件(你的情况)
  • 特点:组件是身体的一部分,通常比较固定。

  • 做法:直接写死 C++ 类型。

cpp 复制代码
// .h
UPROPERTY(VisibleAnywhere)
UMover* MoverComp; // 只要存实例指针

// .cpp
MoverComp = CreateDefaultSubobject<UMover>(TEXT("Mover")); // 类型写在尖括号里
场景 B:动态生成物品(比如生成子弹)
  • 特点:不知道具体要生成哪种蓝图子类。

  • 做法:定义一个类型变量让蓝图选。

cpp 复制代码
// .h
UPROPERTY(EditAnywhere)
TSubclassOf<AActor> BulletClass; // 1. 这是一个变量,用来存"类型"

// .cpp
// 2. 使用这个变量里存的类型来生成
GetWorld()->SpawnActor<AActor>(BulletClass, ...);

步骤3: 写组件C++逻辑代码

cpp 复制代码
//Mover.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Mover.generated.h"

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class DUNGEONESCAPE_API UMover : public UActorComponent
{
	GENERATED_BODY()
 
public:	
	UMover(); 
protected:
	virtual void BeginPlay() override;
public:	
	virtual void TickComponent(float DeltaTime, ELevelTick TickType,
		FActorComponentTickFunction* ThisTickFunction) override;
	// 移动偏移量:控制Actor移动的方向和距离(如X=1000表示沿X轴移动1000单位)
	UPROPERTY(EditAnywhere)
	FVector MoveOffset;
    // 移动总时长:Actor从起点到终点的耗时(单位:秒)
    // 作用:保证移动速度均匀,不管偏移量多大,都在设定时间内完成
	UPROPERTY(EditAnywhere)
	float MoveTime = 4.0f;
    // 移动开关:控制Actor是否执行移动(true=移动,false=返回初始位置)
    // - EditAnywhere:编辑器可编辑,默认值false(开局不移动)  
	UPROPERTY(EditAnywhere)
	bool ShouldMove = false;
    // 到达目标标记:显示Actor是否已移动到目标位置(仅可读,不可编辑)
    // - VisibleAnywhere:在编辑器细节面板中可见(能看状态,但不能改)
	UPROPERTY(VisibleAnywhere)
	bool ReachedTarget = false;
    // 初始位置:Actor的起始位置(BeginPlay时记录,无需暴露给编辑器) 
	FVector TargetLocation;
	FVector StartLocation;
 
};
cpp 复制代码
//Mover.cpp

#include "Math/UnrealMathUtility.h"
#include "Mover.h"
UMover::UMover()
{
    PrimaryComponentTick.bCanEverTick = true;
}
// 组件开始播放函数:关卡启动时执行一次,初始化核心变量
void UMover::BeginPlay()
{
    Super::BeginPlay();
    // 记录Actor的初始位置(组件所属Actor的世界坐标)
    // GetOwner():获取当前组件附着的Actor(比如门、平台等需要移动的物体)
    StartLocation = GetOwner()->GetActorLocation();
    // 强制初始化移动开关为关闭状态(确保Actor开局不移动)
    ShouldMove = false;
    // 初始化目标位置为初始位置(Actor开局停在初始点)
    TargetLocation = StartLocation;
}
// 组件Tick函数:每帧执行,核心逻辑是根据ShouldMove状态更新Actor位置
// - DeltaTime:帧间隔时间(秒),用于帧率补偿(保证不同帧率下移动速度一致)
// - TickType:Tick类型(引擎内部使用,新手无需关注)
// - ThisTickFunction:Tick函数的内部参数(新手无需关注)
void UMover::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

    // 第一步:根据ShouldMove状态确定目标位置
    if (ShouldMove == true)
    {
        // 开启移动:目标位置 = 初始位置 + 偏移量(MoveOffset是预设的移动距离/方向)
        TargetLocation = StartLocation + MoveOffset;
    }
    else if (ShouldMove == false)
    {
        // 关闭移动:目标位置 = 初始位置(回到起点)
        TargetLocation = StartLocation;
    }
    // 第二步:获取Actor当前的世界位置
    FVector CurrentLocation = GetOwner()->GetActorLocation();
    // 第三步:判断Actor是否已到达目标位置(Equals默认误差2cm,避免浮点精度问题)
    // ReachedTarget=true → 已到目标,停止移动;false → 未到,继续插值移动
    ReachedTarget = CurrentLocation.Equals(TargetLocation);
    // 第四步:未到达目标位置时,执行平滑插值移动
    if (ReachedTarget == false)
    {
        // 计算移动速度:总偏移长度 / 移动总时间(保证移动耗时固定,不受帧率影响)
        // MoveOffset.Length():获取移动偏移的总距离(比如X轴移1000,长度就是1000)
        // MoveTime:预设的移动总时长(比如2秒,Actor会在2秒内从起点到终点)
        float Speed = MoveOffset.Length() / MoveTime;
        // 匀速插值计算新位置(VInterpConstantTo:固定速度插值,移动更平滑)
        // 参数:当前位置、目标位置、帧间隔时间、移动速度
        FVector NewLocation = FMath::VInterpConstantTo(CurrentLocation,
            TargetLocation, DeltaTime, Speed);
        // 更新Actor的世界位置(核心操作:让Actor移动)
        GetOwner()->SetActorLocation(NewLocation);
    }
}
相关推荐
WinstonJQ1 天前
AirSim无人机仿真入门(一):实现无人机的起飞与降落
python·机器人·游戏引擎·ue4·无人机
垂葛酒肝汤2 天前
Unity中的协程的原理
unity·游戏引擎
垂葛酒肝汤2 天前
Unity第一个项目
unity·游戏引擎
Sator12 天前
Unity的InputSystem常见问题和疑惑解答
java·unity·游戏引擎
郝学胜-神的一滴2 天前
QtOpenGL多线程渲染方案深度解析
c++·qt·unity·游戏引擎·godot·图形渲染·unreal engine
IMPYLH2 天前
Lua 的 Table 模块
开发语言·笔记·后端·junit·游戏引擎·lua
Howrun7773 天前
虚幻引擎_控制角色移动的三种方法
游戏引擎·虚幻
速冻鱼Kiel3 天前
GASP笔记01
笔记·ue5·游戏引擎·虚幻
孟无岐3 天前
【Laya】Animator2D 使用指南
typescript·游戏引擎·游戏程序·laya