UE4の勉強記録

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

「Unreal Engine 4.xを使用してRPGを作成する」の足りない部分を作成する 戦闘システムを完成する

f:id:kazuhironagai77:20201115202544p:plain

<前文>

面白いカードゲームのコンセプトを思い付きましたのでここに書いておきます。

遊戯王系のカードゲームの弱点って、多人数でワイワイ遊べない事だと思うんです。1対1じゃないとルールが難しすぎて運用出来なくなる。

その原因は、持っているカードが無くなる事が勝ちではないからです。トランプは持ち札が0になったら大抵勝ちです。

そこで私の考えたこのカードゲームでは先に持ち札が0になった時点で勝ちになります。

木、火、水の1から13の番号が振ってあり計39枚のカード。最低限、これらのカードを持っている必要があります。更にカードを追加するのはOKです。ただし最大50枚までとします。勿論しなくてもOKです。

2が一番弱く1が一番強いです。ただし2は1にだけは勝ちます。ただし木のカードは火に弱く火のカードは木のカードに対してナンバー+3の強さになります。ただし1と2にはこの原則は通用しません。火と水、水と木の関係も同じです。相手が出したカードより強いカードを出す必要があります。出したカードは廃棄場に行きます。

それぞれのカードには特殊な能力が付随している場合があります。例えば革命、とかスキップ、廃棄場のカードを5枚元の札に戻す。みたいなのです。特別なカードは雷タイプとか、相手のカードから13を捨てさせる魔法カードとか考えられるかもしれません。

このカードゲームだと多人数で遊べます。勿論2人でも遊べます。

うーん。あまりゲームをしない私個人の感想では大変面白そうですし、blockchainの技術と結び付けたら色々な意味でもっと面白くなりそうでもあります。例えば世界で5枚しかないカードとかも作れるかもしれません。

このRPGの作成が終わったら今度はカードゲームを作ってみますか。

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

<本文>

今週は戦闘システムを完成させます。

正しその前に先週やり残したことを直します。

  1. 宿屋に泊まった時に5秒位のラグを作成して寝た感じを出す。
  2. 起きた時は宿屋で起きる。
  3. Event dispatcherについて調べる。特にレベルBPウィジェットBP間で連絡できるかどうか調べる。

戦闘システムで大きな箇所でまだ作成していない箇所は

  1. 戦闘開始時にワープして戦闘終了時に元の箇所にワープして戻ってくる
  2. 敵のモンスターの配置とアニメーション

の二つです。更に敵のモンスターのアニメーションに合わせてコメントが必要になるはずです。

これらをやって行きます。

1.先週の直し

1.1 宿屋に泊まった時に5秒位のラグを作成して寝た感じを出す

以下のイメージを5秒間表示しました。

f:id:kazuhironagai77:20201115202702p:plain

1.2 起きた時は宿屋で起きる。

これは最初から出来ていました。場所が変わるのは別なマップに移動して戻ってくる時でした。

1.3 Event dispatcherについて調べる。特にレベルBPウィジェットBP間で連絡できるかどうか調べる。

まず、Event dispatcherがcustom eventとどう違うかが分からないです。一般的な使用方法を調べます。その上でレベルBPとウィジェットBP間での連絡の取り合いが出来るのか調べます。

大体分かりました。

Third Person Character BP内にEvent Dispatchersを作成しました。

f:id:kazuhironagai77:20201115202733p:plain

このEvent Dispatcherはcall, bindの機能があります。他にもありますがこれだけ知っていれば良いです。

まず、Callですが、

f:id:kazuhironagai77:20201115202752p:plain

手紙の絵がついています。手紙と言うよりは電話です。手紙は好きな時に開ければいいですが、電話は直ぐに出なければなりません。ではこのdispatcherはどこに電話をかけているのでしょうか?

f:id:kazuhironagai77:20201115202817p:plain

そうです。Bindした先のeventです。

こうやってevent dispatcherはBP間でeventのやり取りをします。

しかしここで問題があります。上記の例ではwidget BPとLevel BPの両方からアクセス出来るThirdPersonCharacterクラスにevent dispatcherを作成したのでwidget BPとLevel BP間で連絡とれましたが、level BPにevent dispatcherを作成した場合はどうなるのでしょうか?どのBPからもアクセスする事が出来なくなりますよね。

