tetu式

ゲームと音楽・作曲の自己満足と悩みどころの多いプログラムのブログ。

Unity2D フリック操作

初のUnity記事。

今回はタッチした時の自作フリック操作について。

ぶっちゃけコードはすこぶる汚いので多めに見てやってください・・・

とりあえずタッチ操作の説明から。

タッチパネル上での操作(1本指操作に限る)は簡単に3種類ほどあります。

一つ目、タップ。

恐らく最も使うであろう操作です。

タッチパネルに触れてすぐ離す動作です。

マウスで言うならクリックに該当します。

二つ目、スライド。

タッチパネルに触れながら別の場所に指を動かす操作です。

マウスで言うならドラッグが近い動作です。

そして3つ目、フリック。

タップとスライドを合わせたような操作です。

画面に触れてすぐ離すまでの間にわずかにドラッグするような操作です。

指を真っ直ぐ下ろすのでなくて動かしながらタップするイメージです。

マウスでこの操作を実現することも出来るのでしょうけどこればっかりは代わりになるマウスの操作は通常実装されていないかと思います。

AssetStoreとかでフリック操作を使えるようにするものもあると思いますが今回は自作でフリック操作してみたいと思います。


まずはUnityの基本的なマウスの操作。

Unityだとマウスの左ボタンは1本指タッチ操作と同じように動作してくれます。

タッチ用の操作もあるのですが、PC上でデバッグし辛くなるのでマウスでもフリックっぽいこと出来るように、と。

マウスでフリック操作を実現するにはボタンを押した瞬間、押している間、離した時の処理が必要になります。全部ですね。

今回はUpdateの中に書いていきます。C#仕様で。

private bool isFlick;
private bool isClick;
private Vector3 touchStartPos;
private Vector3 touchEndPos;
private int direction;

public void Update () {
	if(Input.GetKeyDown (KeyCode.Mouse0))
	{
		isFlick = true;
		touchStartPos = new Vector3(Input.mousePosition.x ,
					Input.mousePosition.y ,
					Input.mousePosition.z);
		Invoke ("FlickOff" , 0.2f);
	}
	if(Input.GetKey (KeyCode.Mouse0))
	{
		touchEndPos = new Vector3(Input.mousePosition.x ,
					Input.mousePosition.y ,
					Input.mousePosition.z);
		if(touchStartPos != touchEndPos )
		{
			ClickOff ();
		}
	}
	if(Input.GetKeyUp (KeyCode.Mouse0))
	{
		touchEndPos = new Vector3(Input.mousePosition.x ,
					Input.mousePosition.y ,
					Input.mousePosition.z);
		Debug.Log (touchEndPos);
		if(IsFlick ())
		{
			Debug.Log ("Flick");
			float directionX = touchEndPos.x - touchStartPos.x;
			float directionY = touchEndPos.y - touchStartPos.y;
			Debug.Log ("DirectionX : " + directionX);
			Debug.Log ("DirectionY : " + directionY);
			if(Mathf.Abs (directionY) < Mathf.Abs (directionX))
			{
				if(0 < directionX)
				{
					Debug.Log ("Flick : Right");
					direction = 6;
				}
				else
				{
					Debug.Log ("Flick : Left");
					direction = 4;
				}
			}
			else if(Mathf.Abs (directionX) < Mathf.Abs (directionY))
			{
				if(0 < directionY)
				{
					Debug.Log ("Flick : Up");
					direction = 8;
				}
				else
				{
					Debug.Log ("Flick : Down");
					direction = 2;
				}
			}
			else
			{
				Debug.Log ("Flick : Not, It's Tap");
				FlickOff();
			}
		}
		else
		{
			Debug.Log ("Long Touch");
			direction = 5;
		}
	}
}
public void FlickOff()
{
	direction = 5;
	isFlick = false;
}

public bool IsFlick()
{
	return isFlick;
}


