UE4の勉強記録

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

「Unreal Engine 4.xを使用してRPGを作成する」の足りない部分を作成する MyCombatEngineの作成

f:id:kazuhironagai77:20200614230617p:plain

<前文>

前回、お話した英語の音素の区別がつけれるようになるゲームについての続報ですが、一応、今の所、このゲームの概要は以下のように考えています。

  • テスト版とトレーニング版を作成。テスト版は無料で、利用者が現在どれくらい英語の音素の区別がつけられるのかをテストするためのものです。このテスト版で満点が取れる人は全ての英語の音素の区別がネイティブと同じように出来る人と言う訳ではありませんが、かなり近い存在であると言う事になります。LとR、BとV、ThとSなどの音や、3つのアの音を含む全ての母音、更にR音が被った母音などの日本人が区別出来ていない音素を重点的にテストします。全部で150問程度の問題になるようにする予定です。
  • レーニング版は大体1500円位の価格を考えています。内容はRPGです。世界の各地に散らばっているボスのモンスターを倒すためには、10問連続で出される問題を全て正解しなければなりません。例えばLとRの音素の区別が出来ているかどうかのテストならRightとLightのどちらかの単語が発音されるので、正しいと思う方をプレイヤーが選択します。このテストで10回連続で正解する必要があります。倒したボスモンスターはカードになってプレイヤーのブックに保存されます。
  • 勿論、トレーニング版ですので、ボスのモンスターにチャレンジする前にたっぷりと練習出来る場所を作成しておきます。この練習で先週説明した凄い効果の出ている練習を行います。大体20分から1時間練習すればほとんどの人がその音に関しては区別が付くはずです。ゲームとしても丁度いい難度だと思います。
  • ゲームとしての面白さもないとユーザーは途中でゲームを止めてしまうので、ライバルや行方不明の主人公の親とか、海賊の財宝や世界征服を企む魔王なんかも登場する予定です。
  • 70から120のボスモンスターを倒す位の強さでゲームとしてはクリア出来るようにして残りは、ポケモンみたく集めるのが好きな人用に残しておきます。

私の周りの極少数の人に試しただけですが、このゲームで作成したトレーニングは英語の音素の区別に関しては凄い効果がありました。これだけの短い時間でこの効果を出せるなら大金を取っても全く問題ないと思いました。この言い方が分かり易い例えかどうか分かりませんが、外資や海外での転職を考えるビジネスマンにとっては20万や30万だしてもお釣りがくるトレーニングだと思いました。次の課題としてはどうやって商業的な成功を収めるのかと思っています。

それでは今週の勉強を始めます。

<本文>

やっと今週からCombatEngineの自作に取り掛かれます。ブログの名前も今週からMyCombatEngineの作成に変更しました。

いつもならバグ直しから始めるのですが、早く自前のCombatEngineを作成したいので、今週はバグ直しはなしで始めます。

1. CombatEngineの改良

1. CombatUIの直し

CombatUIのwidgetを以下の様に直しました。

f:id:kazuhironagai77:20200614230832p:plain

こんな感じで表示されます。

f:id:kazuhironagai77:20200614230911p:plain

2. CombatUIの青色のボックス内にコメントを表示させる。

以下の文章を青色のtext blockに表示させます。

f:id:kazuhironagai77:20200614230952p:plain

どうやってテキストに文字を表示させていたのか忘れてしまったのでちょっと復習します。

f:id:kazuhironagai77:20200614231029p:plain

これを見るとNPCDialog2からテキストを参照して表示していますね。その元のNPCDialog2はどこにあるのでしょうか?

f:id:kazuhironagai77:20200614231056p:plain

ありました。このWidgetの中です。

f:id:kazuhironagai77:20200614231121p:plain

うん。大体分かりました。これに今週はこれに沿って作成します。

まず、BlueprintImplementableEventで関数を作成します。

f:id:kazuhironagai77:20200614231146p:plain

以下に示すようにこの関数がBeginMakeDeiciion()関数が呼ばれた時に呼ばれるようにセットします。

