目录
一、简单使用
1.1 基础设置
【编辑】-【编辑器偏好设置】-【通用】-【源代码】。

【编辑】-【编辑器偏好设置】-【编译】。

1.2 创建 C++ 项目
1.2.1 蓝图项目转为 C++ 项目
现在我们有一个纯蓝图项目,我们怎么将其转为 C++ 项目呢?
蓝图项目目录如下:

- Config:存放项目的配置文件。(重要)
- Content:游戏资源文件夹,包括蓝图、材质、动画等。(重要)
- DerivedDataCache:缓存文件。
- Intermediate:存放中间构建文件。
- Saved:存放自动保存文件、日志等。
- .uproject:项目描述文件(JSON格式)。(重要)
我们接下来做如下设置:
【工具】-【新建 C++ 类】。

【类的类型】-【公有】-【创建类】。

这样我们的蓝图项目就转为 C++ 项目啦。
C++ 项目目录如下:

- .idea:Rider 工程配置文件。
- .vs: Visual Studio 临时项目配置和缓存。
- Config:存放项目的配置文件。(重要)
- Content:游戏资源文件夹,包括蓝图、材质、动画等。(重要)
- DerivedDataCache:缓存文件。
- Intermediate:存放中间构建文件。
- Saved:存放自动保存文件、日志等。
- Source:存放 C++ 代码。(重要)
- .vsconfig:Visual Studio 组件配置说明。
- .sln:解决方案文件。
- .uproject:项目描述文件(JSON格式)。(重要)
1.2.2 直接创建 C++ 项目

1.3 打开 C++ 项目

对于纯蓝图项目,我们通常双击 UECPP.uproject 进入虚幻引擎编辑器。但是对于 C++ 项目,我们通常双击 UECPP.sln 进行 C++ 编程与调试。
如果 UECPP.sln 丢失,可以右键 UECPP.uproject → Generate Visual Studio project files 进行重建。https://www.bilibili.com/opus/882311364936204310
1.4 启动虚幻引擎
在 Rider 中,进行如下操作:
-
Ctrl + F5:运行(不调试)项目,启动虚幻编辑器。
-
F5:调试模式启动虚幻编辑器。
1.5 编译代码
当我们禁用实时代码编写时,如果我们修改代码,需要关闭运行项目(不是关闭虚幻引擎编辑器)才能编译成功。下面介绍两种方法:
单击小锤子进行编译。

在虚幻引擎中进行编译。

当我们启用实时代码编写时(热重载的升级版),如果我们修改代码,不需要关闭运行项目也可以编译成功。(不推荐!)
但经测试只能在虚幻引擎中进行编译。

二、快捷键
Ctrl + T :全局搜索。
Alt + \ :当前文件搜索。
F12:跳转到定义位置。
Ctrl + 鼠标左键:跳转到定义位置。
Ctrl + K + C :注释。
三、命名规则
3.1 代码命名规则
- 模版类以 T 作为前缀,比如 TArray,TMap,TSet。
- UObject 派生类都以 U 前缀。
- AActor 派生类都以 A 前缀。
- SWidget派生类都以 S 前缀。
- 全局对象使用 G 开头,如 GEngine。
- 抽象接口以 I 前缀。
- 枚举以 E 开头。
- bool 变量以 b 前缀,如 bPendingDestruction。
- 其他的大部分以 F 开头,如 FString, FName。
- typedef 的以原型名前缀为准,如 typedef TArray FArrayOfMyTypes;
- UHT 在工作的时候需要你提供正确的前缀,所以虽然说是约定,但你也得必须遵守。
- UE遵循帕斯卡(大驼峰)命名法则。
3.2 其余命名规则
略
四、再谈 Actor
4.1 创建 Actor
在 Rider 中创建。

在虚幻引擎中创建。


