Androidで指より小さい対象物のタッチ判定をする方法


#Androidゲームでは自分の指より小さい対象のタッチが難しい
AndEngineにはデフォルトでタッチ判定の機能が搭載されていますが、16x16サイズの小さな対象にタッチ判定処理を実装しようとしたところ上手く判定できませんでした。
そのため、小さい対象のタッチ操作を行う場合は自力でタッチ判定を実装する必要があります。
一般的なタッチ判定は以下のようなものになるでしょう。

```
private boolean isTouch(Rect rect , int x , int y)
{
	if(rect.left < x  && x <rect.right && rect.top < y && y < rect.bottom)
	{
		return true;
	}
	else
	{
		return false;
	}
}
```
デスクトップパソコンではマウスカーソルを使って細かいクリック位置を指定できるので問題ありません。
ですが、android端末のように指を使って対象をタッチする場合はマウスほど細かい位置指定ができません。
以下のようにタップ対象が指よりも大きな場合はこの方法でも問題ありませんが
![touch.png](https://qiita-image-store.s3.amazonaws.com/0/92839/c90bd497-c9ee-9b39-d0fa-18017ae075a0.png)

以下のようにタッチ対象が指よりも小さな場合だとタッチポイントが上手くタッチ対象の範囲内に入らずにタッチ判定ができません。
![tach2.png](https://qiita-image-store.s3.amazonaws.com/0/92839/54d8c7d2-17d0-8ad7-a504-0aa0753d6f90.png)


小さい対象を拾おうとして安易にタッチ範囲を広げると、以下のようにタッチ対象が複数に増えた場合の誤検知が多くなってしまいます。
![tach3.png](https://qiita-image-store.s3.amazonaws.com/0/92839/3decf0f2-2ab0-0910-8bf8-ea7cfb4685c0.png)





#解決方法
タッチポイントとタッチ対象との距離を基にしてタッチ判定をする方法を使います。

```
final int DISTANCE_TOUCH=32;


private boolean isTouch(Rect rect , int x , int y)
{
	//タッチポイント(x,y)とタッチ対象の中心点(rect.centerX(),rect.centerY())との距離(distance)を取得
	double distance =
Math.sqrt( Math.pow(x-rect.centerX(),2) + Math.pow(y-rect.centerY(),2) );

	//距離(distance)がDISTANCE_TOUCHよりも小さければタッチ認定
	if(distance <= DISTANCE_TOUCH)
	{
		return true;
	}
	else
	{
		return false;
	}
}
```
![tach3.png](https://qiita-image-store.s3.amazonaws.com/0/92839/80111b3d-9865-fc43-1c5f-369fd079098e.png)

上記のコードではタッチポイントとの距離が32以内であればその対象をタッチしたと見なします。
このDISTANCE_TOUCHの値を大きくすればタッチ範囲を広げられます。
距離という単一の値を使ってタッチ判定を行っているので、複数のタッチ対象が近くにある場合に、どれが最も近い距離にあるのかを簡単に比較することができます。
これにより、小さいタッチ対象を拾うためにタッチ範囲を広げた(DISTANCE_TOUCHの値を48へ変更した)としても、その中からより近くにある対象を選り分けられるので、タッチ対象が近くに複数ある場合の誤検知を防ぐことができます。

以下は、Rectの配列rectsの中からタッチポイントと最も近い位置にある対象を探す例です。

```
int min = 100000;
int select = -1;

final int length=rects.length;
for(int i = 0 ; i < length ; i++)
{
	//タッチ範囲外の場合は次のループへ
	if( isTouch(rects[i],x,y) == false )
	{
		continue;
	}

	//タッチポイントとタッチ対象の中心点との距離を取得
	double distance =
Math.sqrt( Math.pow(x-rects[i].centerX(),2) + Math.pow(y-rects[i].centerY(),2) );

	//距離がこれまでのタッチ対象の最小距離(min)より小さければタッチ認定
	if(min > distance )
	{
		min = distance ;
		select = i;
	}

}

if(select != -1)
{
	//タッチ対象rects[select]をタッチした時の処理
}
```



#その他のアプローチ
最も近い位置にある対象を探すのではなく、RANGE_TOUCHの範囲内にあるのであれば最初に見つけたものをタッチしたと見なすやり方もあります。

```
final int length=rects.length;
for(int i = 0 ; i < length ; i++)
{
	if( isTouch(rects[i],x,y) == true )
	{
		select = i;
		break;
	}
}

if(select != -1)
{
	//タッチ対象rects[select]をタッチした時の処理
}
```

先ほどのやり方より精度は落ちますが、最小値を求めるための変数や条件式を減らしたためコードがすっきりし、forループを最後まで回さないので僅かですが処理も早く済みます。
タッチ対象がそれほど密集していない場合はこちらのほうが良いかもしれません。

私の場合は以下のような<a href="https://play.google.com/store/apps/details?id=com.westplain.ant" target="_blank">小さな対象物が大量に動き回るアプリ</a>では精度の高い前者の方法を使い

![sample.png](https://qiita-image-store.s3.amazonaws.com/0/92839/cd77f633-f92c-f9d6-15b4-e4986c173bf6.png)


対象物が離れており、<a href="https://play.google.com/store/apps/details?id=com.westplain.metal" target="_blank">位置固定の場合</a>はシンプルな後者の方法を使っています。
![sample2.png](https://qiita-image-store.s3.amazonaws.com/0/92839/3bdfd6dc-4215-1c81-20aa-298e91f106d8.png)

<br>

参考にしたページ
<a href="http://www.matim-dev.com/tutorials.html" target="_blank">AndEngine Tutorials</a>
<a href="http://westplain.sakuraweb.com/translate/AndEngine/" target="_blank">上記の日本語訳サイト(非公式)</a>

記事内の画像には以下サイトの素材を使用しています。
<a href="http://sozai-good.com/" target="_blank">素材Good</a>