UE4の勉強記録

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

「Unreal Engine 4.xを使用してRPGを作成する」の足りない部分を作成する モンスターを配置する。

f:id:kazuhironagai77:20201227204610p:plain

<前文>

発音記号の謎

前回、言い足りなかった事を補足します。IPAのVowel Diagramは口の開く大きさ、下の形、唇の丸さで、全ての言語の母音を表せるはずです。更に一つの音に一つのsymbolが割り当てられているはずで、英語のaのように二つ以上の音を一つのsymbolで表す事は禁じられているはずです。

所が、IPAのVowel Diagram でʌの位置を見ると、open-midのBackで唇は丸めない。とありますが、別の特にアメリカで作成されたVowel Diagramの内の幾つかはʌが中央に位置しています。

これは、

  1. ʌが2つの別な音を一つのシンボルで表している。
  2. 口の開く大きさ、下の形、唇の丸さだけでは、英語の音を正確に表す事は出来ない。

のどちらかになってしまうのではないのか?そうなるとどちらの場合も発音記号の定義に矛盾が生じるのではないでしょうか?

と言う事が言いたかったんです。

言語学は門外漢ですが、これって熱力学で仕事の向きによって式に‐がついたり、つかなかったりするのとは訳が違うレベルの矛盾に見えます。

発音記号に拘る理由

私が今回、こんなに発音記号に拘ったのは、こんな問題を抱える発音記号ですが、可能性も秘めている気がしているからです。口の開く大きさ、下の形、唇の丸さで、音を指定すると言う方法は誰でも学べます。Minimal pairのアプリを作成していて分かった事の一つに、Minimal pair で訓練すると大人でもɑ 、æ 、ʌの音の違いは直ぐに理解出来るのですが、次の日になると簡単に忘れてしまいます。

所が、顎の下に手を置いて、ʌの音を発する時は顎が絶対下がらない様に、逆にɑの音の時は顎が必ず下がる様にアと言うと教えると、誰でもそれなりにɑ とʌの音を区別して発せる事が出来ます。Minimal pair で訓練した時に比べて音の違いを認識する事に関しては非常に劣りますが、この方法だと次の日になっても忘れません。

この二つの学習方法は補い合う事で、学習者に正しい発音を教授する事が出来るのではないでしょうか?と思っているからです。

発音記号に拘る理由2

全く個人的な意見ですが「Rの音は3つある。Lの音は2つある。」気がしています。

これは、minimal pairでrとlを比較して分かった事なんですが、Rの音には、

  1. Erなどのr音で、発音記号で言うɝ
  2. 動物が唸るような音が付随するr
  3. 日本語のラリルレロと全く区別付かないr

の3つが少なくとも私にとっては存在しています。

そしてL の音には、

  1. Elなどのl音で、所謂dark lの音。多くのアメリカ人はThのように舌を突き出してこの音を発音している。n音に似ている。
  2. 日本語のラリルレロと全く区別付かないl

の二つが存在しています。

しかし普通のアメリカ人にとってはこれらの音は全てLかRの音で2つの音です。

口の開く大きさ、下の形、唇の丸さで音を指定すると、これらの音が違う音であると指定出来ます。そしてそうすると学習する方は非常に楽になります。

日本人に特化した英語発音の訓練方法

余りに発音記号ばっかり検索していたらYouTubeのお勧めが全て発音記号に関するビデオになってしまいました。その中で面白かったのが、日本人に特化した英語発音の訓練方法を紹介しているアメリカ人が何人もいた事です。その中で特に優秀だと思った人の英語発音の説明ですが、口の開く大きさ、下の形、唇の丸さ、に

  • 顎の付け根の開く大きさ、
  • 唇の横に対する開き具合、
  • イントネーション

が追加されていました。英語の文を読むのにイントネーションが大切なのは実感していますが、英語の音素にもイントネーションが大切とは盲点でした。

後、私が追加したいのはあいうえおなどの日本語の発音です。日本人は、日本語の発音が完璧なのにそれを利用しない手はないです。

