UE4の勉強記録

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

Unreal Engine4.xを使用してRPGを作成する」の10章、レベリング、能力、セーブを勉強する。

<前文>

f:id:kazuhironagai77:20190922172024p:plain

私にとって統計学ほど、得意なのに苦手な学問はないです。まず統計学が好きで勉強する事が苦しくない。使われている数学は一般的なレベルからしたら簡単ではないかもしれないけど、私には問題ない。

なのに根本が分からない。本当に良く分からない。何でこの式をこの場合は使用して、あの場合は使用しないの。みたいなのが全然分からない。有意差があるとか、ないとかを調べるのに何でその計算で分かるのかが全然分からない。

例えばこの前、ポケモンGOのレイドバトルを5人で6回したのですが、色違いが2人だけ出ました。確率は20分の1と聞いていたのですが、これって本当に20分の1の確率で色違いが出ているのか統計学の手法で証明したいのに、どうやったらいいのか全く分からない。

そしたらある人からこの本を進められました。

f:id:kazuhironagai77:20190922172432p:plain

マンガでわかる統計学 素朴な疑問からゆる~く解説 (サイエンス・アイ新書) 新書 – 2012/1/17

大上 丈彦  (著), メダカカレッジ (監修), 森皆 ねじ子  (イラスト)

この本、何故か表紙のイラストが違うバージョンが何個もあって、本屋に寄った時に無かったとと思ったらあった。という最初から何か変な出会いをした本でした。が、読み始めたら止まらなかったです。これが分からなかったという部分がドンピシャで説明されいました。山が一つ以上あるデータは使用出来ないとか、右のページに書かれているマンガで質問してる内容が、まさしく私が長年ずっと聞きたかった事だったりとかでした。

ちょっともう一度、統計学を勉強してみようかなと思っています。

それでは今週も勉強を始めていきます。

<本文>

<目的>

「Unreal Engine4.xを使用してRPGを作成する」の10章、レベリング、能力、セーブ(Leveling, Abilities, and Saving Progress)を勉強します。

教科書によると以下の事を勉強するそうです。

  1. プレイヤーがレベルを上げる事を可能にします。そのためにそれぞれのキャラクターに経験値システムを設定します。
  2. 戦闘のダメ―ジを一定にします。そうすると戦闘における攻撃にハードコードされた値ではなくキャラクターのステータスが関係するようになります。
  3. 戦闘のロジックを直します。
  4. レベルを得る事でキャラクターが活性化(activated)するための能力の作成に移ります。

具体的な節は、

  1. XPソースコードの水平化(XP and leveling source code)
  2. データテーブルの最初の値(Data Table starting value)
  3. ポーズメニュー内にレベルと経験を表示(Display levels and experience in the pause menu)
  4. 戦闘内における正しいダメージの適用(Applying the correct damage in combat)
  5. 能力の配列を作成する(Setting up the ability array)
  6. 能力のロジック(Abilities logic)
  7. セーブする(Saving)
  8. ロードする(Loading)

とありました。

<方法と結果>

<Step.0>

まずは、サンプルコードの10章を解凍してビルドします。

f:id:kazuhironagai77:20190922172759p:plain

出来ました。もう楽勝ですね。こんなんに苦労した時があったとは信じられないくらいです。

それではやって行きます。

<Step.1>

XPソースコードの水平化(XP and leveling source code)

Leveling source codeをソースコードの水平化と訳しましたが、レベルアップのためのコードが正しいのかもしれません。

  • パーティのメンバーが戦闘から経験値を得るには、コードに経験値(XP)用の変数を追加する必要があります。
  • XP変数は与えられたXPのキャップ(最大XPの略としてMXPと呼ぶ予定)まで、集める必要があり、もしそのキャップに当たったらプレイヤーのレベルが上がります。
  • これを行うためにはは、変数をソースコードに直接追加し、ゲーム内のそれぞれのパーティのメンバーと敵にそれを適用する必要があります。

ここまで読めば、レベルアップのためにUE4C++のコードに追加変更を行う事だったと分かりますね。

