UNITY
ゲーム制作_AimTarget
公開日:
2020/12/01
ゲーム制作_AimTarget

はじめに

こんにちは,三回生のふじいです.
2020年の新歓イベントに向けて作ったAimTargetというゲームを紹介します.

作品について

webが日本語対応していなく,ちょっと重いし,よくわからんことになってますが,できるひとはやってみててくださいまし. ゲームはこちら.

AimTargetはAim Labを丸パクリ,もとい参考にして作ったFPSエイム練習ゲームです.

ゲーム内にはエイム練習ができる三つのミニゲームがあり,それぞれ3ポイントゲーム,フリックゲーム,トラッキングゲームです.
全てのゲームに言えることですが,制限時間30秒の間にターゲットをFPS視点で正確に狙うことで高得点を目指すことでエイム練習をするのが目的です.

3ポイントゲームは,ある範囲内に出てくる3つのターゲットをモグラたたきのように狙うゲームです.ターゲットは弾が当たらなければ消えることはなく,範囲内に必ず3つ存在するようになっています.

フリックゲームは,現れては1秒ほどで消えてしまう1つのターゲットをモグラたたきのように狙うゲームです.

トラッキングゲームは,ランダムに動く一つのターゲットにエイムを合わせ続けるゲームです.

みんなで一緒につよつよエイムになりましょう.

頑張ったところ

  • できるだけすぐにゲームを繰り返せるように画面推移を無くして,スクリーンみたいなものを浮かしてそれを銃で狙うことでボタンを表現しました.これ結構頑張ったんですよ.ほんとに.
  • BGMとかあとカーソルとかをたくさん選べるようにしました.練習となると長時間やるのでできるだけプレイヤーにストレスを与えない方がいいと思ったので.
  • ターゲットが壊れるときのエフェクトもこだわりました.といってもアセットましましなわけですが... UnityのParticle Systemは奥が深そうですね(他人事).
  • FPS視点カメラをクォータニオンを使って制御しました.プレイヤーにカメラの子オブジェクトをつけてもよかったのですが,クォータニオンの練習のために頑張りました.
  • トラッキングゲームで,ターゲットが完全にランダムに移動するとフィールド外に出て行ってしまうのをどうにかして出ていかないようにしました.
  • Unityではオブジェクトにテクスチャをはっつけ,オブジェクトを大きくすると,テクスチャが引き延ばされてしまいます.今回はShaderを用いてテクスチャが引き延ばされないように解決しました.
    このゲームを作っているときに思い出したのですが,スーパーモンキーボールもこんな感じの手法を用いてたんだろうなと思いました.
  • 良いコーディングを目指して独立性・保守性を意識しました.といったものの可読性はあるのかよくわからないし,要素の独立性は高いものの高すぎると拡張時に加えないといけないコードが多くなり,加えてクラスは多いし難しいなあと感じます.

反省点

反省はしない,ポジティブに行こう.強いて言うならちょっとバグがあるくらい.さっさと作ればよかった...

技術的なお話

Unityの話を主に上記の頑張ったところについて真面目に長々とします.興味のある部分だけご覧ください.
カーソルの色
クォータニオン
FPS視点
マウスカーソルの判定

カーソルの色

カーソルの種類が10種類ほどあったと思いますが,すべてのカーソルは赤色になっています.ですが実際のところ,カーソルのための画像には赤色ではないものや,赤色であったとしても配色が微妙に違うものが含まれていました.
私はどうにかして同じ配色の赤色に統一したかったのですが,これをShaderで解決しました.
Shaderとは素材となる画像をどのように描画するかを記述したプログラミングです.
画像のグレースケールが一定以下,すなわち黒に近い色をすべて赤色にするというShaderをカーソルに当てはめることで解決しました.

クォータニオン

このページこのページを参考にしています.

クォータニオンとは,数学的には複素数を拡張した数体系でありq=ai+bj+ck+dq = ai + bj + ck + dで表され,i2=j2=k2=ijk=1i^2 =j^2=k^2=ijk = -1が成り立ちます.
つまり任意のクォータニオンは4次元ベクトル空間のベクトルで表され,数式で表すとq=(a,b,c,d)q = (a,b,c,d)となります.

そんなことはどうでもよく,ゲームを制作する上では,クォータニオンは姿勢,もしくは回転を表すのに便利なものとしてだけ扱います.実際Unityではオブジェクトの姿勢を表す変数はクォータニオンになってます.クォータニオンはベクトルや他のクォータニオンに対する回転を表現することができ,数式で表すと以下のようになります. \begin{align} v' &= q \otimes v \otimes \bar{q} \\ p' &= q \otimes p \otimes \bar{q} \\ \end{align} \begin{align} q : 回転クォータニオン,v,v' : ベクトル,p,p':姿勢クォータニオン \end{align} つまり上のように掛けると,vvqqだけ回転されたvv'に,ppqqだけ回転されたpp'になったということです.
例えば,北を向いているプレイヤー姿勢ppを東を向けたければ,右回りに90°回転するクォータニオンqqを上のように掛けると,東を向いたプレイヤー姿勢pp'となります.
Unityでは