英語発音の学習用ソフトウェア

発音を聞いてVowel Diagram内にその人の発音を記録するソフトウェアがYouTubeのお勧めに出てました。試してみた所、再現性に難がありましたが面白い試みだと思いました。

Scientific computing は元々、数値解析やその計算結果の可視化から始まったため、化学、工学、医学なとの所謂理系の研究とは密接に結びついていますが、言語学などの文系分野との連携はあんまり聞きません。先程出てたʌの音ですが、ʌを構成する要素(口の開け具合、舌の位置、唇の形、イントネーションなど)を定性的に特定して、それらを定量的に測定出来れば、英語学習者がもっと簡単にʌの音を理解出来るソフトウェアが開発出来ると思われます。

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

<本文>

先週、戦闘システムを一応完成させてから色々考えたんですが、バグは沢山あるだろうと。その沢山のバグを見つけるためには、いっぱい戦闘する必要があると思いました。色々な条件で、沢山の戦闘を行うには、マップ上に沢山のモンスターを配布して、プレイヤーが操作するキャラにそのマップ内を探検させるのが最適だと思います。

後、先週、戦闘時のBGMを追加すると言って忘れていました。それを追加します。

今週の作業

  • 戦闘時のBGMの追加
  • モンスターを配置する

1.戦闘時のBGMの追加

1.1 BGMを追加します。

マップBattleFieldに移ったら流れるBGMを追加しました。

f:id:kazuhironagai77:20201227204946p:plain

Mega Game Music Collectionの音楽を使用しました。

f:id:kazuhironagai77:20201227205005p:plain

無料の時に貰ったんですが、今見たら5000円位の値段で売られていました。無料の時に貰っておいて良かったです。

1.2  「ボタン」を押したら音がするようにする

クリックした時に音がしたら、クリック出来た事が簡単に確認出来ますね。

f:id:kazuhironagai77:20201227205028p:plain

しました。音はstarter contentsにあるサンプルを使用しました。

f:id:kazuhironagai77:20201227205048p:plain

その他のボタンにも音を追加します。

f:id:kazuhironagai77:20201227205107p:plain

1.3 勝利の時に別の音楽が流れるようにする

そのためには、まず今まで流れていた音楽を止める必要があります。

このサイトに以下の様に書かれていました。

f:id:kazuhironagai77:20201227205136p:plain

マジで…

やり直します。

調べたら、BattleFieldのBGM以外は直す必要がなさそうなのでそれだけSpawnSound2Dに変更します。

f:id:kazuhironagai77:20201227205206p:plain

またLevelBPとWidgetでreferenceのやり取りをする必要が出て来ました。

RPGGameModeBaseBP内でEvent Dispatchersを使用します。名前はCombatOverVictoryとします。

f:id:kazuhironagai77:20201227205226p:plain

BattleFieldのレベルマップ内でbindします。

f:id:kazuhironagai77:20201227205245p:plain

戦闘に勝利した時に、CombatOverVictoryを呼び出してBGMを止めます。

f:id:kazuhironagai77:20201227205301p:plain

試してみます。

音楽はここには載せられませんが、音がぶつ切りです。徐々に音を小さくしたいです。

調べたのですが、音を徐々に小さくする機能はないみたいなので、以下の様にしました。

f:id:kazuhironagai77:20201227205319p:plain

BattleFieldのTick()関数に以下のように実装しました。

f:id:kazuhironagai77:20201227205337p:plain

試します。

綺麗にBGMがfade outしました。

Victoryの音楽も同様にします。

と思ったのですが、victoryの音楽を流しているRPGGameModeBaseBPはTick()関数をUE4C++ で使用していて呼べなかったです。

前に試した実験では、UE4C++ のTick()関数内でSuperを使用すればBPで Event Tick()が呼べたはずです。試してみましょう。

f:id:kazuhironagai77:20201227205356p:plain

BGMの音を小さくした方法と全く同じやり方で実装しました。

