ゲームをつくる：どのヘックスをタッチしたか判定する

#この記事は、
ヘックスマップが好きな筆者が、最近スマホでイケてるヘックスSLGが無いので自分で作る奮闘記です。

# この記事のまとめ
タッチしたヘックスの判定は、

- ざっくり計算する。エッジケースの誤判定が発生する
- 誤判定の発生を三角形の比から検出する
- 誤判定を修正する

という形で実現しました。
[こちらから動くもの](https://storage.googleapis.com/hex_slg/hex_touch_test/index.html)を確認できます。

## 今回目指すもの
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157328/2ec4ecae-2f32-63e1-7a47-e52497afc340.png">
タッチしたら、それぞれのヘックスの座標、例えば5,5とか6,4とかが取得できること。



## ヘックス描画の前提
タッチ位置検出は、ヘックスの描画方法から逆算して判定します。
まずは、ざっくりおさらい。下の赤い三角形の各辺の長さから、ヘックスを描画しています。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157328/27e7d63a-e25e-fb37-4af0-37b0bd26ac93.png">
詳しくは[こちらの記事](https://qiita.com/papipu-games/items/c9e24e4c1b68d868cad8)で。


## タッチ位置から、ざっくりタッチしたヘックスを判定
描画から逆算して、こんな感じでざっくりヘックス座標を計算します。

```javascript:
    let mapY = Math.round(hexMapTouchPos.y / (UNIT + UNIT_H));
    let mapX = Math.round((hexMapTouchPos.x / UNIT_B - mapY % 2) / 2);
```
これだとエッジケースで誤判定が発生します。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157328/3dd38f95-f05b-871a-2d98-507e273a9c93.png">
それぞれの座標の判定範囲を紫の四角で示しました。ヘックスの上端と下端で別のヘックスをタッチしたと判定されています。
(5, 5)の上端の左側をタッチすると(5, 6)をタッチしたと判定されてしまいます。
実際のゲームで考えると、際どいところをタッチするのが悪いのであって、タッチし直してもらえば良いか思いますが、せっかなのできちっと判定しましょう。

## 誤判定の領域かどうかを検出
タッチしたの場所が、上端下端の三角形の範囲内かどうかを判定します。
まずは、タッチした場所と、ざっくり判定したヘックスの中心位置の差、distXとdistYを計算します。

```javascript:
    // 仮座標の、ヘックスの中心点を取得

    let drawCenterPosX = (mapX * 2 + mapY % 2) * UNIT_B;
    let drawCenterPosY = mapY * (UNIT + UNIT_H);

    // タッチ場所とヘックス中心点の差を計算
    let distX = touchDrawPos.x - drawCenterPosX;
    let absDistX = Math.abs(distX);
    let distY = touchDrawPos.y - drawCenterPosY;
    let absDistY = Math.abs(distY);
```
後で使うので、このタイミングで絶対値absも計算しておきます。
<img width="388" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157328/4999f9ca-a350-de9b-d6b2-5ae22d8674db.png">

上の図から、以下の判定で誤差領域の範囲を絞り込みます。

```javascript:
    // 不確定領域の絞り込みを行う
    // X座標による絞り込み
    if (absDistX > UNIT_B / 2) {

      // Y座標による絞り込み
      if (absDistY > UNIT_H) {
        // 不確定領域の可能性あり
      }
    }
```

上図の黒い三角形と、黒い三角形が紫の線で区切られた上部の三角形は、合同で、ちょうど倍大きさが違います。
このことから、X座標のタッチ位置差分が UNIT_B / 2の場合、タッチしたX座標は誤差可能性範囲内にあることがわかります。

続いてY座標の判定を行いますが、少し注意が必要です。
素直に考えると、(absDistY > UNIT_H * 3 / 2)という判定にしたくなりますが、実際に誤差が発生していた場合、absDistYが UNIT_X *3 / 2を上回ることはありません。なぜならば、mapYが5ではなく、6になっているからです（誤判定）。
そこで、Y座標はUNIT_Hで絞り込みます。


ここまでで、タッチ領域が下図の点線領域内であることを判定できました。
<img width="276" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157328/ed13b369-3b5d-ef40-5870-0ddf6bcb542c.png">
タッチした座標が、黒い三角形の斜辺の上と下、どちらなのかを判定すれば、誤判定が発生しているかどうか検出できます。

## タッチ位と斜辺の位置関係から誤判定の発生を検出
以下のように判定します。

```javascript:
// 三角形の比から、ずれているかどうか判定する
    let y = UNIT - absDistY;
    let A = UNIT_B / 2;
    let B = (UNIT - UNIT_H) / 2;

    // 斜辺の上をタッチしているかどうか
    if (B * absDistX / A > y) {
      // 斜辺の上だった = ずれている
    }
```
y はタッチしたｙ座標
上図の黒い四角と紫の線に区切られた三角形を、△Sとすると、
Aは、△Sの底辺の長さ、Bは、△Sの高さです。

absDistXの底辺の長さを持つ、△Sと合同の△Tを考えた場合、△Tの高さは、B * absDistX / A となります。
これが、ｙより大きかった場合、斜辺の上をタッチしたと判定できます。

## 誤判定のズレを修正する
誤差が発生している可能性がある場所のタッチが検出できたので、どのような座標のズレが発生したか判定しまて、ズレを修正します。
以下のように判定します。

```javascript:
    // Y座標のズレを修正
    if (distY > 0) {
      mapY++;
    }
    else {
      mapY--;
    }

    // X座標のズレを修正
    // Y座標に応じてズレの修正方向が異なる
    if (mapY % 2) {
      // Y座標が奇数
      if (distX < 0) {
        mapX--;
      }
    }
    else {
      // Y座標が偶数
      if (distX > 0) {
        mapX++;
      }
    }
```
Y座標のズレは、distYが正か負かで、ヘックスの上端と下端、どちらをタッチしたか判定して、修正します。
次に、ｘ座標のズレを修正しますが、ずれる方向はy座標が偶数か奇数かで異なります。マップはヘックスを横一直線に並べているために、y座標が偶数が奇数かで、x座方向に互い違いにヘックスが並んでいるからですね。

これで、完全に正しくタッチしたMap座標を取得できました。
次は[タッチしたヘックスのビジュアル表示と、２ヘックス間の距離を計算](https://storage.googleapis.com/hex_slg/hex_touch_test/index.html)しようと思います。