零、开篇

ActionRPG(以下简称示例)是官方GAS系统的使用示例,可于虚幻商城免费下载。本篇教程将围绕UEGameplay框架,GAS做浅析,逐步了解在动作游戏中,官方对虚幻的Gameplay框架和GAS系统的应用。

一、基于MVC思想的Gameplay游戏框架

1、MVC是什么

Model View Controller结构(简称MVC)详细解释请自行百度。
三个模块映射到UE的Gameplay框架中:
Model-GameInstance、GameMode、GameState
View-Actor、Level
Controller-PlayerController、AIController
其中不同的角色拥有不同的功能(服务器、客户端)ActionRPG是一个存粹的单机示例,并没有运用网络相关内容,这里就不详细展开了。

2、将MVC思想与游戏结合

直接说结论,玩家应该以Controller的身份参与到游戏中,通过Posses方法去拥有并控制View,Model则负责监控整个游戏的进度(倒计时),并更新和保存玩家(Controller)相关的状态(得分)

3、回到示例

只看功能结构,具体功能怎么实现的就不细说了


先看Model

示例重新实现了GameMode,添加了几个常用的游戏进度控制功能接口,然后在蓝图中实现功能。
GameMode蓝图共有两种,分别对应主菜单和游戏中,我们只看游戏中。GM主要实现了

  • 播放过场动画
  • 注册计时器更新倒计时
  • 控制玩家和敌人的生成
  • 回合控制
  • 击杀计数
  • 控制游戏结束时的慢动作和弹出结算界面

总体看下来都是整局游戏过程相关的逻辑。
PS:我觉得击杀计数应该写到GameState中,官方重写了GS,但仅仅是创建了个蓝图,并没有实现任何逻辑,感觉只是偷了个懒。


再看Controller

代表玩家的PlayerController和代表敌人的AIController。
PlayerController有C++和蓝图实现,先看C++:
继承自APlayerController和IRPGInventoryInterface

class ACTIONRPG_API IRPGInventoryInterface
{
	GENERATED_BODY()

public:
	/** Returns the map of items to data */
	virtual const TMap<URPGItem*, FRPGItemData>& GetInventoryDataMap() const = 0;

	/** Returns the map of slots to items */
	virtual const TMap<FRPGItemSlot, URPGItem*>& GetSlottedItemMap() const = 0;

	/** Gets the delegate for inventory item changes */
	virtual FOnInventoryItemChangedNative& GetInventoryItemChangedDelegate() = 0;

	/** Gets the delegate for inventory slot changes */
	virtual FOnSlottedItemChangedNative& GetSlottedItemChangedDelegate() = 0;

	/** Gets the delegate for when the inventory loads */
	virtual FOnInventoryLoadedNative& GetInventoryLoadedDelegate() = 0;
};

主要是查询玩家背包功能相关的接口函数。
而PC主体也只是实现了背包内容的增删改查和相关逻辑通知。

蓝图:

  • 大部分按键输入(暂停、打开背包、自动战斗、旋转相机、移动)
  • UI控制
  • Posses处理
  • 背包道具操作的详细实现(获得和消耗灵魂、背包改变事件)

最后是View层

再看一下C++层RPGCharacter,出了继承自ACharacter外,还有两个特别的接口,IAbilitySystemInterface、IGenericTeamAgentInterface,分别为GAS系统和敌人的AI系统提供支持。GAS后面细说,AI后面再开篇单独分析。

  • 角色状态相关Getter函数(血量、蓝量、移动速度、等级)
  • GAS相关接口实现,并在构造函数中挂载了GAS组件
  • 玩法功能事件(回血回蓝受伤之类的)
  • 装备改变事件

整个CPP文件中,GAS相关功能实现占了大部分。

蓝图:
直接继承的蓝图有BP_Character,再往下有PlayerCharacter和EnemyCharacter,EnemyCharacter放在AI篇系锁。

BP_Character比较简单,主要还是GAS相关逻辑,如执行攻击、技能、使用道具、播放蒙太奇、判断是否死亡等通用功能

PlayerCharacter:

  • 部分输入功能处理(奔跑、攻击、使用道具、翻滚、切换武器)
  • 受击与死亡判定(通知控制器更新)
  • 切换武器技能道具等相关逻辑
  • 慢动作功能