f:id:kazuhironagai77:20201227205414p:plain

試してみます。

音がいきなり小さくなってなくなってしまいました。

音を小さくするのは最後にしました。

f:id:kazuhironagai77:20201227205432p:plain

最低限の音は出る様になりました。

2. モンスターの配置について

2.1 モンスターの出現方法についての考察

モンスターの出現方法ですが、現在3つ知っています。

  1. UE4C++からSpawnする。
  2. BPからSpawnする。
  3. 直接Map上に配置する。

それぞれの長所と短所を明らかにして、どの方法でモンスターを配置するのがベストなのかを決定します。

Googleで調べたんですが全然、この話題、全然引っかからないです。

それで、自分でまとめてみました。一応断っておきますが、あくまで現状における私の推測なので実際は違うかもしれません。

2.1.1 動的な生成か、静的は生成か

BPからspawnする場合は動的、直接map上に配置する場合は静的と表現されています。この表現を一般的なC++と同じ意味だと考えると、直接map上に配置する場合はモンスターのデータはstack上に、BPからspawnする場合はheap上に配置されると推測されます。UE4C++はscript言語としてC++を採用しているだけで、BPと機能は同じである。と考えるとUE4C++からSpawnする場合も動的にモンスターを生成していると考えられます。

このゲームのモンスターはcharacter classから派生していますので、サイズは結構大きいはずです。動的な生成方法の方が良いと思われます。

2.1.2  生成のスピード

モンスターは大量に生成するので、生成のスピードが速いほうが好ましいです。この点ではUE4C++が最も優れていると考えられます。

2.1.3  生成のランダム性

UE4C++とBPはランダムな位置にモンスターを生成出来ますが、直接mapに配置する方法はランダムな位置での生成は出来ません。

2.1.4  Compileの時間

UE4++は一寸コードを変えるだけでもbuildに時間が取られますが、BPは直ぐに終わります。

2.1.5  モンスターの出現位置の可視化

動的にモンスターを生成する場合は、実際にゲームをプレイするまでマップのどの辺にモンスターが出現するのか不明です。直接Map上に配置するとその辺りの事は段違いに分かり易いです。

2.1.6  まとめ

それぞれの方法に長所と短所がある事が分かりました。それらの点を考慮すると制作の段階において、変えるのがベストな気がします。

初期段階:試しにモンスターを生成する

この時は、直接map上に配置するのがベストと思われます。ゲームをプレイしなくても、モンスターの位置を視覚的に確認出来ます。ある面積内にランダムにモンスターを生成したい時だけ、BPを使用すべきです。

中期段階:モンスターの生成する位置がどんどん決定していく

一端、モンスターの生成する位置が決定したら、BPで動的に生成する方法に変更すべきです。上記の理論が正しいと言う前提の話ですが、そうする事でstack memoryへの負担が減り、沢山のモンスターを生成出来ると思われるからです。

最終段階:モンスターの生成場所が完全に決定した後

全てのモンスターの生成をBPでするようになったら、製品化する前にUE4++でモンスター生成の部分を書き直すべきです。そうする事で素早くモンスターを生成出来るしPCへの負担も少なくなるはずだからです。

2.2 モンスターを直接Map上に配置してみる

直接map1にモンスターを配置してみます。

f:id:kazuhironagai77:20201227205625p:plain

あれ、モンスターがいません。

モンスターBPを見たら、モンスターが出現する時のアニメーションを追加していました。

f:id:kazuhironagai77:20201227205645p:plain

このアニメーションのせいでモンスターが地面に潜っています。

f:id:kazuhironagai77:20201227205704p:plain

それはともかくとしてこの方法で生成したモンスターでも機能するのか試してみます。

f:id:kazuhironagai77:20201227205722p:plain

領域内に侵入すると凄い勢いで追いかけて来ます。

f:id:kazuhironagai77:20201227205743p:plain

捕まると戦闘画面になります。

モンスターを倒した後、map1に戻ってくると、復活したモンスターとの戦闘がもう一度始まってしまいます。

