pythonのarcadeでhexmapを作成してみた！

# コードの実行結果
- ヘックスマップの描画
![スクリーンショット 2024-10-31 12.58.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3769985/88a5e72d-1735-0648-4610-440cf6f7a87a.png)

- そのヘックスを押すと、周囲3hex分が薄い赤色で表示される
![スクリーンショット 2024-10-31 12.58.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3769985/2f9ee88b-7b17-efcf-9f29-fab16bbeefe9.png)


```python

import arcade
import math

import arcade.key

WIDTH = 800
HEIGHT = 600
RADIUS = 30  # 半径
GRID_SIZE = 10  # hexの個数
TITLE = "test_hexmap"


class Hexmap:
    """
    六角形グリッドマップを生成・管理し、描画、クリック検出を行うためのクラス。

    初期化引数:
        radius: 六角形の半径 (int or float)
        grid_size: グリッドのサイズ (行と列の数) (int)
    """

    def __init__(self, radius, grid_size):
        """
        Hexmapオブジェクトを初期化します。

        引数:
            radius: 六角形の半径。
            grid_size: グリッドのサイズ（行と列の数）。
        """
        self.radius = radius
        self.grid_size = grid_size
        self.hex_centers = []

    def hexagon_vertices(self, x, y):
        """
        指定された中心座標(x, y)を持つ六角形の頂点座標リストを返します。

        引数:
            x: 六角形の中心 x 座標。
            y: 六角形の中心 y 座標。

        戻り値:
            [(x1, y1), (x2, y2), ..., (x6, y6)]: 六角形の頂点座標のリスト。
        """
        vertices = []  # verticesは頂点という意味
        for i in range(6):
            angle = i * math.pi / 3  # pi/3 * 0~5
            vx = x + self.radius * math.cos(angle)
            vy = y + self.radius * math.sin(angle)
            vertices.append((vx, vy))
        return vertices

    def draw_hexagon(self, clicked_hex=None):
        """
        grid_size で指定された個数のhexを描画します。
        クリックされたhexとその周囲3ヘックス分を赤色で描画します。

        引数:
            clicked_hex: クリックされた六角形の中心座標 (tuple)。
        """
        # 各六角形の位置を計算
        root_3 = math.sqrt(3)
        horizontal_spacing = 3 * self.radius / 2
        vertical_spacing = root_3 * self.radius

        for row in range(self.grid_size):  # 行(縦方向)
            for col in range(self.grid_size):  # 列（横方向）
                x = col * horizontal_spacing  # 列間隔⇨hori
                y = row * vertical_spacing  # 行間隔⇨vert

                # yが奇数の時は、縦方向で1.5倍の距離にする⇨これによって、ジグザグになる
                if col % 2 == 1:
                    y += vertical_spacing / 2
                self.hex_centers.append((x, y))
                points_list = self.hexagon_vertices(x, y)

                # クリックされたhexとその周囲3ヘックス分を赤色で描画
                """
                    ① かくhexの中心座標とクリックされた位置との距離を計算
                    ② その距離が 半径*5.5 という決め以内だったら、そのhexを塗りつぶす
                    ③ この5.5というのは、決めの部分なので、適宜調整する
                    ここの5.5の部分はいい感じに調整しなくてはいけない・・・
                """
                if clicked_hex:
                    dx = x - clicked_hex[0]
                    dy = y - clicked_hex[1]
                    distance = math.sqrt(dx**2 + dy**2)
                    if distance <= self.radius * 5.5:
                        arcade.draw_polygon_filled(point_list=points_list,
                                                    color=(255, 0, 0, 50))
                        arcade.draw_polygon_outline(point_list=points_list,
                                                    color=arcade.color.BLACK,
                                                    line_width=2)
                    # それ以外はいつもどりの描写をする
                    else:
                        arcade.draw_polygon_outline(point_list=points_list,
                                                    color=arcade.color.BLACK,
                                                    line_width=2)
                else:
                    arcade.draw_polygon_outline(point_list=points_list,
                                                color=arcade.color.BLACK,
                                                line_width=2)

    def get_clicked_hex(self, mouse_x, mouse_y):
        """
        クリックされた六角形の中心座標を返します。

        引数:
            mouse_x: マウスの x 座標。
            mouse_y: マウスの y 座標。

        戻り値:
            (center_x, center_y): クリックされた六角形の中心座標。
            None: クリックされた六角形がない場合。
        """
        for center in self.hex_centers:
            dx = mouse_x - center[0]
            dy = mouse_y - center[1]
            distance = math.sqrt(dx**2 + dy**2)
            if distance <= self.radius:
                return center
        return None


class MyHexMap(arcade.Window):

    def __init__(self, width, height, title):
        super().__init__(width, height, title)
        self.hex_map = Hexmap(radius=RADIUS, grid_size=GRID_SIZE)
        self.clicked_hex = None

    def setup(self):
        arcade.set_background_color(arcade.color.WHITE)

    def on_draw(self):
        arcade.start_render()
        self.hex_map.draw_hexagon(self.clicked_hex)

    def on_mouse_press(self, x, y, button, modifiers):
        self.clicked_hex = self.hex_map.get_clicked_hex(mouse_x=x,
                                                        mouse_y=y)
        if self.clicked_hex:
            print(f"Clicked hex point {self.clicked_hex}")

    def on_key_press(self, symbol, modifiers):
        if symbol == arcade.key.Q:
            arcade.close_window()


def main():
    myhexmap = MyHexMap(width=WIDTH, height=HEIGHT, title=TITLE)
    myhexmap.setup()
    arcade.run()


if __name__ == "__main__":
    main()

```
# 参考記事
- geminiやperplexityによるコード
- [計算方法や配置を理解するの参考になった良記事](https://www.redblobgames.com/grids/hexagons/)

# 最後に
もし、arcadeで何かしらのhexmapのゲームを作っている方の参考になれば幸いです。

# 他にもこんなことやってます
- [Python&arcadeで簡単！レトロ風シューティングゲームを作ろう！🚀](https://qiita.com/nami_fumi/items/dc679b47f57fa5a18f68)
- [pythonのarcadeを使って、ユニット選択とボタンの表示をやってみた](https://qiita.com/nami_fumi/items/c819bd71dac6123f942d)