<前文>
Vtuberについて、もうちょっとだけ
中国とアメリカの対決とか、Epic game社の裁判の行方とか、アメリカ国内におけるtiktokなどの中国系企業の活動、さらに来年の大統領選挙の行方とかを予測するのが私は大得意です。ある程度情報が集まるとパッと未来の結果が見えて来て絶対その通りになるんです。それが今回はどれもどうなるのか分かりません。
理由の一つは、Youtubeなどで私の視聴履歴を元に作成されるはずのお勧めのビデオが、一定の思想に基づいたある意味偏見の元に作成された物ばかり表示されるようになったからです。例えばEpic game社の裁判の行方についてはEpic game社が悪いと最初から決めつけている動画ばかり表示されるようになってしまいました。もし私が、Epic game社が悪いと最初から信じて、そのような動画ばかり見ていたなら、その視聴履歴がAIに反映されて、今のような結果になるのも分かるのですが、私はUE4をもう3年近く無料で使用させてもらっています。本当に、Epic game社には感謝しかありません。ので今回の裁判でも少しでもEpic game社の立場に立っている人の動画を探して見続けているにも関わらず、YouTubeのお勧めに現れる動画はEpic game社が悪いと最初から決めつけている動画ばかりです。
片っ端からそういう動画をブロックしたら今度は、一見公平を装っていながら最終的にEpic game社をメタメタに叩く動画ばかり表示されるようになりました。これだけ見たら全世界がEpic game社が悪いと言っている様に見えます。しかし自分で動画を探せば沢山の人がEpic game社が正しい、もしくは応援していると言っています。自分で探した結果を元に述べれば、Epic game社が悪いと言っている人達の方が少数派に見え、Epic game社が正しい、もしくは応援していると言っている人の方が多い感じがしました。
こういう、サブリミナルな情報操作を、一見公平な情報提供サイトでされると偏った情報に基づいて未来の予測をするため、当てるのはかなり難しくなります。偏った情報を元に正しい予測をするには、その予測の元となる情報が、その母集団から無作為に抽出したサンプルからどれだけ乖離しているのかを統計学的な処理をして一々確認する必要があるからです。
しかし私がそう言う問題の予測が出来なくなってしまった本当の理由は、Vtuberの動画ばかりを見るようになって、世界情勢なんか興味が無くなってしまったからです。
先日、Vtuberの切り抜きをまとめた動画を見ていたのですが、あるVtuberがスーパーチャットの内容で泣いてしまった話がありました。そのスーパーチャットをした人がツイッターか何かで、意地悪するつもりはなかったが結果的にそうなってしまってすみません。と謝ったらそのVtuberの人が、わざわざ、その人の所に、私が泣いちゃったから、心無い人からあなたも沢山攻撃されてしまったでしょうね。ごめんなさいね。と返信されたそうです。
それでその事は水に流してお終いになったそうです。
ここから真面目な話になりますが、その動画を見た時、私は素直に感動しました。この人達は他人の痛みを自分の痛みとして感じる事が出来きる知性(empathy?)と、中国の古典で言われる大きな責任*を果たせなかった事に対してごめんなさいが言える勇気がありました。その二つがあったから、この問題が大事に成らずに解決しました。それに私は感動しました。
*自分が正しく行動する事は小さな責任を果たす事になります。自分の周りの人が幸せに過ごせるように行動する事は大きな責任を果たす事になります。小人はこの小さな責任すら守れない人を指すそうです。中人は小さな責任は守れる人を指します。大人は大きな責任も果たせる人を指すそうです。昔読んだ中国の古典に書かれていた内容ですが、どの本に書かれていたのかまでは覚えていません。
人間関係の問題を解決するのに必要なのは、他人の痛みを自分の痛みとして感じれる事と、ごめんなさいが言える勇気だけだったんです。この事が分かっただけでもVtuberを見た甲斐がありました。
今、IT技術の発展はとんでもない勢いで進んでいます。しかし人間関係の問題を解決するのに必要なこの二つの事を支える技術なんでしょうか?例えば上記に記した偏った情報を示すAIの開発はむしろ逆なんじゃないでしょうか?
中国とアメリカの対決だって、21世紀の支配者を決める対決とか色々言われていますが、自分が痛いから相手をぶん殴っていいと考える人達と自分が正しければ幾らでも周りに迷惑かけて構わないという低レベルな人達の争いなんじゃないでしょうか?
そういうわけでますますVtuberの動画ばかり見る様になっています。
それでは今週の勉強を始めます。
<本文>
先週、デザインの途中で止めてしまったのでそれからやります。
Design part
- 宿屋がない事に気が付きました。宿屋を作成します。
- 完成した村を今のプロジェクトに移し、村人を配置します。
- 村のタイルの大きさをもっと小さくします。
プログラミングは以下の事をやります。
Programming part
- 戦闘中に「魔法」を選択した時に実行されるコードが攻撃を代用している状態なのでそれを直します。
- 魔法のコメントを正しくします。
- 先週のバグを直します。
Design Part
1.1 宿屋の作成
よくよく考えたら宿屋に来たからと言ってベッドが必要なわけではありませんでした。
道具屋や武器屋の建物をそのまま流用しました。
何か簡単でしたね。
中の人を作成しなければなりませんがそれはprogramming partでやる事なのでここまでで完成とします。
2.2完成した村を今のプロジェクトに移し、村人を配置します。
先にタイルの大きさを調節します。
以下のように変えました。
それではCh4_3にこのマップを写します。
中々いい感じです。
モンスターが生成されちゃってますね。
モンスターとアイテムの生成はそれぞれのレベルによって違う事と、別のレベルに移動したら、新しく再生される事の両方を考慮して、もう一度作成し直さないと駄目ですね。
折角なので、町をmap1に追加してみます。
出来ました。
中を見てみます。
開始した時点で結構な迫力がありますね。
地面が平らなのと、地面に貼ってあるmaterialが合って無いのが気になりますが、それ以外は結構いい感じです。
町の入り口です。
入口の傍にある家です。付属の家をそのまま使用していますので、中に入る事は出来ません。
道具屋の前です。
道を挟んで反対側に武器屋があります。
中の人も正しい場所に配置されています。
奥の空き地に行くと、地面が平ら過ぎます。後で直します。
町には4つの出入り口があります。その内の3つはどこにもつながっていなくて落ちるだけです。
村人を配置します。
と言っても村人に当たるメッシュが無いのでグレイマンをそのまま代用します。
話しかけても全員同じ回答しかしません。これも後で直します。
この村人の会話をどうやって管理するかが大問題なのですが、それは後で考えます。2. Programming Part
2.1 戦闘中に「魔法」を選択した時に実行されるコードが攻撃を代用している状態なのでそれを直します。
基本的には戦闘中のアイテムの使用と同じになるはずです。まずアイテムを使用した時の復習をします。
2.1.1 戦闘時のアイテム使用を元に復習する
先週作成したFlow Chartを元にコードを追っていきます。
CombatUIWidgetクラスのAttackTarget()関数が呼ばれていますね。
しかし、実際の操作ではまず、CombatUIウィジェットの道具ボタンが押さる所からitemのコードが実行されるので、先週作成したFlow Chartに注意を払いつつ、Itemボタンが押された時から見て行きましょう。
色々やっていますが、結局はItemウィジェットを開く事が目的です。
Itemウィジェットのボタンをクリックしたら、今度はTargetCharacterウィジェットを開きます。
アイテムを使用する相手を決定したら、何かのイベントを発して他のクラスに連絡すると思っていたのですが、そのような事はしていませんでした。ここでこのコードは終了していました。
うーん。
分かりました。TargetCharacterウィジェットの相手を選択するボタンを押した時にComatUIWidgetクラスの関数、UseItemInCombat()が呼ばれていました。これを見逃していました。
先週のブログを読み直したら分かり易く書かれていました。
以下にUseItemInCombat()の実装部を示します。
ここまで、魔法を選択した場合を振り返ります。
Magicウィジェットを作成します。
しかし作成するための元のデータは、
Magicsと言う名前のdataTableから取っています。(以下にMagics のdata tableを示します。)
ここは、GameCharacterクラスのMagics変数から取るように変更しないといけません。
更に、Magicウィジェットから使用する魔法を選択すると
AttackTargetOptionウィジェットが開かれます。このウィジェットは攻撃を選択した時に開かれるウィジェットです。魔法を使用した時に開く別なウィジェットの作成も必要です。
AttackTargetOptionウィジェットが呼ばれるので実行される関数は当然AttackTarget()関数になります。CombatUIWidgetクラス内に魔法専用のAttackTarget()関数の作成が必要です。
以下にCombatUIWidgetクラス内にある攻撃、アイテム、逃げるに対応した関数を示しておきます。
それぞれの関数内では、それぞれのアクションに対応したICombatAction クラスを継承したTestActionクラスが作成されます。
魔法を使用した時のためのTestActionクラスの作成も必要ですね。
ここまでで。やっと先週作成したflow chartの最初のブロックが終わりました。
ここから先のコードを読んでいきますが、先週のブログの復習になります。重要なのでここでも記録を残しながらやります。
UseItemInCombat()関数内で、変数finishedDecisionがtrueになります。
CombatUIWidgetクラスのMakeDecision()関数がtrueを返すようになります。
ここで情報の伝達先が切れてしまって、次どうなるのか分からなくなってしまったんですが、先週のブログと先週作成したflow chartにしっかり続きが書いてありました。
GameCharacterクラスのMakeDecision()関数に行くらしいです。
成程、GameCharacterのdecisionMaker変数は、
RPGGameModeBaseクラスのtestCombat()関数内で、
作成されたCombatUIWidgetクラスであるCombatUIInstanceがassignされています。
のでGameCharacterクラスのMakeDecision()関数内の変数tmpResultはtrueを返します。
結果、GameCharacterクラスのMakeDecision()関数もtrueを返します。
先週書いたブログによれば、この後、CombatEngineクラスのTick()関数に行くそうです。
見てみましょう。
この辺は、先週のブログと全く同じに成りますが、この確認は非常に重要なのでそのまま続行します。
先週書いた通りですが、ここでcurrentTickTarget変数のBeginExecutionAction()関数が一回だけ実行され、その後、currentTickTarget変数のExecutionAction()関数が実行されます。
先週のflow chartで言えば以下の部分に当たります。
ここも全く先週と同じですが、以下にGameCharacterクラスのBeginExecutionAction()関数の実装部を示します。
ここでCombatEngine変数にassignされていた、それぞれのアクションに対応したICombatAction クラスを継承したTestActionクラスのBeginExecutionAction()関数が実行されます。
以下にTestCombatActionItemクラスのBeginExecutionAction()関数の実装部を示します。
今回、魔法に対応したTestActionクラスを作成します。
今度は以下の図の②の部分が実行されます。
先週のフローチャートでは黄色の枠で囲った部分です。
実際のコードを以下に示します。
CombatActionはTestCombatActionItemクラスのBeginExecutionAction()関数の実装部を示します。
はい。
これで戦闘中にItemを選択した時の流れの復習が出来ました。これを元に魔法を選択した場合を直していきましょう。
2.1.2 戦闘時の魔法使用のBPコードを直す
まず、AttackTargetOptionウィジェットの代わりを作成します。
AttackTargetOptionウィジェットをduplicateしてAttackTargetOptionForMagicウィジェットと名付けました。
コメントが攻撃の時のものなのでTargetCharacterウィジェット(道具を使用した時に対象者を選ぶウィジェット)を元にコードとコメントを直しました。
その結果、CombatUIとMagicウィジェットのコードも少し変わりました。
Magicウィジェットはコメントの部分を変えました。
取りあえずコメントの変化だけテスト結果をしめします。
まず、魔法を選択し、炎(小)そして魔法の対象にゴブリンを選択しました。
魔法を使用した時のコメントです。
あんまり関係ないですが、一個バグを発見しました。
道具を一個も持たない状態で道具を選択すると、何も出来ない状態になります。
このバグは後で直します。
2.1.3 AttackTarget() 関数に代わる関数の作成
現在、戦闘中に魔法を選択した場合、最終的にCombatUIWidgetクラスのAttackTarget()関数が呼ばれます。
この関数は、攻撃を選択した場合のための関数ですので、戦闘中に魔法を選択した場合の関数をCombatUIWidgetクラス内に作成します。
Itemの場合を参考にして、HPとMPの値もパスするようにします。
実装部はとりあえず、AttackTarget()関数と同じにしておきます。
Buildも通りましたのでテストしてみます。
CombatUIWidgetクラスのAttackTarget()関数をUseMagicInCombat()関数に変更します。
これでテストします。
全く前と同じに動きました。成功です。
2.1.4 TestCombatActionクラスに代わるクラスの作成
TestCombatActionクラスは攻撃を選択した時のためのICombatActionの派生クラスなので魔法専用のクラスを作成します。
中身はとりあえず、TestCombatActionItemクラスと同じにします。
ビルドします。
成功しました。
テストします。
表示がオカシイです。直していきます。
2.1.5 コメントの直し
まず、魔法のMPとHPですがHPは相手に与えるダメージですが、MPはプレイヤーが操作するキャラの消費分です。
以下のように直しました。
後、MPが足りない時の場合も考えないといけない事に気が付きました。これは後で考えます。
これがきちんと機能するかテストします。
まず、最も基本的な状態でのテストです。敵に50のダメージを与え、MPを20消費します。
以下に示したように、KUMOのMPは50、ゴブリンのHPは100です。
KUMOのMPは30、ゴブリンのHPは50になるはずです。
なっていますね。成功です。
その他の条件のテストも行いましたが予想通りの結果になりました。
次はコメントを直します。
以下の関数のargumentの値を変更しました。MPの場合はTargetをcharacterにも変更しました。
更にRPGGameModeBaseクラスのReportCharacterHPisDamagedとReportCharacterMPisDamagedに実装されているBPのコードにプラスとマイナスが間違っているミスがあったのでそれも直しました。
結果、以下のようにコメントも直りました。
2.1.6 使用した魔法に基づいたHPやMPをUseMagicInCombat()関数にパスする。
今は適当な値をパスしてるので使用した魔法に基づいた値をパスします。
以下のように改良しました。
AttackTargetOptionMagicウィジェットに新しい変数、nameOfMagicを作成しそこに選択した魔法の名前を保持させます。
その名前を使用してData Table、Magicsからその魔法のデータを引き出します。
テストします。
炎(小)を使用しました。
データ通りの結果になっています。
炎(大)を使用した時もデータ通りの結果になりました。
2.1.7 使用出来る魔法をDataTableのMagicsから取るのではなく、GameCharacterクラスのMagics変数から取るようにする
以下に示したGameCharacterクラスのMagics変数から使用出来る魔法を得るようにします。
この変数に直接、日本語を入れると最終的に文字化けしてしまうので、今回は以下に示したように、
M keyを押したらdata tableのMagicsのデータをRPGGameInstanceのMyYourHero変数のMagics変数に保持する事にしました。
余り、前回と変わっていませんが、魔法はキャラクターのレベルが上がったら使用出来るように考えていますので、レベルの上昇のコードを改良する前にあまりいじれないので今回はそうしました。
テストします。
魔法が全くない時です。
道具が全くない時と同じになりました。
魔法が使用出来る時は、以下に示したように
今まで通りに使用出来ます。
更に使用した後も
同じです。
これで戦闘中における魔法の使用もコードの上では他の選択と全く同じ方法で動くようになりました。
2.2 魔法のコメントを正しくします。
2.1の時点で直しました。
2.3 先週のバグを直します。
道具や魔法が無い時は選択出来ない様にします。
以下のようにしました。
道具や魔法が無い時は以下に示したようになります。
3. まとめと感想
今週はこれで終わりにします。