UE4の勉強記録

UE4の勉強の記録です。個人用です。

12章9節 GameplayAbilities API – アクターのゲームプレイアビリティ(gameplay abilities)をゲームコントロール(game controls)から引き起こす。Part3

<前文>

f:id:kazuhironagai77:20190113224701p:plain

で勉強しています。今回も前回の続きです。

前々回は、GameplayAbilities APIについて全く分からないまま取りあえずレシピを途中まで勉強しました。これはGameplayAbilities APIについての情報が全くネットから手に入らなかったからです。その時勉強して分かった事の一つが「Gameplay Ability Systemと今回のレシピで勉強している“GameplayAbilities API …”は同じ事。」です。

そこで、前回はGameplay Ability Systemについてネットで調べてまとめました。Gameplay Ability Systemの情報はそこそこですがネット上にありましたので、GameplayAbilities APIの全体像と目的が大分見えて来ました。

以下に前回まとめた「GameplayAbilities APIの目的と全体像についての推測」を示します。

1. GameplayAbilities APIの目的はアクションRPGマルチプレイ式のゲームにおいてプレイヤーとその敵とのお互いの干渉(例えば攻撃とそのダメージ)とそれぞれのキャラクターの持つパラメーターを管理するためのAPIであると考えられます。

2. このAPIを使用する最重要な点はマルチプレイ式のゲームにおいて複製と予測を行う事で、サーバーの許可がない状態でもクライアントの扱うキャラクターは攻撃出来き、もし後でその攻撃が無効だった場合、その攻撃がなかった事かつそれ以外はあった事にゲーム内の現実を変化する事が出来る事です。これによってクライアントはサーバーの返事を待たずに攻撃などの行動を行う事が出来き、ゲーム内のプレイヤーの反応が非常にスムーズになります。

3. GameplayAbilities APIは、主にAbilitySystemComponent, AttributeSets, GameplayTags, GameplayEffects,  GameplayCues, GameplayAbilities, GameplayTasksとGameplayEventsで構成されるとみられます。

今回は、前回出来なかったGameplayAbilities and You (Unreal Wiki)と日本語のサイトの勉強をします。

<本文1>

<前回の「上記のサイトらを読んで」の続き>

前回、GameplayAbilities APIの目的と全体像に対する大体のイメージが掴めたのでそれ以上の情報やそれに反する内容に注目して読んでいきます。

GameplayAbilities and You (Unreal Wiki)

GameplayAbilities and Youを読んでいきます。このサイトは他のGameplayAbilities APIのサイトの作者からも素晴らしいサイトであると紹介されていたのであえて最後に残しておきました。このサイトの作者の名前がKAZとあったので前回述べたように日本人なのかなと思っていたのですが、このサイトにはKJZに書かれたとあり、KAZじゃないじゃんと思っていたらフォーラムの方でKAZと呼ばれるけどKZJと書いてねとありました。(それでもKJZとは違うのね。)

最初にGameplayAbilityについて解説がありました。

f:id:kazuhironagai77:20190113224951p:plain

Dotaとかそれに類するゲーム内のアビリティのようなもの。

ファイヤーボールを投げてプレイヤーに当てたり(ダメージの量をセットする事で)爆発させたり、爆炎の半径内の全ての人を固定(時間によるダメージ)します。

一方で、ファイヤーボールを投げたプレイヤーはいくらかのマナを失いクールダウンに入ります。

やっぱりアビリティは炎を投げたり、水を浴びせたりする事も含まれるみたいですね。基本的には、Gameplay Abilities in Action RPGと同じ事を言っていますね。マルチプレイにおけるGameplayAbilities APIの役割についてはあえて省いているのでしょうか。

f:id:kazuhironagai77:20190113225031p:plain

やっぱり、みんなこれが疑問になるのですね。

この後、具体的なチュートリアルに入るのですが、このチュートリアルが教科書とやっている事が違うので、先に教科書のレシピを終わらせてから読む事にします。

日本語のサイトも教科書のレシピに戻る前に読んでおきます。

