在ROK、SOS、WOS等SLG游戏中,都是使用菱形瓦片
(也称为斜方格
或轴对称网格
)进行地图布局。
它的实际角度为
arctan(1/2)
,由于它是一个菱形,所以这里我们称它为菱形瓦片。
但是我们在屏幕里的视野,就是一个标准正交坐标系,所以很多场景下我们需要进行做一个转换
一、坐标系定义
1. 菱形网格坐标系
- 轴向坐标:以
菱形网格
对角方向为基准轴,记作(q, r)
- 特征参数:
菱形瓦片
宽度 w=2a,高度 h=a(宽高比2:1)
2. 笛卡尔坐标系
- 标准正交坐标系,记作
(x, y)
- 菱形网格中心点对齐笛卡尔坐标原点
二、正向转换(菱形→笛卡尔)
公式:
几何解释:
- 横向位移由 q 和 r 共同驱动,纵向位移由两者差值决定
- 参数 a 为基本单位长度(例如像素尺寸)
示例:
若 a=1,菱形坐标 (3,2) 对应笛卡尔坐标:
三、逆向转换(笛卡尔→菱形)
公式:
运算步骤:
转换代码
//! 瓦片地图
template<Vector2 TILE_SIZE>
class DiamondSlant
{
public:
/**
* @brief 地图坐标 -> 世界坐标
*/
constexpr Vector2 Map2World(const Point& xy)
{
return { (xy[1] + xy[0]) * TILE_SIZE[0] / 2.0, (xy[1] - xy[0]) * TILE_SIZE[1] / 2.0};
}
/**
* @brief 世界坐标 -> 地图坐标
*/
constexpr Point World2Map(const Vector2& pos)
{
Vector2 xy_div = pos / TILE_SIZE;
return toPoint(Vector2{ xy_div[0] - xy_div[1] + 0.5, xy_div[0] + xy_div[1] - 0.5 });
}
};
调试工具
import tkinter as tk
from math import sqrt
class DiamondGridDebugger:
def __init__(self, master, cell_size=40, rows=15, cols=15):
self.master = master
self.cell_size = cell_size # 对应参数a
self.rows = rows
self.cols = cols
# Canvas尺寸自适应
canvas_width = cell_size * 2 * cols
canvas_height = cell_size * rows
self.canvas = tk.Canvas(master,
width=canvas_width,
height=canvas_height,
bg='white')
self.canvas.pack(fill=tk.BOTH, expand=True)
# 坐标系转换参数
self.origin_x = canvas_width // 2
self.origin_y = canvas_height // 2
# 交互元素
self.selected_cell = None
self.canvas.bind("<Motion>", self.highlight_cell)
self.canvas.bind("<Button-1>", self.select_cell)
self.draw_grid()
def qr_to_xy(self, q, r):
"""菱形坐标转笛卡尔坐标(原点在画布中心)"""
x = self.cell_size * (q + r)
y = self.cell_size * (r - q) / 2
return x + self.origin_x, -y + self.origin_y # Y轴翻转
def draw_diamond(self, q, r, color='lightgray'):
"""绘制单个菱形单元"""
center_x, center_y = self.qr_to_xy(q, r)
# 计算四个顶点(宽高比2:1)
points = [
(center_x + self.cell_size, center_y),
(center_x, center_y + self.cell_size//2),
(center_x - self.cell_size, center_y),
(center_x, center_y - self.cell_size//2)
]
# 绘制图形
cell = self.canvas.create_polygon(
points,
outline='gray',
fill=color,
tags=('cell', f'cell_{q}_{r}')
)
# 添加坐标标签
label = self.canvas.create_text(
center_x, center_y,
text=f'({q},{r})',
font=('Arial', 8),
tags=('label', f'label_{q}_{r}')
)
return cell
def draw_grid(self):
"""绘制完整网格"""
for q in range(-self.cols//2, self.cols//2):
for r in range(-self.rows//2, self.rows//2):
self.draw_diamond(q, r)
def highlight_cell(self, event):
"""鼠标悬停高亮"""
x = event.x - self.origin_x
y = self.origin_y - event.y
# 逆向坐标转换
q = round((x - 2*y) / (2*self.cell_size))
r = round((x + 2*y) / (2*self.cell_size))
if self.selected_cell != (q, r):
self.canvas.delete('highlight')
self.draw_diamond(q, r, color='#e0f0ff')
self.canvas.addtag_withtag('highlight', f'cell_{q}_{r}')
self.canvas.addtag_withtag('highlight', f'label_{q}_{r}')
def select_cell(self, event):
"""点击选中单元格"""
x = event.x - self.origin_x
y = self.origin_y - event.y
q = round((x - 2*y) / (2*self.cell_size))
r = round((x + 2*y) / (2*self.cell_size))
# 显示详细信息
info = f"Selected Cell: ({q}, {r})\nCartesian: ({q + r}, {(r - q)/2:.1f})"
self.canvas.itemconfig('info', text=info)
# 绘制选中框
self.canvas.delete('selection')
center_x, center_y = self.qr_to_xy(q, r)
self.canvas.create_rectangle(
center_x - self.cell_size,
center_y - self.cell_size//2,
center_x + self.cell_size,
center_y + self.cell_size//2,
outline='red',
tags='selection'
)
self.selected_cell = (q, r)
if __name__ == '__main__':
root = tk.Tk()
root.title("Diamond Grid Debugger (2:1)")
debugger = DiamondGridDebugger(root, cell_size=40, rows=15, cols=15)
root.mainloop()