v1 = q * v 
p1 = q * p

と,単に掛け算で表すことができます
また,ある軸vvまわりにs回転するクォータニオンqqをUnityでは以下のよう生成できます.

Quaternion q = Quaternion.AngleAxis(s,v);

うん,便利.

以上クォータニオンの説明でしたが,これを用いたFPS視点の実装の話に移ります.

FPS視点

FPS視点にて横向きの軸まわりの回転をピッチといいます.カメラが真上に向いたときピッチは90°なわけですが,この時制御がおかしくなりやすく,できれば真上は向かないようにしたいです.
作品の実装では左右にyoko°yoko°,上下にtate°tate°回転させたいとき以下のように実装しました.

/* Transform pt : プレイヤーのトランスフォーム(playerTransform)
** Transform ct : カメラのトランスフォーム(cameraTransform)
** float pitch : 現在のピッチ
** float maxPitch : 最大ピッチ
*/
/***まず左右の回転***/
pt.rotation = Quaternion.AngleAxis(yoko, pt.up) * pt.rotation ;//左右にs°回転
/***次に上下の回転***/
pitch += tate ; //ピッチの計算
pitch = Mathf.Clamp(pitch, -maxPitch, maxPitch) ; //ピッチを最大ピッチ以下にする
Quaternion PitchQ = Quaternion.AngleAxis(pitch, tf.right) ; //ピッチの分だけ回転するクォータニオン
Vector3 viewForward = PitchQ * pt.forward ; //プレイヤーの向きを計算
ct.rotation = Quaternion.LookRotation(viewForward) ;  //プレイヤーをviewForwardの方に向かせる

なんだか難しい感じですが,ピッチ範囲を考慮したFPS視点の実装は,子オブジェクトにカメラを入れるだけでは難しく,クォータニオンを使った方が簡単だと思います.

マウスカーソルの判定

トラッキングゲームやボタンのような空中スクリーンでもマウスカーソルを乗せるだけで反応したり,マウスカーソルを外すと反応が消えたりなど細かい制御が必要でした.またそれぞれのオブジェクトが起きてほしい反応は違います.
私は今回,MousePointerとMousePointeeというコンポーネントを作り,MousePointerをPlayerに,MousePointeeを反応が起きてほしいオブジェクトに加えることで解決しました.
それぞれのComponentを以下に示します.ちなみにその実装に使っているUnityEventとはインスペクターでどのような関数を実行するかを決められるイベント変数です.

public class MousePointee : MonoBehaviour {
	public UnityEvent onEvent;         //カーソルが乗っているときのイベント
	public UnityEvent downEvent;  //カーソル上でマウスを押しているときのイベント
	public UnityEvent upEvent;        //カーソル上でマウスを離したときのイベント
	public UnityEvent clickEvent;    //カーソル上でマウスを押したときのイベント
	public UnityEvent offEvent;        //カーソルが離れた時のイベント
}
public class MousePointer : MonoBehaviour {
	void Update(){
		 MouseEvent();
	}
	void MouseEvent() {
	//カーソル方向にオブジェクトがあるかどうかを判定
		Physics.Raycast(cameraT.position, cameraT.forward, out mRaycastHit); 
		/*以下は簡単に言うと判定されたオブジェクトがMousePointeeを
		**持っているかを判定し,その判定により適したUnityEventを処理する
		*/
		if (!mRaycastHit.transform) {
			mMousePointee = null;
			mBeforeMousePointee = null;
			return;
		}
		mMousePointee = mRaycastHit.transform.GetComponent<MousePointee>();
		if (mBeforeMousePointee != null && mBeforeMousePointee != mMousePointee) {
			mBeforeMousePointee.offEvent.Invoke();
		}
		if (mMousePointee == null) {
			mBeforeMousePointee = null;
			return;
		}
		mMousePointee.onEvent.Invoke();
		if (Input.GetMouseButton(0))
			mMousePointee.clickEvent.Invoke();
		if (Input.GetMouseButtonDown(0))
			mMousePointee.downEvent.Invoke();
		if (Input.GetMouseButtonUp(0))
			mMousePointee.upEvent.Invoke();
		mBeforeMousePointee = mMousePointee;
		return;
	}
}

独立性・拡張性

コードの独立性・拡張性などを意識していた結果,大量にUniRx(Unity用ライブラリ)を使いまくっていて,その説明をしようと思ったのですが
ブログを書くのに疲れてしまいました(;・∀・).また今度書くかもしれません.

余談

もともとC++でフレームワークを作り,ゲームを作ろうとしていましたがさすがに時間が無いですね,というか作れる技量がないです.
Unityさいこー.

loading...