四、RPGGameplayAbility.h/cpp & GameplayAbility

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。官方推荐玩家继承FGameplayEffectContextFGameplayAbilityTargetData来实现自己的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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352