<前文>
の3章3節の3、パーティメンバーを勉強します。
<本文>
<目的>
3章3節の3、パーティメンバーを勉強します。
<方法と結果>
<Step.0>
前回と同じようにCh2 を使用します。取りあえずビルドしてみると普通に成功しました。それではやって行きます。
<Step.1>
教科書によると「パーティメンバーの記録を作る前にキャラクターの現在の状態を記録する方法が必要です。」と書かれていました。私はこの発想の仕方が大好きです。実用的なんです。Aをするために先にBを用意しておきます。単純ですが正しくそれを言うには深い経験と鋭い洞察力が必要です。ゲームにおけるソフトウェアデザイン力につながる大切な発想でもあります。
新しいクラス、Game CharacterクラスをUObjectクラスから作成します。以下に示すようにPublicで作成しました。
まず、教科書に書かれているインクルードから加えていきます。
何故、EnemyInfo.hがエラーに?
スペルを間違えて作っていました。直します。
Rebuild します。成功しました。念のためVSからUE4エディターを起動しました。
Enemiesデータテーブルを作り直します。
セーブします。
これがまだエラーを吐いていますね。
実質エラーではないですが気持ち悪いのでVSをUE4からリフレッシュします。
綺麗に治りました。Game Characterクラスに残りのコードを追加していきます。
ここまで来て気が付いたのですがデータテーブルのクラスのファイルの名前は接頭語のFが入っているんですね。これを直すのは大変なので今回はFは付けないで行きますがこういう細かい部分を間違えていると後で大きな問題を起こしそうです。
教科書には以下のような解説が書かれていました。
- 今はキャラクターの名前の記録、キャラクターのソースクラスの情報、そしてキャラクターの現在の状態の記録を保持してます。
- 後でブループリントへ情報を発するためにUCLASSとUPROPERTYのマクロを使用します。
- コンバットシステムを作成する時に他の情報を追加します。
キャラクターの名前はCharacterNameが保持します。キャラクターのソースの情報とは何でしょうか?
3章2節の1、クラスで述べているキャラクターのパラメーターの事を指しているみたいですね。以下に示した通り3章2節の1で述べているHealth(HP)、Maxhealth(MHP)、Magic(MP)、Maxmagic(MMP)、Attack power(ATK)、Defense(DEF)、そしてLUCKのそれぞれがあります。
となると現在の状況とはこの中のHPとMPを指しているのでしょうね。
次はGame Characterクラスのcppファイルについてです。
CreateGameCharacter関数を実装します。
ここで、データテーブルCharacterClassesのアドレスが"DataTable'/Game/Data/CharacterClasses.CharacterClasses'"となっているのですがこれが正しいのか分かりません。
以下に示すようにCharacterClassesの上にマウスを置いてパスを表示させると確かに/Game/Dataと表示されています。
が実際のフォルダーにGameはありません。DataフォルダーはContentフォルダー内に配置されています。
実際のファイルが配置されているフォルダーを調べても、
ファイルは
の配置になっています。
今度はBeginDestroy()関数を実装します。
これだけでした。しかしSuperを使っていると言う事は元のクラスからオーバーライドしていると言う事なので、UObjectクラスのBeginDestory()関数を少し調べてみましょう。
UE4のAPIによれば
Remark
オブジェクトを破壊する前に呼ばれる。これはオブジェクトを破壊する決定をした直後に非同期のクリーンアップの処理開始を許可するために呼ばれる。
とありましたが良く分かりませんね。
教科書を読んでいたらデータテーブルのパスについての記述がありました。
- キャラクタークラスデータテーブルがどこに配置されているかを示すパスを見る事が出来ます。
- コンテントブラウザー内のデータテーブルを右クリックしCopy Referenceを選択しそしてその結果をコードにペーストする事でこのパスを得る事が出来ます。
とありました。早速試してみます。
DataTable'/Game/Data/CharacterClasses.CharacterClasses'と出て来ました。実際のコードに書かれたアドレスはDataTable'/Game/Data/CharacterClasses.CharacterClasses'なので合ってるみたいですね。
<Step.2>
GameInstanceクラスを作成します。教科書にはGameInstanceクラスを使用する事でレベルが移動してもパーティメンバーの情報が保持出来ると説明しています。これは私は前から知っていましたが最初に知った時は結構衝撃でした。それを知る前は一生懸命ゲームモードクラスにデータを保持していたのですがレベルを変更したら全部保持したデータが消えてしまいました。途方に暮れていたらGameInstanceクラスを使用しないといけないとネットのどこかで見つけて知ったのです。教科書でも同じような説明がされています。
RPGGameInstanceクラスをGameInstanceクラスから作成します。
Publicを選択し作成します。以下に示したようにRPGGameInstanceクラスが作成されました。
教科書に書かれているようにRPGGameInstanceクラスの変数やメンバー関数をヘッダーファイルに追加します。
しました。
PartyMemberクラスのTArrayを作成する事で全てのキャラクターの情報をGameInstanceクラス内に保持出来る訳ですね。大変頭のいい方法です。教科書に書かれていた「パーティメンバーの記録を作る前にキャラクターの現在の状態を記録する方法が必要です。」とはこれの事ですね。私が前に自作したゲームはGameInstanceに沢山の変数を作成して保持していたのですが私の方法ではゲームが複雑になったらどのデータが何を指しているのかすぐに分からなくなってしまいます。この教科書の方法ならゲームが複雑になってもそれぞれのキャラクターを基軸としてそのキャラクターのデータを取り出せばいいのでどのデータが何を指しているのか分からなくなる事は起きませんね。
今度はソースファイルにコードを実装します。教科書に書かれているままに実装しました。
Superの部分がエラーになっていますがビルドしたら普通に成功したのでこのままにしておきます。
教科書を読むとここでコンパイルするとリンクエラーになるので一度全部セーブして閉じてプロジェクトを再起動して下さいとありました。UE4エディターからコンパイルしても4.22ではエラーにはならないのでこのままで行きます。
以下に示すようにRPGGameInstanceクラスをこのプロジェクトのGame Instance Classに指定します。
<Step.3>
RPGGameInstanceクラスのinit()メンバー関数をRPGGameModeクラスのBeginPlay()メンバー関数から呼び出します。
まず教科書ではRPGGameModeクラスのヘッダーファイルにBeginPlay()を追加してオーバーライドしてます。このときにPublicなどを指定していないのですが指定しなくて良かったのでしようか?これは考察で検討します。
教科書通りにBeginPlay()メンバー関数を宣言しました。
更にRPGGameModeクラスのソースファイル上でBeginPlay()メンバー関数を実装します。
GetGameInstance()関数については考察で調べます。至極当たり前のやり方と思いますが教科書を見るまでGetGameInstance()関数でRPGGameInstanceクラスにアクセスする方法を思いつきませんでした。
ここでビルドしてみると成功しました。
今回はここまでとします。
<考察>
<Step.1>
- 「パーティメンバーの記録を作る前にキャラクターの現在の状態を記録する方法が必要です。」
についてもう少しだけ考察したいです。このソフトデザインの肝はキャラクター単位でそれぞれのキャラクターのプロパティを管理して事だと思うんです。これを全てのキャラクターのHPだけ集めた配列を作って管理したら訳が分からなくなってしまいます。キャラクターの持つHPが正しいかどうかはそのキャラクターを見て判断出来るじゃないですか。ゆえにこれがゲームのデザインにおいて正しい形なんです。キャラクターがまずありそのキャラクターに追随する形でそのキャラクターのプロパティが存在するという人間の思考の順番について正しく理解した結果このソフトウェアデザインが考案されたと考えられます。
- データテーブルのアドレスについて
これはちょっと調べてのですが分かりませんでした。Contentがgameになっているのでしょうか?そうだとしてもオカシイ事ばかりです。
<Step.2>
1.GameInstance
GameInstance について一応調べておきます。UE4C++のAPIによると
Remark
- ゲームインスタンス:実行しているゲームのインスタンスのための高レベルの管理オブジェクト。
- ゲームが作成された時に生成されゲームインスタンスがシャットダウンされるまで破壊されない。
- スタンドアローンなゲームとして実行された場合これらの一つが在るようになる。
- 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によると
Remark
- オプションとしてのアウターとファイルネームの特定によるストリングの名前によってオブジェクトをロードするか見つけるかします。
- これらはオプションです。何故ならInNameが全ての必要な情報を持つ事が出来るからです。
とありました。これはパラメーターについて述べていますね。パラメーターを見てみましょう。
超訳ですが訳してみました。
これでは何だか分かりません。サンプルコードをみてみます。
あれ?パラメーターが3つしかありません。どれがどれなのでしょうか?
マウスをStaticLoadObjectの上に配置すると以下の解説が表示されました。
これをみると最初の3つ以外はオプションに出来るようですね。となると
となります。Objectクラスは分かります。UDataTableが当たる訳ですから。しかしNameは全然分かりません。APIの説明ではNameはInOuterかFileNameの助けが必要と書かれているにも関わらすこのサンプルではInOuterはNULL、FileNameは提供していません。このサンプルコードの書き方ならNameはInouterかFileNameの助けなしに使用可能と考えるべきなのでしょうか?
StaticLoadObject() 関数にはもう一つ疑問があります。この関数はスタティックなのでしょうか?名前からするとそうみたいですが確認したいです。APIによるとこの関数はUObjectGlobalsファイルにあるみたいです。
UObjectGlobal.cppをみると
スタティックですね。
3.TArrayクラスのAdd関数
TArrayについては先週出来るだけの事はしましたが私がC++で独自のArrayを作成した事が無い事から今一長所と短所が分からなかったです。今回は単にそのTArrayクラスの関数の一つであるAddを調べて見ます。
Add()メンバー関数は2種類あって一つはパラメーターがConstの場合もう一方はそうでない場合のためのようです。更に解説で配列全てを再配列する可能性に述べています。この辺はしっかりC++の配列を勉強した人なら納得の説明になりそうですが私はまだ分かりません。
<Step.3>
1.GameModeクラスのBeginPlay()関数
RPGGameInstanceクラスでBeginPlay()関数をオーバーライドしているので一応調べておきます。
そしたらGameModeクラスにはBeginPlay() 関数がありませんでした。これはうれしい誤算ですね。調べた甲斐がありそうです。
UE4C++のGameModeのAPIによると以下に示すように
GameModeクラスの親クラスがありました。一個ずつ調べたらAActorクラスのメンバー関数にBeginPlay() 関数がありました。
2.GetGameInstance()関数
UE4のAPIからGetGameInstance()関数を調べたらWorldクラスとActorクラスの両方にGetGameInstance()関数がありました。どっちのGetGameInstance()関数を使用しているのでしょうか?
Actorクラスの方ですね。ではActorクラスのGetGameInstance()関数のAPI を見てみましょう。
Remark
このアクターを究極的には保持しているGameInstanceをテンプレートタイプにキャストして得ます。
もしキャストが失敗した場合はNULLを返します。
サンプルコードをみると
となっています。こうやってキャストするようですね。
<おまけ>
RPGGameInstanceクラスでBeginPlay()関数をオーバーライドしていますが、publicにしなくていいのか?と言う疑問を考察で調査すると<方法と結果>の<Step.3>で書いておいてすっかり忘れていました。これは来週調べます。