f:id:kazuhironagai77:20200614231205p:plain

以下に示すようにCombatUIのBP内でこの関数、BeginMakeDecisionStart()関数を実装します。

f:id:kazuhironagai77:20200614231227p:plain

f:id:kazuhironagai77:20200614231244p:plain

テストします。

以下のように表示されました。

f:id:kazuhironagai77:20200614231332p:plain

一応、考えた通りに表示されています。shift+enterを使用する事で改行も出来ました(このサイトを参照)。

BlueprintImplementableEventで作成した関数は、どのBPからでも呼び出せるのでしょうか?

GameCharacterクラス内にBlueprintImplementableEventの特性を持つ関数を作り、その実装をBP内で出来たら作成がかなり楽になります。

ただしGameCharacterクラス自体にはBPはありません。

このサイトによれば、

f:id:kazuhironagai77:20200614231402p:plain

BlueprintかLevelのBlueprintで実装出来るとあります。

試してみます。

色々試しましたが、UE4C++から派生したBPからしか呼べないみたいです。

それよりもRPGGameModeBaseBP内に書いたコードが全く実行されません。勿論、RPGGameModeBaseBPをGameMode にセットしています。

f:id:kazuhironagai77:20200614231426p:plain

うーん。RPGGameModeBaseクラス内にBlueprintImplementableEventの特性を持つ関数を作成してRPGGameModeBaseBPと他のBPとコミュニケーションを取ろうと思っていたのですが…。

ダメ元で試してみます。

f:id:kazuhironagai77:20200614231447p:plain

f:id:kazuhironagai77:20200614231454p:plain

f:id:kazuhironagai77:20200614231503p:plain

テストします。

f:id:kazuhironagai77:20200614231527p:plain

あれ?出来ました。

RPGGameModeBaseクラスのBeginPlay()関数とTick()関数にはUFUNCTION()が付いていないし、実装でもSuper()を使用していないので、

f:id:kazuhironagai77:20200614231547p:plain

それらが原因でBP内のコードが実行されないのかもと思い、ひょっとしたらRPGGameModeBaseクラスのBeginPlay()関数から呼ばれた関数の場合は、BPで実装されていても実行されるかもと思いました。

それで試してみたのですが、この考え方が正しいのかどうかは分かりませんが、兎に角動きました。

GameCharacterクラス内にRPGGameModeBaseの変数を作成してそこから、BlueprintImplementableEventの特性を持つ関数を呼び出す事で、変則的ですが、GameCharacterクラスのそれぞれの関数が呼ばれた時にCombatUIのBP内のコメント欄に反映する事が出来そうです。

3. GameCharacterクラスからRPGGameModeBaseクラスのBlueprintImplementableEventの特性を持つ関数を呼び出す。

最初の問題は、RPGGameModeBaseクラスはGameCharacter.hをインクルードしているので、GameCharacter.hでRPGGameModeBase.hをインクルードするとcircular dependencyに成ってしまう事です。

以下に示すようにGameCharacter.hで

f:id:kazuhironagai77:20200614231625p:plain

Forward declarationをしてGameCharacter.cpp内で

f:id:kazuhironagai77:20200614231644p:plain

インクルードしました。

何故、RPGGameModeBase.hをGameCharacter.hでインクルードしなかったのかと言うと、それをするとbuildした時にCircular dependencyでエラーになったからです。今まで、散々、interfaceの時のcircular dependencyはForward declarationとインクルードを同時にヘッダーファイルでやっても勝手にcompilerが判断してcircular dependencyを回避しつつインクルードもしてくれるみたいだ。と言っていたのですが、今回のケースは全くそう言う事は起きなかったです。

うーん。良く分かりませんね。

来週考えます。

RPGGameModeBaseクラスのtestCombat()で以下に示すように敵のモンスターをGameCharacterクラスのコンストラクターを使用して作成していますが、

f:id:kazuhironagai77:20200614231706p:plain

良く見ると、RPGGameModeBaseのインスタンスをパスしています。

のでGameCharacterクラスのCreateEnemyMoster()関数内に、以下に示したコードを追加しました。