2.2.1 モンスターが復活しないようにする

色々な方法を検討したんですが、BPで動的にモンスターをspawnさせてから復活しないようにする方が簡単であるとの結論になりました。のでそっちを先にやります。

2.3 モンスターをBPから動的に生成してみる。

今のモンスターの位置を記録しておきます。

f:id:kazuhironagai77:20201227205817p:plain

同じ場所にモンスターを生成してみます。

f:id:kazuhironagai77:20201227205836p:plain

テストします。

f:id:kazuhironagai77:20201227205906p:plain

出来てました。簡単過ぎて拍子抜けです。

2.4 倒したモンスターを消滅させる

2.4.1 モンスターを2体spawnさせる

倒したモンスターだけを消滅させる必要があるので、モンスターを2体spawnさせます。

モンスターが徘徊する場所を作成します。

f:id:kazuhironagai77:20201227205930p:plain

それぞれのモンスターを管理するためのstructを作成します。

f:id:kazuhironagai77:20201227205948p:plain

RPGGameInstanceBP内にこのstructを使用してArrayを作成します。

f:id:kazuhironagai77:20201227210012p:plain

このArrayでそれぞれのモンスターの消滅を管理します。

Map1のレベルBP内に以下の関数を作成します。この関数でモンスターをspawnします。

f:id:kazuhironagai77:20201227210035p:plain

上記の関数、SpawnMonstersをLevelBPのEvent BeginPlay内で呼びます。

f:id:kazuhironagai77:20201227210058p:plain

テストします。

f:id:kazuhironagai77:20201227210116p:plain

モンスターが2体出現しています。

成功ですが、向きがオカシイです。調節します。

f:id:kazuhironagai77:20201227210135p:plain

直しました。

ついでに戦闘も試してみます。

f:id:kazuhironagai77:20201227210150p:plain

手前のモンスターの領域にしか入っていないのに、後ろのモンスターも動き始めました。しかも全く同じ動きをしています。

これは直さないと。

確認のためNav Mesh Bound Volumeを以下の様に設置してモンスターの発生箇所を道の端に変更しました。

f:id:kazuhironagai77:20201227210210p:plain

2匹のモンスターは別々な動きをしていました。

f:id:kazuhironagai77:20201227210227p:plain

正し、モンスターのAIをもう少し発展させる必要はありそうです。この辺は来週以降、検討します。

2.4.2 MonsterBPの改良

倒したモンスターのみ消滅するためには、MonsterBPから作成したそれぞれのInstanceに名前を付ける必要があります。ので新しいnameタイプの変数をMosterBPに追加します。MonsterNameと名付けました。

f:id:kazuhironagai77:20201227210258p:plain

後、この変数の初期化を忘れるとモンスターを倒した時に初期化しないので、Expose on Spawnにチェックしました。

MonsterBP生成時に、MonsterNameのargumentをパスするように変更します。

f:id:kazuhironagai77:20201227210316p:plain

Play中に生成されたそれぞれのモンスターのMonster Nameを見ると、

f:id:kazuhironagai77:20201227210332p:plain

f:id:kazuhironagai77:20201227210340p:plain

それぞれの名前が保持されています。

2.4.3 倒したモンスターのみを消滅させる

RPGGameInstanceBPにNameタイプの変数を作成します。

f:id:kazuhironagai77:20201227210404p:plain

ここに戦闘中のモンスターの名前を記録しておきます。

以下に示した様に、戦闘開始時にmonsterBP内で記録しました。

f:id:kazuhironagai77:20201227210421p:plain

戦闘終了時に、以下に示した方法でRPGGameInstanceBP内のMonsterSpawnData内にある戦闘中のモンスターのSpawnMonsterをfalseに変更します。

f:id:kazuhironagai77:20201227210440p:plain

ゴチャゴチャし過ぎて分かりづらいですが、後で綺麗に整理し直します。

Boolean変数のSpawn MonstersをfalseにしてしまうとUE4C++から生成する全てのモンスターの生成を止めてしまうので、一寸外しておきます。

