0%

前言

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

1
2
3
/** Look in these paths for GameplayCueNotifies. These are your "always loaded" set. */
UPROPERTY(config)
TArray<FString> GameplayCueNotifyPaths;
1
GameplayCueNotifyPaths.Add(TEXT("/Game"));

如果需要修改路径可以修改插件内容

这里需要注意,如果是比较大型的游戏, 游戏中会有数量非常庞大的GameplayCueNotifies ,如果游戏启动的时候就加载所有,那么必然造成很多的内存浪费,如果有这个问题, 可以继承GameplayCueManager,然后重写方法

1
virtual bool ShouldAsyncLoadObjectLibrariesAtStart() const { return false; }

当然我们必须在此类创建的时候需要如下修改GlobalGameplayCueManagerClass或者GlobalGameplayCueManagerName的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 ,在GEDisplay选项中添加CGN;

image-20201207174308746

新建static类型的CGN

在蓝图类中设置GC对应的Tag

image-20201207174456052

编辑器模式下会自动扫描有引用关系的类,这个蛮好用的

如果GEInstant模式,那么在GCN中会先后调用HandleExcute事件一次

如果是有持续时间的,那么Handle会调用多次,因为Handle本身就有EventType类型以及其他因素, Excute会在初始调用一次, 如果有Period>0,那么每隔一段时间会执行HandleExecute一次

这个GC的各个函数调用有点容易混淆,这里做一次测试

测试GA持续3秒后结束,测试GEPeriod设置1秒(非instant)

执行顺序:

HE:HandleExcuted

HOA:HandleOnActive

HWA:HandleWhileActive

HR:HandleRemove

OA:OnActive

OE:OnExcute

OR:OnRemove

WA:WhileActive

测试逻辑大致见下图

image-20201208102303011

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模式一致

我们可以发现staticactor模式的GCN机制类似, 按照文档说明的, staticExecute以及适合瞬间触发的效果,ActorAddRemove的方式用到有持续时间的效果也有一定道理;

一般瞬间的GCN重写Excute方法即可

持续性的看情况重写OnActiveWhileActive以及Remove

本地GCN

GCN默认都是RPC同步的,那么对于部分的本地效果肯定是没必要的, 我们可以直接调用GameplayCueManager->HandleGameplayCue()来执行本地GCN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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的标签

image-20201226155642603

image-20201226155658792

  • 单人测试

录制_2020_12_26_15_57_23_916

  • 双人测试

录制_2020_12_26_15_58_26_889


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

image-20201226160731595

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

image-20201226160804931

其他

优化

对于大量RPC任务而言, 可以设置AbilitySystem.AlwaysConvertGESpecToGCParams 1,这将转换GameplayEffectSpecsFGameplayCueParameter结构和RPC,而不是整个FGameplayEffectSpecForRPC。这节省了带宽,但也有较少的信息

Ability System Globals

Ability System Globals类保存了GAS系统的多数全局信息

4.24版本之后在程序启动之初需要对数据进行初始化,否则会引起报错甚至奔溃,可以到引擎子系统中初始化或者如ActionRPG中使用自定义的AssetManager类来初始化

1
2
3
4
5
void USREngineSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UAbilitySystemGlobals::Get().InitGlobalData();
}

前言

上一篇讲述了GE的一些基本概念, 非常概念化没有实战部分, 很容易混淆和忘记, 本片我们结合实际项目再配合GA的使用来模拟几个技能效果

伤害计算

我们在前面测试GA的时候已经简单测试了伤害,不过那个伤害就固定的一个值, 下面我们用自定义的类GEEC来计算这个伤害

1
USRGEEC_Damage : public UGameplayEffectExecutionCalculation

image-20201218172355966

给上我们的GEEC

阅读全文 »

前言

GameplayEffectGAS框架中的重要性不言而喻, 内容非常多,,但是本身基本上只作为一个数据载体而存在,蓝图中也无法重写和执行任何事件.

本篇梳理一下GE的常用属性和基本概念, 实战部分我们在下一篇展开

基础定义

GE是属性修改的容器, 分为如下几个类型

Duration Type GameplayCue Event When to use
Instant Execute BaseValue的立即生效的处理,GameplayTags 不会被应用到, 即使在一帧之内
Duration Add & Remove CurrentValue持续修改.可以应用GameplayTags.持续事件在GE类中指定
Infinite Add & Remove 类似Duration,但是是永久的直到手动移除(如通过ASC)

DurationInfinite模式会出现Period选项

image-20201201143832164

如果ExecutePeriodicEffectOnApplication=true,那么每隔Period秒就执行

periodic inhibition policy刷新策略,如是否覆盖