まず、最初にXPとレベル上げのデータをDataクラスに追加します。

教科書に書かれている通りに以下の変数をFCharacterClassInfoクラスに追加します。

f:id:kazuhironagai77:20190922172900p:plain

何かエラー表示になってますね。問題ないはずですが一応ビルドしてみます。

f:id:kazuhironagai77:20190922172919p:plain

ビルドは問題ないですね。VSをリフレッシュしてみます。

f:id:kazuhironagai77:20190922172942p:plain

はい。エラー表示が消えました。

<Step.2>

次にFEnemyInfo.hにXP変数を追加します。

はい。以下に示すように教科書に書かれている通りにXP変数をFEnemyInfo.hに追加しました。

f:id:kazuhironagai77:20190922173019p:plain

また、エラー表示になっている。

f:id:kazuhironagai77:20190922173038p:plain

ビルドは問題ないですが気になります。もう一度、VSをリフレッシュしておきますか。

f:id:kazuhironagai77:20190922173056p:plain

はい。直りました。

<Step.3>

このゲームのGameCharacterインスタンス内でこれらの変数を使用する必要があります。

はい。教科書通りに3つの変数をGameCharacter.hに追加しました。(サンプルコードと同じようにGold変数の下に追加しました。)

f:id:kazuhironagai77:20190922173211p:plain

こっちはエラー表示になりませんね。やっぱりFTableRowBaseクラスから派生しているFCharacterClassInfoクラスとFEnemyInfoクラスは普通のUE4C++のクラスと違う振る舞いをする場合があるのでしょうね。

<Step.4>

GameCharacter.cpp内でキャラクターのXP、MXP、そしてLvlをパーティのメンバーと同じようにセットします。

教科書の説明通りに実装していきます。

f:id:kazuhironagai77:20190922173246p:plain

追加しました。追加している時に気が付いたのですが、私のプロジェクトでは、

f:id:kazuhironagai77:20190922173332p:plain

isPlayerが括弧の外側に書かれていますが、教科書では括弧の内側に書かれています。

f:id:kazuhironagai77:20190922173350p:plain

間違えて書いてしまったのでしょうか?

考えてみました。

考えたんですが、このプレイヤーか敵かを知らせる変数は、データテーブルからキャラクターの特性が読めない場合でも必要な情報だと思います。ただしデータテーブルからキャラクターの情報が読めない場合は、

f:id:kazuhironagai77:20190922173420p:plain

UE_LOGでErrorになるんです。Errorの時はどうなるんでしたっけ。ひょっとしてプログラムの実行は止まる…のかも?

止まらないならisPlayerは括弧の外側の方が正しいと思うんですが。

うーん。

最初にisPlayerを書いた個所を探してみますか。

ありました。以外に簡単に見つかりました。3章のExploration and Combatの4節Turn-based Combat内のTarget Selectionにありました。1399/3870の位置です。

f:id:kazuhironagai77:20190922173450p:plain

この時は、isPlayerは括弧の外側に書かれています。

どの章で、isPlayerが括弧内に移ったかはサンプルコードをみれば分かります。それを調べて見ましょう。

なんと9章までisPlayerは括弧の外側に書かれていました。

f:id:kazuhironagai77:20190922173531p:plain

しかしこの9章のサンプルコード、良く見たら、未だにTestDecisionMaker()関数が残っています。単純にこのサンプルコードが正しいとは言えないような気がします。

やっぱり教科書でisPlayerを記載している箇所を逐一読んでいくしかないみたいです。

こういう時に、Kindleは便利ですね。すぐに検索できました。

f:id:kazuhironagai77:20190922173608p:plain

結論から言うとisPlayerをカッコ内に移動する記述はなかったです。今ここで突然、isPlayerは括弧内に移動しました。

ほっとく事にします。

因みに、TestDecisionMaker()関数は、以下に示すように3章のExploration and Combatの4節Turn-based Combat内のUI-driven decision making、1562/3870で外すように書かれています。

f:id:kazuhironagai77:20190922173633p:plain

伊達に3カ月もかけて3章をやっていたわけではないです。かなり細かい事まで覚えています。

