ActionRPG(启动逻辑)

ActionRPG解读(启动逻辑)

数据结构

RPGItem

先看数据类型。游戏中的右下角有一些图标,包括药剂、武器等,这些都可以抽象为RPGItem,根据类别不同,派生出更具体的RPGPotionItemRPGSkillItemRPGTokenItemRPGWeaponItem

image-20230125173211454

image-20230125173435231

RPGItem继承于UPrimaryDataAsset,可以派生出DataAsset。

image-20230125173701343

可以在编辑器中对DataAsset进行方便的配置,实现数据驱动。

image-20230125173733315

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的映射。

image-20230125181303335


分析游戏逻辑可以先康康每个游戏最重要的几个类:

image-20230126232216631

GameInstance

Init

首先加载保存的游戏数据。

image-20230125181603437

SlotName和UserIndex在构造函数中进行了设置。

image-20230125181641800

加载完成后,调用HandleSaveGameLoaded。

image-20230125182021660

HandleSaveGameLoaded

image-20230125182135941

如果bSavingEnabled为false,则把SaveGameObject置为nullptr,表示放弃之前保存的游戏数据。

这里把SaveGameObject转型为URPGSaveGame,如果不为nullptr,则说明成功加载了游戏数据。

如果为nullptr则会创建一份新的游戏数据。

无论是否加载存档成功,都会添加默认inventory。

AddDefaultInventory

image-20221002204453498

这里会加入默认的物品,默认物品在蓝图中进行了设置,包含1个斧头。

image-20221002203857062

GameMode

根据BP_GameMode,管理敌人的逻辑是放在GameMode中的。

image-20230126232004322

PlayerCanRestart

值得注意的是这里重写了一个PlayerCanRestart方法,这样就不会自动为玩家生成一个Pawn。

image-20230127002413330

BeginPlay

BeginPlay中调用了一下PlayDefaultIntroCutscene播放开场动画,并且播放一下开场音乐。

image-20230126232820044

PlayDefaultIntroCutscene

image-20230126233519037

首先找到场景中放置的第一个LevelSequenceActor,作为参数调用BP_PlayerController的PlaySkippableCutscene方法。如果没找到则直接调用StartGame开始游戏。

PlaySkippableCutscene

播放一下Sequence,注册一个播放完成时的事件,并且可以随时按下Enter走播放完成时的事件逻辑。这里的Do Once很巧妙地保证了只会执行一次播放完成时的逻辑。

image-20230126233714891

播放完成时会调用BP_GameMode的StartGame方法开始游戏。

image-20230126234113632

StartGame

image-20230127002732432

这里设置游戏正在运行,并调用RestartPlayer生成一个玩家Pawn,然后开始玩家计时器,开始生成敌人。

StartPlayerTimer 倒计时逻辑

这里的逻辑是倒计时,每隔一秒时间减一,并更新UI,当时间归0时游戏结束。

image-20230127003059571

PlayerController

BeginPlay

image-20221003085023054

LoadInventory

image-20221003093811420

通过一个bFoundAnySlots 来判断能否在当前存档中获取Slot,如果不行,则调用FillEmptySlots来填充

FillEmptySlots

注意这里的InventoryData在前面的AddDefaultInventory已经填充了默认值

image-20221003093319310

FillEmptySlotWithItem

找到一个合适的Slot放置Item。

image-20230125163547612

Possess

在Possess中创建所有武器

image-20221003084117008

Create All Weapons

调用PlayerController的GetSlottedItems方法来获取装备的Items。遍历所有武器Item,逐一Spawn出来。

image-20230125165437563

这里通过设置Transform,把spawn出来的武器放到很遥远的位置。

image-20230125165355752

第一次附加武器时会调用Attach Next Weapon

image-20221003094329532

Attach Next Weapon

image-20221003094515376

不断增加Cur Weapon Index后超出边界会重新设置为0,实现循环next的效果。

然后使用当前索引来调用Weapon Attach Method

image-20221003094733908

Weapon Attach Method

首先会把当前武器detach

image-20221003094850267

根据传入的Index,从Equipped Weapons中获得武器并且设置给Current Weapon,取得Current Weapon的Slot设置给Current Weapon Slot。然后把武器附加到角色Mesh的Socket上,实现握持效果。

image-20221003094956036

Character

PossessedBy

image-20221003100554066

注意这里将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_SpiderBosBP_PlayerCharacterNPC_GoblinBP 中进行了设置。

image-20230125170817527

BP_PlayerCharacter设置的为GE_PlayerStats

image-20230201002120074

该GE对角色的各项属性进行了初始配置,比如第一项是配置角色的最大生命值。

image-20230201002247253

最后调用AddSlottedGameplayAbilities

AddSlottedGameplayAbilities

image-20221003101219408

调用FillSlottedAbilitySpecs ,然后使用填充了的SlottedAbilitySpecs来进行遍历,取出其Value调用GiveAbility,把能力赋予角色。

SlottedAbilities中保存的是Slot到Slot上Item的能力的Handle的映射,后面会使用它来放技能。

FillSlottedAbilitySpecs

注意该方法没有返回值,而是通过一个TMap类型的引用参数。该方法会向Map中填充ItemSlot到该Slot上的Item拥有的能力的映射。

image-20221003101835352

首先先通过DefaultSlottedAbilities添加了角色的默认能力。

然后才是添加物品的能力。

image-20221003102119340

前面在PossessedBy时设置的InventorySource就有用了。

对于玩家来说,InventorySource即为PlayerController,其实现了GetSlottedItemMap接口,可以得到SlottedItems,这是LoadInventory时填充的。

image-20221003102252540

SlottedItems是一个Map,其Value即为URPGItem,URPGItem中有一个可以配置的GrantedAbility字段,这里拿到该值,构造一个FGameplayAbilitySpec,添加到SlottedAbilitySpecs

URPGItem继承自UPrimaryDataAsset,是可以配置的数据资产

posted @ 2023-01-30 17:44  iku-iku-iku  阅读(208)  评论(0编辑  收藏  举报