f:id:kazuhironagai77:20201115202856p:plain

しかし普通にLevel BPにもEvent Dispatcherがあるんです。

うーん。この辺がまだ疑問ですね。

1.4 Event Dispatcherを使用して宿屋に泊まった時に朝になるようにする。

RPGGameInstanceBP内にevent Dispatchers、SetTimerSpend0を作成します。

f:id:kazuhironagai77:20201115202932p:plain

Widget、Inn_WelcomeBPで泊まるボタンを押した時にSetTimerSpend0がcallされるようにします。

f:id:kazuhironagai77:20201115202951p:plain

LevelBPのEvent BeginPlayでSetTimerSpend0をEvent Time SpendにBindします。

f:id:kazuhironagai77:20201115203010p:plain

これでInn_WelcomeウィジェットのBP 内でSetTimerSpend0がcallされる度にLevelBP内でEvent Time Spendが実行されるはずです。

テストします。

f:id:kazuhironagai77:20201115203029p:plain

泊まった後は朝に成りました。出来ました。

完璧とは言えませんが前回の方法よりはコストが低くなっているはずです。

2. 戦闘システムにワープ機能を追加する

2.1 ワープ先のマップを作成する。

Infinity Blade:Propsをプロジェクトに追加しました。

f:id:kazuhironagai77:20201115203116p:plain

Infinity Blade:Propsから以下のPropsをコピーして、

f:id:kazuhironagai77:20201115203137p:plain

以下に示したマップを作成しました。

f:id:kazuhironagai77:20201115203156p:plain

更に、GoodSkyのBPを追加して天気を嵐にしました。

f:id:kazuhironagai77:20201115203216p:plain

実際のplay画面です。

f:id:kazuhironagai77:20201115203236p:plain

今必要な事は戦闘になった時にワープするための別のmapで、そのmapにデザインの良さや完成度は必要ありません。しかしこの2~3週間レベルデザインの勉強に費やしたので、アートとしてもマシなデザインを少しだけ心がけました。

2.2 プログラミングスキルを持つデザイナー

以下のマップは、私が最初にInfinity Blade:Propsを使用しないで作成したmapです。

f:id:kazuhironagai77:20201115203314p:plain

f:id:kazuhironagai77:20201115203322p:plain

デザイン的に満足出来ないので没にしました。

ここで気が付いたんですが、デザイナーが望むデザインをUE4内で作成するためには、結局デザイナーもプログラミングスキルをマスターする必要があると言う事でした。

例えばこの動画UE4 Optimization: Instancing)にあるように

f:id:kazuhironagai77:20201115203344p:plain

非常に沢山の隕石をmap上に配置したいとして単にstatic mesh actorを沢山配置していったらクラッシュします。もしデザイナーがこのような3D空間を再現したいならば、きちんとしたプログラミングの技術が必要になります。

今まで何となくですが、学校の勉強と芸術の能力は相反するので優れたデザイナーは英語や数学は出来ない。と言うイメージで居たのですが、実際は作りたい芸術が最初にあってそれを実現するためにプログラミングも学習すると言う、学校の勉強が出来るデザイナーも必要みたいですね。

ただ本格的にプログラミングするためには、どうしても理系の大学数学を理解していないと出来ませんが、そういうのってデザイナーはどこで勉強するんでしょうか?国立の美大とかはそういうのも勉強するんでしょうか?入試に数学があるんですかね?それとも元々理系の院卒の人で、どうしてもゲームが作りたい人がゲーム制作の専門学校なんかでデザインを勉強するんでしょうか?

2.3 ワープして戻ってくる。

結局、戦闘用のマップは以下の様になりました。

f:id:kazuhironagai77:20201115203409p:plain

f:id:kazuhironagai77:20201115203417p:plain

もう少し時間を掛ければもっと良くなりそうです。

ワープ機能を作成します。

RPGGameInstanceBPにGameCharacterの位置を保持する変数、

f:id:kazuhironagai77:20201115203442p:plain

を作成します。Defaultの値はmap1のThirdPersonCharacterの配置している場所を入れておきます。

f:id:kazuhironagai77:20201115203518p:plain

Map1のBPに以下のコードを実装しました。別なマップに移動する前にGameCharacterの位置をRPGGameInstanceBPのThirdPersonCharacterにセットしておきます。

f:id:kazuhironagai77:20201115203549p:plain

