UE4の勉強記録

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

「Unreal Engine4.xを使用してRPGを作成する」の3章の3.3を勉強する

<前文>

f:id:kazuhironagai77:20190526204904p:plain

の3章3節の3、パーティメンバーを勉強します。

<本文>

<目的>

3章3節の3、パーティメンバーを勉強します。

<方法と結果>

<Step.0>

前回と同じようにCh2 を使用します。取りあえずビルドしてみると普通に成功しました。それではやって行きます。

<Step.1>

教科書によると「パーティメンバーの記録を作る前にキャラクターの現在の状態を記録する方法が必要です。」と書かれていました。私はこの発想の仕方が大好きです。実用的なんです。Aをするために先にBを用意しておきます。単純ですが正しくそれを言うには深い経験と鋭い洞察力が必要です。ゲームにおけるソフトウェアデザイン力につながる大切な発想でもあります。

新しいクラス、Game CharacterクラスをUObjectクラスから作成します。以下に示すようにPublicで作成しました。

f:id:kazuhironagai77:20190526205011p:plain

まず、教科書に書かれているインクルードから加えていきます。

f:id:kazuhironagai77:20190526205032p:plain

何故、EnemyInfo.hがエラーに?

f:id:kazuhironagai77:20190526205134p:plain

スペルを間違えて作っていました。直します。

f:id:kazuhironagai77:20190526205153p:plain

f:id:kazuhironagai77:20190526205202p:plain

f:id:kazuhironagai77:20190526205212p:plain

f:id:kazuhironagai77:20190526205224p:plain

f:id:kazuhironagai77:20190526205240p:plain

Rebuild します。成功しました。念のためVSからUE4エディターを起動しました。

Enemiesデータテーブルを作り直します。

f:id:kazuhironagai77:20190526205418p:plain

f:id:kazuhironagai77:20190526205435p:plain

セーブします。

これがまだエラーを吐いていますね。

f:id:kazuhironagai77:20190526205508p:plain

実質エラーではないですが気持ち悪いのでVSをUE4からリフレッシュします。

f:id:kazuhironagai77:20190526205552p:plain

綺麗に治りました。Game Characterクラスに残りのコードを追加していきます。

f:id:kazuhironagai77:20190526205622p:plain

f:id:kazuhironagai77:20190526205632p:plain

ここまで来て気が付いたのですがデータテーブルのクラスのファイルの名前は接頭語のFが入っているんですね。これを直すのは大変なので今回はFは付けないで行きますがこういう細かい部分を間違えていると後で大きな問題を起こしそうです。

教科書には以下のような解説が書かれていました。

f:id:kazuhironagai77:20190526205710p:plain

  • 今はキャラクターの名前の記録、キャラクターのソースクラスの情報、そしてキャラクターの現在の状態の記録を保持してます。
  • 後でブループリントへ情報を発するためにUCLASSUPROPERTYのマクロを使用します。
  • コンバットシステムを作成する時に他の情報を追加します。

キャラクターの名前はCharacterNameが保持します。キャラクターのソースの情報とは何でしょうか?

3章2節の1、クラスで述べているキャラクターのパラメーターの事を指しているみたいですね。以下に示した通り3章2節の1で述べているHealth(HP)、Maxhealth(MHP)、Magic(MP)、Maxmagic(MMP)、Attack power(ATK)、Defense(DEF)、そしてLUCKのそれぞれがあります。

f:id:kazuhironagai77:20190526205825p:plain

となると現在の状況とはこの中のHPとMPを指しているのでしょうね。

次はGame Characterクラスのcppファイルについてです。

CreateGameCharacter関数を実装します。

f:id:kazuhironagai77:20190526205904p:plain

ここで、データテーブルCharacterClassesのアドレスが"DataTable'/Game/Data/CharacterClasses.CharacterClasses'"となっているのですがこれが正しいのか分かりません。

以下に示すようにCharacterClassesの上にマウスを置いてパスを表示させると確かに/Game/Dataと表示されています。

f:id:kazuhironagai77:20190526205931p:plain

が実際のフォルダーにGameはありません。DataフォルダーはContentフォルダー内に配置されています。

f:id:kazuhironagai77:20190526205952p:plain

実際のファイルが配置されているフォルダーを調べても、

f:id:kazuhironagai77:20190526210025p:plain

ファイルは

f:id:kazuhironagai77:20190526210056p:plain

の配置になっています。

今度はBeginDestroy()関数を実装します。

f:id:kazuhironagai77:20190526210117p:plain

これだけでした。しかしSuperを使っていると言う事は元のクラスからオーバーライドしていると言う事なので、UObjectクラスのBeginDestory()関数を少し調べてみましょう。

UE4のAPIによれば

f:id:kazuhironagai77:20190526210143p:plain

Remark

オブジェクトを破壊する前に呼ばれる。これはオブジェクトを破壊する決定をした直後に非同期のクリーンアップの処理開始を許可するために呼ばれる。

とありましたが良く分かりませんね。

教科書を読んでいたらデータテーブルのパスについての記述がありました。

