概念:
虚幻引擎的标签系统(Tag System)是一种用于标识和分类Actor、Component以及其他对象的灵活机制。标签是简单的字符串,可以附加在对象上,然后通过标签来查询和识别对象。标签系统在游戏逻辑中非常有用,比如触发事件、分类对象、过滤查询等。
前置知识(选择性看):
关于FName的问题:
标签系统主要分为三种:
-
Actor标签(Actor Tags)
直接附加在Actor上的标签, 每个Actor都可以有多个标签.
AMyActor::AMyActor() { // 在构造函数中添加标签 Tags.Add("Enemy"); Tags.Add("Boss"); Tags.Add("FireDamage"); } // 或者在运行时添加 MyActor->Tags.Add("RecentlySpawned"); -
组件标签(Component Tags)
附加在组件上的标签, 每个组件可以拥有多个标签.
UCapsuleComponent* Capsule = CreateDefaultSubobject<UCapsuleComponent>("Collision"); Capsule->ComponentTags.Add("PlayerCollision"); Capsule->ComponentTags.Add("Trigger"); -
游戏标签系统(Gameplay Tags)
特别牛逼的更高级的标签系统,支持层级结构!
想要使用这个Gameplay Tags:1.必须要先打开此插件 2.在cpp文件中包含头文件 3.在 YourProject/Config/目录下的DefaultGameplayTags.ini文件中定义所有的要使用的标签 4.未定义的标签传入RequestGameplayTag()函数返回的FGameplayTag是无效的.
层级结构+如何启动插件:


完整的 DefaultGameplayTags.ini 文件示例:
[/Script/GameplayTags.GameplayTagsSettings] ConfigFileName=GameplayTagsList.ini ImportTagsFromConfig=True WarnOnInvalidTags=True FastReplication=True [GameplayTags] +GameplayTagList=(Tag="Character") +GameplayTagList=(Tag="Character.State") +GameplayTagList=(Tag="Character.State.Alive") +GameplayTagList=(Tag="Character.State.Dead") +GameplayTagList=(Tag="Character.State.Stunned") +GameplayTagList=(Tag="Character.Type") +GameplayTagList=(Tag="Character.Type.Player") +GameplayTagList=(Tag="Character.Type.Enemy") +GameplayTagList=(Tag="Character.Type.Enemy.Boss") +GameplayTagList=(Tag="Character.Type.Enemy.Minion") +GameplayTagList=(Tag="Character.Type.NPC") ; 可以继续添加更多标签 +GameplayTagList=(Tag="Ability") +GameplayTagList=(Tag="Ability.Attack") +GameplayTagList=(Tag="Ability.Attack.Melee") +GameplayTagList=(Tag="Ability.Attack.Ranged")相关的接口如何使用:
// 需要启用GameplayTags插件和包含头文件 #include "GameplayTagContainer.h" // 添加Gameplay Tag FGameplayTag Tag = FGameplayTag::RequestGameplayTag("Character.State.Dead"); MyGameplayTagContainer.AddTag(Tag); // 检查Gameplay Tag if (MyGameplayTagContainer.HasTag(Tag)) { // 拥有该标签 } // 检查父标签:例如,检查是否有任何以"Character.State"开头的标签 FGameplayTag ParentTag = FGameplayTag::RequestGameplayTag("Character.State"); if (MyGameplayTagContainer.HasTag(ParentTag)) { // 拥有父标签或其下的任何子标签 }网络同步功能:
(目前我正在研究中)
-
三种标签的对比总结:
特性 角色标签 组件标签 游戏标签系统Gameplay Tags 存储位置 Actor的Tags数组 Component的ComponentTags数组 通常存储在FGameplayTagContainer中,可以附加到任何支持的对象上(如Actor、Component) 层次结构 无,平坦的字符串列表 无,平坦的字符串列表 有,树状结构 网络同步 需要手动同步 需要手动同步 内置网络同步 性能 简单的字符串比较 简单的字符串比较 基于树的比较,效率高 灵活性 较低,只能精确匹配 较低,只能精确匹配 高,支持父标签查询 使用场景 标识Actor的整体属性 标识Component的属性 复杂的游戏状态、能力、效果管理
标签系统的主要特点:
-
标签是字符串,可以任意命名,但通常使用点分隔的命名方式(例如:
Player.Main)来组织层次结构。 -
一个对象可以拥有多个标签。
-
标签可以动态添加和移除。
-
可以通过标签来快速查找和过滤对象。
-
Actor 的 Tags:定义为 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Actor", AdvancedDisplay) TArray<FName> Tags;
-
Component 的 Tags:继承自 UActorComponent,同样是 TArray<FName> Tags 属性。
标签系统的常见操作(Actor和组件):
1.添加标签:
方法1: 在代码中,可以使用Add方法。
方法2: 在编辑器中,可以在Actor或组件的细节面板中的“Tags”部分添加标签。
Tags.Add("MyTag");
2.移除标签:
方法1: 在代码中,使用Tags.Remove()方法,建议先检测是否拥有再删除;
方法2: 直接把添加标签的代码删掉
方法3: 在编辑器中,可以在Actor或组件的细节面板中的“Tags”部分删除标签。
// 从Actor移除标签
void AMyActor::RemoveTagFromActor(FName TagName)
{
if (Tags.Contains(TagName))
{
Tags.Remove(TagName);
UE_LOG(LogTemp, Warning, TEXT("Removed tag '%s' from actor '%s'"),
*TagName.ToString(), *GetName());
}
}
3.检查标签:
方法1(组件和Actor通用): 使用Tags.Contain()方法检查对象是否拥有某个标签。
方法2(Actor专用): 使用ActorHasTag()函数检查Actor是否拥有某个标签。
方法3(组件专用): 使用ComponentHasTag()函数检查组件是否拥有某个标签。
/*ActorHasTag() 是 UActor类提供的成员函数,
其底层实现直接调用了 Tags.Contains()*/
bool AActor::ActorHasTag(FName TagName) const
{
return Tags.Contains(TagName);// 仅一行代码,直接"转发调用"
}
// 也支持FString参数的重载(自动转换为FName)
bool AActor::ActorHasTag(const FString& TagName) const
{
return Tags.Contains(FName(TagName));
}
// ComponentHasTag() 的源码本质就是直接调用 Tags.Contains()
bool UActorComponent::ComponentHasTag(FName TagName) const
{
return Tags.Contains(TagName);
}
//方法1:
if (Actor1->Tags.Contains("ActorTag1"))
{// 执行相关逻辑}
if (Component1->Tags.Contains("ComponentTag1"))
{// 执行相关逻辑}
//方法2:
if (Actor2->ActorHasTag("ActorTag2"))
{// 执行相关逻辑}
//方法3:
if (Component2->ComponentHasTag("ActorTag2"))
{// 执行相关逻辑}
/*题外话:这种简单的 “转发函数” 会被编译器自动优化为内联函数(Inline Function),
编译器会直接将 ComponentHasTag() 的代码替换为 Tags.Contains() 的逻辑,
消除函数调用的开销(比如栈帧创建 / 销毁)。最终生成的机器码完全一致。*/
4.查找带有特定标签的Actor:
使用UGameplayStatics::GetAllActorsWithTag函数可以获取世界中所有拥有指定标签的Actor。
注意: FoundActors 是一个虚幻引擎的动态数组(TArray),专门用于存储指向拥有指定标签的 Actor 实例的指针,是代码中承接 UGameplayStatics::GetAllActorsWithTag 函数查询结果的 “容器”,承接过程是自动的,FoundActors不需要程序员显式接受GetAllActorsWithTag的返回值,而且这函数返回的是void.
TArray<AActor*> ActorsWithTag;
UGameplayStatics::GetAllActorsWithTag(GetWorld(), "MyTag", ActorsWithTag);
for (AActor* Enemy : FoundActors)
{
// 处理所有敌人
}
/*此函数的签名:
static void GetAllActorsWithTag(UWorld* World, FName Tag,
TArray<AActor*>& OutActors);
*/
5.在蓝图中使用标签:
蓝图中有专门的节点用于处理标签,例如“Actor Has Tag”、“Get All Actors With Tag”等。
(本人目前主用C++,等我学会这个再来填坑)
常见的应用场景:
1. 对象分类和过滤
// 区分不同类型的敌人
Enemy->Tags.Add("Enemy.Type.Melee");
// 或者
Enemy->Tags.Add("Enemy.Type.Ranged");
// 战斗中过滤目标
if (Target->ActorHasTag("Enemy.Type.Melee"))
{
// 使用远程攻击对付近战敌人
UseRangedAttack();
}
2. 交互系统
// 函数定义:检查玩家周围的可交互物体
void APlayerCharacter::CheckInteractable()
{
// 创建一个空的Actor数组,用于存储找到的可交互物体
TArray<AActor*> InteractableActors;
// 使用UGameplayStatics的全局函数查找世界中所有带有"Interactable"标签的Actor
// 参数1: GetWorld() - 获取当前游戏世界上下文
// 参数2: "Interactable" - 要查找的标签名称
// 参数3: InteractableActors - 输出参数,存储找到的所有符合条件的Actor
UGameplayStatics::GetAllActorsWithTag(GetWorld(), "Interactable", InteractableActors);
// 开始遍历找到的所有可交互Actor
// InteractableActors是一个数组,这里使用范围for循环逐个处理
for (AActor* Interactable : InteractableActors)
{
// 对每个可交互Actor,检查玩家是否在其交互范围内
// IsInRange是一个自定义函数,应该返回bool值
// 参数: Interactable - 当前正在检查的可交互Actor
if (IsInRange(Interactable))
{
// 如果玩家在交互范围内,显示交互提示
// ShowInteractionPrompt是另一个自定义函数
// 参数: Interactable - 需要显示提示的可交互Actor
ShowInteractionPrompt(Interactable);
}
}
}
3. 伤害系统
// 函数:对目标应用伤害,考虑目标的伤害免疫和脆弱性
// 参数:Target - 要受到伤害的目标Actor
void AWeapon::ApplyDamage(AActor* Target)
{
// 检查目标是否对当前伤害类型免疫,并作出反馈
// 指针是否有效&&如果目标有"Immune.Fire"标签 && 伤害类型是火焰,则免疫伤害
if (Target && Target->ActorHasTag("Immune.Fire") && DamageType == EDamageType::Fire)
{
// 目标免疫这种伤害类型,直接返回,不应用任何伤害
return; // 目标免疫火焰伤害
}
// 检查目标是否对当前伤害类型脆弱(受到额外伤害)
// 如果目标有"Vulnerable.Lightning"标签且伤害类型是闪电
if (Target->ActorHasTag("Vulnerable.Lightning") && DamageType == EDamageType::Lightning)
{
// 目标对闪电伤害脆弱,将基础伤害值翻倍
Damage *= 2.0f; // 对闪电伤害脆弱,伤害加倍
}
// 调用实际应用伤害的函数,传递目标Actor和计算后的伤害值
ApplyDamageToTarget(Target, Damage);
}
4. AI行为
// 函数:敌人AI选择攻击目标
// 功能:从所有玩家中选择一个优先攻击的目标
void AEnemyAI::ChooseTarget()
{
// 创建一个空的Actor数组,用于存储找到的所有玩家
TArray<AActor*> PlayerActors;
// 使用全局函数查找世界中所有带有"Player"标签的Actor
// 这假设所有玩家角色都被标记了"Player"标签
UGameplayStatics::GetAllActorsWithTag(GetWorld(), "Player", PlayerActors);
// 遍历找到的所有玩家Actor,寻找优先攻击目标
// 使用范围for循环逐个检查每个玩家
for (AActor* Player : PlayerActors)
{
// 检查当前玩家是否带有"LowHealth"标签(低血量状态)
// 这是AI的优先级逻辑:优先攻击血量低的玩家
if (Player->ActorHasTag("LowHealth"))
{
// 找到低血量玩家,立即将其设置为当前攻击目标
SetCurrentTarget(Player);
// 找到目标后立即返回,不再检查其他玩家
// 这实现了"优先攻击低血量玩家"的AI行为
return;
}
}
// 注意:如果循环结束都没有找到低血量玩家,函数会自然结束
// 这意味着没有设置任何目标,这可能是个问题
}
项目例子:
在虚幻引擎的角色蓝图的细节面板的Actor组件里面就可修改标签,如果我需要创建一个标签用来管理所有的能够触发压力板的物体.可以加入一个PressurePlateActivator标签.
在蓝图面板修改后记得点左上角的编译.


这样就成功为一个角色蓝图添加上了一个标签:PressurePlateActivator
那么现在如何在程序中访问这个标签,检测这个标签呢?
有一个函数专门用于检测角色是否拥有某个标签,返回值是bool类型的:
bool ActorHasTag("PressurePlateActivator");
/*第一个绑定函数:此函数通过修改门的Mover组件的ShouldMove变量, 控制门的移动,
函数名可自定义,参数必须严格符合OnComponentBeginOverlap委托的定义*/
void UTriggerComponent::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor && OtherActor->ActorHasTag("PressurePlateActivator")) {
if (Mover) {
Mover->ShouldMove = true;
}
}
}
//第二个绑定函数:此函数同理,函数名可自定义,参数必须严格符合OnComponentEndOverlap委托的定义
void UTriggerComponent::OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OherBodyIndex)
{
if (OtherActor && OtherActor->ActorHasTag("PressurePlateActivator")) {
if (Mover) {
Mover->ShouldMove = false;
}
}
}
这样一个压力板就只能由角色触发了,如果还想要别的角色也能触发这个压力板(比如一个椅子),只需在椅子的细节面板给椅子加上一个Actor标签"PressurePlateActivator",注意不是组件标签,别搞错了.

然后还要开启椅子的生成重叠事件,不然椅子无法触发OnComponentBeginOverlap委托和OnComponentEndOverlap委托,也就无法传达椅子和压力板的碰撞行为.

2291

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



