view Paper/chapter/3-WorkingInUnity.tex @ 53:7142a147b9ab

fix
author riono <e165729@ie.u-ryukyu.ac.jp>
date Sat, 12 Feb 2022 19:20:12 +0900
parents ea7e856b50e8
children 74fb935dc5b5
line wrap: on
line source

\chapter{Christie Sharpの実装の詳細}

\section{Unity API}
Unityでは、ゲーム開発を行うためのライブラリとしてUnity APIが公開されている。
ソースコード\ref{src:UnityExample}はUnity APIを使用したスクリプトの例である。
Spaceキーを押し続けている間、playerObjectは物理演算に従って、毎フレーム$\vec{A} = (10, 5, 10)$だけ力を加えられ移動する例である。
4行目では変数が初期化されていないまま使用されているように見えるが、Unityではpublic修飾子を使用するとUnityのエディタ上で事前に変数に代入することが可能になる。

\lstinputlisting[label=src:UnityExample, caption=Unity APIを使用した記述例]{src/Unity/UnityScriptExample.cs}

Unity APIではC\#にはないVector3やGameObject、Rigidbodyなどの特別な型があり、これらはUnityEngineをusingすることで使用可能である。
またMonoBehaviourを継承することによって、StartメソッドやUpdateメソッドなどのUnity上で決められた、あるタイミングで呼び出される関数を使用することが可能である。


Startメソッドはゲームの開始時、1フレーム目が始まる前に1回だけ実行され、主に初期化に使用されることが多い。
今回はplayerObjectに付与されている物理演算のComponentであるRigidbodyを取得している。


UpdateメソッドはStartメソットの実行後に毎フレーム呼び出される関数であり、ゲームのメインロジックで使用されることが多い。
15行目ではキー入力の有無を確認しており、Spaceキーの入力を監視している。
続く16行目では、Startメソッドにて取得したRigidbodyに対して、3次元ベクトルを力として与えている。
これを行うことによって、Spaceキーを押している間$\vec{A} = (10, 5, 10)$がplayerObjectに与えられ、playerObjectは物理演算に従って移動する。


UnityではStartメソッドやUpdateメソッドがある代わりに、C\#の一般的な実行メソッドであるMainメソッドによる処理の開始ができない。
そのため、Unityで処理を行うためには、MonoBehaviourにより提供されているメソッドを通して処理を実行する必要がある。


\section{StartCodeGearを使用しないChristie Sharpの実行}
% 例題など
% やったこと全部書いたほうがいい/できたこと
% 多重継承の話

Christie SharpではStartCGにMainメソッドを記述してChristie Sharpを実行したが、Unityで動作させるためにはStartメソッドやUpdeteメソッドを使用す必要がある。
そのため、Christie Sharpの実行にStartCGを使用せずに実行できるよう変更を行った。

\lstinputlisting[label=src:CountUpScript, caption=Unity上でのChristie Sharpの実行Script]{src/Unity/CountUpScript.cs}

ソースコード\ref{src:CountUpScript}はソースコード\ref{src:CSStartCGExample}をUnityで動作するように書き換えたものであり、StartCGを使用せずに、Unity APIであるStartメソッドよりChristie Sharpを実行している。
StartCGは、CGMの初期化や処理の開始位置を明確化させるためのクラスであるため、Christie Sharpの実行に必須ではない。
また、C\#では多重継承が禁止されている。そのため、StartCGを継承しつつ、MonoBehaviourを継承してStartメソッドを使用する、と言うことはできない。
そのため可読性が下がってしまう可能性はあるが、CGMの初期化やSetupメソッドを直接Startメソッド内に記述できる。


\section{Unity上での動作確認}
% RemoteDG使ったやつ
% 2台で通信した場合
% LateUpdateの話
% UniRXのDispatcherとUnity APIの仕様の話

Christie Sharpの動作確認を行うため、以下の3点の確認を行った。
LocalDGMに関してはソースコード\ref{src:CountUpScript}で正しく動作することを確認している。

\begin{itemize}
  \item CodeGearを使用した、GameObjectの位置の操作
  \item 同一プロセスでの、複数DGMを使用したGameObjectの位置の同期
  \item PC2台を使用した、LAN上でのGameObjectの位置の同期
\end{itemize}


\lstinputlisting[label=src:TransformMoveTest, caption=GameObjectの位置の操作の準備を行うScript]{src/Unity/TransformMoveTest.cs}
\lstinputlisting[label=src:PositionAssignCodeGear, caption=GameObjectの位置の操作を行うCodeGear]{src/Unity/PositionAssignCodeGear.cs}

ソースコード\ref{src:TransformMoveTest}、\ref{src:PositionAssignCodeGear}は、CodeGearを使用した、GameObjectの位置の操作を行う処理である。
ソースコード\ref{src:TransformMoveTest}を付与したGameObjectの位置より、常にx軸に+3、z軸に+3離れた位置に他のGameObjectであるotherTransfromが移動をする。
19行目のLateUpdateメソッドはMonoBehaviourから継承している関数であり、全てのUpdateメソッドが実行された後、次のフレームに移行する直前に実行される関数である。

UnityでChristie Sharpを使用する際に一つ問題が起きた。
Unity APIでサポートされている関数や型はMain Threadで動作時のみ処理が行われるという仕様がある。
これはUnityの根本的な考え方であり、Unityでは基本的にシングルスレッドでの動作を想定している。
非同期処理を行うための機能として、CoroutineやInvoceなどの機能が提供されており、擬似的にMulti Threadのように処理を行うことが可能である。
しかしChristie SharpではCodeGearをTaskで処理させている。
このためCGに直接Transformを操作しても処理が実行されない。