如果Tag匹配或者不匹配, GE可以临时性的关闭或者开启,这移除操作不会移除GE,只是临时的移除GE的修改效果

如果要手动的直接应用GE的修改, 可以调用

1
UAbilitySystemComponent::ActiveGameplayEffects.SetActiveGameplayEffectLevel(FActiveGameplayEffectHandle ActiveHandle, int32 NewLevel)

GE的属性设置一般建议在编辑器用蓝图编辑, 理论上可以用cpp设定参数, 但是不直观也比较麻烦


阅读全文 »

前言

前文已经大概了解了GA的大致内容,本片结合项目来详细使用一下技能.

因为技能可以是通过按键直接触发, 我们在GA中多数是播放动画来表示技能执行的, 所以我们需要一个一个方便我们GA使用的播放动画的节点

GAS中有很多已经封装好的异步节点, 很多都是非常有用的,如下图(太多无法截全)

image-20201217142941527

其中有一个PlayMontageAndWait的节点, 可以用这个来播放动画蒙太奇, 然后通过动画通知来开启攻击能力, 攻击者(或者武器)触发攻击事件, 然后触发对应的GE来达到伤害的目的

那么我们重新捋一下, 我们在GA中播放动画, 然后外面的动画通知和检测我们不管, 我们还在GA这里监听某些Tag的事件, 响应以后我们在这里应用GE效果(ActionRPG的思路),这样是不是更简单直观一点

照着这个思路我们来做一下

阅读全文 »

前言

在第一篇初始化的时候简单讲解了技能的添加和使用, 但是没有对技能GameplayAbility(GA)做详细介绍,本片开始对GA中的主要功能注意剖析

注册/移除技能

之前我们通过配置的方式自动注册起始技能, 那么我们肯定需要动态的增加或者删除技能, 为了方便使用, 封装蓝图库

阅读全文 »

前言

这一篇单独创建几个非常有用的异步节点,如UAsyncTask_ListenAttributeChanged可以实时监听任意属性的更改情况,在初期我们用于临时UMG中,方便我们查询和debug;

可以使用继承自UBlueprintAsyncActionBase的基本异步类, 也可以使用GAS框架内的UAbilityTask来制作

image-20201214155057270

异步事件:属性监听

这个节点相对比较简单, 原理就是通过ASC中的GetGameplayAttributeValueChangeDelegate()绑定对应属性的代理,然后简单的派发异步节点中的代理, 这是GASD项目采用的方案,

阅读全文 »

前言

引用官网的一段话介绍一下GAS系统

Gameplay技能系统 是一个高度灵活的框架,可用于构建你可能会在RPG或MOBA游戏中看到的技能和属性类型。你可以构建可供游戏中的角色使用的动作或被动技能,使这些动作导致各种属性累积或损耗的状态效果,实现约束这些动作使用的”冷却”计时器或资源消耗,更改技能等级及每个技能等级的技能效果,激活粒子或音效,等等。简单来说,此系统可帮助你在任何现代RPG或MOBA游戏中设计、实现及高效关联各种游戏中的技能,既包括跳跃等简单技能,也包括你喜欢的角色的复杂技能集。

此篇为GameplayAbilitySystem入门文档的开篇,

此系列文档会从零开始记录用UE4 GAS插件为基础, 尝试开发一个简单的ARPG游戏的案例

准备工作

  • 创建C++工程SuperRoad(也可以先创建蓝图工程,然后添加任意c++类), 目前引擎已经升级到4.26.0, 就以此版本为基础开发
  • 暂不导入美术资源, 使用默认TopDown模板的基础资源
  • 打开引擎插件, 开启GameplayAbilities并重启项目

打开项目Build.cs

暂添加如下模块

1
2
3
4
PrivateDependencyModuleNames.AddRange(new string[] {
"GameplayAbilities",
"GameplayTags",
"GameplayTasks" });
阅读全文 »

前言

模型的边缘高光应该是老生常谈的话题了, 不过还是有必要详细记录一下各种主流的两个实现方案, 同样的, 这个问题也经常在TA或者客户端面试中被问到

后期方案

先看效果动图

录制_2020_12_15_16_03_11_453

阅读全文 »

前言

对于很多情况下, 我们都希望我们可以在游戏运行状态下自定义UE4的按键映射, 目前的UE已经支持多数平台的按键在蓝图中实时的更改按键设置, 直接保存到 EngineInput.ini文件

写了一个案例插件,提供了几个解决方案的Demo和几个方便蓝图使用的库函数

RuntimeInputMapping

API

我们项目设置里的Input栏的内容其实是封装在一个UObjectUInputSettings中, 他其实就是一个配置类, 有一个静态方法可以直接获取该类

1
static UInputSettings* GetInputSettings();
阅读全文 »