猫でも分かる UE4の新しいサンプル「Action RPG」について(p77~)

f:id:kazuhironagai77:20190113225057p:plain

ここで、処理を非同期に実行可能と述べているのは、「サーバーが許可する前にプレイヤーの魔法が取りあえず発動して…」の事を指していると思われます。

更にその後、処理の流れのイメージとして回復薬をゲットした例が載っているますが非常に分かり易い。今まで読んだ中で、一番単刀直入でズバズバと解説してます。

作者の名前が和也(敬称略)となっていたのですが、この方がGameplayAbilities and Youの作者なのでしょうか?

f:id:kazuhironagai77:20190113225115p:plain

これを見るとやっぱしアビリティは炎を投げたり、水を浴びせたりするアニメーションも管理しているみたいですね。更にGameplayAbilities はBPでも管理可能と書かれていました。これがGameplayAbilities and YouではGameplay AbilityのC++のコードが無い理由なのかもしれません。

GameplayAbilitiesの使い方(セットアップ編)

最後になってしまいましたが、おかわりはくまい氏による解説を見てみます。このサイトは「猫でも分かる UE4の新しいサンプル「Action RPG」について(p77~)」でも紹介されてたので最後にしました。

サラッと読んだ限りでは、GameplayAbilities and Youと「猫でも分かる UE4の新しいサンプル「Action RPG」について(p77~)」の両方を兼ねて、更に実際にコードを実装した場合の注意点が書かれているようです。実際にコードを書いて実行する段階になったら何回もお世話になる気がします。

このブログの最後に書かれていたのが、以下の文章です。

f:id:kazuhironagai77:20190113225234p:plain

やっぱりあえてマルチプレイ式のゲームにおけるGameplayAbilities APIについては抜かしているのですね。学習者が理解しやすいように削れるだけ削った基本のみを教えているのようです。

<取りあえずここまでのまとめ>

前回まとめた「GameplayAbilities APIの目的と全体像についての推測」に反する内容はなかったです。

更に、追加で以下に示す内容が分かりました。

  1. GameplayAbilities はBPでも管理可能
  2. 学習者が理解しやすいように、マルチプレイ式のゲームにおけるGameplayAbilities APIについてはあえて述べてないようです。

<本文2>

今週はこれで終わりにするには流石に短すぎるので前々回のPart1の続きを勉強します。

<方法>

Step.3

f:id:kazuhironagai77:20190113225508p:plain

前回は、ActivateAbilityメンバー関数まで調べたので、今回はInputPressedメンバー関数から調べていきます。

InputPressed

サンプルコードを以下に示します。

f:id:kazuhironagai77:20190113225529p:plain

スタブとバインドするインプット

コメントがそれしか書かれていませんでしたので、UE4の元のコードを見てみます。

f:id:kazuhironagai77:20190113225619p:plain

同じでした。もう少しこの関数の解説が見つからないか色々探したのですが見つかりませんでした。

次にこの関数のパラメーターについて調べて見ます。

<FGameplayAbilitySpecHandle

前々回、CanActivateAbilityメンバー関数に使用されるパラメーターとして調査済み。

<FGameplayAbilityActorInfo

このパラメーターもCanActivateAbilityメンバー関数に使用されるパラメーターとして調査済み。

<FGameplayAbilityActivationInfo

前々回、ActivateAbilityメンバー関数に使用されるパラメーターとして調査済み。

前々回のプロジェクトChapter12part8にこの関数を追加してビルドしたら

f:id:kazuhironagai77:20190113225646p:plain

エラーになってしまいました。この関数を抜いてもう一度ビルドしても同じエラーが出るようになってしまいました。

ウーン良く分からないですが、2週間前に何かしてしまったのかも。そういえばスクリーンショットの撮影をするために元のコードを何行にも分割した時に、このコードは読み専用だけと変えていいんですか?みたいなダイアログが表示されたような。

面倒なので、最初から作り直します。今度のプロジェクト名はChapter12Part9とします。

GameplayAbility_Attackを派生してビルドするとやっぱりエラーになってしまいます。しかたないのでversion4.21.1を再インストールしました。

