- Published on
GameplayAbilitySystem入门与实战(八):GameplayCueNotify
- Authors

- Name
- 东哥
前言
GameplayCueNotify(GCN)用于非逻辑层的效果,如音效,特效,相机震动等
CGN有两个基类,如下图
GameplayCue Class | Event | GE类型 | Description |
|---|---|---|---|
GameplayCueNotify_Static | Execute | Instant or Periodic | 一般处理一次性触发的效果如打击效果 |
GameplayCueNotify_Actor | Add or Remove | Duration or Infinite | 此类GC是实例化的,它们可以随着时间进行操作,直到它们被“删除”。当支持的“Duration”或“Infinite”GE被移除或手动调用remove时,这些对于循环声音和粒子效果是很好的。它们还提供了一些选项来管理允许同时“添加”的数量,以便多个应用程序使用相同效果时只启动一次声音或粒子。 |
使用
GameplayCueNotify_Actor时注意Auto Destroy on Remove选项,否则后续调用add可能不能正常工作
Gameplay Cue Manager
GameplayCueManager会扫描指定路径的所有GameplayCueNotifies 然后加入到内存中,默认路径保存在AbilitySystemGlobals中
/** Look in these paths for GameplayCueNotifies. These are your "always loaded" set. */
UPROPERTY(config)
`TArray<FString>` GameplayCueNotifyPaths;
GameplayCueNotifyPaths.Add(TEXT("/Game"));
如果需要修改路径可以修改插件内容
这里需要注意,如果是比较大型的游戏, 游戏中会有数量非常庞大的GameplayCueNotifies ,如果游戏启动的时候就加载所有,那么必然造成很多的内存浪费,如果有这个问题, 可以继承GameplayCueManager,然后重写方法
virtual bool ShouldAsyncLoadObjectLibrariesAtStart() const { return false; }
当然我们必须在此类创建的时候需要如下修改GlobalGameplayCueManagerClass或者GlobalGameplayCueManagerName的值
UGameplayCueManager* UAbilitySystemGlobals::GetGameplayCueManager()
{
//*******************
// Load specific gameplaycue manager object if specified
if (GlobalGameplayCueManagerName.IsValid())
{
GlobalGameplayCueManager = `LoadObject<UGameplayCueManager>`(nullptr, *GlobalGameplayCueManagerName.ToString(), nullptr, LOAD_None, nullptr);
if (GlobalGameplayCueManager == nullptr)
{
ABILITY_LOG(Error, TEXT("Unable to Load GameplayCueManager %s"), *GlobalGameplayCueManagerName.ToString() );
}
}
// Load specific gameplaycue manager class if specified
if ( GlobalGameplayCueManager == nullptr && GlobalGameplayCueManagerClass.IsValid() )
{
UClass* GCMClass = `LoadClass<UObject>`(NULL, *GlobalGameplayCueManagerClass.ToString(), NULL, LOAD_None, NULL);
if (GCMClass)
{
GlobalGameplayCueManager = `NewObject<UGameplayCueManager>`(this, GCMClass, NAME_None);
}
}
//*****************
}
应用
CGN可以依附与GE ,在GE的Display选项中添加CGN;

新建static类型的CGN
在蓝图类中设置GC对应的Tag

编辑器模式下会自动扫描有引用关系的类,这个蛮好用的
如果GE是Instant模式,那么在GCN中会先后调用Handle和Excute事件一次
如果是有持续时间的,那么Handle会调用多次,因为Handle本身就有EventType类型以及其他因素, Excute会在初始调用一次, 如果有Period>0,那么每隔一段时间会执行Handle和Execute一次
这个
GC的各个函数调用有点容易混淆,这里做一次测试测试
GA持续3秒后结束,测试GE的Period设置1秒(非instant)执行顺序:
HE:HandleExcuted
HOA:HandleOnActive
HWA:HandleWhileActive
HR:HandleRemove
OA:OnActive
OE:OnExcute
OR:OnRemove
WA:WhileActive
测试逻辑大致见下图