<Step.5>

次に敵のためのCreateGameCharacter()関数内にXPをセットします。

教科書の説明通りにXPの値をセットしました。

f:id:kazuhironagai77:20190922173734p:plain

<Step.6>

XPフレームワークCombat Engineに追加します。

  1. XPTotal変数をpublicで追加します。
  2. このXPTotalは全ての敵を倒した時に戦闘で得られる全部のXPの値を保持します。

はい追加しました。

f:id:kazuhironagai77:20190922173839p:plain

<Step.7>

更に、XPの値をCombat Engineクラスのソースファイル内で実装します。

教科書通りに書きました。

f:id:kazuhironagai77:20190922173934p:plain

戦闘で手に入れるゴールドと全く同じやり方ですね。

<Step.8>

それぞれのパーティメンバーに獲得したXPをRPGGameModeに追加します。

しました。

f:id:kazuhironagai77:20190922174003p:plain

ここからはゴールドと違うみたいです。ゴールドは全部一括で管理しますが、経験値はそれぞれのパーティのメンバーによって別々に管理されるので別々に追加する必要があると言う事ですね。

<Step.9>

  • このForループの中で、XPMXPをチェックします。
  • もしパーティのメンバーの誰かのXPMXPを超えていたら、そのメンバーのレベルが上がります。

以下に示すように、Forループ内に教科書通りのコードを追加しました。

f:id:kazuhironagai77:20190922174053p:plain

うーん。これだと、レベルが上がっても、それぞれのパラメータが1しか上がりませんね。レベル上げに必要な経験値も単に前回の2倍にしています。レベルが上昇するのはRPGにおける最も楽しい瞬間なはずです。のでこれらの値を最適にする事はゲームの面白さを最大にするために最重要なはずです。これらの値は絶対にプログラマーが勝手にUE4C++内で決定していい事ではないはずです。ここは必ず、データシートを別に作成してそのデータシートを読み込む形にして、そのデータシートの値を変更する事でゲームデザイナーがレベル上げに関するそれぞれの値の最適値をC++をいじる事なく設定出来る様にすべきです。

しかし今回はこのままで行きます。私が自分のRPGを作成する時にどうすればいいか考える事にします。

<Step.10>

勝利の条件内のコードは以下に示すもの(教科書ではコードが書かれている)と同じになっているはずです。確認して下さい。

確認したら、以下に示すコードが私の方にはありません。

f:id:kazuhironagai77:20190922174224p:plain

ただし、その先、括弧を過ぎた後に、いくつかのコードがあり、さらにその先に

f:id:kazuhironagai77:20190922174241p:plain

ありました。

サンプルコードと比較すると結構違います。

f:id:kazuhironagai77:20190922174259p:plain

サンプルコードの方では、さらに私のプロジェクトにある、

f:id:kazuhironagai77:20190922174316p:plain

が無くなっています。

サンプルコードを見ると3章でこのコードは追加されています。その時は今の私のコードと全く同じです。更に続きを見ると、6章までは私のコードと同じで7章から現在のコードに変化していました。

これは7章で何かするのを忘れたみたいですね。

7章をもう一度見てみます。

確かに7章で変わっていました。7章の最初の節Setting and getting gold instances内で、Kindleの位置番号は2706/3870でした。ここで確かに、SetActorTickEnabled(true)の位置が変わり、更にbShowMouseCursorもなくなっています。

ただし何も説明はありませんでした。全く説明がなかったです。

ので、どちらが正しいか、分からないのでそのままにして使用します。コメントに以下のように書き残しました。

f:id:kazuhironagai77:20190922174402p:plain

ただ、今理解している限りでは、どっちに置いても同じ結果に成るような気がします。

例えば、bShowMouseCursorでマウスの表示を消していますが、widget内のブループリントでもWidgetを閉じる時はマウスの表示を消すコードを書いた気がします。

のでここでマウスの表示を消さなくても結果的には消えるような気がします。

SetActorTickEnabled(true)も戦闘が終了して味方が全滅した場合はGame Overになってしまうので必要ないでしょう。必要なのは勝利した場合のみです。