今度は、ビルドしました。

f:id:kazuhironagai77:20190113225709p:plain

前々回はここから、コンストラクターを追加したのですが、無くてもいいような気がしますので、CanActivateAbility、CheckCost、ActivateAbilityそしてInputPressedメンバー関数のみを追加します。

f:id:kazuhironagai77:20190113225751p:plain

もうめんどくさいのでサンプルコード通りに全部ヘッダーファイルに追加してしまいました。

f:id:kazuhironagai77:20190113225817p:plain

ビルドも出来ました。

Step.4

f:id:kazuhironagai77:20190113225850p:plain

具体的なやり方が書いていないのですが、以下の方法で派生しました。

f:id:kazuhironagai77:20190113225906p:plain

Step.5

f:id:kazuhironagai77:20190113225936p:plain

選択しました。

f:id:kazuhironagai77:20190113230024p:plain

名前が分からない。適当でいいはずですがDataフォルダーに入っているこれらのファイルの中で多分これを表しているWarriorGameplayAbilitySetとします。

f:id:kazuhironagai77:20190113230107p:plain

GameplayAbilitySetを調べて見ます。

f:id:kazuhironagai77:20190113230133p:plain

これはアビリティのセットを定義するために使用されるDataAssetの一例です。それはAbilitySystemComponentに与えるためのものでインプットコマンドにバインドします。

しかしながらそれが望んでもあなたのプロジェクトがこれを実装する事は自由です。

とありました。

f:id:kazuhironagai77:20190113230303p:plain

と紹介されていたのでそれも調べてみると

f:id:kazuhironagai77:20190113230330p:plain

確かに唯一のメンバー関数としてGiveAbilities()があります。

Step.6

f:id:kazuhironagai77:20190113230404p:plain

名前が分からないので勝手にWarriorGameplayAbilitySetとしましたが合ってました。

Step.7

f:id:kazuhironagai77:20190113230436p:plain

WarriorAbilitySetをダブルクリックしたら以下に示すイメージが現れました。

f:id:kazuhironagai77:20190113230456p:plain

どこにもTArrayオブジェクトの+などないじゃないか。

確認のためにサンプルコードの方のWarriorAbilitySetを強引にChapter12Part9プロジェクトのコンテントフォルダー内に移して表示させてみました。

f:id:kazuhironagai77:20190113230757p:plain

全然違う。

もう一度作り直して見ました。

f:id:kazuhironagai77:20190113230818p:plain

今度はきちんと出来ました。ので説明にあるようにBP_GameplayAbility_Attackを選択しました。

Step.8

f:id:kazuhironagai77:20190113230851p:plain

まず、Warriorクラスって何ですか?いつそんなの作ったの?と。こんな時はまずサンプルコードに当たります。

ありました。

f:id:kazuhironagai77:20190113230913p:plain

取りあえず、Characterクラスの派生クラスとして作成していますね。このまま作成します。

f:id:kazuhironagai77:20190113230940p:plain

こんな感じに出来ました。

f:id:kazuhironagai77:20190113231032p:plain

インターフェイスがありませんがそのまま行ってみます。

UGameabilitySet…を追加します。

f:id:kazuhironagai77:20190113231055p:plain

f:id:kazuhironagai77:20190113231103p:plain

ビルドしてみます。

f:id:kazuhironagai77:20190113231121p:plain

成功しました。

次の手順ですが、

f:id:kazuhironagai77:20190113231245p:plain

どうやって選択するのか分かりません。