到这里已经可以看出游戏中接触到的大部分功能的定义的位置了,简单分析一下。

  • GM主要负责处理游戏整体玩法规则相关逻辑。
  • Controller代表了玩家的状态和思想(输入)。且装备、道具之类的所有权应归于Controller也就是玩家,而不是玩家正在扮演的角色(角色只是使用权,道具的所有权属于归腾...玩家)
  • 某个角色的逻辑,播放的动画,特效,则实现在Character中,以借助角色体现玩家的思想(输入)。角色不应该有控制整局规则的能力(如暂停、开始与结束)。

4.总结

整体结构就像是在下棋,GM是旁边裁判,规定了卒只能一格一格走。而棋手则负责指挥棋子每一步的行动。棋子会告诉棋手当前所处的位置,便于让棋手根据棋盘上的点和线做决策(而不是根据棋子在棋盘上的坐标。UE也是用这个思路给能够被棋手Controller控制的棋子起名叫做Pawn,这可不是巧合)

二、GAS系统

0、简介

GAS(Gameplay Abilities System 玩法能力系统)是一个官方插件,能够让开发者快速构建技能系统。采用数据驱动的方式,并对网络同步有着良好的支持。ActionRPG中绝大多数的技能甚至平A都是基于GAS构建的。Pawn可以通过继承IAbilitySystemInterface,并挂载UAbilitySystemComponent实现相关功能

1、从玩家的普通攻击Melee开始

在BP_PlayerCharacter中接受了玩家的鼠标点击操作,并调用重载后的DoMeleeAttack,蓝图会通过调用ActivateAbilitiesWithItemSlot进入到C++代码中,C++代码通俗的讲就是:

  • ItemSlot->GAHandle->GA

其中ItemSlot以参数形式传入,GAHandle保存在RPGCharacter中的一个成员TMap中,将拿到的Handle交给AbilitySystemComponent处理。
那么GA是如何保存到AbilitySystemComponent中的呢?答案是通过AbilitySystemComponent中的GiveAbility接口实现。从AddSlottedGameplayAbilities函数可以看到相关过程:

void ARPGCharacterBase::AddSlottedGameplayAbilities()
{
	TMap<FRPGItemSlot, FGameplayAbilitySpec> SlottedAbilitySpecs;
	FillSlottedAbilitySpecs(SlottedAbilitySpecs);
	
	// Now add abilities if needed
	for (const TPair<FRPGItemSlot, FGameplayAbilitySpec>& SpecPair : SlottedAbilitySpecs)
	{
		FGameplayAbilitySpecHandle& SpecHandle = SlottedAbilities.FindOrAdd(SpecPair.Key);

		if (!SpecHandle.IsValid())
		{
			SpecHandle = AbilitySystemComponent->GiveAbility(SpecPair.Value);
		}
	}
}

其中FGameplayAbilitySpec是负责保存GA生成的具体对象。

FillSlottedAbilitySpecs则是通过数据资源构建的,这些资源保存在/Items文件夹中。

注意:ActionRPG是通过引擎自带的AssetManager管理这些资源的查询和加载,故使用前需要再项目设置-AssetManager提前注册这些资源。

ActionRPG的所有GAS资源文件均继承自URPGItem类型,再细分为Weapon、Skill等。
以URPGWeaponItem举例,资源中出了包含了武器Actor类型、名称等游戏相关内容外,最重要的是Abilities栏中的GrantedAbility。这个值指向了一个具体的GA类型,当我们按下鼠标左键是,GAS具体执行的也就是这个类型中定义的功能。、

如默认武器Axe的普通攻击就是通过GA_PlayerAxeMelee实现。

找到并打开GA_PlayerAxeMelee后,可以看到角色普通攻击使用的蒙太奇动画,至此整个GAS的执行逻辑框架已经浮现。

动画中包含了大量的事件为了实现每个动画部分的具体逻辑功能。

2、Gameplay Effect

GAS中最复杂的模块,目的在于将技能的效果以数据的形式编辑保存。

参数太多了,建议边用边查资料。


程序还有个需要注意的东西

Modifier Magnitude Calculation(MMC)

一个自定义计算器,通过重写float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec & Spec) const;函数并返回一个float值来实现特殊逻辑。