4.2 在世界场景中生成 Actor
静态生成:直接拖拽进世界场景中。优点是无需编码,更加直观简单;缺点是影响游戏启动速度,增加场景构建负担。
动态生成:通过编码动态生成。相对于前者复杂度上升,但是可控性更强,动态生成的 Actor 会持有有效的操作指针,我们可以根据实际情况进行生成,更加灵活。
下面举一个简单的例子,Actor 的动态生成流程如下:
- 构造函数调用
- 初始化成员变量
- 如有蓝图,则初始化蓝图数据
- 构建组件
- BeginPlay(标志着 Actor 被创建到世界当中)
- Tick
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "MyGameModeBase.generated.h"
/**
*
*/
UCLASS()
class UECPP_API AMyGameModeBase : public AGameModeBase
{
GENERATED_BODY()
protected:
// UFUNCTION(Exec)表示该函数可以通过命令行或控制台(Console)执行
UFUNCTION(Exec)
void func();
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyGameModeBase.h"
#include "MyActor.h"
void AMyGameModeBase::func()
{
// Actor 的生成与销毁 (不能简单的使用 new 和 delete)
// 生成 Actor
AMyActor* MyActor = GetWorld()->SpawnActor<AMyActor>(); // // GetWorld() 全局函数 SpawnActor<T>() 返回 T 类型的指针
if (MyActor) // 判断指针是否有效
{
}
// MyActor->AddActorWorldOffset(); 调用成员函数
// 立刻删除
// MyActor->Destroy(); // 销毁对象(会通知世界和与这个 Actor 相关的内容一并进行销毁)
// 滞后删除
MyActor->SetLifeSpan(5.f); // 实际内部也会调用 Destroy()
MyActor = nullptr;
}
4.3 消亡 Actor 通知
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS()
class UECPP_API AMyActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMyActor();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// 当 Actor 被标记为销毁(调用 Destory 或 SetLifeSpan)(非内存删除)时触发。
// 注意:Actor 仍存在于内存中,直到 EndPlay 被调用
virtual void Destroyed() override;
// 当 Actor 彻底从内存中删除时触发
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
五、屏幕日志输出
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
// 声明自定义日志类别(默认情况下,Unreal Engine 提供了一个全局日志类别 LogTemp)
// 参数1:自定义日志类别名称 参数2:日志默认级别,一般使用 Log 参数3:日志编译级别,一般用 All
DECLARE_LOG_CATEGORY_EXTERN(LOGME, Log, All);
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "MyGameModeBase.generated.h"
/**
*
*/
UCLASS()
class UECPP_API AMyGameModeBase : public AGameModeBase
{
GENERATED_BODY()
protected:
// UFUNCTION(Exec)表示该函数可以通过命令行或控制台(Console)执行
UFUNCTION(Exec)
void func();
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyGameModeBase.h"
#include "MyActor.h"
// 定义自定义日志类别(必须在 CPP 文件中进行)
DEFINE_LOG_CATEGORY(LOGME);
void AMyGameModeBase::func()
{
// 打印日志
// 屏幕日志输出
// 参数1:-1 代表每次添加新消息,不会覆盖旧消息 参数2:显示时长 参数3:文本颜色 参数4:显示的信息
GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Green, TEXT("Hello"));
// 控制台日志输出(日志会写入本地缓存) UE_LOG(LogCategory, Verbosity, Format, ...);
// 参数1:日志类别 参数2:日志级别 参数3:日志内容
// UE_LOG(LogTemp, Log, TEXT("Hello")); // 常规日志级别
// UE_LOG(LogTemp, Warning, TEXT("Hello")); // 警告日志级别
// UE_LOG(LogTemp, Error, TEXT("Hello")); // 错误日志级别
// UE_LOG(LogTemp, Verbose, TEXT("Hello")); // 详细日志级别,用于非常详细的调试信息。
// UE_LOG(LogTemp, VeryVerbose, TEXT("Hello")); // 非常详细日志级别,用于极端详细的调试信息。
// UE_LOG(LogTemp, Fatal, TEXT("Hello")); // 致命日志级别(引擎会崩溃)
int32 Number = 10;
UE_LOG(LogTemp, Log, TEXT("%d"), Number); // %s:字符串(const TCHAR*) %d:整数 %f:浮点数 %d:布尔 %p:指针
AMyActor* MyActor = GetWorld()->SpawnActor<AMyActor>();
UE_LOG(LogTemp, Log, TEXT("%p"), MyActor);
UE_LOG(LOGME, Warning, TEXT("OK"));
}
六、UE 数据类型
6.1 基本数据类型
为了方便跨平台,UE 对于 C++ 基本数据类型进行深度重定义,方便平台扩展特性,增加 UE 的移植便捷性。
布尔类型:bool
字符类型:TCHAR
整型:uint8(1字节)、uint16(2字节)、uint32(4字节)、uint64(8字节)、int8(1字节)、int16(2字节)、int32(4字节)、int64(8字节)
浮点型:float(4字节)、double(8字节)
6.2 字符串类型
- 宏 TEXT:将字符串字面量转换为 const TCHAR* 类型。
- FString:class FString。内部重载解引用操作符 *,返回 const TCHAR* 类型。
- FName:class FName
- FText:class FText
FName:资源命名字符串。
- 存储不可变的字符串,必须用 TEXT 宏包裹字符串。
- 相对轻量级的字符串类型,散列存储。
- 相同字符串在内存中只存储一份,不区分大小写且只读。
FText:用于 UI 上的文本显示。
- 存储不可变的字符串。
- 处理用户的显式文本,提供对字符串的格式化、本地化等操作。
- 提供了对文本的多语言支持,可以根据不同的语言提供不同的显示内容。
FString:处理字符串的主要类型。
- 用于存储可变的字符串,必须用 TEXT 宏包裹字符串。
- 开销大于其他类字符串类型。
基本操作如下:
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyGameModeBase.h"
#include <ThirdParty/ShaderConductor/ShaderConductor/External/DirectXShaderCompiler/include/dxc/DXIL/DxilConstants.h>
#include "MyActor.h"
// FText 构建方式二
#define LOCTEXT_NAMESPACE "MyGameModeBase"
void AMyGameModeBase::funcName()
{
// --------------- FName ---------------
FName Name1 = TEXT("Hello");
FName Name2(TEXT("World"));
// 比较操作(最核心、最高频)
if (Name1 != Name2)
{
UE_LOG(LogTemp, Log, TEXT("%s"), *Name1.ToString());
}
// --------------- FText ---------------
// 构建方式一(推荐):宏 NSLOCTEXT
// 参数1:命名空间(我们一般把同一个页面的文本放在一个命名空间) 参数2:Key(同命名空间内唯一) 参数3:默认显示的文本
FText t1 = NSLOCTEXT("MyGameModeBase", "Key1", "hello");
// 构建方式二:宏 LOCTEXT
// t2 和 t3 在同一个命名空间
FText t2 = LOCTEXT("Key2", "二狗");
FText t3 = LOCTEXT("Key3", "张三");
// 格式化文本
FNumberFormattingOptions Options;
Options.UseGrouping = false; // 取消数字分组
FText t5 = FText::Format(LOCTEXT("Key4","我叫{0}, 今年{1}岁。"), t2, FText::AsNumber(20000, &Options)); // FText::AsNumber() 将数字转换为 FText
UE_LOG(LogTemp, Log, TEXT("%s"), *t5.ToString());
// --------------- FString ---------------
FString str1 = TEXT("Hello");
FString str2(TEXT("World"));
UE_LOG(LogTemp, Log, TEXT("%s"), *str1);
// 1 .Len() 获取字符个数
UE_LOG(LogTemp, Log, TEXT("%d"), str1.Len()); // 5
// 2 .Contains() 查找是否包含指定字符串(返回 bool) 默认忽略大小写
UE_LOG(LogTemp, Log, TEXT("%d"), str1.Contains(TEXT("He"))); // 1
// 3 .find() 查找指定字符串的位置(如果没有找到,返回-1) 默认忽略大小写
UE_LOG(LogTemp, Log, TEXT("%d"), str1.Find(TEXT("llo"))); // 2
// 4 .Split() 分割字符串
FString str3 = TEXT("你好_小明");
FString str4, str5;
if (str3.Split(TEXT("_"), &str4, &str5))
{
UE_LOG(LogTemp, Log, TEXT("%s %s"), *str4, *str5);
}
// 5 + 或者 .Append() 组合字符串
UE_LOG(LogTemp, Log, TEXT("%s"), *(str1 + str2));
UE_LOG(LogTemp, Log, TEXT("%s"), *(str1.Append(str2))); // str1 变化
// 6 == 或者 .Equals() 比较两个字符串是否相同
FString str6 = TEXT("test");
FString str7 = TEXT("test");
if (str6 == str7)
{
UE_LOG(LogTemp, Log, TEXT("same"));
}
if (str6.Equals(str7))
{
UE_LOG(LogTemp, Log, TEXT("same"));
}
// 7 .Compare() 比较两个字符串(返回 int32)
FString str8 = TEXT("123");
FString str9 = TEXT("125");
UE_LOG(LogTemp, Log, TEXT("%d"), str8.Compare(str9)); // -1
UE_LOG(LogTemp, Log, TEXT("%d"), str8.Compare(str8)); // 0
UE_LOG(LogTemp, Log, TEXT("%d"), str9.Compare(str8)); // 1
// 8 .EndsWith() .StartsWith() 检索头尾是否是指定的字符串
FString str10 = TEXT("test.txt");
if (str10.EndsWith(".txt"))
{
UE_LOG(LogTemp, Log, TEXT("结尾是.txt"));
}
// 9 .Empty() 清空字符串并释放内存 .Reset() 清空字符串但不释放内存
// str10.Empty();
// str10.Reset();
// 10 .ToUpper() .ToLower() 大小写转换
UE_LOG(LogTemp, Log, TEXT("%s"), *(str10.ToUpper()));
UE_LOG(LogTemp, Log, TEXT("%s"), *(str10.ToLower()));
// 11 .Replace() 替换
UE_LOG(LogTemp, Log, TEXT("%s"), *(str10.Replace(TEXT(".txt"), TEXT(".html"))));
}
// FText 构建方式二
#undef LOCTEXT_NAMESPACE
三者之间转换如下:
我们怎么记忆呢?转换成某种类型,这种类型有专门的规律。
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyGameModeBase.h"
#include <ThirdParty/ShaderConductor/ShaderConductor/External/DirectXShaderCompiler/include/dxc/DXIL/DxilConstants.h>
#include "MyActor.h"
void AMyGameModeBase::funcName()
{
FName n1 = TEXT("我是FName");
FText t1 = NSLOCTEXT("MyGameModeBase", "keyt1", "我是FText");
FString s1 = TEXT("我是FString");
// FName 转换成 FString
FString s2 = n1.ToString();
// FName 转换成 FText
FText t2 = FText::FromName(n1);
// FText 转换成 FString
FString s3 = t1.ToString();
// FText 转换成 FName
FName n2 = FName(*t1.ToString());
// FString 转换成 FName
FName n3 = FName(*s1);
// FString 转换成 FText
FText t3 = FText::FromString(s1);
}
6.3 容器类型
TArray<T>:内存连续的动态数组
- 是一个同质容器,即其所有元素均完全为相同类型;
- 具有速度快、内存消耗小、安全性高等特点;
- 与 std::vector 类似,可动态扩充内存。
TSet<T>:存储唯一值的集合
- 是一个同质容器,即其所有元素均完全为相同类型;
- 默认不允许元素重复,存储唯一元素;
- 基于哈希表实现,提供了快速的查找、插入和删除操作;
TMap<k, T>:key-value 类型容器
- 是一个同质容器,即其所有元素均完全为相同类型;
- 键必须是唯一的,而值不是唯一的;(键也可以为自定义类型,但需要经过处理)
- 基于哈希表实现,提供了高效的查找性能;
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyGameModeBase.h"
#include <ThirdParty/ShaderConductor/ShaderConductor/External/DirectXShaderCompiler/include/dxc/DXIL/DxilConstants.h>
#include "MyActor.h"
void AMyGameModeBase::funcName()
{
// ----------------------------- TArray -----------------------------
// 1. 构建
TArray<int32> array;
// 2. 初始化
array.Init(10, 5);
// 3. 返回元素个数
UE_LOG(LogTemp, Log, TEXT("%d"), array.Num()); // 5
// 4. 遍历
for (int32 i = 0; i < array.Num(); ++i)
{
UE_LOG(LogTemp, Log, TEXT("%d"), array[i]); // 10 10 10 10 10
}
for (const auto& v : array) // 本质是迭代器遍历
{
UE_LOG(LogTemp, Log, TEXT("%d"), v); // 10 10 10 10 10
}
// 5. 添加元素
array.Add(20); // 10 10 10 10 10 20
array.AddUnique(20); // 若存在重复元素,则添加失败
array.Insert(999, 0); // 999 10 10 10 10 10 20
// 6. 设置容器大小,类似于 vector 的 resize()
array.SetNum(10); // 999 10 10 10 10 10 20 0 0 0
array.SetNum(7); // 999 10 10 10 10 10 20
// 7. 转换为普通数组(返回容器模板类型的指针)
int32* ptr = array.GetData();
// 8. 常规查询函数 略
// 9. 常规移除函数 略
// ----------------------------- TSet -----------------------------
// 1. 构建
TSet<int32> set;
// 2. 添加元素
set.Add(1);
set.Add(2);
// 3. 删除元素
set.Remove(2);
// 4. 返回元素个数
UE_LOG(LogTemp, Log, TEXT("%d"), set.Num());
// 5. 查询是否包含某个元素
if (set.Contains(1))
{
UE_LOG(LogTemp, Log, TEXT("包含"));
}
// 6. 遍历
for (const auto& v : set)
{
UE_LOG(LogTemp, Log, TEXT("%d"), v);
}
// 7. 清空
// set.Empty(); // 清空映射并释放内存
// set.Reset(); // 清空映射但不释放内存
// ----------------------------- TMap -----------------------------
// 1. 构建
TMap<int32, FString> map;
// 2. 添加元素
map.Add(10, TEXT("小猫"));
map.Add(20, TEXT("小狗"));
map.Add(30, TEXT("小虎"));
// 3. 删除元素
map.Remove(20);
// 4. 更改元素
map[10] = TEXT("小蛇");
// 5. 返回元素个数
UE_LOG(LogTemp, Log, TEXT("%d"), map.Num()); // 2
// 6. 查询是否包含某个元素
if (map.Contains(10))
{
UE_LOG(LogTemp, Log, TEXT("包含"));
}
// 7. 遍历
for (const auto& kv : map)
{
UE_LOG(LogTemp, Log, TEXT("%d: %s"), kv.Key, *(kv.Value));
}
// 8. 组合
TMap<int32, FString> TempMap;
TempMap.Add(999, TEXT("test"));
map.Append(TempMap);
// 9. 清空
// map.Empty(); // 清空映射并释放内存
// map.Reset(); // 清空映射但不释放内存
}
6.4 类类型
Object:UE5 中的根基类。包括但不限于以下几点功能:
- GC(垃圾回收机制):当你创建一个继承自 UObject 的对象(需标记为 UPROPERTY)时,引擎会自动追踪它的引用关系。只要这个对象还被其他"存活"的对象引用着,它就不会被销毁;一旦所有引用都消失了,GC 就会在适当的时候自动回收它。见下方案例。
- 序列化:序列化就是将内存中对象的变量(需标记为 UPROPERTY)转换为可存储的数据格式(通常是二进制),反序列化则是反向过程。例如保存 / 加载关卡等。
- 反射:允许程序在运行时查询、访问和操作类、变量和函数的信息。
6.5 结构体类型
#include "CoreMinimal.h" // 需要包含此头文件(自动包含)
// 允许作为蓝图中的变量类型使用,需要添加 BlueprintType
USTRUCT(BlueprintType)
struct FMyStruct // 必须 F 开头
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, EditAnywhere)
int32 Num;
};
6.6 枚举类型
#include "CoreMinimal.h" // 需要包含此头文件(自动包含)
// 允许作为蓝图中的变量类型使用,需要添加 BlueprintType
UENUM(BlueprintType)
enum class EEnum : uint8 // 必须 E 开头
{
EONE,
Menu,
Close
};
七、反射
7.1 反射概念
Unreal Engine 的反射系统允许程序在运行时查询、访问和操作类、变量和函数的信息。然而,标准的 C++ 本身并不支持这种级别的反射。
其中,宏标记的作用就是为 HUT 提供指令。UHT 在编译前扫描你的代码,根据这些标记生成额外的反射代码(*.generated.h 文件),从而将你的 C++ 代码“暴露”给 UE 的各个系统,如蓝图编辑器、序列化、网络复制和垃圾回收。
7.2 宏
注意:UFUNCTION()、UPROPERTY()、UCLASS() 等宏中的这些标记,不会影响 C++ 代码的编译或运行逻辑,只会影响蓝图系统、反射系统和编辑器的行为。
7.2.1 UCLASS() - 类标记
作用:将一个 C++ 类声明为 UE 可识别的、可参与引擎对象管理的 UObject。
常用选项如下(会被继承):
- Blueprintable:允许在蓝图中基于此类创建蓝图子类(默认行为)。例如 AActor 通常带有这个标记。
- NotBlueprintable:禁止蓝图继承此类。适用于仅供 C++ 使用的类。
- BlueprintType:允许此类作为蓝图中的变量类型或参数类型使用。
- NotBlueprintType:禁止该类在蓝图中被引用(如变量类型)。
- Abstract:声明该类为抽象类(无法直接实例化,只能被继承)
示例代码如下:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h" // 必须放在最后
UCLASS(Blueprintable)
class MYGAME_API AMyActor : public AActor
{
GENERATED_BODY() // // 必须包含在类体内
public:
AMyActor();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
};
补充:
U 类对象实例化:通过工厂方法 NewObject<class>()。
U 类对象销毁:xx = nullptr。
A 类对象实例化:GetWorld()->SpawnActor<class>()。
A 类对象销毁:xx.Destory()。
7.2.2 UFUNCTION() - 函数标记
作用:暴露函数给蓝图系统。(不建议将私有域中的函数进行暴露)
常用选项如下(会被继承):
- BlueprintCallable:函数可在蓝图中调用(有执行引脚)。
- BlueprintPure:函数可在蓝图中调用(函数必须要有返回值)(没有执行引脚)。
- BlueprintImplementableEvent:在 C++ 中声明,但在蓝图中定义的事件/函数(可以在函数重载中找到。和蓝图中直接创建的事件不同,该事件/函数无法在蓝图中调用,除非加 BlueprintCallable 标记)。(类似纯虚函数)
- BlueprintNativeEvent:在 C++ 中声明并定义(定义时会生成一个带有后缀的函数,但是调用时需要调用没有后缀的函数),也可在蓝图中重写。(可以在函数重载中找到。和蓝图中直接创建的事件不同,该事件/函数无法在蓝图中调用,除非加 BlueprintCallable 标记)(类似虚函数)
- Category="xx|xx|xx...":设置蓝图节点的分类名。
- meta=(...):附加元信息(如参数提示、隐藏某参数、限制输入范围等)
UFUNCTION(BlueprintImplementableEvent, meta=(DisplayName = "Tick")) // 修改在蓝图中的节点名称
ENGINE_API void ReceiveTick(float DeltaSeconds);
7.2.3 UPROPERTY() - 变量标记
作用:暴露变量给属性系统,使其可被编辑器详情面板显示、被蓝图访问、参与网络复制、序列化等。
- BlueprintReadOnly:蓝图只读。 最常用、最安全。
- BlueprintReadWrite:蓝图可读也可写。
- EditAnywhere: 在类默认值和实例上可见且可编辑(我们很少在指针类型(A 类)变量上加此标记。此外,对于资产类型的指针我们可以加此标记,例如 UTexture* 等(硬引用))。
- EditDefaultsOnly:仅在类默认值中可编辑,实例上不可编辑。 用于定义类的固有属性(如最大血量、速度)。
- EditInstanceOnly:仅在实例上可编辑,类默认值中不可编辑。 用于每个实例都不同的属性(如 NPC 的名字)。
- VisibleAnywhere:在类默认值和实例上可见但不可编辑 (唯一用法:标记组件)。
1万+

被折叠的 条评论
为什么被折叠?