UPROPERTY(EditAnywhere, BlueprintReadWrite…とある事からエディター上かBP内で選択するのでしょうが具体的な方法は書かれていません。

ここは無視していきます。

Step.9

f:id:kazuhironagai77:20190113231428p:plain

やっぱりインターフェイスからも派生していないといけないのですね。

f:id:kazuhironagai77:20190113231446p:plain

f:id:kazuhironagai77:20190113231454p:plain

と追加しました。ビルドをすると

f:id:kazuhironagai77:20190113231534p:plain

エラーになってしまいました。

E0077をクリックしたら、C++ object of abstract class type "AWarrior" is not allowed: pure virtual function "IAbilitySystemInterface::GetAbilitySystemComponent" has no overriderとありました。

なので、GetAbilitySystemComponentメンバー関数を追加します。

サンプルコードのGetAbilitySystemComponentメンバー関数:

f:id:kazuhironagai77:20190113231555p:plain

とそれを実装するのに必要なコードを追加しました。

f:id:kazuhironagai77:20190113231618p:plain

f:id:kazuhironagai77:20190113231626p:plain

f:id:kazuhironagai77:20190113231635p:plain

ビルドします。

f:id:kazuhironagai77:20190113231659p:plain

今度は成功しました。

Step.10

f:id:kazuhironagai77:20190113231750p:plain

GameplayAbilitySetクラスのGiveAbilities()メンバー関数は使用すべきではないとテップで述べていますがここでは使用するのでしょうか?

先を読んでみます。

Step.11

f:id:kazuhironagai77:20190113231817p:plain

これは何を言っているのか分かります。まず、サンプルコードのSetupPlayerInputComponent関数を見てみます。

f:id:kazuhironagai77:20190113231847p:plain

同じコードをWarrior.h ファイルに追加します。と思ったら元からこのコードがありました。

f:id:kazuhironagai77:20190113231906p:plain

サンプルコードのWarrior.cppファイルには、SetupPlayerInputComponentメンバー関数の実装が書かれていますが、今ここでそれをしないといけないのでしょうか?

残りはティプだけでした。

f:id:kazuhironagai77:20190113231924p:plain

やります。

まず、サンプルコードから見てみます。

f:id:kazuhironagai77:20190113231942p:plain

This, &AWarrior::Forward)はAWarrior::Forward関数を指しているはずです。しかしForward関数らはないので作る必要があるはずです。

サンプルコードをみると

f:id:kazuhironagai77:20190113231959p:plain

f:id:kazuhironagai77:20190113232010p:plain

ありました。まず、これから作っていきます。

以下のコードをWarrior.hファイルに追加しました。

f:id:kazuhironagai77:20190113232029p:plain

次にWarrior.cppファイルに以下のコードを追加します。

まずlastInputを初期化します。

f:id:kazuhironagai77:20190113232049p:plain

実装します。コードはサンプルコードと全く同じです。

f:id:kazuhironagai77:20190113232120p:plain

サンプルコードではTick関数でlastInputをアップデートしていたので、そのコードをそのままコピーしました。

f:id:kazuhironagai77:20190113232140p:plain

準備が出来たので、SetupPlayerInputComponent関数に追加します。

f:id:kazuhironagai77:20190113232159p:plain

f:id:kazuhironagai77:20190113232212p:plain

次にサンプルコードではAbilitySystemCompnenetをアクターのインプットコンポーネント(input component)に連結しています。のでそのままコピーしました。

f:id:kazuhironagai77:20190113232231p:plain

Forループがありました。教科書には、

GameplayAbilitySetAbilityグループ内でリスト化したそれぞれのGameplayAbilityを反復(iterate)します。

とありましたが、これをこのForループで行っているのでしょうか?見ていきましょう。

f:id:kazuhironagai77:20190113232339p:plain

GameplayAbilitiySet内にあるそれぞれのBindInfoを調べます。

それぞれのAbilitySystemComponentを始動し試してみます。

Warriorクラスのメンバー変数でUGameplayAbilitySetクラスから作成したオブジェクト、GameplayAbilitySetはエディター上かBP内で、私がUGameplayAbilitySetクラスから作成したBPであるWarriorAbilitySetを選択するはずなので、Abilityはその中にあるBP_GameplayAbility_Attackのみが選ばれるはずです。

ちなみに、GameplayAbilitySetクラスのメンバー変数のAbilityは以下に示すようにFGameplayAbilityBindInfoクラスのTArrayです。

f:id:kazuhironagai77:20190113232428p:plain