<Step.11 >

コンパイルしてプロジェクトを再起動してください。

はい。

f:id:kazuhironagai77:20190922174438p:plain

成功しました。

再起動する必要性は感じませんが、一応しました。

<Step.12>

データテーブルの最初の値(Data Table starting value)

やっと最初の節が終わりました。もうクタクタです。この節は簡単であってほしいです。

CharacterClassesデータテーブルを開き、SoldierのXPを0、MXPを200、Lvlを1にセットします。

f:id:kazuhironagai77:20190922174534p:plain

しました。

<Step.13>

  • 今度は、Enemiesデータテーブルを開きます。
  • XP50に設定します。

f:id:kazuhironagai77:20190922174610p:plain

はい。しました。

<Step.14>

ポーズメニュー内にレベルと経験を表示(Display levels and experience in the pause menu)

レベルと経験値をUIに表示します。

教科書によると、Pause_Mainウィジェットのデザイナーモードを見ると、編集可能なテキストボックスがあります。と説明されていますが、私のPause_Mainにはありませんでした。

f:id:kazuhironagai77:20190922174644p:plain

追加します。

f:id:kazuhironagai77:20190922174705p:plain

Text blockにsoldierの現在のレベルを表示します。

f:id:kazuhironagai77:20190922174734p:plain

XP/Next lvl.の隣のテキストブロックにXP/MXPを表示します。

f:id:kazuhironagai77:20190922174757p:plain

テストしてみます。

f:id:kazuhironagai77:20190922174820p:plain

表示されました。

試しにゴブリンを倒してその後にもう一度Pause_mainを表示しました。

f:id:kazuhironagai77:20190922174839p:plain

経験値が50ほど上昇しています。

f:id:kazuhironagai77:20190922174858p:plain

設定通りになっています。

<Step.15>

戦闘内における正しいダメージの適用(Applying the correct damage in combat)

  • 現在、プレイヤーと敵は、何をしても10ポイントのダメージを与えるだけです。
  • これを直していきます。

直しました。

f:id:kazuhironagai77:20190922174944p:plain

教科書が指定した方法で直しました。これを見ると、攻撃力(ATK)が防御力(DEF)を上回った分が、ダメージとして計算されるみたいです。

この辺はC++で固定してしまっていいのでしょうか。直観的には良いような気がします。ゲームのバランスはそれぞれのメンバーや敵のATKやDEFを調節する事で可能になるからです。ただしDQメタルスライムのような偶に攻撃が入るタイプの敵はこの計算方法では作成する事は出来ないと思います。

もう一つ、このif節の書き方についてですが、個人的にはあんまり好きではありません。普通のif節を読む時は、少なくとも脳の普段文章を読む部分を使用している感じがしますが、このif節を読むときは脳の数式を読む部分を使用している気がします。

読みやすさの点から素直にifを使用すべきだと思います。

この後、教科書ではBeginExecuteAction()関数の実装部を全て表示していて同じかどうか確認して下さいとあります。

確認したところ、全く同じでした。(良かった。)

<Step.16>

能力の配列を作成する(Setting up the ability array)

まずFStringクラスの配列である LearnedAbilities変数をGameCharacter.hに作成します。

しました。

f:id:kazuhironagai77:20190922175056p:plain

<Step.17>

GameCharacter.cppを開きLearned Abilities 変数をclass infoのLearnedAbilitiesアサイン(assign)します。

しました。

f:id:kazuhironagai77:20190922175132p:plain

教科書の例では、isPlayerが括弧の中にありましたが、前述したようにその部分は無視して先に進めます。

<Step.18>

コンパイルしてプロジェクトを再起動してください。

はい。

f:id:kazuhironagai77:20190922175216p:plain

出来ました。ここでも、再起動する必要性は感じませんが一応しました。

<Step.19>

  • 能力を保持し使用するためのスポットを作成します。
  • このゲームでは、能力は戦闘でしか使用しません。
  • まず、新しいボタンを戦闘のためのインターフェイスに追加します。