public void ClickOn()
{
	isClick = true;
	Invoke ("ClickOff" , 0.2f);
}

public bool IsClick()
{
	return isClick;
}

public void ClickOff()
{
	isClick = false;
}


ちょいと長いですね・・・フリック以外にタップ(Click)の挙動もちょっと混ぜ込んであります。これの理由は後述。

まずはボタンを押した瞬間の処理。

isFlick = true;
touchStartPos = new Vector3(Input.mousePosition.x ,
			Input.mousePosition.y ,
			Input.mousePosition.z);
Invoke ("FlickOff" , 0.2f);

まずはisFlickをtrueにしてフリック操作を受け付ける状態にします。
touchStartPosでタップ(クリック)した位置を取得します。
InvokeはUnity独自の関数で一定時間後に自クラス内の関数を呼び出す時間差関数です。
ただし関数名だけを第1引数にします。()は不要。
第2引数が小さいほど時間差が短くなります。
FlickOff()はisFlickをfalseにするだけの関数です。

続いて押している間の処理。

touchEndPos = new Vector3(Input.mousePosition.x ,
			Input.mousePosition.y ,
			Input.mousePosition.z);
if(touchStartPos != touchEndPos )
{
	ClickOff ();
}

ここでやってる処理は単純です。
押してる間は常に座標をとり続け、最初にタップした位置からズレたらクリックの判定を無くすというもの。

ただこれだと1ドット動いただけでクリック判定ではなくなるのでクリックならまだしもタップの判定はかなり厳しそう・・・
始点と終点の座標の差を見てある程度の遊び判定を用意するといいかもですね。

そして全ての処理が決まる離した時の動作。

touchEndPos = new Vector3(Input.mousePosition.x ,
			Input.mousePosition.y ,
			Input.mousePosition.z);
Debug.Log (touchEndPos);
if(IsFlick ())
{
	Debug.Log ("Flick");
	float directionX = touchEndPos.x - touchStartPos.x;
	float directionY = touchEndPos.y - touchStartPos.y;
	Debug.Log ("DirectionX : " + directionX);
	Debug.Log ("DirectionY : " + directionY);
	if(Mathf.Abs (directionY) < Mathf.Abs (directionX))
	{
		if(0 < directionX)
		{
			Debug.Log ("Flick : Right");
			direction = 6;
		}
		else
		{
			Debug.Log ("Flick : Left");
			direction = 4;
		}
	}
	else if(Mathf.Abs (directionX) < Mathf.Abs (directionY))
	{
		if(0 < directionY)
		{
			Debug.Log ("Flick : Up");
			direction = 8;
		}
		else
		{
			Debug.Log ("Flick : Down");
			direction = 2;
		}
	}
	else
	{
		Debug.Log ("Flick : Not, It's Tap");
		FlickOff();
	}
}
else
{
	Debug.Log ("Long Touch");
	direction = 5;
}

Debug.Logの部分は確認用に出してるだけなので無くても大丈夫です。
まずはif(IsFlick())
わざわざ関数で見てますが自クラス内なので変数のisFlickでもOKです。
画面に触れてから先ほどのInvokeが実行されるまでの間に指を離せばこのif文が通るというものです。

次に最初にタッチした位置と離した位置の差分をとります。

float directionX = touchEndPos.x - touchStartPos.x;
float directionY = touchEndPos.y - touchStartPos.y;

離した位置から開始位置を引き算することでタップした位置から離した位置の方向を見ることができます。

その方向を判定するのが次のif文になります。

if(Mathf.Abs (directionY) < Mathf.Abs (directionX))

Mathf.Abs()は引数の絶対値を返す関数です。
純粋に-(マイナス)を取ってくれる関数と見てもいいかもです。
これで縦と横、どちらが大きく動いているかを判定し、上下左右の4方向の判定をします。
このif文の場合は横に大きく動いた時に通るif文です。
そうでなければ縦に大きく動いたかどうかを見ます。