UとFからUGameplayAbility_AttackはFGameplayAbilityBindInfoの派生ではないと考えられるので、FGameplayAbilityBindInfoクラスのメンバー変数であるAbilityがUGameplayAbilityクラスからの派生クラスUGameplayAbility_Attackから作成したBP_GameplayAbility_Attackを保持出来る理由が分かりません。

FGameplayAbilityBindInfoがどんなクラスか元のコードを当たって見ました。

f:id:kazuhironagai77:20190113232448p:plain

はい、良く分かりました。これならFGameplayAbilityBindInfo構造体は、UGameplayAbilityクラスからのオブジェクトを何個も保持できますね。

f:id:kazuhironagai77:20190113232507p:plain

このまんまの構造体ですね。

次のコードを見ていきます。

f:id:kazuhironagai77:20190113232525p:plain

BindInfoは2つのメンバーを持ちます。

Command (Enum value)

GameplayAbilityClass (UClass of a UGameplayAbility)

そのままの説明がサンプルコードにありました。

このif文はGameplayAbilityクラスが本当に存在しているのかチェックしています。GameplayAbilityクラスの指定はエディター上で行われるのでこのチェックは絶対必要ですね。

f:id:kazuhironagai77:20190113232611p:plain

FGameplayAbilitySpecについて調べます。

f:id:kazuhironagai77:20190113232629p:plain

アビリティシステムコンポーネント(ability system component)にホストする、活動できるアビリティのスペック。

これはアビリティが何であるか(クラス、レベル、インプットバインドなど)と

アビリティの外側でインスタンス化/始動を保持しなければならないランタイムの状態の維持の両方を定義します。

アビリティシステムコンポーネント(ability system component)が何なのか不明ですね。

取りあえず、以下にFGameplayAbilitySpec関数のパラメーターから調べていきます。

f:id:kazuhironagai77:20190113232811p:plain

まず、最初のパラメーターInAbilityClassはUGameplayAbilityクラスです。これは解説文にある「アビリティが何であるか(クラス、レベル、インプットバインドなど)の定義」におけるクラスの部分を担当していると考えられます。更に次のパラメーターであるInLevelがレベル、その次のパラメーターであるInInputIDがインプットバインドを担当しているのでしょう。そこだけは分かりました。インプットバインドにBindInfo.Commandをパスするのは何となく分かりますが、レベルが1とは実際は何を指しているのか全く分からないですね。

もうちょっとFGameplayAbilitySpecクラスのコードを調べて見ると、このレベルはFGameplayAbilitySpecクラスメンバー変数であるLevelを定義するのに使用しているようです。

f:id:kazuhironagai77:20190113232835p:plain

これはひょっとするとキャラクターのレベルを表しているのかもしれませんね。キャラクターのレベルによって攻撃力は変化しますし。

以下に示したようにそのままコピーしました。

f:id:kazuhironagai77:20190113232853p:plain

次の行に行きます。

f:id:kazuhironagai77:20190113232911p:plain

後にアビリティの呼び出しをするためのアビリティのハンドルを保持する。

まず、FGameplayAbilitySpecHandleクラスを調べます。

f:id:kazuhironagai77:20190113233018p:plain

特定の許可されたアビリティへのポイントを扱う。

全体で唯一である。

とありました。後クラスでなく構造体(struct)でした。次の

f:id:kazuhironagai77:20190113233112p:plain

AbilitySystemComponentですがGetAbilitySystemComponentメンバー関数を追加した時に作成したのですが、そのクラスが何をするクラスなのか調べる事は忘れていました。今、調べます。

f:id:kazuhironagai77:20190113233135p:plain

う!

何だかスゴイクラスですね。よく見たら、GameplayAbilities APIを構成すると思われる8大コンセプトのAbilitySystemComponent, AttributeSets, GameplayTags, GameplayEffects,  GameplayCues, GameplayAbilities, GameplayTasksとGameplayEventsの内の一つですね。これはしっかり調べておいた方がいいみたいですね。

来週やります。

今週はここで終了します。

<中断>

f:id:kazuhironagai77:20190113233216p:plain

取りあえずここまでビルド出来る事は確認しました。続きは来週やります。