4.7.1 技能任务的定义 - Ability Task Definition
GameplayAbilities
只能在某一帧执行,这样的机制导致其并没有很大的灵活性。为了实现那些随时间变化的行为,或是在一定时间后响应委托的行为,我们可以使用延迟行为,也即AbilityTasks
。
GAS自己带了一些可以直接使用的AbilityTasks
:
- 基于
RootMotionSource
的用于角色移动的Task - 播放动画蒙太奇的Task
- 响应
Attribute
的变化的Task - 响应
GameplayEffect
的变化的Task - 响应玩家输入的Task
- 等等
UAbilityTask
的构造函数中硬编码了同一时间最多能够运行1000个并行的AbilityTasks
。请谨记,当为游戏设计GameplayAbilities
时,像RTS这种游戏可是在某个时间点会同时有上百名角色。
4.7.2 自定义技能任务 - Custom Ability Tasks
你可能会需要创建一些自定义的AbilityTasks
(在C++中)。示例项目中建立了两个自定义的AbilityTasks
:
PlayMontageAndWaitForEvent
是将默认的PlayMontageAndWait
和WaitGameplayEvent
两种AbilityTasks
进行了结合。这可以使用动画蒙太奇利用AnimNotifies
给播放他们的GameplayAbility
发送事件。使用这种方式在动画蒙太奇播放过程中的特定时间点来触发指定的行为。WaitReceiveDamage
会监听OwnerActor
接收伤害的事件。被动护甲的GameplayAbility
在英雄接收到伤害时移除一层护甲。
AbilityTasks
的实现需要有:
- 一个静态函数创建这个
AbilityTask
的实例 - 一些委托,绑定到
AbilityTask
实现其目标 Activate()
函数,以开始其核心任务,绑定外部委托等等OnDestroy()
函数,用来进行清理,包括一些绑定的外部委托- 绑定的外部委托的回调函数
- 成员变量和内联的辅助函数
注意:AbilityTasks
只能声明一种类型的委托,你的所有的输出委托都必须是这个类型,无论对应的参数是否使用。未使用的委托参数会传递默认值。
AbilityTasks
只运行在运行所属GameplayAbility
的服务器或者客户端上;但是,AbilityTasks
可以通过在构造函数中设置bSimulatedTask = true;
,重载virtual void InitSimulatedTask(UGameplayTasksComponent& InGameplayTasksComponent);
,并且设定成员变量未复制,从而运行在模拟客户端上。这只用在很少的情况,如模拟运动的AbilityTasks
,其中你并不想复制所有的运动变化,而是模拟整个运动的AbilityTask
。所有的RootMotionSource
的 AbilityTasks
都是在做这件事。参阅AbilityTask_MoveToLocation.h/.cpp
。
如果你在构造函数中设置bTickingTask = true;
并且重写virtual void TickTask(float DeltaTime);
的话,AbilityTasks
是可以执行Tick
类似的工作的。如果你希望去逐帧插值的话,这就非常有用了。参见AbilityTask_MoveToLocation.h/.cpp
。
4.7.3 使用技能任务 - Using Ability Tasks
为了在C++中创建和激活AbilityTask
(GDGA_FireGun.cpp
),需要做:
UGDAT_PlayMontageAndWaitForEvent* Task = UGDAT_PlayMontageAndWaitForEvent::PlayMontageAndWaitForEvent(this, NAME_None, MontageToPlay, FGameplayTagContainer(), 1.0f, NAME_None, false, 1.0f);
Task->OnBlendOut.AddDynamic(this, &UGDGA_FireGun::OnCompleted);
Task->OnCompleted.AddDynamic(this, &UGDGA_FireGun::OnCompleted);
Task->OnInterrupted.AddDynamic(this, &UGDGA_FireGun::OnCancelled);
Task->OnCancelled.AddDynamic(this, &UGDGA_FireGun::OnCancelled);
Task->EventReceived.AddDynamic(this, &UGDGA_FireGun::EventReceived);
Task->ReadyForActivation();
在蓝图中,我们仅需要使用为AbilityTask
而构建的蓝图节点即可。而且不需要去调用ReadyForActivate()
。它会由Engine/Source/Editor/GameplayTasksEditor/Private/K2Node_LatentGameplayTaskCall.cpp
自动调用。如果在你的AbilityTask
类中有BeginSpawningActor()
和FinishSpawningActor()
的话,K2Node_LatentGameplayTaskCall
也将其自动调用(参见AbilityTask_WaitTargetData
)。这里再强调一遍,K2Node_LatentGameplayTaskCall
仅仅针对蓝图做了这些神奇的操作。在C++中,我们还是需要手动调用ReadyForActivation()
,BeginSpawningActor()
以及FinishSpawningActor()
。
若需要取消某个AbilityTask
,只要在蓝图或C++中的AbilityTask
对象上(即Async Task Proxy
)调用EndTask()
即可。
4.7.4 Root Motion Source Ability Tasks
GAS带有一些能够处理角色随时间移动的AbilityTasks
,比如角色的击退,复杂的跳跃,拉,冲刺,这些都可以使用Root Motion Sources
以及响应的CharacterMovementComponent
里的对应功能来实现。
注意: 带预测的RootMotionSource
的AbilityTasks
在版本4.19和4.25之后的版本可以正常运行,而在4.20-4.24之间的版本是有问题的;但是,AbilityTasks
仍然会在多玩家下利用镜像网络矫正来执行其功能,且在单人玩家环境下运行良好。如果要强行使用,建议参考prediction fix。