UE4の勉強記録

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

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

<前文>

f:id:kazuhironagai77:20190414194435p:plain

今週は特に話したい話題がないので前文はなしです。

<本文>

<目的>

前回は、TurnとTurnrateの違いなどの前々回で勉強した内容に対する考察を行いました。今回は、前々回の続きでRPGCharacterクラスの作成をやって行きます。

<方法と結果>

Step.1 MoveForward関数の 実装>

今回はMoveForward()関数からです。まず教科書のコードから見てみます。

f:id:kazuhironagai77:20190428205119p:plain

まず、最初にControllerがNullでないか調べているのですが、Controllerと言う変数を作成した記憶がないです。

サンプルコードの方のRPGCharacterクラスをみてもControllerと言う変数はありません。VS上でRPGCharacteter.cpp内でControllerと書いてみると、

f:id:kazuhironagai77:20190428205141p:plain

APawnクラスの変数とあります。UE4C++のAPIを調べて見ると、

f:id:kazuhironagai77:20190428205207p:plain

このアクターに今、所有されるコントローラー

とありました。なるほど。「CharacterクラスはPlayerControllerクラスが付くのでそれをここで呼び出すのにControllerを使用すればいいのか。」と納得しました。が良く見るとPlayerControllerでなく単なるControllerですね。うーん。

AControllerクラスも調べて見ます。

UE4C++のAPIによると、

f:id:kazuhironagai77:20190428205259p:plain

AControllerクラスはPlayerControllerクラスの親クラスですね。更にRemarkには、

f:id:kazuhironagai77:20190428205319p:plain

コントローラーは物質的でないアクターでアクションをコントロールするためにそのPawnを所有する事が出来ます。

PlayerControllerは人間のプレイヤーがポーンをコントロールするために使用されます。一方でAIControllerAIが実装されます。そのAIはそのポーンをコントロールします。

ControllerPossess()メソッドを使用してポーンをコントロールします。そしてUnPossess() 関数を使用してコントロールを放棄します。

コントローラーは、彼らがコントロールしているポーンのために起きている沢山のイベントから通知を受け取ります。これはコントローラーに、このイベントに反応する(イベントに干渉し、ポーンのデファルトでの振る舞いを停止する)という振る舞いを実装する機会を与えます。

ControllRotationGetControllRotationを通じてアクセス)はコントロールさてれいるポーンの視線と射程の方向を決定し、マウスやゲームパットなどからのインプットによって影響されます。

とありました。

となると、最初の予測通り、このControllerにはplayerControllerが入っていると考えていいと思います。Characterクラスの派生クラスでplayerControllerにアクセスしたい時は、単にControllerと打てばいいと言う事でしょうか。それが正しいなら大変便利です。

実際のif節は以下の様になっています。

f:id:kazuhironagai77:20190428205407p:plain

つまり、エディターかBPでControllerを初期化すると言う事でしょう。そしてControllerがPlayerControllerだったら

f:id:kazuhironagai77:20190428205435p:plain

エディターのWorld Setting でPlayerControllerをセット出来ます。万事解決ですね。

次のコードを見てみましょう。

f:id:kazuhironagai77:20190428205542p:plain

正面を見つけます。と解説していますが、このコードで正面が見つけられるのでしょうか?

GetControlRotation()関数が何をするかによりますが、この関数が正面を指定してくれるなら出来そうですね。

とここまで書いて、AControllerクラスのremarkに、

f:id:kazuhironagai77:20190428205628p:plain

f:id:kazuhironagai77:20190428205636p:plain

と書かれていました。たった今読んだにもかかわらず、本当の意味を理解していませんでした。GetControlRotation()関数が正面を決定するんですね。

次のコードYawRotationは、このゲームのPlayerPawnは左右だけしか動かないので、Yawの値だけ抜き出してると考えられます。

次に行きます。

f:id:kazuhironagai77:20190428205657p:plain

まずFRotationMatrixクラスの解説をUE4C++のAPIから示します。

f:id:kazuhironagai77:20190428205721p:plain

回転の行列で転移を含まない。

更に、GetUnitAxis関数について、ここから引用すると

f:id:kazuhironagai77:20190428205931p:plain

この行列の単位軸の長さを得ます。

とありました。

と言う事は!何となく分かりかけて来ました。ここで行っているのは単位の変換だと思います。

まず、

f:id:kazuhironagai77:20190428210041p:plain

でPlayerPawnの正面が向いている方向を得ます。この値の単位ですが、pitch、yaw、rowなので当然角度になります。

FRotatorのAPIにしっかりと

f:id:kazuhironagai77:20190428210100p:plain

