ゲームをつくる：Hex描画を実装

#この記事は、
ヘックス・ターン制SLG好きが、最近スマホでその手の面白いゲームが無いので自分で作る、と頑張る記録です。

# この記事のまとめ
- Hexを、座標計算してCocosで線で描画してみた
- 正六角形は、正三角形が６個集まったものと考えて座標計算した

実装してわかった、設計上のポイントは、[こちらの記事](https://qiita.com/papipu-games/items/8e1ef93a2c9c74670418)。

## Cocos Creatorを使ってJavaScriptでヘックスマップを描画してみた
まずはプリミティブなLine描画を使って、ヘックスマップを実装してみます。
まず動くものを作って実践して、雰囲気を掴みます。作ってみないとわからないことが沢山あると思うからです。ぶっちゃけ、マップは最終的にはスプライト表示になるので、このライン描画は使わないと思うのですが（その割には結構苦労した）、まずは動くものを作ります。

## こんな感じのものを作りました。
CocosCreatorで作成して、Web viewで動作しています。
<img width="906" alt="スクリーンショット 2019-10-29 20.47.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157328/ec585066-bc3d-dfc3-85b8-e066bb689349.png">
今後は動くものを貼っていきたいので、どこか無料で、良いHTML5ホスティングサービスがあったら教えて下さい。


## どのように実装したか

正六角形は、正三角形が6個集まってできています。
正三角形は、角度30°・60°・90°からなる直角三角形が２つ合わさったものだと考えることができます。
<img width="335" alt="スクリーンショット 2019-10-29 22.49.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157328/27e7d63a-e25e-fb37-4af0-37b0bd26ac93.png">
この直角三角形：図では赤の三角形に注目します。
この赤の三角形の斜辺の長さ：UNITは、正六角形に外接する円の半径、と考えることができます。
ここから、底辺の長さと高さ、UNIT_BとUNIT_Hをsin,cosを使って計算することができます。

```javascript:DrawHex.js
import CONST from '../../Defines/Constant'; // 定数

// JSはクラスメンバ参照が遅いので、高速化のためグローバル変数を宣言
// 高速化のため、CONSTの値をローカル変数に再代入
let UNIT = CONST.UNIT;
let UNIT_B = CONST.UNIT_B;
let UNIT_H = CONST.UNIT_H;
let HeightMod = CONST.HeightMod; // 視点の傾きの高さ補正

// 描画用変数群
let BaseNode; // 描画用の親ノード
let Grp; // cc.Graphics
let hexPoints = []; // 描画するHexの座標群

let Nodes = []; // マップ座標の文字表示用のノード

// *********************************
// Hexを描画する DrawHex
// ********************************
class DH {

  // *********************************
  // DrawHesクラスの初期化
  // node: 描画の親ノード
  // ********************************
  static init(node) {
    BaseNode = node;

    // cc.Graphicsの初期化
    Grp = node.addComponent(cc.Graphics);
    Grp.lineWidth = 3;
    Grp.strokeColor = cc.Color.RED;

    // ヘックスの描画用の座標6つをvec2で確保
    for (let loop = 0; loop < 6; loop++) {
      hexPoints[loop] = new cc.Vec2();
    };

    // 高さの補正を計算
    UNIT *= HeightMod;
    UNIT_H *= HeightMod;
  }

  // **********************************************************
  // 与えられたマップ座標からヘックスを１つ描画
  // mapX, mapY: マップ座標
  // **********************************************************
  static DrawHex(mapX, mapY) {
    // マップ座標からヘックス描画の中心座標を計算
    let drawCenterPosX = (mapX * 2 + mapY % 2) * UNIT_B;
    let drawCenterPosY = mapY * (UNIT + UNIT_H);

    // ヘックスを構成する座標群を計算
    // ヘックスの上の座標
    hexPoints[0].x = drawCenterPosX;
    hexPoints[0].y = drawCenterPosY + UNIT;
    // ヘックスの右上の座標
    hexPoints[1].x = drawCenterPosX + UNIT_B;
    hexPoints[1].y = drawCenterPosY + UNIT_H;
    // ヘックスの右上の座標
    hexPoints[2].x = drawCenterPosX + UNIT_B;
    hexPoints[2].y = drawCenterPosY - UNIT_H;
    // ヘックスの下の座標
    hexPoints[3].x = drawCenterPosX;
    hexPoints[3].y = drawCenterPosY - UNIT;
    // ヘックスの左下の座標
    hexPoints[4].x = drawCenterPosX - UNIT_B;
    hexPoints[4].y = drawCenterPosY - UNIT_H;
    // ヘックスの左上の座標
    hexPoints[5].x = drawCenterPosX - UNIT_B;
    hexPoints[5].y = drawCenterPosY + UNIT_H;

    // 座標群を渡してヘックスを描画
    DH.DrawLine(hexPoints);

    if (CC_DEBUG) { // 開発用座標表示
      DH.DrawMapCord(mapX, mapY, drawCenterPosX, drawCenterPosY);
    }
  }

  // **********************************************************
  // 線を描画
  // points: 線を描画するvex2の配列
  // **********************************************************
  static DrawLine(points) {
    Grp.moveTo(points[0].x, points[0].y); // 開始座標をセット
    let length = points.length;
    for (let loop = 1; loop < length; loop++) {
      Grp.lineTo(
        points[loop].x, points[loop].y);
    };
    Grp.close(); // 最後に線を閉じる
    Grp.stroke(); // 描画
  }

  // **********************************************************
  // マップ座標を描画(開発用)
  // 念の為、何度呼び出されても良いような作り
  // x, y: マップ座標
  // drawX, drawY: 描画座標
  // **********************************************************
  static DrawMapCord(x, y, drawX, drawY) {
    // Nodeがない場合、新規に作成する
    if (!Nodes[x]) {
      Nodes[x] = [];
    }
    if (!Nodes[x][y]) {
      let node = new cc.Node(); // ノードを作成
      Nodes[x][y] = node;
      node.label = node.addComponent(cc.Label);　// ラベルコンポーネントをセット
      node.label.fontSize = 32;
      node.label.lineHeight = 32;
      node.color = cc.Color.BLUE;
      node.parent = BaseNode;
      // cc.log('Node Created: ' + x + ', ' + y);
    }
    Nodes[x][y].setPosition(drawX, drawY); // ラベル表示位置をセット
    Nodes[x][y].label.string = x + ', ' + y; // ラベル文字をセット
  }

  // **********************************************************
  // Graphoc描画のクリア用関数
  // **********************************************************
  static Clear() {
    Grp.clear();
  }

}
export default DH;
```

外部で宣言している定数はこんな感じ。

```javascript:Constant.js
const CONSTANT = {
  UNIT: 80, // 六角形を構成する12の直角三角形の斜辺＝六角形に概説する円の半径
  UNIT_B: 80 * Math.cos(30 * Math.PI / 180), // 六角形を構成する12の直角三角形の底辺(長い方):Bottom
  UNIT_H: 80 * Math.sin(30 * Math.PI / 180), // 六角形を構成する12の直角三角形の高さ(短い方):Height

  HeightMod: 0.7, // マップ視点の傾きの高さ補正
};
export default CONSTANT;
```

Nodeツリー内のNodeに、以下のコンポーネントをつけて呼び出します。

```javascript:MapManager.js
const { ccclass, property } = cc._decorator;
import CONST from '../Defines/Constant'; // 定数宣言
import DH from './DrawHex'; // ヘックス描画クラス

// *********************************
// Map Manager
// ********************************
@ccclass
class MAPM extends cc.Component {

  // **********************************************************
  // オンロード
  // **********************************************************
  onLoad() {
    DH.init(this.node); // 初期化
    DH.Clear(); // 描画クリア
    for (let loop1 = 0; loop1 < 11; loop1++) {
      for (let loop2 = 0; loop2 < 12; loop2++) {
        DH.DrawHex(loop2, loop1); // ヘックス描画
      };
    };
  }
}
export default MAPM;
```

次は、[タッチした座標を取得するインターフェイスを実装](https://qiita.com/papipu-games/items/f96832eb665627d41d80)しようと思います。