GCN类型 | 激活方式 | GE持续性 | 执行顺序 | 结果 |
|---|---|---|---|---|
static | GE | Instant | HE->OE, | 没有调用OR |
static | GE | Duration=3 | HOA->OA->HOA->OA->HWA->WA->ActiveAbility继续执行->HE->OE..........(HE->OE)*2....->EndAbility->OE->HE->HR->OR->HR->OR | ActiveAbility执行完以后才调用第一次HE+OE, 最后EndAbility运行以后执行一次OE+HE和两次HR+OR |
static | GE | Infinite | HOA->OA->HOA->OA->HWA->WA->HE->OE..........(HE->OE)*n | 前两部分同上,没有结束, 手动remove以后同上一致 |
static | Add | 与GE无关 | HOA->OA->HWA->WA.....HOR->OR. | 随GA结束而结束,如果提前Remove也正常调用HOR和OR |
static | Excute | 与GE无关 | HE->OE | 没有调用OR,没法Remove |
Actor | GE | Instant | 与static模式一致 | |
Actor | GE | Duration=3 | 与static模式一致 | |
Actor | GE | Infinite | 与static模式一致 | |
Actor | Add/Execute | 与GE无关 | 与static模式一致 |
我们可以发现static和actor模式的GCN机制类似, 按照文档说明的, static用Execute以及适合瞬间触发的效果,Actor用Add和Remove的方式用到有持续时间的效果也有一定道理;
一般瞬间的GCN重写Excute方法即可
持续性的看情况重写OnActive和WhileActive以及Remove等
本地GCN
GCN默认都是RPC同步的,那么对于部分的本地效果肯定是没必要的, 我们可以直接调用GameplayCueManager->HandleGameplayCue()来执行本地GCN
void UFlib_VGAS::ExecuteGameplayCueLocal(UAbilitySystemComponent* AbilityComponent, const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters)
{
UAbilitySystemGlobals::Get().GetGameplayCueManager()->HandleGameplayCue(AbilityComponent->GetOwner(), GameplayCueTag, EGameplayCueEvent::Type::Executed, GameplayCueParameters);
}
void UFlib_VGAS::AddGameplayCueLocal(UAbilitySystemComponent* AbilityComponent, const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters)
{
UAbilitySystemGlobals::Get().GetGameplayCueManager()->HandleGameplayCue(AbilityComponent->GetOwner(), GameplayCueTag, EGameplayCueEvent::Type::OnActive, GameplayCueParameters);
UAbilitySystemGlobals::Get().GetGameplayCueManager()->HandleGameplayCue(AbilityComponent->GetOwner(), GameplayCueTag, EGameplayCueEvent::Type::WhileActive, GameplayCueParameters);
}
void UFlib_VGAS::RemoveGameplayCueLocal(UAbilitySystemComponent* AbilityComponent, const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters)
{
UAbilitySystemGlobals::Get().GetGameplayCueManager()->HandleGameplayCue(AbilityComponent->GetOwner(), GameplayCueTag, EGameplayCueEvent::Type::Removed, GameplayCueParameters);
}
测试
新建一个静态瞬间的GCN,另外我们在之前的测试GE中加入这个GCN的标签


- 单人测试

- 双人测试

如果在生命回复中加入一个回复效果的GCN效果, 比如下图

那么实测只会在服务端有特效, 这是因为我们StartupEffect这种GE效果是只在服务端应用,我们使用一般的GA来添加GCN效果就可以了

其他
优化
对于大量RPC任务而言, 可以设置AbilitySystem.AlwaysConvertGESpecToGCParams 1,这将转换GameplayEffectSpecs到FGameplayCueParameter结构和RPC,而不是整个FGameplayEffectSpecForRPC。这节省了带宽,但也有较少的信息
Ability System Globals
Ability System Globals类保存了GAS系统的多数全局信息
4.24版本之后在程序启动之初需要对数据进行初始化,否则会引起报错甚至奔溃,可以到引擎子系统中初始化或者如ActionRPG中使用自定义的AssetManager类来初始化
void USREngineSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UAbilitySystemGlobals::Get().InitGlobalData();
}