全ての回転の値は度(°)で保存される。

と書かれていました。

このゲームではplayerPawnは左右にしか回転しませんので、Yaw以外は要りません。ので消します。

f:id:kazuhironagai77:20190428210140p:plain

これを、AddMovementInput()関数に方向として渡せば、万事解決なのですがAddMovementInput()関数は方向はベクトルで表示して渡して下さいと言っています。ので、

f:id:kazuhironagai77:20190428210245p:plain

でベクトルに変換します。

これが、この実装部の意味だと思われます。

ですがこの場合だとEAxis::Xが何をしているのか不明です。それはここから調べます。

まず最初にFRotationMatrixでどのような4x4の行列が作成されるのかを見てみます。

FRotationMatrix(YawRotation)は、以下のように実装されていました。

f:id:kazuhironagai77:20190428210319p:plain

ので、FRotationTranslationMatrixクラスを見ると、以下の方法で4x4の行列が作成されていました。

f:id:kazuhironagai77:20190428210414p:plain

この行列は何を表しているのでしょうか?(前提条件としてOriginは0なので3x3の部分だけ注目します。)

まず思ったのがConversion between quaternions and Euler anglesにある回転行列です。

f:id:kazuhironagai77:20190428210508p:plain

しかし回転を表している部分だけを見ても完全には一致しませんね。

UE4のFRotationTranslationMatrixクラスの行列の回転部分だけを抜き出して上の行列を真似て書いてみると、

f:id:kazuhironagai77:20190428210544p:plain

一部の符号が反対に成っている事が分かります。

Conversion between quaternions and Euler anglesにある回転行列は以下に示す3つの行列から計算されたのですが、

f:id:kazuhironagai77:20190428210610p:plain

この行列を少し変形して以下の様にすると

f:id:kazuhironagai77:20190428210630p:plain

計算結果は、UE4のFRotationTranslationMatrixクラスの行列の回転部分と同じになります。

うーん。カンですが、この二つは同じ事を表していると思うんです。ただ座標軸の配置の仕方が違うので少しだけ回転行列の内容が違っているだけと思うです。

どうすればそれを証明出来るでしょうか?

まずこの3つの行列をどのように求めたのかを調べます。そして同じ方法でUE4の座標軸に対して計算してみます。もし同じ方法でUE4の座標軸の回転行列を求めてFRotationTranslationMatrixクラスの行列の回転部分の行列の値と同じになれば、UE4のFRotationTranslationMatrixクラスのこの部分の行列は回転行列を表していると言えると思います。

ウーン!見つからない。簡単な説明は沢山あるのですが厳密にステップバイステップで説明しているサイトは見つからないです。

やっと見つけたのがここです。回転行列の導き方をかなり詳しく説明しています。この説明に基づいてUE4の座標軸での回転行列を求めてみます。

まず、このサイトに詳しく説明されていたのですが、UE4の座標軸は以下のようになっています。

f:id:kazuhironagai77:20190428210856p:plain

この座標軸それぞれに以下に示すように単位ベクトルp, q, rを作成します。

f:id:kazuhironagai77:20190428210918p:plain

P、q、rのそれぞれのベクトルを行列で表すと、

f:id:kazuhironagai77:20190428210945p:plain

となります。このpqrベクトルをx軸を中心に角度φだけ回転させます。(どちらの回転方向を正とすべきとの指摘をしているサイトが見つからなかったので時計回りを正として計算します。)

すると、以下に示すようにpqrベクトルが角度φだけx軸を中心に回転します。

f:id:kazuhironagai77:20190428211029p:plain

この図は大変見にくいので、x軸の方から見て2次元のグラフにすると以下の様になります。

f:id:kazuhironagai77:20190428211524p:plain

良く見ると移動後のpqrベクトルのそれぞれの値も計算出来る事に気が付きます。

まず、x軸上のベクトル、pですが、全く動いていないので、前と同じ(1,0,0)です。

次に、y軸上のベクトルqですがx軸を中心に回転しているので、xの値は変わらず0、Yの値は角度φだけ回転したのでcosφ、zの値も角度φだけ回転したのでsinφとなり、(0,  cosφ, sinφ)となります。

最後に、z軸上のベクトルrですが、xの値は変わらず0、Yの値は角度φだけマイナス側に回転したので-sinφ、zの値は角度φだけプラス側で回転したのでcosφとなります。

これを行列で表すと、

f:id:kazuhironagai77:20190428211610p:plain

となります。これを、UE4のFRotationTranslationMatrixクラスの行列の回転部分だけを抜き出した行列と比較すると、