テストします。

一体目のモンスターを倒してmap1に戻ってきた所です。

f:id:kazuhironagai77:20201227210457p:plain

一体目のモンスターは消滅していますが、二体目のモンスターは普通にいます。ここまでは計画通りです。

二体目のモンスターも倒しました。戦闘画面から戻ってくるとモンスターは消滅しています。

f:id:kazuhironagai77:20201227210542p:plain

成功しました。

2.5 UE4C++で生成したモンスターについて

こっちのモンスターはUE4C++から生成しているので戦闘で倒しても消滅しません。Boolean変数のSpawn Monstersをtrueのままにしているからです。

f:id:kazuhironagai77:20201227210604p:plain

UE4C++から生成しているモンスターは、今は単なる邪魔者なので生成するのを中止しておきます。

f:id:kazuhironagai77:20201227210622p:plain

消えました。

f:id:kazuhironagai77:20201227210640p:plain

特に問題もないみたいです。

2.6 Error 表示について

あれ、Output Logにエラーが表示されています。

f:id:kazuhironagai77:20201227210704p:plain

調べます。

こいつのせいでした。

f:id:kazuhironagai77:20201227210719p:plain

正直すっごい時間かかりました。すっかりこれを作成した事を忘れていました。消します。

直りました。

2.7 MonsterBPに追加したMonsterName変数に対する考察

f:id:kazuhironagai77:20201227210741p:plain

Monster Nameはexpose spawnと設定したのでMonsterBPを生成する時にMonster Nameをパラメーターとしてパスする必要があります。

こんな感じにです。

f:id:kazuhironagai77:20201227210759p:plain

しかしUE4C++からMonsterBPを生成する場合、以下に示した様に、モンスターを生成するためのクラスはMonsterクラスの派生クラスである事しか指定していません。

f:id:kazuhironagai77:20201227210815p:plain

f:id:kazuhironagai77:20201227210823p:plain

そしてMonsterクラスの派生クラスである、MonsterBPはその条件に一致するため生成する事が出来ます。

f:id:kazuhironagai77:20201227210846p:plain

しかしこの方法だと、Monster Name変数はMonsterBPからinstanceを作成する時に指定されません。

勿論エラーにはなりませんが、出来るだけMonsterBPのinstanceが生成される時に、Monster Name変数の値を指定しておきたいです。後でこの問題について検討します。

2.8  RPGGameInstanceBP内のMonsterSpawnData内のSpawnをfalseに変更する部分の整理

今週は時間が余ったので、2.4.3で述べた部分の実装部がかなり不必要に複雑になってしまった部分の整理を今やってしまいます。

複雑になっている原因はRPGGameModebaseBP内でEvent BeginPlayが使用出来ないため、一々GameInstanceにアクセスして、RPGGameInstanceBPにcastしてRPGGameInstanceBPの変数にアクセスしているからです。

RPGGameModebaseBP内でEvent BeginPlayが使用出来る様に直します。

f:id:kazuhironagai77:20201227210942p:plain

テストします。

f:id:kazuhironagai77:20201227211000p:plain

f:id:kazuhironagai77:20201227211008p:plain

Event BeginPlayが使用出来る様に成りました。

Event BeginPlayでGame InstanceをcastしてRPG Game Instance BPにします。それをMy Game Instanceに保持します。

f:id:kazuhironagai77:20201227211028p:plain

MonsterSpawnData内のSpawnをfalseに変更する部分はそれでも複雑なので関数にしました。

f:id:kazuhironagai77:20201227211046p:plain

その関数を呼び出して終りです。

f:id:kazuhironagai77:20201227211105p:plain

テストします。

f:id:kazuhironagai77:20201227211123p:plain

前と同じ様に戦闘出来ました。問題なしです。

3. まとめと感想

今週はここまでです。来週はもっと広範囲にモンスターを配置して、配置したモンスターと戦う事で見つかっていないバグを発見したいです。