その後のif文はどちら側に動いたかを見ます。プラスかマイナスか。

それぞれの結果としてdirectionに値を代入します。
2468とか5っていうのは・・・格闘ゲームの攻略サイトとかで見るレバー入力方向を示す数字です。
236+Pだったら波動拳とかそういう感じです。
テンキーや電卓を想像すると分かりやすいかも。


さて、改めてコードを見て気付いたのですがこれだと斜め45度ちょうどのフリックをした時はどっちも通らないようになっています。
どちらかのif文の比較演算子を < から <= にすれば大丈夫ですが・・・横と縦どちらの判定をするかは個人の好みということで。


で、一番最後。離した時点でInvokeが通った時はLong Touchということになります。


一通り作って割とどこでも使えそうな感じになったのでシングルトンで作ってどこからでも呼び出せるといい感じ。




さて、ではタップとフリックの困ったことについて。
Unity2Dでよくやりそうな動作としてコードにOnMouseDown()というオーバーロード関数をつけたりします。
これは配置しているオブジェクトをクリック(タップ)したら実行される関数です。
忘れがちですがCollider 2Dのコンポーネントがオブジェクトに入ってないと反応しません。

で、これの対となる関数がOnMouseUp()になります。
これは"オブジェクトがクリックされた後"に離すと実行される関数です。
離す場所についてはオブジェクトのCollider 2Dの外側でもOKでした。

このOnMouseUp()なのですが離した瞬間どの処理よりも早く実行されるという特徴がある感じです。
Unityではスクリプトファイルの実行順を簡単に並び変えられるExecution Orderという機能があります。
スクリプトファイルを選択するとInspector上の右上あたりに Open... と Execution Order... というボタンが出てるので確認してみてください。

これで処理する順序を上から下に並べる事が出来るのですがFlickスクリプトのUpdate()よりも下にあるスクリプトのOnMouseUp()が先に反応してしまいました。

isFlickがちゃんと確定するのがOnMouseUp()よりも後になってしまうため、オブジェクト上でフリックすると OnMouseUp() 内に if(!Flick.Instance.IsFlick()) というフリック操作じゃなければ、というif文があっても確定する直前のisFlickで見てしまいます。

こうなると困るのがタップしただけの処理でもフリック判定で見てしまうこと。

void OnMouseUp()
{
	if(!Flick.Instance.IsFlick())
	{
		//ふにゃ
	}
}

これだけのif文になってしまうとただタップした時だけ入りたくてもフリックしたと見られて入れなくなってしまいます。
isFlickが自動的にfalseになってからじゃないと //ふにゃ に入れなくなってしまうのですね。
わずか0.2秒程度のものなのですが・・・納得いかないのでisClickというクリック用の判定も用意してあげました。

これはFlickのスクリプト内では基本的に常にfalseにしていますが、他のオブジェクトがクリックされたときにtrueにするようにします。

void OnMouseDown()
{
	Flick.Instance.ClickOn();
}
void OnMouseUp()
{
	if(!Flick.Instance.IsFlick() || Flick.Instance.IsClick())
	{
		//ふにゃ
	}
}

ClickOnは呼ばれた瞬間にInvoke("ClickOff" , 0.2f)でフリックと同じ時間クリックと見るようにします。
ただし、isClickは動いたらfalseになります。
この判定はFlickで押しっぱなしの間の処理になるので流石に離すよりは先の処理になります。
こうしてif文にクリック判定でも入れるようにすればタップでも自然に //ふにゃ に入れます。

                                                                                                                                                                        • -


2014/01/24追記

if(0 < directionX)やif(0 < directionY)の後のelseがそのままなので0の時でも反応してしまいました。
それぞれelse if(0 > directionX) 、 else if(0 > directionY)に直せばOKです。

                                                                                                                                                                        • -

8方向フリックもやってみました。
ヒマがあったら読んでみてください。