f:id:kazuhironagai77:20190526210224p:plain

  • キャラクタークラスデータテーブルがどこに配置されているかを示すパスを見る事が出来ます。
  • コンテントブラウザー内のデータテーブルを右クリックしCopy Referenceを選択しそしてその結果をコードにペーストする事でこのパスを得る事が出来ます。

とありました。早速試してみます。

f:id:kazuhironagai77:20190526210350p:plain

DataTable'/Game/Data/CharacterClasses.CharacterClasses'と出て来ました。実際のコードに書かれたアドレスはDataTable'/Game/Data/CharacterClasses.CharacterClasses'なので合ってるみたいですね。

<Step.2>

 GameInstanceクラスを作成します。教科書にはGameInstanceクラスを使用する事でレベルが移動してもパーティメンバーの情報が保持出来ると説明しています。これは私は前から知っていましたが最初に知った時は結構衝撃でした。それを知る前は一生懸命ゲームモードクラスにデータを保持していたのですがレベルを変更したら全部保持したデータが消えてしまいました。途方に暮れていたらGameInstanceクラスを使用しないといけないとネットのどこかで見つけて知ったのです。教科書でも同じような説明がされています。

RPGGameInstanceクラスをGameInstanceクラスから作成します。

f:id:kazuhironagai77:20190526210452p:plain

Publicを選択し作成します。以下に示したようにRPGGameInstanceクラスが作成されました。

f:id:kazuhironagai77:20190526210524p:plain

教科書に書かれているようにRPGGameInstanceクラスの変数やメンバー関数をヘッダーファイルに追加します。

f:id:kazuhironagai77:20190526210545p:plain

しました。

PartyMemberクラスのTArrayを作成する事で全てのキャラクターの情報をGameInstanceクラス内に保持出来る訳ですね。大変頭のいい方法です。教科書に書かれていた「パーティメンバーの記録を作る前にキャラクターの現在の状態を記録する方法が必要です。」とはこれの事ですね。私が前に自作したゲームはGameInstanceに沢山の変数を作成して保持していたのですが私の方法ではゲームが複雑になったらどのデータが何を指しているのかすぐに分からなくなってしまいます。この教科書の方法ならゲームが複雑になってもそれぞれのキャラクターを基軸としてそのキャラクターのデータを取り出せばいいのでどのデータが何を指しているのか分からなくなる事は起きませんね。

今度はソースファイルにコードを実装します。教科書に書かれているままに実装しました。

f:id:kazuhironagai77:20190526210638p:plain

Superの部分がエラーになっていますがビルドしたら普通に成功したのでこのままにしておきます。

教科書を読むとここでコンパイルするとリンクエラーになるので一度全部セーブして閉じてプロジェクトを再起動して下さいとありました。UE4エディターからコンパイルしても4.22ではエラーにはならないのでこのままで行きます。

以下に示すようにRPGGameInstanceクラスをこのプロジェクトのGame Instance Classに指定します。

f:id:kazuhironagai77:20190526210711p:plain

<Step.3>

RPGGameInstanceクラスのinit()メンバー関数をRPGGameModeクラスのBeginPlay()メンバー関数から呼び出します。

まず教科書ではRPGGameModeクラスのヘッダーファイルにBeginPlay()を追加してオーバーライドしてます。このときにPublicなどを指定していないのですが指定しなくて良かったのでしようか?これは考察で検討します。

教科書通りにBeginPlay()メンバー関数を宣言しました。

f:id:kazuhironagai77:20190526210823p:plain

更にRPGGameModeクラスのソースファイル上でBeginPlay()メンバー関数を実装します。

f:id:kazuhironagai77:20190526210910p:plain

GetGameInstance()関数については考察で調べます。至極当たり前のやり方と思いますが教科書を見るまでGetGameInstance()関数でRPGGameInstanceクラスにアクセスする方法を思いつきませんでした。

ここでビルドしてみると成功しました。

今回はここまでとします。

<考察>

<Step.1>

  1. 「パーティメンバーの記録を作る前にキャラクターの現在の状態を記録する方法が必要です。」

についてもう少しだけ考察したいです。このソフトデザインの肝はキャラクター単位でそれぞれのキャラクターのプロパティを管理して事だと思うんです。これを全てのキャラクターのHPだけ集めた配列を作って管理したら訳が分からなくなってしまいます。キャラクターの持つHPが正しいかどうかはそのキャラクターを見て判断出来るじゃないですか。ゆえにこれがゲームのデザインにおいて正しい形なんです。キャラクターがまずありそのキャラクターに追随する形でそのキャラクターのプロパティが存在するという人間の思考の順番について正しく理解した結果このソフトウェアデザインが考案されたと考えられます。

  1. データテーブルのアドレスについて

これはちょっと調べてのですが分かりませんでした。Contentがgameになっているのでしょうか?そうだとしてもオカシイ事ばかりです。

<Step.2>

1.GameInstance

GameInstance について一応調べておきます。UE4C++のAPIによると

f:id:kazuhironagai77:20190526211122p:plain