f:id:kazuhironagai77:20190428211746p:plain

最後の行列と同じに成ります。オー。

次にRyの行列を求めます。まず図を元に戻してY軸を中心にして角度θだけ回転してみます。

ここでRと同じように図に書いて説明しようと思ったのですが、あまりにも図を作成するのは大変なので止めました。結果だけ示します。手順は全くRxと同じです。

f:id:kazuhironagai77:20190428211826p:plain

これも、UE4のFRotationTranslationMatrixクラスの行列の回転部分だけを抜き出した行列の真ん中の行列と同じになりました。

それで最後の軸回転の計算、z軸を中心にして角度ψだけ回転した場合のpqrベクトルの行列を計算するのですが、結論から言うとUE4のFRotationTranslationMatrixクラスの行列と同じには成らなかったです。

それで、確認の意味も込めてここで計算を一つ一つやって行きます。

まず、グラフを最初の状態に戻します。

f:id:kazuhironagai77:20190428211919p:plain

z軸を中心にしてpqrベクトルを角度ψだけ時計回りに回転させます。

f:id:kazuhironagai77:20190428212008p:plain

分かり易くするためにz軸方向から眺めた図を作成します。

f:id:kazuhironagai77:20190428212204p:plain

まず、z軸上のベクトルrですが、全く動いていないので、前と同じ(0, 0, 1)です。

次に、y軸上のベクトルqですがz軸を中心に回転しているので、zの値は変わらず0、Yの値は角度φだけプラスに回転したのでcosψ、xの値も角度φだけマイナス側に回転したので-sinψとなり、(-sinψ, cosψ, 0)となります。

最後に、x軸上のベクトルpですが、zの値は変わらず0、Yの値は角度ψだけプラス側に回転したのでsinψ、xの値も角度ψだけプラス側で回転したのでcosψとなり、(cosψ, sinψ, 0)となります。

これを行列で表すと、

f:id:kazuhironagai77:20190428212324p:plain

となり、UE4のFRotationTranslationMatrixクラスの行列の回転部分だけを抜き出した行列の最初の部分と

f:id:kazuhironagai77:20190428212352p:plain

似ていますがはっきりと違う行列が出来ました。

うーん。もしz軸の回転を逆にすると、

f:id:kazuhironagai77:20190428212417p:plain

となり、UE4のFRotationTranslationMatrixクラスの行列の回転部分だけを抜き出した行列の最初の部分と全く同じになります。

軸の回転方向を時計回りにしたのは、全くの独断ですがz軸だけ逆に回転すべきなんて事があるのでしょうか?

多分そうなんでしょう。

UE4のFRotationTranslationMatrixクラスの行列の回転部分は回転行列を表していると考えられます。

次に、GetUnitAxies(EAxis::X)について考察します。

UE4のFRotationTranslationMatrixクラスの行列の回転部分、

f:id:kazuhironagai77:20190428212607p:plain

を計算すると

f:id:kazuhironagai77:20190428212627p:plain

となります。

GetUnitAxies(EAxis::X)の実装部を以下に示しますが、

f:id:kazuhironagai77:20190428212647p:plain

f:id:kazuhironagai77:20190428212656p:plain

最初の行の(consθcosψ、cosθsinψ、sinθ)を返しています。これは回転後のX軸の方向を示しているそうです。

f:id:kazuhironagai77:20190428212843p:plain

そしてX軸方向はForwardです。

Step.2 MoveRight関数の 実装>

f:id:kazuhironagai77:20190428213010p:plain

f:id:kazuhironagai77:20190428213019p:plain

Forward関数との違いはEAxis::Yだけです。EAxis::Yは2番目の行の値を返し、この値はY軸方向つまり右を差しています。

<まとめ>

以上です。回転の行列についてのz軸の回転結果が合わなかった事についてはもっと調査が必要と思っています。それ以外はほとんど理論通りの結果でした。今週中に、3章を終わらせようと思っていたのですが、全く進みませんでした。

<おまけ>

このチャンネルではもっとオイラーの行列への変換を丁寧に説明していました。そう今回の角度から行列を生成する方法は、オイラー角をオイラーの行列に変換していたんです。それをquaternion から探したのでこんなに時間がかかってしまったんです。ただしこのチャンネルの最後の答えは以下に示すようになっています。

f:id:kazuhironagai77:20190428213124p:plain

以下に示した所までの計算過程までは全く同じだったのですが、

f:id:kazuhironagai77:20190428213234p:plain

最後で違う結果に成ってます。

正直に言うとこの部分、どうせ同じ答えになると思ったのできちんと計算をしてませんでした。この辺は来週検討します。