f:id:kazuhironagai77:20200614231808p:plain

テストしますと、

f:id:kazuhironagai77:20200614231831p:plain

RPGGameModeBaseクラスのBlueprintImplementableEventの特性を持つ関数、TestBlueprintImplementableEvent()が見事に実行されています。

4. RPGGameModeBaseクラスのTestBlueprintImplementableEvent()を実装してCombatUIの青色のコメント欄に文字を表示させる。

色々試したのですが、以下の方法で出来ました。

f:id:kazuhironagai77:20200614231909p:plain

GetGameMode()関数を使用してRPGGameModeBaseを獲得する。

そこからTestBlueprintImplementableEvent()関数を呼び出す。

例えばRPGGameModeBaseの変数をGameCharacter.hで宣言するとTestCombat()関数を実行するとエラーになりエディターがクラッシュします。

実装は以下に示したように行いました。

f:id:kazuhironagai77:20200614231935p:plain

結果は、

f:id:kazuhironagai77:20200614231956p:plain

となり、GameCharacterクラスのBeginMakeDecision()関数からRPGGameModeBaseクラスのTestBlueprintImplementableEvent()関数を呼び出す事に成功しました。

このやり方が、最も一般性がありそうです。このやり方で統一します。

5. UIwidgetの青色のコメント欄に以下のようなコメントを表示させる。

f:id:kazuhironagai77:20200614232052p:plain

RPGGameModeBase.h内に以下の関数を宣言しました。

f:id:kazuhironagai77:20200614232110p:plain

それぞれの関数をGameCharacterクラス内の関数から呼び出します。

f:id:kazuhironagai77:20200614232130p:plain

f:id:kazuhironagai77:20200614232138p:plain

f:id:kazuhironagai77:20200614232148p:plain

f:id:kazuhironagai77:20200614232155p:plain

更に、RPGGameModeBaseBP内でそれぞれの関数を実装します。

f:id:kazuhironagai77:20200614232215p:plain

f:id:kazuhironagai77:20200614232224p:plain

f:id:kazuhironagai77:20200614232234p:plain

f:id:kazuhironagai77:20200614232241p:plain

結果を示します。

f:id:kazuhironagai77:20200614232303p:plain

やっぱり、4.のやり方の方が見た目が良いですね。ただまだ追加しなければならない機能が2つあります。

まず、名前が表示されていません。

コメントも次から次へと表示されるので、プレイヤーが読む時間がありません。

これらの機能を作成していきましょう。

6. UIwidgetの青色のコメント欄のコメントに名前を表示させる。

どうやって表示させようか考えていたら、既にUIで名前を表示していました。

f:id:kazuhironagai77:20200614232329p:plain

これらがどうやって表示したのかを見ると、

f:id:kazuhironagai77:20200614232348p:plain

f:id:kazuhironagai77:20200614232356p:plain

単にパラメーターとしてパスしているだけでした。

同じ方法でやってみます。

f:id:kazuhironagai77:20200614232414p:plain

f:id:kazuhironagai77:20200614232422p:plain

取りあえず、一個だけパラメーターにGameCharacterを追加しました。

f:id:kazuhironagai77:20200614232443p:plain

BPの方での実装はこんな感じです。よく考えたらパスされたGameCharacterは攻撃されるターゲットではなくて攻撃する側でしたね。名前をselectedCharacterに変更しました。

f:id:kazuhironagai77:20200614232516p:plain

うん。良さそうですね。残りの関数も同じように改良します。

f:id:kazuhironagai77:20200614232533p:plain

いい感じで表示されていますね。

7. UIwidgetの青色のコメント欄に新しいコメントが表示されたら、プレイヤーがクリックするまで次のコメントは表示しないで待機する。

色々考えたんですが、結構難しいです。

取りあえずGameCharacterクラスのMakeDecision()関数の返り値が

f:id:kazuhironagai77:20200614232607p:plain

UIwidgetの青色のコメント欄内にコメントが表示された後にtrueになるように変更してみます。

GameCharacterクラス内に以下のコードを追加します。

