Gameplay Ability
对于Gameplay Ability的官方说明:
/**
* UGameplayAbility
*
* Abilities define custom gameplay logic that can be activated or triggered.
*
* The main features provided by the AbilitySystem for GameplayAbilities are:
* -CanUse functionality:
* -Cooldowns
* -Costs (mana, stamina, etc)
* -etc
*
* -Replication support
* -Client/Server communication for ability activation
* -Client prediction for ability activation
*
* -Instancing support
* -Abilities can be non-instanced (native only)
* -Instanced per owner
* -Instanced per execution (default)
*
* -Basic, extendable support for:
* -Input binding
* -'Giving' abilities (that can be used) to actors
*
*
* See GameplayAbility_Montage for an example of a non-instanced ability
* -Plays a montage and applies a GameplayEffect to its target while the montage is playing.
* -When finished, removes GameplayEffect.
*
* Note on replication support:
* -Non instanced abilities have limited replication support.
* -Cannot have state (obviously) so no replicated properties
* -RPCs on the ability class are not possible either.
*
* To support state or event replication, an ability must be instanced. This can be done with the InstancingPolicy property.
*/
整体上的概括就是:
Abilities define custom gameplay logic that can be activated or triggered.
Abilities定义可激活或触发的自定义游戏玩法逻辑。
所以才有“Everything can be a gameplay ability”的说法。同样注意,这里说Ability是逻辑,也就是这个类主要是实现各种行为,比如:技能释放、撤销、目标选择等等。可以通过tag来影响Ability的运作逻辑,通过effect来影响actor的属性(attributes)。
UGameplayAbility
类中重要的函数有:
// ----------------------------------------------------------------------------------------------------------------
//
// The important functions:
//
// CanActivateAbility() - const function to see if ability is activatable. Callable by UI etc
//
// TryActivateAbility() - Attempts to activate the ability. Calls CanActivateAbility(). Input events can call this directly.
// - Also handles instancing-per-execution logic and replication/prediction calls.
//
// CallActivateAbility() - Protected, non virtual function. Does some boilerplate 'pre activate' stuff, then calls ActivateAbility()
//
// ActivateAbility() - What the abilities *does*. This is what child classes want to override.
//
// CommitAbility() - Commits reources/cooldowns etc. ActivateAbility() must call this!
//
// CancelAbility() - Interrupts the ability (from an outside source).
//
// EndAbility() - The ability has ended. This is intended to be called by the ability to end itself.
//
// ----------------------------------------------------------------------------------------------------------------
之外,还有大量的行为(函数),头文件中对他们做了分类:
- Accessors : 一系列的访问函数
- CanActivateAbility :判断能否释放技能
- CancelAbility : 撤销技能
- CommitAbility : 确认技能释放
- Input: 用户输入相关
- Animation : 动画
- Ability Levels and source object: 技能等级和技能来源
- Interaction with ability system component:与Ability System Component的信息传递
- EndAbility:结束技能
- Ability Tasks: 异步任务
- Apply/Remove gameplay effects: 应用或移除gameplay effects
............
头文件含865行代码,内容较多,不一一列出了。
我们的自定义逻辑,主要是ActivateAbility()
中完成,该方法可支持蓝图实现。
// --------------------------------------
// ActivateAbility
// --------------------------------------
/**
* The main function that defines what an ability does.
* -Child classes will want to override this
* -This function graph should call CommitAbility
* -This function graph should call EndAbility
*
* Latent/async actions are ok in this graph. Note that Commit and EndAbility calling requirements speak to the K2_ActivateAbility graph.
* In C++, the call to K2_ActivateAbility() may return without CommitAbility or EndAbility having been called. But it is expected that this
* will only occur when latent/async actions are pending. When K2_ActivateAbility logically finishes, then we will expect Commit/End to have been called.
*
*/
在谈RPGGameplayAbility.h之前,我们需要先看RPGAbilityTypes.h。
RPGAbilityType.h/cpp
头文件:
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
// ----------------------------------------------------------------------------------------------------------------
// This header is for Ability-specific structures and enums that are shared across a project
// Every game will probably need a file like this to handle their extensions to the system
// This file is a good place for subclasses of FGameplayEffectContext and FGameplayAbilityTargetData
// ----------------------------------------------------------------------------------------------------------------
#include "ActionRPG.h"
#include "GameplayEffectTypes.h"
#include "Abilities/GameplayAbilityTargetTypes.h"
#include "RPGAbilityTypes.generated.h"
class URPGAbilitySystemComponent;
class UGameplayEffect;
class URPGTargetType;
/**
* Struct defining a list of gameplay effects, a tag, and targeting info
* These containers are defined statically in blueprints or assets and then turn into Specs at runtime
*/
USTRUCT(BlueprintType)
struct FRPGGameplayEffectContainer
{
GENERATED_BODY()
public:
FRPGGameplayEffectContainer() {}
/** Sets the way that targeting happens */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = GameplayEffectContainer)
TSubclassOf<URPGTargetType> TargetType;
/** List of gameplay effects to apply to the targets */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = GameplayEffectContainer)
TArray<TSubclassOf<UGameplayEffect>> TargetGameplayEffectClasses;
};
/** A "processed" version of RPGGameplayEffectContainer that can be passed around and eventually applied */
USTRUCT(BlueprintType)
struct FRPGGameplayEffectContainerSpec
{
GENERATED_BODY()
public:
FRPGGameplayEffectContainerSpec() {}
/** Computed target data */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = GameplayEffectContainer)
FGameplayAbilityTargetDataHandle TargetData;
/** List of gameplay effects to apply to the targets */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = GameplayEffectContainer)
TArray<FGameplayEffectSpecHandle> TargetGameplayEffectSpecs;
/** Returns true if this has any valid effect specs */
bool HasValidEffects() const;
/** Returns true if this has any valid targets */
bool HasValidTargets() const;
/** Adds new targets to target data */
void AddTargets(const TArray<FHitResult>& HitResults, const TArray<AActor*>& TargetActors);
};
这个文件中定义了两个结构体,用来辅助ability。官方推荐玩家继承FGameplayEffectContext
和FGameplayAbilityTargetData
来实现自己的ability复杂逻辑。这些结构体围绕的通常是这两个点:Effect 和Target,即技能携带的效果和技能释放目标。
注意上面代码注解中的"processed version"。这很切合数据流的思维。FRPGGameplayEffectContainer的数据作为输入,经过某个系统的一系列行为处理之后,输出FRPGGameplayEffectContainerSpec供下一阶段使用。在Unreal Engine中可能所有带"Spec"后缀的都是类似这种情况(注:没有证明的猜测)。
RPGGameplayAbility.h/cpp
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "ActionRPG.h"
#include "GameplayAbility.h"
#include "GameplayTagContainer.h"
#include "Abilities/RPGAbilityTypes.h"
#include "RPGGameplayAbility.generated.h"
/**
* Subclass of ability blueprint type with game-specific data
* This class uses GameplayEffectContainers to allow easier execution of gameplay effects based on a triggering tag
* Most games will need to implement a subclass to support their game-specific code
*/
UCLASS()
class ACTIONRPG_API URPGGameplayAbility : public UGameplayAbility
{
GENERATED_BODY()
public:
// Constructor and overrides
URPGGameplayAbility();
/** Map of gameplay tags to gameplay effect containers */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = GameplayEffects)
TMap<FGameplayTag, FRPGGameplayEffectContainer> EffectContainerMap;
/** Make gameplay effect container spec to be applied later, using the passed in container */
UFUNCTION(BlueprintCallable, Category = Ability, meta=(AutoCreateRefTerm = "EventData"))
virtual FRPGGameplayEffectContainerSpec MakeEffectContainerSpecFromContainer(const FRPGGameplayEffectContainer& Container, const FGameplayEventData& EventData, int32 OverrideGameplayLevel = -1);
/** Search for and make a gameplay effect container spec to be applied later, from the EffectContainerMap */
UFUNCTION(BlueprintCallable, Category = Ability, meta = (AutoCreateRefTerm = "EventData"))
virtual FRPGGameplayEffectContainerSpec MakeEffectContainerSpec(FGameplayTag ContainerTag, const FGameplayEventData& EventData, int32 OverrideGameplayLevel = -1);
/** Applies a gameplay effect container spec that was previously created */
UFUNCTION(BlueprintCallable, Category = Ability)
virtual TArray<FActiveGameplayEffectHandle> ApplyEffectContainerSpec(const FRPGGameplayEffectContainerSpec& ContainerSpec);
/** Applies a gameplay effect container, by creating and then applying the spec */
UFUNCTION(BlueprintCallable, Category = Ability, meta = (AutoCreateRefTerm = "EventData"))
virtual TArray<FActiveGameplayEffectHandle> ApplyEffectContainer(FGameplayTag ContainerTag, const FGameplayEventData& EventData, int32 OverrideGameplayLevel = -1);
};
官方建议,大多游戏通常要拓展UGameplayAbility
。因为不同游戏的Ability可能需要额外的不同的数据。
ActionRPG中添加了
TMap<FGameplayTag, FRPGGameplayEffectContainer> EffectContainerMap;
可以方便在蓝图中设定GameplayTag、TargetType(TargetType是项目自定义的,继承自UObject)和GameplayEffect。
添加的行为主要是:
/** 将Container的数据处理成ContainerSpec,只有当数据变为ContainSpec时,才做好了释放的准备。
会调用到 UGameplayAbility::MakeOutgoingGameplayEffectSpec(...)*/
virtual FRPGGameplayEffectContainerSpec MakeEffectContainerSpecFromContainer
(const FRPGGameplayEffectContainer& Container, const FGameplayEventData& EventData, int32 OverrideGameplayLevel = -1);
/** 应用之前的Spec数据中 GameplaySpecHandle对应的GameplayEffect。
会调用到 UGameplayAbility::K2_ApplyGameplayEffectSpecToTarget(...)
K2 可能代表着:kismet 2代,也就是蓝图系统。1代源自虚幻3 */
virtual TArray<FActiveGameplayEffectHandle> ApplyEffectContainerSpec(const FRPGGameplayEffectContainerSpec& ContainerSpec);
我们会发现在GameplayAbility中会大量的和GameplayEffect打交道,毕竟最终作用于Gameplay Attributes的是GameplayEffect。
网友评论