ActionRPG(启动逻辑)
ActionRPG解读(启动逻辑)
数据结构
RPGItem
先看数据类型。游戏中的右下角有一些图标,包括药剂、武器等,这些都可以抽象为RPGItem,根据类别不同,派生出更具体的RPGPotionItem
,RPGSkillItem
,RPGTokenItem
,RPGWeaponItem
。
RPGItem继承于UPrimaryDataAsset
,可以派生出DataAsset。
可以在编辑器中对DataAsset进行方便的配置,实现数据驱动。
RPGTypes
值得学习的是,这里使用了一个RPGTypes.h头文件来定义结构、枚举、委托等类型。文件中解释了这样做的好处:
// This header is for enums and structs used by classes and blueprints accross the game
// Collecting these in a single header helps avoid problems with recursive header includes
// It's also a good place to put things like data table row structs
FRPGItemSlot
这是该文件中定义的第一个数据结构,用来表示屏幕右下角的那四个槽位,也称为Slot。
每个Slot有自己的类型,比例药剂、武器等,只有对应类型的Item才能放到该Slot上。
RPGSaveGame
RPGSaveGame继承于USaveGame,用于保存游戏数据。
主要保存两个映射,保存物品Id到Data的映射,以及Slot到Slot上物品的Id的映射。
分析游戏逻辑可以先康康每个游戏最重要的几个类:
GameInstance
Init
首先加载保存的游戏数据。
SlotName和UserIndex在构造函数中进行了设置。
加载完成后,调用HandleSaveGameLoaded。
HandleSaveGameLoaded
如果bSavingEnabled为false,则把SaveGameObject置为nullptr,表示放弃之前保存的游戏数据。
这里把SaveGameObject转型为URPGSaveGame,如果不为nullptr,则说明成功加载了游戏数据。
如果为nullptr则会创建一份新的游戏数据。
无论是否加载存档成功,都会添加默认inventory。
AddDefaultInventory
这里会加入默认的物品,默认物品在蓝图中进行了设置,包含1个斧头。
GameMode
根据BP_GameMode,管理敌人的逻辑是放在GameMode中的。
PlayerCanRestart
值得注意的是这里重写了一个PlayerCanRestart方法,这样就不会自动为玩家生成一个Pawn。
BeginPlay
BeginPlay中调用了一下PlayDefaultIntroCutscene播放开场动画,并且播放一下开场音乐。
PlayDefaultIntroCutscene
首先找到场景中放置的第一个LevelSequenceActor,作为参数调用BP_PlayerController的PlaySkippableCutscene方法。如果没找到则直接调用StartGame开始游戏。
PlaySkippableCutscene
播放一下Sequence,注册一个播放完成时的事件,并且可以随时按下Enter走播放完成时的事件逻辑。这里的Do Once很巧妙地保证了只会执行一次播放完成时的逻辑。
播放完成时会调用BP_GameMode的StartGame方法开始游戏。
StartGame
这里设置游戏正在运行,并调用RestartPlayer生成一个玩家Pawn,然后开始玩家计时器,开始生成敌人。
StartPlayerTimer 倒计时逻辑
这里的逻辑是倒计时,每隔一秒时间减一,并更新UI,当时间归0时游戏结束。
PlayerController
BeginPlay
LoadInventory
通过一个bFoundAnySlots
来判断能否在当前存档中获取Slot,如果不行,则调用FillEmptySlots
来填充
FillEmptySlots
注意这里的InventoryData
在前面的AddDefaultInventory
已经填充了默认值
FillEmptySlotWithItem
找到一个合适的Slot放置Item。
Possess
在Possess中创建所有武器
Create All Weapons
调用PlayerController的GetSlottedItems方法来获取装备的Items。遍历所有武器Item,逐一Spawn出来。
这里通过设置Transform,把spawn出来的武器放到很遥远的位置。
第一次附加武器时会调用Attach Next Weapon
Attach Next Weapon
不断增加Cur Weapon Index
后超出边界会重新设置为0,实现循环next的效果。
然后使用当前索引来调用Weapon Attach Method
Weapon Attach Method
首先会把当前武器detach
根据传入的Index,从Equipped Weapons
中获得武器并且设置给Current Weapon
,取得Current Weapon
的Slot设置给Current Weapon Slot
。然后把武器附加到角色Mesh的Socket上,实现握持效果。
Character
PossessedBy
注意这里将InventorySource
设置为了Controller。之后会从Controller中获取Inventory。
然后初始化ASC,调用AddStartupGameplayAbilities
来添加初始时候的能力。
AddStartupGameplayAbilities(初始化能力和应用初始GE)
这里值得学习,所以直接放代码
void ARPGCharacterBase::AddStartupGameplayAbilities()
{
check(AbilitySystemComponent);
if (GetLocalRole() == ROLE_Authority && !bAbilitiesInitialized)
{
// Grant abilities, but only on the server
for (TSubclassOf<URPGGameplayAbility>& StartupAbility : GameplayAbilities)
{
AbilitySystemComponent->GiveAbility(FGameplayAbilitySpec(StartupAbility, GetCharacterLevel(), INDEX_NONE, this));
}
// Now apply passives
for (TSubclassOf<UGameplayEffect>& GameplayEffect : PassiveGameplayEffects)
{
FGameplayEffectContextHandle EffectContext = AbilitySystemComponent->MakeEffectContext();
EffectContext.AddSourceObject(this);
FGameplayEffectSpecHandle NewHandle = AbilitySystemComponent->MakeOutgoingSpec(GameplayEffect, GetCharacterLevel(), EffectContext);
if (NewHandle.IsValid())
{
FActiveGameplayEffectHandle ActiveGEHandle = AbilitySystemComponent->ApplyGameplayEffectSpecToTarget(*NewHandle.Data.Get(), AbilitySystemComponent);
}
}
AddSlottedGameplayAbilities();
bAbilitiesInitialized = true;
}
}
首先从GameplayAbilities
中获取StartupAbility
,也就是初始能力,构造一个FGameplayAbilitySpec
以调用GiveAbility
,但是在蓝图中好像只有NPC_SpiderBoss
设置了GameplayAbilities
。
然后会应用GE,这些GE是在角色被创建的时候进行触发的,在NPC_SpiderBos
、BP_PlayerCharacter
、NPC_GoblinBP
中进行了设置。
BP_PlayerCharacter设置的为GE_PlayerStats
该GE对角色的各项属性进行了初始配置,比如第一项是配置角色的最大生命值。
最后调用AddSlottedGameplayAbilities
。
AddSlottedGameplayAbilities
调用FillSlottedAbilitySpecs
,然后使用填充了的SlottedAbilitySpecs
来进行遍历,取出其Value调用GiveAbility,把能力赋予角色。
SlottedAbilities
中保存的是Slot到Slot上Item的能力的Handle的映射,后面会使用它来放技能。
FillSlottedAbilitySpecs
注意该方法没有返回值,而是通过一个TMap类型的引用参数。该方法会向Map中填充ItemSlot到该Slot上的Item拥有的能力的映射。
首先先通过DefaultSlottedAbilities
添加了角色的默认能力。
然后才是添加物品的能力。
前面在PossessedBy
时设置的InventorySource
就有用了。
对于玩家来说,InventorySource
即为PlayerController
,其实现了GetSlottedItemMap
接口,可以得到SlottedItems
,这是LoadInventory
时填充的。
SlottedItems
是一个Map,其Value即为URPGItem,URPGItem中有一个可以配置的GrantedAbility
字段,这里拿到该值,构造一个FGameplayAbilitySpec,添加到SlottedAbilitySpecs
中
URPGItem继承自UPrimaryDataAsset,是可以配置的数据资产