学校での学習の一環でpythonライブラリ「pygame」でゲームを作っています。シミュレーションゲームなのでよくあるヘックスマップを作ろうとしましたが、使えるコードがなかなかヒットしない。結局自分で一から作ることになりました。…だったのですが稚拙なコードをQiitaユーザーのshiracamus様に書き直していただきました!ありがとうございます。
参考にしたサイト
http://www.redblobgames.com/grids/hexagons/
http://qiita.com/kapipara-games/items/7978a7b51a47df8ace38
環境
- Windows10 VirtualBox Ubuntu16.04
- python3.5 pygame1.9.3
##コード
my_hexmap.py
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import pygame
from pygame.locals import *
# colors (Red, Green, Blue)
BLACK = (0, 0, 0)
ORANGE = (255, 120, 0)
YELLOW = (255, 255, 0)
CYAN = (0, 255, 255)
class Hexagon:
"""
Hexagon index/pixel coordinate converter
There is no limit to the number of hexagon.
x: 0/4 1/4 3/4 4/4
y: | | | |
0/2-- v0 _____ v1
/ \
1/2-- / \
v5 \ / v2
2/2-- \_____/
v4 v3
"""
VERTEXES = (1, 0), (3, 0), (4, 1), (3, 2), (1, 2), (0, 1)
def __init__(self, size):
self.SIZE = size
self.HALF = size * 1 // 2
self.QUARTER = size * 1 // 4
self.WIDTH = size * 3 // 4
self.HEIGHT = size
def index_at(self, pixel_x, pixel_y):
"""get the index coordinae at the pixel coordinate"""
x = pixel_x // self.WIDTH
x0 = pixel_x % self.WIDTH
if x0 < self.QUARTER: # overlap area
y0 = (pixel_y - (x % 2) * self.HALF) % self.HEIGHT
if y0 >= self.HALF: # lower half
y0 = self.SIZE - y0
is_leftarea = x0 < self.QUARTER - y0 * self.QUARTER // self.HALF
if is_leftarea:
x -= 1
y = (pixel_y - (x % 2) * self.HALF) // self.HEIGHT
return x, y
def origin_of(self, x, y):
return x * self.WIDTH, y * self.HEIGHT + (x % 2) * self.HALF
def vertexes_of(self, x, y):
"""get vertex coodinates of the index coordinate"""
origin_x, origin_y = self.origin_of(x, y)
return [(origin_x + vx * self.QUARTER, origin_y + vy * self.HALF)
for vx, vy in self.VERTEXES]
class HexMap:
"""
Hexagon map
___ x,y ___
/0,0\___/2,0\
\___/1,0\___/
/0,1\___/2,1\
\___/1,1\___/
/0,2\___/2,2\
\___/ \___/
"""
def __init__(self, columns, rows, hexagon_size):
self.COLUMNS = columns
self.ROWS = rows
self.hexagon = Hexagon(hexagon_size)
width, height = self.hexagon.origin_of(self.COLUMNS, self.ROWS)
width += self.hexagon.QUARTER
self.size = width + 1, height + 1
self.clear_clicked()
def clear_clicked(self):
self.clicked = [[False] * self.ROWS for x in range(self.COLUMNS)]
def click_at(self, pixel_x, pixel_y):
x, y = self.hexagon.index_at(pixel_x, pixel_y)
if 0 <= x < self.COLUMNS and 0 <= y < self.ROWS:
self.clicked[x][y] = not self.clicked[x][y]
def is_clicked_on(self, x, y):
return (0 <= x < self.COLUMNS and 0 <= y < self.ROWS and
self.clicked[x][y])
def hexagons(self):
for x in range(self.COLUMNS):
for y in range(self.ROWS - (x % 2)):
yield self.hexagon, x, y
class HexCanvas:
LINE_WIDTH = 1
CURSOR_SIZE = 5
def __init__(self, hexmap):
self.hexmap = hexmap
self.canvas = pygame.display.set_mode(hexmap.size)
self.font = pygame.font.SysFont(None, 17)
self.cursor = None
def cursor_at(self, pixel_x, pixel_y):
self.cursor = pixel_x, pixel_y
def draw(self):
canvas = self.canvas
canvas.fill(BLACK)
hexmap = self.hexmap
for hexagon, x, y in hexmap.hexagons():
vertexes = hexagon.vertexes_of(x, y)
# 六角形塗り潰し
color = ORANGE if hexmap.is_clicked_on(x, y) else YELLOW
pygame.draw.polygon(canvas, color, vertexes)
# 六角形枠線描画
for v1, v2 in zip(vertexes, vertexes[1:] + vertexes[:1]):
pygame.draw.line(canvas, BLACK, v1, v2, self.LINE_WIDTH)
# 座標タグ描画
tag = "{0}, {1}".format(x, y)
text = self.font.render(tag, True, CYAN)
origin_x, origin_y = hexagon.origin_of(x, y)
text_area = text.get_rect(center=(origin_x + hexagon.HALF,
origin_y + hexagon.HALF))
canvas.blit(text, text_area)
# カーソル描画
if self.cursor:
pygame.draw.circle(canvas, BLACK, self.cursor, self.CURSOR_SIZE, 0)
def main():
pygame.init()
FPSCLOCK = pygame.time.Clock()
hexmap = HexMap(columns=20, rows=15, hexagon_size=50)
canvas = HexCanvas(hexmap)
while True:
for event in pygame.event.get():
if event.type == QUIT or event.type == KEYDOWN and event.key == K_q:
pygame.quit()
return
if event.type == MOUSEMOTION:
x, y = pygame.mouse.get_pos()
canvas.cursor_at(x, y)
elif event.type == MOUSEBUTTONDOWN:
x, y = pygame.mouse.get_pos()
hexmap.click_at(x, y)
canvas.draw()
pygame.display.update()
FPSCLOCK.tick(30)
if __name__ == '__main__':
main()