f:id:kazuhironagai77:20200614232657p:plain

f:id:kazuhironagai77:20200614232710p:plain

f:id:kazuhironagai77:20200614232719p:plain

f:id:kazuhironagai77:20200614232729p:plain

RPGGameModeBase内でReportTypingFinished()関数を呼び出す関数を作成します。

f:id:kazuhironagai77:20200614232751p:plain

f:id:kazuhironagai77:20200614232758p:plain

この関数を今度はRPGGameModeBaseBPから呼び出します。

f:id:kazuhironagai77:20200614232825p:plain

Event MakeDecisionの最後に付けました。更にDelayを1秒にセットしたので、結構ゆっくりと表示されるはずです。

f:id:kazuhironagai77:20200614232848p:plain

ああつ。全然想像と違う結果が!

後、textboxって文章が多くなると勝手にスクロールしてくれると思っていたのですが、しませんね。

何か難しい事を考えたくない気分なので今回はここで止めて来週また挑戦します。

余った時間は何か簡単な事をする事にします。

2. Fontについて

f:id:kazuhironagai77:20200614232921p:plain

今利用しているFontはRobotoというやつです。UE4に付属のFontだと思いますが確信はないです。

このサイトによると

f:id:kazuhironagai77:20200614232938p:plain

勝手に使って良いみたいですね。

3. まとめと感想

たまには早く終わらせるのも悪くないので今週はここまでとします。

4. おまけ

1.6が出来ないのが納得出来なかったのでもう一回だけ試してみたら出来ちゃったのでここに記録しておきます。

GameCharacterクラスに二つのBoolean変数を追加します。

f:id:kazuhironagai77:20200614233024p:plain

BeginMakeDecision()関数内でその二つの関数にそれぞれfalseとtrueをアサインします。

f:id:kazuhironagai77:20200614233042p:plain

その二つの変数を使用してMakeDecision()関数を以下のように改良しました。

f:id:kazuhironagai77:20200614233104p:plain

まず、プレイヤーが行動を決定してtmpResult変数がtrueになると、一回だけテキストを表示するための関数ReportMakeDecision()が呼ばれます。呼ばれますがこのMakeDecision()関数はTrueは返しません。このMakeDecision()関数がtrueを返すためには、以下に示すhelper関数であるTextFinishTyping()を呼ぶ必要があります。

f:id:kazuhironagai77:20200614233128p:plain

このTextFinishTyping()関数はRPGGameModeBaseクラスの関数、TextTypingDone()によって呼ばれます。

f:id:kazuhironagai77:20200614233150p:plain

以下に示すようにこのRPGGameModeBaseクラスの関数、TextTypingDone()はBPから呼ぶ事が出来ます。

f:id:kazuhironagai77:20200614233221p:plain

以下に示すようにCombatUIのwidgetに読みましたボタンを追加しました。

f:id:kazuhironagai77:20200614233244p:plain

このボタンをクリックすると、Event DispatcherであるConfirmButtonIsClickedが呼ばれます。

f:id:kazuhironagai77:20200614233302p:plain

それをReportMakeDecision()関数の最後、Textをタイプし終わった後、バインドし、更にTextTypingDone()を呼ぶようにします。

f:id:kazuhironagai77:20200614233322p:plain

以下にテストの結果を示します。

f:id:kazuhironagai77:20200614233402p:plain

最初はこれだけが表示されています。

f:id:kazuhironagai77:20200614233422p:plain

攻撃をクリックしゴブリンをクリックすると

f:id:kazuhironagai77:20200614233440p:plain

と表示されそのままストップしています。

f:id:kazuhironagai77:20200614233458p:plain

をクリックすると

f:id:kazuhironagai77:20200614233518p:plain

が表示され、また一旦停止しました。

もう一度、読みましたボタンをクリックすると

f:id:kazuhironagai77:20200614233559p:plain

次のアクションのセリフが表示され戦闘が開始したのが分かりました。

ただし、文字がテキストに一杯になった後は、セリフが表示されていないみたいです。それは来週直します。