出来ました。

f:id:kazuhironagai77:20190922175250p:plain

f:id:kazuhironagai77:20190922175258p:plain

出来ました。

f:id:kazuhironagai77:20190922175317p:plain

<Step.20>

能力のロジック(Abilities logic)

Attack x 2の能力のロジックを作成します

まず、OnOpening(ComboBox_Abilities)イベントのロジックを作成しました。

f:id:kazuhironagai77:20190922175353p:plain

教科書通りに作成出来ているはずです。

次に、Event Show Action Panelイベントが発動した時に、Attackx2ブーリアン変数がfalseに、RPGGameInstance内のParty Member 配列内の最初の要素のSoldierのATKの値を元に戻して、MPから10を引きました。

f:id:kazuhironagai77:20190922175418p:plain

<Step.21>

教科書はテストしないで先に進んでしまっていますが、テストします。

f:id:kazuhironagai77:20190922175441p:plain

Attackx2を選択して攻撃ボタンを押すとGoblinのHPが20から8になりました。

f:id:kazuhironagai77:20190922175501p:plain

レベルが2になっていたのを忘れていました。ATK=5にレベル上げによる+1、そしてAttack * 2を追加した場合、ATKは12になりますので、20-12=8であってました。

それ以上のテストもしたかったのですが、この辺は自分のRPGを作成する時は作り直すと思うので止めました。

<Step.22>

ゲームのセーブとロードSaving and loading game progress

セーブ(Saving)

  • ほとんどのゲームは沢山の変数をセーブしますが、ここではプレイヤーのゴールドのみをセーブします。
  • 我々が使用する方法を使えば、ゲームの他の変数のセーブの仕方も簡単に判明出来るようになるからです。

とうとう最後の章の最後の節になりました。やって行きましょう。

新しいクラスをブループリントから作成します。親クラスはSaveGameです。名前はNewSaveGameとします。

f:id:kazuhironagai77:20190922175551p:plain

出来ました。

<Step.23>

NewSaveGameにIntegerタイプのGoldと言う名前の変数を追加します。

f:id:kazuhironagai77:20190922175631p:plain

出来ました。

<Step.24>

Pause_MainウィジェットのSaveボタンを押したらSaveを呼べるようにします。

まず、SaveボタンがPause_Mainウィジェット上になかったので作成しました。

f:id:kazuhironagai77:20190922175707p:plain

SaveボタンのonClickにバインドします。

f:id:kazuhironagai77:20190922175740p:plain

はっきり言ってこの部分の内容は全く知りませんでした。まずSaveGameクラスを知りませんでした。さらにSave Game to Slotと言う関数も知りませんでした。

この辺は後で勉強する必要がありますね。

<Step.25>

ロードする(Loading)

  • セーブと同じようにロードも沢山の方法でする事が出来ます。
  • ここではゲームを開始するときにプレイヤーのセーブしたデータをロード出来る様にします。
  • そのためにはFieldPlayerブループリントを使用します。
  • ゲーム中ならFiledPlayerは何時でも存在しているからです。

教科書の説明通りに作成しました。

f:id:kazuhironagai77:20190922175817p:plain

これでセーブしたデータをロード出来るのでしょうか?

<Step.26>

テストを全くしないのもあれなので、セーブとロードがPlayを実行した場合も使用出来るのかは分かりませんが、取りあえず試してみます。

f:id:kazuhironagai77:20190922175922p:plain

一回ゴブリンを倒してSaveボタンを押した後、ESCでゲームを中断してもう一度Playボタンを押してゲームを開始したところ、Goldの値が10になっていました。最初はGoldは0のはずなのでセーブとロードは機能していますね。

<考察>

今週は特に考察したい事は、プロジェクトの作成中にしてしまったので特にないです。

<まとめと感想>

これで、一応この教科書が終わりました。全体の考察は来週やりますが、感想を一言で言うと、大変勉強になった。です。この本に書かれている事だけでは、RPGを作成する事は出来ないと思いますが、この本で勉強した事を核にする事で優れたRPGを作成出来ると思います。