<前文>
前回は、時間だけ経ってあんまり進まないで終わってしまいました。何でかなと思ったら、この前文に時間と能力を掛け過ぎていました。ので今週は前文はスキップします。
それでは勉強を始めます。
<本文>
1.先週の疑問
先週、幾つかの未解決の問題がありました。
- C++のoverrideでvirtual オーバーライドする関数 overrideと書くやり方について
- CombatEngineクラスのdestructorでEnemyMonster変数をnullptrに指定するがcombatantOrder配列内の EnemyMonster要素はnullptrを指定しない理由
- Pure virtual function being called while application was running…と表示されてクラッシュする時がある。
今週は、まずこれらについて解決します。
1.C++のoverrideでvirtual オーバーライドする関数 overrideと書くやり方について
先週も書きましたが、C++におけるoverride のやり方は
CppReferenceのoverride specifier (since C++11)によれば
と書くのが正しいとあります。これはほとんどのC++を使用する人が納得する書き方だと思います。しかしUE4では
Overrideする側の関数にもvirtualを付けています。この教科書でも
そのように書かれています。
昔、どこかの教科書で「これら2つの書き方は結局同じである。」との説明を読んだ気がしますが確かではありません。
Virtual Functions in C++を見たらoverrideは無くてもいいが、あるとその関数があるvirtualな関数をoverrideしているのかどうかチェックしてくれると説明されていました。つまりOverrideはあってもなくてもいいそうです。どうもこれを勘違いしてvirtual overrideとoverrideは同じと思っていたみたいです。
もっと探したらStack overflowにvirtual? override? or both? C++ [duplicate]との質問がありました。私の聞きたい質問そのものを聞いているみたいです。
この解説によるとoverrideした関数にvirtualを使用するとその派生した関数もvirtualである事を確認するとあります。
更にもっと詳しい解説がありました。
それによるとoverrideした関数はその元の関数がvirtualである限り、virtualをつけなくてもvirtualになるとあります。つまりoverrideした関数につけるvirtualはあってもなくても同じである。と言うのが正しいみたいです。
Overrideを付ける事はその関数がoverrideしていなかった場合エラーで教えてくれるので確認の意味で役に立ちますが、virtualをoverrideした側の関数につけるのは無くても勝手にvirtualに成るのなら意味がない行為です。そんな事を天下のUE4がしますかね?感情的にはちょっと納得出来ない部分もありますが、そう言う事みたいです。
ここはStack overflowの質自体にも問題があると思うんです。この質問でも「前に同じ質問した人がいます。」と指摘されていますが、そちらを見たら単にOverrideについての説明がしてあって「それとは全然違う質問だろう。」と思わず画面に向かって叫んでしまいました。UE4のような技術の最先端の塊のようなGame Engineを作成している所で使用されているvirtual 関数() overrideと言う方法について質問しているのに、Stack overflowの管理者がそういう前提条件について全く理解出来ていなくて、その質問はこれと同じとC++の初心者向けの質問に飛ばしてこの質問に対する回答を締め切ってしまっている所からもStack overflowの質に疑問を感じます。
結論をまとめます。
overrideした関数にvirtualを付けるのは意味がなく、以下に示すように
書くのが正しい。
正し私は、UE4では
と書かれているのでUE4C++で書く時は、virtual 関数名 overrideと書くことにします。
と成りました。
2.CombatEngineクラスのdestructorでEnemyMonster変数をnullptrに指定するがcombatantOrder配列内の EnemyMonster要素はnullptrを指定しない理由
以下に例を示しますが、
このch3のコードは私が教科書のサンプルを元に改造したものなので教科書のサンプルをそのまま使用したch2のサンプルを以下に示します。
それぞれの変数は以下に示す形で宣言されています。
以下に示すようにコンストラクター内でplayerParty変数とenemyParty変数は
アサイン(assign)されています。
一方で、combatantOrder変数は以下のような方法で、一要素毎に、
PlayerParty変数の要素とenemyParty変数の要素が追加されてアサイン(assign)されています。
それぞれの変数のアサイン(assign)の方法に違いがあったとしてもheap領域に保持されているデータを指すアドレスを保持している変数の配列である事に変わりはないです。とするとNullptrを全ての配列の変数に追加するか、全部の配列の変数にNullptrを追加しないかのどちらかが正しいと言う事になります。どちらが正しいにしてもenemyParty変数のみnullptrする教科書のやり方は正しくないはずです。
どちらが正しいのでしょうか?
生のC++のケースで考えるならnullptrを使用する時は、あるポインター変数が指す実体がない時にこの変数が指しているアドレスは意味がない事を示すために使用します。今回の
はまさしくそれです。
となるとこれらの変数を保持するオブジェクトがdeleteされる時に、これらすべての変数も消えるので、これらの変数が指してるアドレスも消えるだけでしょう。そのアドレスが指している先のデータは他のクラスのインスタンスの変数が指してるデータでもあるので消す必要もないはずです。
ので、
はいらないと考えられます。
消しました。
3.Pure virtual function being called while application was running…と表示されてクラッシュする時がある。
こいつです。
どんな時にこれが起きるのか分かりました、UE4C++にコードを追加したりした後でビルトをして、その後でUE4エディターからplayをするとこのメッセージが表示されてクラッシュします。
3時間くらい格闘したのですが、原因が分かりません。一人で作成しているのでGitHubを利用する必要も感じていなかったので、このエラーが発生する前の所に戻る事も出来ません。うーん。困ってしまいましたね。
ブログを見直すと、4回分、約一カ月分をこのch3に費やしていますね。
うーん。最初から作り直しますか。
現時点で解決策がない以上、やり直す決断は早い方がこれ以上のダメージを広げなくていいはずですから。
やり直します。
新しいプロジェクトを作成します。名前はch4とします。GitHubは...今回はパスします。
念のためにUE4C++のGameModeBaseクラスに変数を一個追加してビルドし、UE4エディターからPlayしましたが、クラッシュはしませんでした。
3.1 やり直し(4月12日)
2020-04-12のブログを見ると、まずFCharacterClassInfoクラスを作成しています。プレイヤーが操作するキャラクターや敵のモンスターを作成するクラスの名前をGameCharacterクラスとしているのでcharacterと言う場合は敵、味方両方を表す言葉にしたいのですが、このFCharacterClassInfoクラスはプレイヤーが操作するキャラクターのみを指しているクラスなのにCharacterを使用しています。名前を変更します。YourHeroClassInfoクラスとします。
出来ました。
コードはch3の時と全く同じです。
勿論、親クラスをUObjectクラスからFTableRowBaseクラスに変更し、クラスからStructに変更したので、一回UE4エディターはクラッシュしました。しかし、2回目からは問題なく動いています。
このクラスを元にプレイヤーが操作するキャラクターの特性のデータテーブルを作成します。Ch3ではCharacterClassesと名付けたのですがCharacterを使用すると敵のモンスターを指すのか味方のキャラクターを指すのかが曖昧なので、YourHeroClassesと名付けます。
特性のそれぞれの値はch3と全く同じです。
次にGameCharacterクラスを作成します。キャラクターの特性を管理するクラスなのに、敵のモンスターやプレイヤーが操作するキャラの生成のための関数や戦闘エンジンのそれぞれのフェーズに対応した関数の管理を任されたりする気の毒なクラスです。
出来ました、と思ったらまたUE4エディターがクラッシュしました。うーん。ちょっと心配です。
UE4C++のGameCharacterクラスに関数CreateYourHero()を追加しました。
これでビルドしてプレイしてみます。もしこれでエラーになってUE4エディターがクラッシュしたら.25バージョンに問題があるとして前のバージョンで作成し直します。
大丈夫のようです。残りのコードも追加します。
もう一度ビルドしてプレイします。
クラッシュしました。ただし以下に示すように詳しいエラーの内容が示されていました。
Fatal error: [File:D:/Build/++UE4+Licensee/Sync/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectGlobals.cpp] [Line: 2335] Objects have the same fully qualified name but different paths. New Object: GameCharacter /Engine/Transient.Default__GameCharacter Existing Object: HOTRELOADED_GameCharacter_0 /Engine/Transient.Default__GameCharacter
うーん。これって文字通り解釈するならば、出来たオブジェクトが前からある同じ名前のオブジェクトとパスが一致しないのでエラーになりました。新しいオブジェクトのパスはGameCharacter /Engine/Transient.Default__GameCharacterで、現存しているオブジェクトのパスはHOTRELOADED_GameCharacter_0 /Engine/Transient.Default__GameCharacterです。
と言う意味ですね。
このエラーならネットで調べたら、直し方とまでは行かないまでも何らかのヒントは見つかるでしょう。
ここにありました。
Binary fileを消して手動でプロジェクトを開始すれば治るとあります。
これをやる前に、もう一度、UE4エディターからプレイしてみます。
勝手に治っていたみたいです。
では、最も恐ろしいエラー、UE4C++のコードを書き替えるたびにUE4エディターがクラッシュするのはどうでしょうか?
試してみます。
UE4C++のコードを少しだけ変更してビルドしUE4エディターからプレイしました。
大丈夫みたいです。
CreateYourHero()関数を実装します。
中身はch3のCreateMainCharacter()関数と全く同じです。
ビルドしてプレイしてみましたが、クラッシュしませんでした。
ブログには「この関数が動くのか心配なのでテストを先にします。」と書かれています。今回は動くのは分かっているのでテストする必要はないと思いますが、エラーが出るまでブログに書かれた通りにやって行きます。
GameInstanceクラスからRPGGameInstanceクラスをGameModeBaseクラスからRPGGameModeBaseクラスを作成します。
出来ました。と思ったらまたクラッシュです。しかも今回は油断していてエラーメッセージを見逃してしまいました。
一端エディターが復活したら2度とクラッシュしません。のでRPGGameInstanceクラスにコードを足して様子を見てみます。
ビルドしてプレイしてみます。
その時のブロクをみるとこの時点でUE4エディターがクラッシュしています。
うーん。今回はしませんでしたね。
クラッシュしないので続きをやって行きます。
RPGGameInstanceクラスとRPGGameModeBaseクラスからBPを作成しました。
セットします。
レベルBP内に以下のコードを追加します。
テストします。
Playを押した瞬間にクラッシュしました。まず
ここに今までのクラッシュの情報が記録されている事が分かりました。
更に、クラッシュした時の情報で、
のrow変数に何も保持されていない事が分かりました。
理由を調べていたらYourHeroClasses内のデータが消えていました。
今度はしっかりとデータを入力して保存します。
テストします。
はい。しっかりとMyYourHeroインスタンスがGameCharacterクラスから作成されている事が確認出来ました。
3.2 やり直し(4月19日)
4月19日の分をやり直します。ブログはここです。
まず、ItemsDataを作成します。
はい、出来ました。とは成らなかったです。
FItemsDataの部分をUItemsDataのままにしていたのでビルドエラーしてしまい、直すのに結構な時間を取られました。
それ以外は普通に出来ました。
記録のために書き残しておきますがFTableRowBaseから派生したStructを作成する時は必ず一回エディターがクラッシュしますが、今回も一回だけエディターはクラッシュしました。
今度はFItemsDataからItemsとWeaponsを作成します。
Itemsを開きデータを入力します。
回復薬などのイメージをch3からmigrateします。
またエディターがクラッシュしました。
データなどをmigrateされる方のエディターは閉じていないといけないのかも知れません。
Weaponsのデータも入力しました。
UE4C++は弄っていないのでこれでエディターがクラッシュする事はないと思いますが念のため確認します。
大丈夫でした。
今度は、RPGGameInstanceクラスに以下の変数を追加します。
これらの変数はプレイヤーがコントロールするキャラの特性を表しており、別なマップに移動しても消えないようにRPGGameInstanceクラス内に保存しておきます。
ビルドしてプレイしてみます。
エディターはクラッシュしませんでした。大丈夫ですね。
プレイヤーが操作するキャラの3Dモデルに使用するためch3内のModularRPGHeroesPolyartをch4にmigrateします。
ThirdPersonCharacterBPのSkeletal MeshをApprenticeSKに変更します。
キャラの表示が変わりました。
そう言えば、データをインポートしましたがch4のエディターはクラッシュしませんね。
今度はch3のアニメーションのデータをch4にmigrateします。
出来たみたいです。
ThirdPersonCharacterBPのAnimationのAnim ClassにMyThirdPerson_AnimBPをセットします。
アニメーションが作用しているのかテストします。
動いています。
エディターがクラッシュする事もありません。
次なんですが、ブログに沿って続けると以下に示すように
RPGGameModeクラスのコンストラクター内でDefaultPawnClassにCharacterクラスのStaticClass()関数の返し値を渡します。
これがpure virtual functionかもと言う気がしてます。
UObjectのAPIによれば
StaticClass() 関数はUObjectクラスの関数で、
となっています。
ここだけ見ると一応、実存しているCharacterクラスから作成されたオブジェクトを保持しているように思えますが、ThirdPersonのテンプレートとして提供されたGameModeクラスでは以下に示すように
具体的なBPを指定しています。
こっちを使用するのが正しいような気がします。
しかもこのブログのまとめの所で
と述べています。つまりこのPureVirtualFunction云々でエディターがクラッシュする現象はこの週から始まったんです。
滅茶苦茶怪しいです。
そうだ。Ch3で試してみます。
まず、GameModeクラスに関係のない変数を一個作成してビルドします。その後エディター側でプレイをします。
やはりクラッシュしました。
今度は、DefaultPawnClassの設定を変えてみます。
ビルドします。その後エディター側でプレイをします。
PureVirtualFunction云々と出てクラッシュしました。
うーん。ここは関係ないみたいですね。
Ch4に戻り、PRGGameModeBaseクラスのコンストラクターを作成します。
念のためビルドしてプレイしてみます。
ああ、クラッシュしました。しかも
と表示されました。
やっぱりここが問題だったみたいです。
因みにDefaultPawnClassはDefaultPawnにセットされていました。
PRGGameModeBaseクラスのコンストラクターを消して元に戻してみました。
ただし、DefautlPawnClassはDefaultPawnのままですね。
最初はThirdPersonChracterだったと思うですが。
見直しましたが全く記録がないです。ので分かりません。
元の状態は分からないですが、DefaultPawnではエラーになるのは分かっています。
に変更しました。
これでテストしてみます。
うーん。大丈夫みたい?
クラッシュしませんでした。
今度はもう一度、PRGGameModeBaseクラスのコンストラクターを作成してみます。
今度は、DefaultPawnClassの設定はThirdPersonCharacterのままです。
クラッシュしました。
当たり前と言えば当たり前の結果でした。
Ch3で
を追加してもクラッシュするのですからDefautlPawnClassの設定をThirdPersonCharacterにしても同じ結果になるはずですから。
となるとModularRPGHeroesPolyartの方に問題があるのかも知れません。ModularRPGHeroesPolyartの解説文には
となっていて4.25はまだサポートされていません。
うーん。
ここは一端、中止して心を落ち着ける必要がありそうですね。
今週はここまでとします。
2.まとめと感想
まさかpure virtual function云々のエラーを直すのがこんなに大変になるとは思いませんでした。来週まで色々考えてもう一度挑戦します。