こちらは、連載記事の第3回となります。第1回・第2回を読んでいない方は、先にそちらからお読みください。
グレイマンにジャンプさせたり、パンチさせたりしてきましたが、今後の機能を拡張するにあたり、現在の構成のままだと若干不便な為、今回は、今まで作りこんできた構成を作り変えていきます。
GameplayAbilitySystemは、単純にジャンプ能力、パンチ能力を作るだけの仕組みではなく、能力に紐づく値(例えばスタミナ値)の管理や、パーティクルやサウンドの再生などを関連付けることができます。また、ネットワークにも対応したフレームワークなので、綺麗に実装しておけば、ネットワーク対応も容易になる(らしい)です。
前回までは、簡単に実装できる事を目標にしてきた為、簡易的な実装方法になってしまっていたので、今後の機能拡張を見据えてもっと実戦的な実装に切り替えていきましょう。
現在の構成を振り返る
第1・2回で作ってきた構成は以下のような感じです。C++を最小限しか使用せずに、GameplayAbilityを直感的に理解するには良い構成ですが、今後登場するGameplayAttributeを使うにはちょっとパフォーマンスが落ちてしまう構成となってしまいました。
これを以下のように変更します。
グレイマン自体にAbilitySystemComponentを持たせ、能力の追加もグレイマンに能力を渡せば追加されるという感じです。こちらの方が直感的ですね。ただ、この構成にするとなるとC++をもう少し多めに使うことになりますので、難易度が少し上がりますが、頑張って構成を変えていきましょう。
(具体的には、C++がどうこうと言うよりは、クラス設計のお話となります)
GASを使いやすくする為のクラス設計
グレイマンに限らず、キャラクターって「能力」を持っているもんですよね。
そう考えれば、グレイマンのベースとなっているCharacterクラスに対して「能力」を管理できる機能を追加し、それをグレイマンのベースとしてあげれば良さそうです。
何らかのクラスをベースとすることをC++言語では「継承」と言いますので、キャラクタークラスを継承した「能力を付与できるキャラクター」クラスを作成し、「能力を付与できるキャラクター」クラスをベースとしてグレイマンを作ればグレイマンに直接能力を管理させることが出来るようになるという事です。
早速、この構成で作っていきましょう。まずは、上述の通りキャラクタークラスを継承して「能力を付与できるキャラクター」を作ります。名前は”GameCharacter”クラスとしました。これは、このゲームに登場するキャラクターは基本的に全てのキャラクターが能力を保持すべきとの判断から、「このゲームのキャラクターのベース」クラスという意味の名前になります。
“GameCharacter”クラスを追加できたら、”プロジェクト名”.build.csファイルに、使用するモジュールとして”GameplayTags”を追加します。
// Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool;
public class GASSample : ModuleRules
{
public GASSample(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "GameplayAbilities", "GameplayTags" });
PrivateDependencyModuleNames.AddRange(new string[] { });
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}
“GameCharacter.h”は以下の通りです。
説明した通り、GameCharacterクラスには能力を保持させたいので、能力を保持する為の「AbilitySystemコンポーネント」と、それに対して「能力を追加」「能力を発動」させる為の関数(メソッド)を追加していきます。
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "AbilitySystemComponent.h"
#include "GameCharacter.generated.h"
UCLASS()
class GASSAMPLE_API AGameCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AGameCharacter();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
public:
// AbilitySystemコンポーネント
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Abilities, meta = (AllowPrivateAccess = "true"))
class UAbilitySystemComponent* AbilitySystemComponent;
// Abilityの登録
UFUNCTION(BlueprintCallable, meta = (DefaultToSelf = "Target"))
void AddAbility(TSubclassOf<class UGameplayAbility> Ability, int32 AbilityLevel);
// Abilityの発動
UFUNCTION(BlueprintCallable, meta = (DefaultToSelf = "Target"))
bool ActivateAbilitiesWithTags(FGameplayTagContainer AbilityTags, bool bAllowRemoteActivation);
};
それぞれの関数の中身は数行程度です。
コンストラクタ(クラスが生成されたと同時に処理される関数)には、「AbilitySystemコンポーネント」を生成する処理を入れました。
「能力を追加」関数は第1回で作成したBluepirntFunctionLibraryに定義した関数とほぼ同じ内容ですが、関数名やパラメータ(引数)を若干変更しています。(元々はAddAbilityToComponentでしたが、今回はAddAbilityという関数名となっています、これはどちらも「能力の付与」という機能は変わりませんが、前者は「(どのキャラクターに対してか知らんけど、パラメータで指定された)AbilitySystemコンポーネントに対して能力を付与する汎用関数」だったのに対して、後者は「自身(が内部的に保持しているAbilitySystemコンポーネント)に対して能力を付与する関数」に機能が変わっているからです。
また、GameplayAbilityを使った能力には「レベル」を保持させる事ができる為、それもパラメータ化しておきました。(現時点で使う予定はありませんが・・・)
「能力の発動」関数については第1~2回ではBP上でAbilitySystemコンポーネントからTryActivateAbilityWithTagノードをコールしていましたが、今回はキャラクター内部にAbilitySystemコンポーネントを含めた事から、BP上でAbilitySystemコンポーネントを意識する必要が無くなったので、TryActivateAbilityWithTagノードをコールするのではなく、キャラクター側に定義された関数で能力を発動するようになっています。
// Fill out your copyright notice in the Description page of Project Settings.
#include "GameCharacter.h"
// Sets default values
AGameCharacter::AGameCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// AbilitySystemコンポーネントを作成する
AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
}
// Called when the game starts or when spawned
void AGameCharacter::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AGameCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void AGameCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
// Abilityの登録
void AGameCharacter::AddAbility(TSubclassOf<class UGameplayAbility> Ability, int32 AbilityLevel){
if (AbilitySystemComponent && Ability){
AbilitySystemComponent->GiveAbility(FGameplayAbilitySpec(Ability.GetDefaultObject(), AbilityLevel, -1));
}
}
// Abilityの発動
bool AGameCharacter::ActivateAbilitiesWithTags(FGameplayTagContainer AbilityTags, bool bAllowRemoteActivation)
{
if (AbilitySystemComponent){
return AbilitySystemComponent->TryActivateAbilitiesByTag(AbilityTags, bAllowRemoteActivation);
}
return false;
}
早速コンパイルしたくなりますが、ちょっと待ってください。
現在、グレイマン自体にAbilitySystemコンポーネントが追加されている状態です。今後、グレイマンが上記で作成した”GameCharacter”クラスをベースとした場合に、BP側で追加した”AbilitySystemコンポーネント”が邪魔になってきます。(“GameCharacter”クラスに”AbilitySystemコンポーネント”を持っている為です)
ということで、ThirdPersonCharacterブループリントを開き、コンポーネントタブにある「AbilitySysteComponent」を削除します。
削除すると、今までAbilitySystemComponentを使っていた部分にエラーが発生するようになります。
これは、GameCharacterクラスを継承すれば治るので、「クラス設定」をクリックして・・・
詳細タブの「親クラス」にGameCharacterクラスを選択します。これで、今まで作ってきたGameCharacterクラスがグレイマンのベースとなる為、AbilitySystemコンポーネントや、能力の追加・発動の命令が使えるようになります。
BeginPlayノードと、Jumpアクションノードは、GameCharacterクラスに定義したAddAbilityノードとActivateAbilitiesWithTagsノードを呼び出すように修正します。
まとめ
今回は、機能の追加は行っていませんが、能力を与えたり、能力を発動するのがとても簡単に実装できるようになったのではないか?と思います。
UnrealEngineでC++とBPの比率の話しになることがありますが、これは2つの判断基準があると私は思っています。
1つはBPが簡潔に書けるようにベース(C++側)を構築する為のC++コードの量
2つ目はプログラムの実行速度がC++で実装した方が速く動作する為に組むC++コードの量
今回は、前者の対応としての例として見て頂ければと思います。
No responses yet