これでIをクリックしたら別のマップであるBattleFieldに移動するはずです。

BattleFieldのBPでは以下のコードを実装しました。

f:id:kazuhironagai77:20201115203608p:plain

Iを押すと元のマップに戻ります。

Map1に戻ると、EventBeginPlayが発生するので、その時に前にRPGGameInstanceBPのThirdPersonCharacterにセットした値を、GameCharacterのRootComponentの位置にセットします。

f:id:kazuhironagai77:20201115203627p:plain

これで出来るはずです。テストします。

分かり易い様に位置を移動してテストします。

f:id:kazuhironagai77:20201115203649p:plain

戦闘空間に移動しました。

f:id:kazuhironagai77:20201115203708p:plain

戻りました。前と同じ位置に戻っています。

f:id:kazuhironagai77:20201115203727p:plain

時刻がズレているのはここで記事を書いた後でスクリーンショットを取ったからです。

出来ましたね。

3. 敵のモンスターの追加

3.1 ワープ先でTestCombat()関数を呼びだせるようにする

この部分を作る事をすっかり忘れていました。

RPGGameModeBaseクラスの関数、testCombat()のspecifierをexecからBlueprintCallableに変更します。

f:id:kazuhironagai77:20201115203807p:plain

BattleFieldのレベルBP内のEventBeginPlayでRPGGameModeBaseクラスのtestCombat()関数を呼び出します。

f:id:kazuhironagai77:20201115203826p:plain

これで、BattleFieldに移った瞬間に戦闘が開始するはずです。

テストします。

f:id:kazuhironagai77:20201115203847p:plain

開始しました。

その後の戦闘も特に問題なく出来ました。

3.2 敵のモンスターの追加

それでは敵のモンスターを表示します。

3.2.1 CombatEngine内ではどのように敵のモンスターは作成されそのデータは保持されているのか調べる

登場するモンスターは一種類ではないので、色々なモンスターを登場させる必要があります。CombatEngine内でどのように色々なモンスターの登場を管理しているのかを確認します。

RPGGameModeBaseクラスのTestCombat()関数内のモンスターの作成箇所をみると

f:id:kazuhironagai77:20201115203922p:plain

色々なモンスターの登場は管理していませんでした。このやり方だと一種類のモンスターしか登場させる事は出来ません。

ウーン。今回は適当に出現させても良いかもしれませんね。

因みにデータテーブル、EnemyMonstersの内容は以下の様になっています。

f:id:kazuhironagai77:20201115203941p:plain

その後で上記で作成したRow変数をGameCharacterクラスのCreateEnemyMonster()関数にパスしてモンスターは作成されています。

f:id:kazuhironagai77:20201115204002p:plain

因みに、ユーザーが操作するGameCharacterがRPGGameInsatanceクラスで管理されているのと違い、モンスターを管理する変数EnemyMonsterはRPGGameModeBaseクラスにあります。

3.2.2 敵のモンスターの3Dはどのように作成されるのか調べる。

今度は敵のモンスターの3D イメージがどのように作成されるかを確認します。ずっと前に作成した奴です。

f:id:kazuhironagai77:20201115204030p:plain

ゲームを開始すると現れます。どうやって作成したのかはもう全く覚えていません。

確か、RPGGameModeBaseクラスでモンスターの3Dイメージの出現は実装したはずなのでRPGGameModeBaseクラスをまず見てみます。

まずヘッダーファイルから見ます。

f:id:kazuhironagai77:20201115204048p:plain

Ach4_3Monsterクラスって何?

全く覚えていません。Aが付いているってことはactorクラスの派生クラス何でしょうか?

f:id:kazuhironagai77:20201115204118p:plain

Characterクラスから派生させていました。

f:id:kazuhironagai77:20201115204136p:plain

コードを見ましたが特に追加している部分はありません。後で、Characterクラスに無い機能を追加する時のために、念のために作成したのかもしれません。

ここではCharacterクラスと同じとみなして読んでいきます。

モンスターの3Dイメージの出現は実装は、BeginPlay()関数内で実装されていました。

f:id:kazuhironagai77:20201115204156p:plain

思い出してきました。この実装部分、公式のUE4C++のチュートリアルに載っていたやり方を参考にして作成したんでした。

このやり方だと、発生させるモンスターをBP側でコントロール出来るので違うモンスターを選ぶ事が簡単に出来そうです。