Remark

  1. ゲームインスタンス:実行しているゲームのインスタンスのための高レベルの管理オブジェクト。
  2. ゲームが作成された時に生成されゲームインスタンスがシャットダウンされるまで破壊されない。
  3. スタンドアローンなゲームとして実行された場合これらの一つが在るようになる。
  4. PIE(play-in-editor)内で実行するとこれらの一つがPIEインスタンス一個に対して生成される。 

3.と4.についてはちょっと良く分からないです。マルチプレイのゲームとして実行された場合それぞれのクライアントで一個のゲームインスタンスが生成されるという事を言っているのでしょうか?

3.のone of theseのtheseが何を指しているのかが不明です。3.の場合はスタンドアローンのゲームについて語っているのですからGameInstanceは一個生成されれば十分なはずです。何がtheseになるのでしょうか?良く分かりません。4.についてはPIEインスタンスがそれぞれ独立したGameInstanceを持つと考えられますがone of these per PIE instanceとここでも言っています。

このtheseは3.で言っているtheseと同じでしょうか。ただしこのtheseをGameInstanceクラスから派生した全てのクラスを指していると意味は通じます。そういう事何でしょうか?

2.StaticLoadObject() 関数

UE4C++のAPIによると

f:id:kazuhironagai77:20190526211516p:plain

Remark

  1. オプションとしてのアウターとファイルネームの特定によるストリングの名前によってオブジェクトをロードするか見つけるかします。
  2. これらはオプションです。何故ならInNameが全ての必要な情報を持つ事が出来るからです。

とありました。これはパラメーターについて述べていますね。パラメーターを見てみましょう。

f:id:kazuhironagai77:20190526211614p:plain

超訳ですが訳してみました。

f:id:kazuhironagai77:20190526211707p:plain

これでは何だか分かりません。サンプルコードをみてみます。

f:id:kazuhironagai77:20190526211730p:plain

あれ?パラメーターが3つしかありません。どれがどれなのでしょうか?

マウスをStaticLoadObjectの上に配置すると以下の解説が表示されました。

f:id:kazuhironagai77:20190526211811p:plain

これをみると最初の3つ以外はオプションに出来るようですね。となると

f:id:kazuhironagai77:20190526211852p:plain

となります。Objectクラスは分かります。UDataTableが当たる訳ですから。しかしNameは全然分かりません。APIの説明ではNameはInOuterかFileNameの助けが必要と書かれているにも関わらすこのサンプルではInOuterはNULL、FileNameは提供していません。このサンプルコードの書き方ならNameはInouterかFileNameの助けなしに使用可能と考えるべきなのでしょうか?

StaticLoadObject() 関数にはもう一つ疑問があります。この関数はスタティックなのでしょうか?名前からするとそうみたいですが確認したいです。APIによるとこの関数はUObjectGlobalsファイルにあるみたいです。

UObjectGlobal.cppをみると

f:id:kazuhironagai77:20190526211955p:plain

スタティックですね。

3.TArrayクラスのAdd関数

TArrayについては先週出来るだけの事はしましたが私がC++で独自のArrayを作成した事が無い事から今一長所と短所が分からなかったです。今回は単にそのTArrayクラスの関数の一つであるAddを調べて見ます。

UE4C++のAPIのTArrayによると

f:id:kazuhironagai77:20190526212029p:plain

Add()メンバー関数は2種類あって一つはパラメーターがConstの場合もう一方はそうでない場合のためのようです。更に解説で配列全てを再配列する可能性に述べています。この辺はしっかりC++の配列を勉強した人なら納得の説明になりそうですが私はまだ分かりません。

<Step.3>

1.GameModeクラスのBeginPlay()関数

RPGGameInstanceクラスでBeginPlay()関数をオーバーライドしているので一応調べておきます。

そしたらGameModeクラスにはBeginPlay() 関数がありませんでした。これはうれしい誤算ですね。調べた甲斐がありそうです。

UE4C++のGameModeのAPIによると以下に示すように

f:id:kazuhironagai77:20190526212129p:plain

GameModeクラスの親クラスがありました。一個ずつ調べたらAActorクラスのメンバー関数にBeginPlay() 関数がありました。

f:id:kazuhironagai77:20190526212151p:plain

2.GetGameInstance()関数

UE4APIからGetGameInstance()関数を調べたらWorldクラスとActorクラスの両方にGetGameInstance()関数がありました。どっちのGetGameInstance()関数を使用しているのでしょうか?

f:id:kazuhironagai77:20190526212339p:plain

Actorクラスの方ですね。ではActorクラスのGetGameInstance()関数のAPI を見てみましょう。

f:id:kazuhironagai77:20190526212417p:plain

Remark

このアクターを究極的には保持しているGameInstanceをテンプレートタイプにキャストして得ます。

もしキャストが失敗した場合はNULLを返します。

サンプルコードをみると

f:id:kazuhironagai77:20190526212513p:plain

となっています。こうやってキャストするようですね。

<おまけ>

RPGGameInstanceクラスでBeginPlay()関数をオーバーライドしていますが、publicにしなくていいのか?と言う疑問を考察で調査すると<方法と結果>の<Step.3>で書いておいてすっかり忘れていました。これは来週調べます。