Mercurial > hg > Papers > 2019 > hamase-thesis
changeset 1:d7993a65995e
2ndcommit
author | Yuki HAMASE <e155718@ie.u-ryukyu.ac.jp> |
---|---|
date | Mon, 18 Feb 2019 20:50:52 +0900 |
parents | 07e6811b4764 |
children | 374871d138f5 |
files | final_hamase/chapter4.tex final_hamase/fig/RHL01.png final_hamase/fig/RHL02.png final_hamase/fig/RHL03.png final_hamase/fig/player01.png final_hamase/fig/player02.png final_hamase/fig/player03.png final_hamase/fig/player04.png final_hamase/fig/player05.png final_hamase/fig/player06.png final_hamase/fig/player07.png final_hamase/fig/player08.png final_hamase/fig/target01.png final_hamase/fig/target02.png final_hamase/fig/target03.png final_hamase/fig/target04.png final_hamase/fig/targetL01.png final_hamase/main.pdf final_hamase/main.tex |
diffstat | 19 files changed, 307 insertions(+), 5 deletions(-) [+] |
line wrap: on
line diff
--- a/final_hamase/chapter4.tex Fri Feb 15 21:21:12 2019 +0900 +++ b/final_hamase/chapter4.tex Mon Feb 18 20:50:52 2019 +0900 @@ -7,7 +7,7 @@ 以下に、実際に\name で実装されたLeapFloatingHandsCharacterのBPを示す(図\ref{player})。 \begin{figure}[htbp] \begin{center} - \includegraphics[height=8cm]{fig/player.png} + \includegraphics[width=16cm]{fig/player.png} \end{center} \caption{実際のPlayerActor} \label{player} @@ -17,7 +17,7 @@ \begin{figure}[htbp] \begin{center} - \includegraphics[height=8cm]{fig/player1.png} + \includegraphics[height=12cm,angle=90]{fig/player1.png} \end{center} \caption{整理されたもの} \label{player1} @@ -25,15 +25,317 @@ \subsection{手の位置の検出} +手の位置の検出はLeapFloatingHandsCharacter内のイベントRightHandMovedおよびイベントLeftHandMovedに記述される(図\ref{player08})(図\ref{RHL01})。イベントRightHandMovedとイベントLeftHandMovedの内容は同様であるため、ここではイベントRightHandMovedのみ解説する。 + +\begin{figure}[htbp] + \begin{center} + \includegraphics[width=16cm]{fig/player08.png} + \end{center} + \caption{イベントRightHandMoved} +\label{player08} +\end{figure} + +\begin{figure}[htbp] + \begin{center} + \includegraphics[width=16cm]{fig/RHL01.png} + \end{center} + \caption{手の位置の検出・格納} +\label{RHL01} +\end{figure} + +イベントRightHandMovedは、LeapMotionからの右手相対位置が変化するごとに呼び出されるイベントである。このイベントが呼び出された時、右手の複数の情報(手の関節の位置、手首の位置等)が取得できるが、今回はPalmPosition(手の平中央の位置)を元に手の位置を検出する。この際、PalmPositionはLeapMotionから見た際の相対位置であり、LeapMotionはHMD前面に取り付けてあるため必ずしも正面を向いておらず、頭蓋骨中心(カメラ位置)からも離れた場所からの相対位置であることに注意する。 + +PalmPositionに$(6,1,0)$を加算することで、これをLeapMotion中心の相対位置から、カメラ中心からの相対位置に変換する。 + +ここで関数RotateVectorを使用し引数に、カメラ中心の手の相対位置、カメラの絶対角度を指定することで、カメラ中心の手の絶対位置に変換する。 + +これにカメラの絶対位置を加算することで、原点中心の手の絶対位置が得られる。これを変数RightHandLocに格納する。これがセットRightHandLocまでの一連の流れである。 -\subsection{入力方法とその実装} +\subsection{入力方法とその実装(UI)} + +RightHandMovedのセットRightHandLoc以降にUIの入力についての記述が行われる(図\ref{RHL02})(図\ref{RHL03})。 + +\begin{figure}[htbp] + \begin{center} + \includegraphics[width=16cm]{fig/RHL02.png} + \end{center} + \caption{疑似半直線状検出線の作成} +\label{RHL02} +\end{figure} +\begin{figure}[htbp] + \begin{center} + \includegraphics[width=16cm]{fig/RHL03.png} + \end{center} + \caption{UI種類の判定と動作} +\label{RHL03} +\end{figure} + +\name では入力方法として「かざされたもの」を入力対象とする方法を考案した。ここで「かざされたもの」とは「カメラ中心からRightHandLocおよびLeftHandLocを結ぶ半直線で、RightHandLocまたはLeftHandLocを始点とするものの上に存在する全てのオブジェクト」のことを指す。つまり、カメラ中心から手の中心に直線を引き、そのまま延長させた半直線のうち、カメラ中心から手の中心に至る線分以外の部分をオブジェクト検出線とし、プレイヤーはこの検出線上にあるオブジェクトを操作することができる。 + +検出線作成にはMultiLineTraceForObjectsを使用する。これは線分の始点および終点・検出するオブジェクトの種類などを引数とし、検出されたオブジェクトを全て配列で返す関数である。 + +手の位置ベクトルを$\overrightarrow{H}$とし、カメラの位置ベクトルを$\overrightarrow{C}$とするとMultiLineTraceForObjectsの引数のうち +始点$\overrightarrow{S}$を +$$\overrightarrow{S}=\overrightarrow{H}$$ +終点$\overrightarrow{E}$を +$$\overrightarrow{E}=100(\overrightarrow{H}-\overrightarrow{C})+\overrightarrow{C}$$ +とすることにより疑似的な半直線を生成することができる。 + +返された構造体配列はForEachLoopを使用して処理する。 +構造体の内部情報のうち検出されたObjectの情報を使用する。Objectが"Tutorial"または"Normal"である場合、この2つのオブジェクトはタイトル画面での複雑な情報を処理するため、その他のオブジェクトとは異なる処理を行う。具体的にはobject内部のカスタムイベントAddDamageが実行される。 + +検出されたObjectが"Tutorial"または"Normal"ではなく、タグbuttonを所持していた場合、そのobjectにはApplyDamage関数によって「かざされた」ことを通知する。 + +ここで、イベントRightHandMovedは手が動くたびに実行され、上記の処理は常に実行され続けることに注意しなければならない。 +\name において入力を受け付けるUIはユーザビリティ向上のため、入力動作がなくとも「かざされた」通知を受けてアクションを起こし、「かざされ」ていない場合にはアクションを取りやめる。上記処理は、この演出のために用いられるもので、ボタンに入力が行われた場合の処理は別に記述される。 + +\subsection{「かざされている」UIの挙動と実装} + +\subsection{入力方法とその実装(手が握られた時)} + +手が握られた時、かざされているオブジェクトのうち最も「規定タイミングと比較して最も入力が遅かったもの」が入力を受けつけ、アクションを起こす。手を握る時、入力を受け付けるオブジェクトの種類は、「ターゲット」「UI」の二種類である。 + +\subsubsection{イベントHandGrabbed} + +手が握られた(手の開き具合が一定以下になった)時、左右の手に関わらずイベントHandGrabbedが実行される(図\ref{player01})。このイベントが実行された場合、イベントHandGrabbedを呼び出した手が右手である場合にTRUEとなるBoolean型の返り値IsRightによって左右どちらの手であるかを判定する。これがTRUEである場合、右手を開いた場合にTRUEとなるBoolean型変数IsRightReleasingおよび右手でロングターゲットに入力した場合にTRUEとなるBoolean型変数IsLongRightをそれぞれFALSEとする。左手に関しても同様であるため、省略する。 + +手が握られた際に使用する検出半直線は、UIの場合には100であった延長距離を10000としている。その他の処理はUIの検出半直線と同様であるため、省略する。 + +\begin{figure}[htbp] + \begin{center} + \includegraphics[width=16cm]{fig/player01.png} + \end{center} + \caption{イベントHandGrabbedおよび検出半直線に使用する引数の作成} +\label{player01} +\end{figure} + +\subsubsection{右手が閉じられた場合} + +右手が閉じられた場合、検出半直線によるオブジェクトの検出が行われる(図\ref{player03})。 + +\begin{figure}[htbp] + \begin{center} + \includegraphics[width=16cm]{fig/player03.png} + \end{center} + \caption{検出後の処理} +\label{player03} +\end{figure} + +ここで、ターゲットは、検出タイミングがどれだけ規定タイミングと異なっているかを示すScoreという変数を持っている。Scoreは最も規定タイミングより早い場合1、消滅するタイミングの場合0を返すFloat型変数である。これを比較して最も遅いタイミングだったターゲットを決定するのに使用されるNearestscoreに1以上の数値を代入する。また、最終的にアクションを起こすターゲットを格納するためのObject型変数DestroyactorRightにNULLを代入する。ここまでで必要な変数初期化が終了する。 + +ここから、MultiLineTraceForObjectsによって返された構造体配列をForEachLoopによって処理する。 + +検出線によって検出された位置には検出されたことを示すEmitterであるBasicParticleが表示される。 +BasicParticleは関数SpawnEmitter atLocationによって呼び出される。BasicParticleは自動で削除される。 + +検出されたObjectがターゲットだった場合、ターゲット内部の変数Scoreを読む。ScoreがNearestScoreよりも小さい場合(規定タイミングにより近い場合)、そのObjectはDestroyactorRightに格納される。 + +検出されたObjectがターゲットでなかった場合、Checker\_rightというObjectであるかどうかを判定する。Checker\_rightとはタイトル画面に配置されるオブジェクトで、LeapMotionやHMDその他の接続が正しく行われているかを判定するものである。具体的には、Checker\_rightに右手をかざし、右手を閉じる、または開くことでLeapMotionが正しく接続され、右手の検出が正しく行われていることを確認する。検出されたObjectがChecker\_rightであった場合には、これにApplyDamageをすることでChecker\_rightがアクションを起こす。 + +検出されたObjectがChecker\_leftであった場合、これは左手の検出が正しく行われていることを確認するためのオブジェクトであるため、なにもアクションをおこさない。 + +検出されたObjectが最終的にターゲット、Checkerの両方でないことが判明すると、UIであると判定し、これにApplyDamateをすることでアクションを起こさせる。 + +\subsubsection{ForEachLoop終了後} + +ForEachLoopが終了すると、ターゲットへのApplyDamageを行うための処理となる。始めに、DestroyActorがNULLである(Targetを検出しなかった場合)かどうかを判定する。 + +NULLでない場合、DestroyactorRightに格納されたObjectに対してApplyDamageを行う。Damageを加えられたターゲットは、タイミングに応じて点数を加算し、消滅する。ただし、あまりにタイミングから離れている場合には何もアクションを起こさない。 + +ここに、IsRightGrabbingという変数が出てくるが、これは\name において実装を検討中の機能に使用する変数であり、現在は使用されていない。 + + +\subsection{入力方法とその実装(手が開かれた時)} + +手が開かれた時、かざされているオブジェクトのうち最も「規定タイミングに近いもの」が入力を受けつけ、アクションを起こす。手を開く時、入力を受け付けるオブジェクトの種類は、「ターゲット」「UI」の二種類に加えて「ロングターゲット」の三種類となることに注意する。 + +\subsubsection{イベントHandReleased} + +手が開かれた(手の開き具合が一定以上になった)時、左右の手に関わらずイベントHandReleasedが実行される(図\ref{player02})。このイベントが実行された場合、イベントHandReleasedを呼び出した手が右手である場合にTRUEとなるBoolean型の返り値IsRightによって左右どちらの手であるかを判定する。これがTRUEである場合、右手でロングターゲットに入力した場合にTRUEとなるBoolean型変数IsLongRightをFALSEとする。左手に関しても同様であるため、省略する。 + +\begin{figure}[htbp] + \begin{center} + \includegraphics[width=16cm]{fig/player02.png} + \end{center} + \caption{イベントHandReleasedおよび検出半直線に使用する引数の作成} +\label{player02} +\end{figure} + +\subsubsection{オブジェクト検出とループ処理部} + +手が開かれた場合も閉じられた場合と同様の処理を行っていくが、一部処理内容が異なる(図\ref{player05})。 + +\begin{figure}[htbp] + \begin{center} + \includegraphics[width=16cm]{fig/player05.png} + \end{center} + \caption{検出線生成とループ処理} +\label{player05} +\end{figure} + +検出されたObjectがターゲットでない場合、イベントHandReleasedでは次にロングターゲットであるかどうかが判定される。ロングターゲットであり、かつタイミングが検出されたものの中で最もよかった場合、ターゲット同様、DestroyactorRightに格納される。また、ロングターゲットの処理に使われる変数HoldおよびBPMを読み出し、格納する。ここでRightLongHoldTimeはロングターゲットの時間的な長さを示し、BPMは曲の進行速度を表す。最後に右手がロングターゲット処理中である場合にTRUEとなるBoolean型変数IsLongRightをTRUEとしておく。 + +\subsubsection{ループ処理終了後} + +ループ終了直後の処理はHandGrabbedと同様である(図\ref{player06})。 + +\begin{figure}[htbp] + \begin{center} + \includegraphics[width=16cm]{fig/player06.png} + \end{center} + \caption{ループ処理後} +\label{player06} +\end{figure} + +\subsubsection{ロングターゲット処理ループ} + +イベントHandReleasedでは、ロングターゲットの得点計算のための繰り返し処理が最後に追加される(図\ref{player07})。 + +\begin{figure}[htbp] + \begin{center} + \includegraphics[width=16cm]{fig/player07.png} + \end{center} + \caption{ロングターゲット処理} +\label{player07} +\end{figure} + +ロングターゲットは継続して点数が加算され続けるため、一定期間ごとにロングターゲットに対するアクションが成功しているかどうかを判定し、成功している場合には点数を加算する。 + +始めに、ロングターゲットに対するアクションが成功している時にTRUEとなるBoolean型変数RightHoldSuccessを初期化する。 + +ここで、BPMとは曲の進行速度を表す指標の一つで、一分間に四分音符が入る個数を示す。 + +BPMが$X_{bpm}$であったとき、一秒間に入る四分音符の個数$X_s$は +$$X_s=\frac{X_{bpm}}{60}$$ +であるから、この時、四分音符1回の間に経過する時間$T_4$は +$$T_4=\frac{1}{X_s}=\frac{60}{X_{bpm}}$$ +であることがわかる。以上より、十六分音符1回あたりの経過時間$T_{16}$が +$$T_{16}=\frac{T_4}{4}=\frac{15}{X_{bpm}}$$ +であることが導かれる。 + +十六分音符間隔でディレイしたのち、ロングターゲットアクション判定用の検出半直線を生成する。検出半直線上の検出位置には、検出されたことをプレイヤーに通知するためEmitter(パーティクル出力点)BasicParticleを表示する。その後、検出されたObjectの中に一つでもDestroyactorRightに格納されたObjectと一致するものがあれば、ロングターゲットに対するアクションが成功したとして、RightHoldSuccessをTRUEとする。 + +RightHoldSuccessがTRUEであり、かつIsRightReleasingがTRUEの場合には点数を加算し、ロングターゲットに対するアクションの成功失敗検出を繰り返す。RightHoldSuccessあるいはIsRightReleasingがFALSEの場合、ロングターゲットに対するアクションが失敗したとして、ループ処理が終了する。 + +以上の動作が十六分音符間隔でループされる。左手に対する処理も同様である。 \subsection{ターゲットの実装} +ターゲットはBPM・移動経路・その移動速度を設定することができる。移動経路はその経由地点をVector型配列で与え、移動速度は移動経路区間ごとに指定することができる。 + +\begin{figure}[htbp] + \begin{center} + \includegraphics[width=16cm]{fig/target01.png} + \end{center} + \caption{ターゲットのイベントBeginPlay} +\label{target01} +\end{figure} + +ターゲットのイベントBeginPlayは同時に複数の処理を行う(図\ref{target01}) + +\subsubsection{Sequence0・Sequence1} + +Sequence0およびSequence1はビジュアル的演出のために行われる処理であるため、省略する。 + +\subsubsection{Sequence2} + +配列Speedは、移動経路各区間を走破するのに必要な四分音符数を格納している。また、変数Jumyouは全区間を走破するのに必要な四分音符数から1を引いた整数を格納する。これと$\frac{60}{BPM}$との積を求めることで、規定タイミングより1拍前のタイミングまでの時間を得る。ターゲット内部の変数Scoreはタイムライン\_1によって規定タイミングの1拍前から変動し、最終的に1から0へ移動する。Scoreが0になった時、ちょうど規定タイミングから1拍分の時間が経過しており、ターゲットは消滅を開始する。Timelineは関数SetPlayRateにより、その再生速度を変更することができ、与えられたBPMによってTimelineの再生速度を変更する。 + +タイムライン\_2はターゲットの消滅演出を司るTimelineで、これが再生されると関数SetActorScale3Dによりターゲットは縮小を開始する。Timelineの再生が終わり、ターゲットの3Dサイズが$(0,0,0)$となると、ミス回数を追加し、関数DestroyActorにより消滅する。 + +タイムライン\_2はターゲットの規定タイミングをプレイヤーに通知する機能を司るTimelineである。規定タイミングの1拍前にタイムライン\_3が再生を開始され、タイミングを通知するStaticMeshComponentのCircleのサイズが縮小を開始する。規定タイミングになった時、ちょうどStaticMeshComponentのSphereとサイズが一致するようになる。その後はさらに縮小を続け、規定タイミングの1拍後にちょうど3Dサイズが$(0,0,0)$となる。 + +\subsubsection{Sequence3} + +Sequence3はターゲット生成時の演出に用いられる。Timeline、GenerateScaleが再生されると、これを使用して関数SetActorScale3Dによりターゲットが極小な状態から緩やかに拡大し、視認されるようになる。 + +\subsubsection{イベントTick} + +イベントTickは主にターゲットの移動・角度の統御を行う(図\ref{target03})。 + +\begin{figure}[htbp] + \begin{center} + \includegraphics[width=16cm]{fig/target03.png} + \end{center} + \caption{ターゲットのイベントTick} +\label{target03} +\end{figure} + +ターゲットの規定タイミングをプレイヤーに通知するためのStaticMeshであるCircleは平面のポリゴンであり、ターゲットはプレイヤーに対して常に「正面を向く」必要がある。関数FindLookatRotationを用いてカメラへ向けるべきRotationを導き、これを関数SetActorRotationの引数とすることでターゲットは常にプレイヤーに対して正面を向く。 + +ArrayLengthには移動経路の経由点の個数が格納されている。イベントTargetmoveはカスタムイベントであり、始点と終点、移動速度を引数に、現在の時刻からターゲットが今現在いるべき三次元座標を求め、移動させるイベントである。一区間の移動が終了するまで関数Delayを使用して待機し、待機終了後ふたたび移動があれば、変数KeikajikanおよびJikanfromstartを初期化する。 + +\subsubsection{イベントTargetMove} + +イベントTargetMoveは引数として、移動始点、移動終点およびその移動区間の移動にかかる拍数(四分音符数)を受け、現在Targetが存在するべき位置を求め、その位置にTargetを移動させるイベントである(図\ref{target04})。 + +\begin{figure}[htbp] + \begin{center} + \includegraphics[width=16cm]{fig/target04.png} + \end{center} + \caption{カスタムイベントTargetMove} +\label{target04} +\end{figure} + +現在Targetが存在するべき位置を求めるため、移動始点に到達した時刻を$T_f$、現在の時刻を$T_n$、移動始点の位置ベクトルを$\overrightarrow{S}$、移動終点の位置ベクトルを$\overrightarrow{E}$、区間移動にかかる拍数を$N$、楽曲のBPMを$N_{bpm}$とする。 + +区間移動にかかる時間は、1拍あたりの秒数が$\frac{60}{N_{bpm}}$であることを利用すると +$$\frac{60N}{N_{bpm}}$$ +である。また、移動始点を基準にした移動終点の位置ベクトルは +$$\overrightarrow{E}-\overrightarrow{S}$$ +である。以上よりTargetの移動速度は +$$\frac{\overrightarrow{E}-\overrightarrow{S}}{\frac{60N}{N_{bpm}}}$$ +と求められる。ここで、移動始点に到達してからの経過時間は +$$T_n-T_f$$ +で与えられることから、$T_n$秒の時点での移動始点からの位置ベクトルは +$$\frac{(T_n-T_f)(\overrightarrow{E}-\overrightarrow{S})}{\frac{60N}{N_{bpm}}}$$ +であり、Targetが存在するべき位置のベクトルは +$$\overrightarrow{S}+\frac{(T_n-T_f)(\overrightarrow{E}-\overrightarrow{S})}{\frac{60N}{N_{bpm}}}$$ +であることが求められた。 + +\subsubsection{スコアの加算} + +TargetはApplyDamageされるとそのタイミングに応じて加点をする(図\ref{target02})。 + +\begin{figure}[htbp] + \begin{center} + \includegraphics[width=16cm]{fig/target02.png} + \end{center} + \caption{点数計算部} +\label{target02} +\end{figure} + +イベントAnyDamageは、ObjectがApplyDamageされた時にApplyDamageされた側で実行されるイベントである。 + +TargetはApplyDamageされるとPlaySound2Dによって効果音hitを鳴らす。その後、Scoreが2.5以上7.5以下場合、良いタイミングであると判定し、点数Greatを加点する。また、Scoreが0.25未満、または7.5より大きいの場合、惜しいタイミングであると判定し、点数Nearに加点する。加点後、ターゲットはDestroyActorにより消滅する。 + \subsection{ロングターゲットの実装} -\subsection{手の相対位置の検出} +ロングターゲットは一部を除きほぼすべてターゲットと内容が一致する(図\ref{LT01})。 + +\begin{figure}[htbp] + \begin{center} + \includegraphics[width=16cm]{fig/targetL01.png} + \end{center} + \caption{ロングターゲットBPのうちイベントBeginPlay部分} +\label{LT01} +\end{figure} + +ここで、特に違いがあるのはSequence4が追加されたである。 + +Sequence4では、AddParticleSystemComponentによって帯状のParticleSystemを追加する。これはロングターゲットの軌跡を表示するParticleSystemである。帯状のParticleSystem自体は RibbonEmitterを使用して簡易に実装できるため、省略する。 + +Particleの残留時間を調整することで、帯の長さを調節することができる。作成したRibbonEmitterのLife(Particleが表示され、消滅するまでの時間)をParameterに変更し、外部から変更可能な状態にしておく。そのようにすることで、BP上で参照した時にInstanceParametersとしてLifeを変更することが可能となる。Lifeを、$\frac{60}{BPM}$とアクションを取り続けるべき四分音符数との積とすることで、ロングターゲットのアクションが終わるタイミングで帯が途切れるようになる。 + +最終的に、ロングターゲットを追うように、移動経路、移動速度をロングターゲット自身と同じく指定したターゲットを生成することで、途切れた帯を隠すことができる。 + +また、ロングターゲットに対するアクションで得られる点数はPlayerが加点していくので、ロングターゲット自身は点数加算を行わない。 + +\subsection{プレイヤーの移動} + +\name では、プレイヤーは自動で曲ごとに固定された方向に移動し、角度を変える。 + \section{音楽ゲームの実装}
--- a/final_hamase/main.tex Fri Feb 15 21:21:12 2019 +0900 +++ b/final_hamase/main.tex Mon Feb 18 20:50:52 2019 +0900 @@ -9,7 +9,7 @@ \setlength{\itemsep}{-1zh} \title{VRフリーハンド音楽ゲーム\\「\name」の実装} \icon{ - \includegraphics[width=80mm,bb=0 0 595 842]{fig/ryukyu.pdf} + \includegraphics[width=50mm]{fig/ryukyu.pdf} } \year{平成Y年度 卒業論文} \belongto{琉球大学工学部情報工学科}