f:id:kazuhironagai77:20201115204215p:plain

正し、問題もあって、BeginPlay()関数を以下に示したような形でoverrideしているせいなのか

f:id:kazuhironagai77:20201115204235p:plain

BP側のBeginPlay()関数は機能しなくなっています。

f:id:kazuhironagai77:20201115204256p:plain

この問題が何故起きるのか、どうにかすれば回避できるのか、又回避すべきなのか、全然分かりません。後で勉強します。

色々課題はありますが、モンスターの3Dイメージの出現方法はこれで分かりました。

3.2.3 敵のモンスターの3Dを出現させる。

まず、モンスターを出現させます。

と言うか前のコードをみれば新しいmapが生成された時に、既に何処かにはモンスターは出現しているのは明白なので

f:id:kazuhironagai77:20201115204322p:plain

その出現する位置を調節します。

GameCharacterの位置から推測して以下の場所に設定しました。

f:id:kazuhironagai77:20201115204343p:plain

テストします。

f:id:kazuhironagai77:20201115204403p:plain

向きが横を向いています。

Yawの値を90.0fに変えました。

f:id:kazuhironagai77:20201115204425p:plain

テストします。

f:id:kazuhironagai77:20201115204446p:plain

直りました。

正しこの方法だと、Map1に出現するモンスターの位置も変わってしまいます。

f:id:kazuhironagai77:20201115204506p:plain

これだけ直します。

3.2.4 敵のモンスターの3Dの出現位置をmap毎に変える。

最近、GetMapName()関数を知ったので試しに使用してみようと思います。

色々試して以下の方法で実装しました。

f:id:kazuhironagai77:20201115204540p:plain

まず、GetMapNameで帰ってくるマップの名前は、

f:id:kazuhironagai77:20201115204601p:plain

マップの名前+実行しているPlayの種類?に成っているのでcontainsで調べます。

BattleField(戦闘用のマップ)以外のマップではモンスターは今までの指定された位置に出現します。

f:id:kazuhironagai77:20201115204627p:plain

戦闘画面では別な位置に出現しています。

f:id:kazuhironagai77:20201115204650p:plain

後で調べたらこのサイトにGetMapName()関数の詳しい使い方が載っていました。

以下の方法で、マップの名前に付いているPrefixを外せるそうです。

f:id:kazuhironagai77:20201115204715p:plain

4. 敵のモンスターのアニメーションの追加

4.1 アニメーションの復習

もうどうやってアニメーションを作成したのか覚えていません。復習から始めます。

PlayAnimationでAttack01_BowAnimをplayしていました。

f:id:kazuhironagai77:20201115204750p:plain

このアニメーションを表示出来る様にします。

f:id:kazuhironagai77:20201115204808p:plain

f:id:kazuhironagai77:20201115204816p:plain

以下に示したようなコードを追加しました。

f:id:kazuhironagai77:20201115204834p:plain

f:id:kazuhironagai77:20201115204843p:plain

テストします。

はい。戦闘中の攻撃のアニメーションが追加されました。

f:id:kazuhironagai77:20201115204902p:plain

カメラワークを作っていないので画面は小さいですがしっかり攻撃しています。

4.2 カメラモーションの追加

前にカメラを沢山つけて映画のマトリックスみたいな映像を流す技術が紹介されている動画を見た記憶があります。

探しましたが見つかりませんでした。しょうがないので今回はモンスター攻撃時に、モンスター用のカメラに移動するだけにします。

MonsterBPを見たら、カメラ自体がなかったです。

付けました。

f:id:kazuhironagai77:20201115204932p:plain

f:id:kazuhironagai77:20201115204940p:plain

RPGGameModeBaseクラスのBP内で以下に示したようにモンスターの攻撃アニメーション時にMonsterBPのカメラに切り替わるようにします。

f:id:kazuhironagai77:20201115205003p:plain

当然ですが、攻撃が終わったら、映像を写すカメラは元のカメラに戻します。

f:id:kazuhironagai77:20201115205030p:plain

テストします。

f:id:kazuhironagai77:20201115205050p:plain

あれ、滅茶苦茶カッコイイです。

5. まとめと感想

一応、今週の予定してた部分は終わりました。戦闘システムには色々直さなくてはいけない部分が沢山ありますが、来週以降直します。