コードの実行結果
ユニットを選択すると赤い囲いが出てきます。
この状態の時にhexmap上を移動できます。
マウスでhexmap上をクリックすると、そのhexmapの中心座標位置に移動します。
コードの概要
前回記事にコードを付け加えて、hexmap上でユニットをクリックして移動させるインタラクティブなデモを作成しました。
画像は適当なものを使ってください。
ポイント
-
Hexマップの描画:
HEXMAP
クラスでHexマップの描画と、マウス座標から最も近いHexの中心座標を取得する処理を実装しています。 - ユニットの選択: マウスプレスイベントでユニットをクリックしたかを判定し、選択状態を管理します。
- ユニットの移動: マウスリリースイベントで、選択されたユニットをHexマップの中心座標へスムーズに移動させます。スムーズな移動は、移動ベクトルの正規化によって実現しています。
- ハイライト表示: クリックされたHexの周囲をハイライト表示することで、移動先を分かりやすくしています。←今回のコードでは、使われていません
コード
main.py
import arcade
import math
from components.HEXMAP import Hexmap
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
SCREEN_TITLE = "hexmap上のユニット移動の例"
UNIT_FILEPATH = "xxxx.png"
RADIUS = 30 # 半径
GRID_SIZE = 10 # hexの個数
UNIT_SCALE = 0.3
UNIT_SPEED = 3
class MyUnit(arcade.Sprite):
def __init__(self, filename, scale, speed):
super().__init__(filename, scale)
self.speed = speed
self.destination_x = None # destinationは方向という意味。
self.destination_y = None
def update(self):
# 方向先が指定されたら、ユニットを移動
if self.destination_x is not None and self.destination_y is not None:
# 移動方向の正規化
"""
現在地とマウスクリック時の距離を計算し、移動するときに反映
# Geminiさんに移動の正規化をするようにした理由を説明
ユニットがクリックされた地点へ一定の速度で滑らかに移動するようにするためです。
正規化によって、移動ベクトル (dx, dy) の長さを1に保ち
方向のみを表す単位ベクトルに変換しています。
"""
dx = self.destination_x - self.center_x
dy = self.destination_y - self.center_y
distance = math.sqrt(dx**2 + dy**2)
if distance > self.speed:
# 移動方向を正規化
# 移動のスケールをmax1にしている
dx = dx/distance
dy = dy/distance
# ユニットの移動
self.center_x = self.center_x + dx
self.center_y = self.center_y + dy
else:
self.center_x = self.destination_x
self.center_y = self.destination_y
# 初期値に戻す
self.destination_x = None
self.destination_y = None
class MyWindow(arcade.Window):
def __init__(self, width, height, title):
super().__init__(width, height, title)
self.unit_list = arcade.SpriteList()
self.hex_map = Hexmap(radius=RADIUS, grid_size=GRID_SIZE)
self.highlight = False
self.select_unit = None
self.unit_position = None
def setup(self):
# 背景の設定
arcade.set_background_color(arcade.color.AQUAMARINE)
# ユニットの初期設定
self.unit = MyUnit(filename=UNIT_FILEPATH, scale=UNIT_SCALE, speed=UNIT_SPEED)
self.unit.center_x = 225.0 # hexmap上の中心位置 X
self.unit.center_y = 233.8268590217984 # hexmap上の中心位置 Y
self.unit_list.append(self.unit)
def on_draw(self):
arcade.start_render()
self.unit_list.draw()
self.hex_map.draw_hexagon(highlight_trigger=self.highlight, clicked_hex=self.unit_position)
# ユニットが選択されたら、周りを赤い視覚で囲む
if self.select_unit:
arcade.draw_rectangle_outline(
center_x=self.select_unit.center_x,
center_y=self.select_unit.center_y,
width=self.select_unit.width + 10, # 少し大きめに枠を描画
height=self.select_unit.height + 10,
color=arcade.color.RED,
border_width=2
)
def on_mouse_press(self, x, y, button, modifiers):
print("on_mouse_press")
unit_hit = arcade.get_sprites_at_point(point=(x, y), sprite_list=self.unit_list)
if unit_hit:
self.select_unit = unit_hit[0]
print(f"click unit")
# self.unit.center_x = x
# self.unit.center_y = y
def on_mouse_release(self, x, y, button, modifiers):
print("on_mouse_release")
if self.select_unit:
self.unit_position = self.hex_map.get_hex_center_position(mouse_x=x, mouse_y=y) # ユニットのhexmap上の場所
if self.unit_position:
self.select_unit.center_x = self.unit_position[0]
self.select_unit.center_y = self.unit_position[1]
else:
# ユニットが選択されていない、つまりユニット以外のところを押した
self.highlight = False
self.select_unit = None
print(f"unit point: X = {self.select_unit.center_x}, Y = {self.select_unit.center_y}")
def on_update(self, delta_time):
self.unit.update()
def on_key_press(self, symbol, modifiers):
if symbol == arcade.key.Q:
print("Q key press")
arcade.close_window()
def main():
mywindow = MyWindow(width=SCREEN_WIDTH, height=SCREEN_HEIGHT, title=SCREEN_TITLE)
mywindow.setup()
arcade.run()
if __name__ == "__main__":
main()
components.HEXMAP
import arcade
import math
class Hexmap:
"""
六角形グリッドマップを生成、描画するクラス。
ハイライト表示は、3hex分
Attributes:
radius (float): 六角形の半径。
grid_size (int): グリッドのサイズ (行と列の数)。
hex_centers (list): 各六角形の中心座標を格納するリスト。
"""
def __init__(self, radius, grid_size):
"""
Hexmapクラスのコンストラクタ。
Args:
radius (float): 六角形の半径。
grid_size (int): グリッドのサイズ。
"""
self.radius = radius
self.grid_size = grid_size
self.hex_centers = []
# 頂点の計算
def hexagon_vertices(self, x, y):
"""
六角形の頂点座標を計算する。
Args:
x (float): 六角形の中心x座標。
y (float): 六角形の中心y座標。
Returns:
list: 六角形の頂点座標のリスト。
"""
vertices = []
for i in range(6):
angle = i * math.pi / 3
vx = x + self.radius * math.cos(angle)
vy = y + self.radius * math.sin(angle)
vertices.append((vx, vy))
return vertices
# ヘックスマップの描画
def draw_hexagon(self, highlight_trigger=False, clicked_hex=None):
"""
六角形グリッドマップを描画する。
Args:
highlight_trigger (bool): ハイライトを有効にするかどうか。
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
y = row * vertical_spacing
if col % 2 == 1:
y += vertical_spacing / 2
self.hex_centers.append((x, y))
points_list = self.hexagon_vertices(x, y)
self.draw_single_hexagon(x, y, points_list, clicked_hex, highlight_trigger)
# ハイライトの表示
def draw_single_hexagon(self, x, y, points_list, clicked_hex, highlight_trigger):
"""
個々の六角形を描画する。ハイライトの有無を判断。
Args:
x (float): 六角形の中心x座標。
y (float): 六角形の中心y座標。
points_list (list): 六角形の頂点座標リスト。
clicked_hex (tuple): クリックされた六角形の中心座標。
highlight_trigger (bool): ハイライトを有効にするかどうか。
"""
if clicked_hex and highlight_trigger:
# 距離を計算
dx = x - clicked_hex[0]
dy = y - clicked_hex[1]
distance = math.sqrt(dx**2 + dy**2)
if distance <= self.radius * 5.5: # ハイライト範囲内
self.draw_highlighted_hexagon(points_list)
else:
self.draw_normal_hexagon(points_list)
else:
self.draw_normal_hexagon(points_list)
# ハイライトヘックスを表示
def draw_highlighted_hexagon(self, points_list):
"""
ハイライトされた六角形を描画する。
Args:
points_list (list): 六角形の頂点座標リスト。
"""
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)
# 通常のヘックスを表示
def draw_normal_hexagon(self, points_list):
"""
通常の六角形を描画する。
Args:
points_list (list): 六角形の頂点座標リスト。
"""
arcade.draw_polygon_outline(point_list=points_list, color=arcade.color.BLACK, line_width=2)
def get_hex_center_position(self, mouse_x, mouse_y):
"""
マウス座標から最も近い六角形の中心座標を取得する。
Args:
mouse_x (float): マウスのx座標。
mouse_y (float): マウスのy座標。
Returns:
tuple: 最も近い六角形の中心座標。見つからない場合は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
ほかにもこんなのやってます
- pythonのarcadeでhexmapを作成してみた!
- Python&arcadeで簡単!レトロ風シューティングゲームを作ろう!🚀
- pythonのarcadeを使って、ユニット選択とボタンの表示をやってみた
- Pythonのarcadeでクリックでユニットをスムーズに移動
参考文献
-
The Python Arcade Library
- arcade公式サイト
-
Hexagonal Grids
- Hexmapの計算方法や配置を理解するの参考になった良記事
- Gemini
- コードをよりよくするときにいいです
-
perplexity
- 初期コードを書かせる時にいいです
-
Hexagonal Grids
- Hexmapの計算方法や配置を理解するの参考になった良記事
- pythonのarcadeでhexmapを作成してみた!
- Pythonのarcadeでクリックでユニットをスムーズに移動