この問題を解決するために、UniRx\cite{unirx}を導入することで使用可能なMainThreadDispatcher.Postメソッドを使用した(ソースコード\ref{src:PositionAssignCodeGear} 6行目)。
UniRxとはUnity向けに作成された非同期処理の外部ライブラリである。
MainThreadDispatcher.Postを使用することで、Main ThreadでないThreadで行っている処理をMain Threadに移譲することが可能となる。
Postメソッドは第一引数にデリゲートの一種であるAction$<$T$>$を渡すため、ラムダ式での記述が可能である。


\lstinputlisting[label=src:RemoteMoveTest, caption=RemoteDGMでGameObjectの位置の操作の準備を行うScript]{src/Unity/RemoteMoveTest.cs}
\lstinputlisting[label=src:ObjectMoveCodeGear, caption=RemoteDGMでGameObjectの位置の操作を行うCodeGear]{src/Unity/ObjectMoveCodeGear.cs}
\lstinputlisting[label=src:Vector3Cmd, caption=GameObjectの位置データとして送信されるオブジェクトのクラス]{src/Unity/Vector3Cmd.cs}

ソースコード\ref{src:RemoteMoveTest}、\ref{src:ObjectMoveCodeGear}、\ref{src:Vector3Cmd}は同一プロセスにおいて、
RemoteDGMを使用しGameObjectの位置を操作するScriptである。

ソースコード\ref{src:RemoteMoveTest}のStartメソッドでは、playerCGMとenemyCGMの2つのDGMを用意し、
playerCGMの方では他方のenemyCGMをRemoteDGMとして指定を行っている。
また、enemyCGMではSetupを行い処理の待機状態にしている。
Updateメソッドでは、このScriptが付与されたGameObjectの位置を取得し、x軸に+3、z軸に+3離れた位置にotherTransformを移動させるようにし、
送信用クラスに変形を行っている。
21行目ではotherTransformを持っているenemyCGMではなく、playerCGMからPutを行っている。

MessagePack CSharpではUnityにも対応しており、Unity APIが提供している値型のVector3やQuaternionなどもSerialize/Deserialize可能である。
しかし、Christie SharpではDGでデータを管理する際に全ての型に対応できるようObject型にCastをしている。
このObject型のCastが原因によりVector3型を直接送信することができない。
そのため、ソースコード\ref{src:Vector3Cmd}のようにデータをプリミティブ型に分割してデータを送信している。

ソースコード\ref{src:ObjectMoveCodeGear}はソースコード\ref{src:PositionAssignCodeGear}と同様にMainThreadDispatcher.Postによって、
処理はMain Threadに移譲されて実行される。


\lstinputlisting[label=src:ObjectMoveServer, caption=GameObjectの位置をRemoteDGMに送信するScript]{src/Unity/ObjectMoveServer.cs}
\lstinputlisting[label=src:ObjectMoveListener, caption=GameObjectの位置を受信して位置を操作するScript]{src/Unity/ObjectMoveListener.cs}


ソースコード\ref{src:ObjectMoveServer}、\ref{src:ObjectMoveListener}は2台のPCを使用しLAN内でGameObjectの位置を通信して操作するScriptである。
CGや、送信するDGについては、ソースコード\ref{src:ObjectMoveCodeGear}、\ref{src:Vector3Cmd}を流用している。


ソースコード\ref{src:ObjectMoveServer}、\ref{src:ObjectMoveListener}それぞれの3行目ではhostNameを指定しており、通信先のLocal IPアドレスを入力することでSocket通信を行っている。
ソースコード\ref{src:ObjectMoveServer}は位置データの送信元であり、RemoteDGMとして指定したlistenerに向けて、自身のGameObjectの位置をPutしている。
ソースコード\ref{src:ObjectMoveListener}は何もデータをPutしておらず、LateUpdateメソッドでSetupを行うことでソースコード\ref{src:ObjectMoveServer}から受け取ったデータを基にGameObjectの位置を変更している。


以上のテストにより、Christie Shrapの機能が問題なくUnity上でも機能することが確認できた。

\section{UnityでのChristie Sharpの役割}
UnityでCodeGearを動作させる場合、特にUnity APIを使用して処理を行う際は、MainThreadDispatcher.Postメソッドを利用してMain Threadに処理を移譲する必要がある。
他方、UnityではUpdateメソッドやFixedUpdateメソッドなどUnityの実装に従って、フレーム単位や時間単位でのメソッド呼び出しが保証されている。
CG内でMainThreadDispatcher.Postメソッドを使用しGameObjectの移動などのUnity APIを利用した処理を行うことで、処理が行われるタイミングが不安定になることがあると考えられる。

そこで、ネットワーク上で通信が必要なものだけをChristie Sharpで通信を行い、受信データをMonoBehaviourを継承したクラスで処理・動作させるという方法を考えた。
Unityには非同期処理としてCoroutineやTaskなどがあるが、シングルスレッドでの動作を前提として利用されている。
この手法を取ることにより、Unity上の処理を行うタイミングが保証されると共に、
並列処理やデータのやり取りをChristieに一任することができ、Multi Threadによる並列処理が可能となる。


またUnity上で操作、処理を行っているGameObject自体をDGに継続にする、直接的にはDGにすることによってより並列処理などを意識することなくゲーム開発が可能となる。
GameObject自体をDGにすることにより、通信に必要なデータを送信するためのコードを書かずに待ち合わせが可能となる。
また、待ち合わせを行っていることによりデータがNullのまま処理を開始することがなくなる。
位置などの毎フレーム必要なデータや、画面の描画に必要なデータはPeekで取得を行うことで、通信が途中で途切れてしまった場合でもその直前のデータは参照可能であり、
接続の復帰も行いやすいと考えられる。