虚幻引擎_创建组件

章节介绍:

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

步骤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);
    }
}
相关推荐
小小数媒成员5 小时前
如何优化代码适应托管内存?
游戏·unity·游戏引擎
狂人开飞机13 小时前
21、游戏架构设计模式
游戏·游戏引擎·godot
狂人开飞机2 天前
14、游戏手感与视觉打磨
游戏·游戏引擎·godot
狂人开飞机2 天前
15、音频系统
游戏引擎·godot
狂人开飞机3 天前
16、粒子线条与视觉特效
游戏引擎·godot
心前阳光3 天前
Unity发布PC软件图标不能改变
unity·游戏引擎
an86950013 天前
unity如何在visual studio中打断点debug
unity·游戏引擎·visual studio
xcLeigh3 天前
Unity基础:使用Transform控制物体移动——Translate与position详解
unity·游戏引擎
FairGuard手游加固3 天前
Unity小游戏加密:global-metadata.dat与AssetBundle保护
游戏·unity·游戏引擎
狂人开飞机3 天前
11、UI系统
游戏引擎·godot