Gamerus
Вот какую систему координат использовал я. По сути она схожа с Вашей, только несколько перевёрнутой.
Замечу, что тут сразу показано, как эта система отображается на плоскости, чтобы легче было рассчитывать преобразование одних координат в другие. Подразумевается, что m, w и h - величина соответствующих частей хекса в переносе на плоскость.
Теперь код модуля, отвечающего за эту карту. Я его так и не закончил (работа подвалила, и идеями стало некогда баловаться), но кое-что полезное там есть
Заголовочный файл
hexmap.h:
Скрытый текст:
Код:
#ifndef HexMapH
#define HexMapH
//Тип каждого HEX'а
struct HEX_BASE_TYPE {
unsigned char hex_type;
unsigned short move_cost;
HEX_BASE_TYPE(): hex_type(0), move_cost(0xFFFFu) {}
};
//Здесь хранятся данные о цене вхождения на каждую клетку
//с соответствующим типом
//Типов может быть до 255 штук, и каждый из них
//может иметь цену входа от 1 до 255 или 0 (непроходимо)
class HexCosts {
private:
unsigned char n;//количество
unsigned char *costs;//цены входа
public:
HexCosts(unsigned char number);
unsigned char GetCost(unsigned char n) const {return costs[n];}
void SetCost(unsigned char n, unsigned char move_cost) {costs[n] = move_cost;}
~HexCosts();
};
class HexMap {
protected:
HEX_BASE_TYPE **m;
unsigned short w, h;//Ширина-высота
struct __HM_POINT {unsigned short x, y, c; __HM_POINT(unsigned short X, unsigned short Y, unsigned short C) : x(X), y(Y), c(C) {}};
public:
HexMap();
HexMap(unsigned short width, unsigned short height);
void Create(unsigned short width, unsigned short height);
void Clear();
unsigned short GetWidth() const {return w;}
unsigned short GetHeight() const {return h;}
HEX_BASE_TYPE **GetMap() const {return m;}
void BuildMoveZone(unsigned short startX, unsigned short startY, unsigned short maxCost, const HexCosts *hexCosts);
~HexMap();
};
class GameHexMap : public HexMap {
protected:
float k_w, k_m, k_h;//Ширина, средняя часть, высота хекса в условных коорд.
bool is_last_row;//Присутствует ли на карте нижний ряд
float map_width, map_height;//Ширина и высота карты в условных коорд.
public:
GameHexMap(
unsigned short width, unsigned short height,//Ширина / высота карты
float hexWidth,//Ширина хекса в условных координатах
float hexMiddle,//Ширина верхней стороны хекса в условных координатах
float hexHeight,//Высота хекса в условных координатах
bool isLastRow//Есть ли последний, самый нижний ряд (нечётн. x, y = height - 1)
);
virtual ~GameHexMap() {}
//Получение хекса в определённых координатах. Координаты идут из (0,0) к (map_width, map_height)
//Возвращает false, если координаты находятся вне хексов; иначе true. При этом hexX и hexY всегда корректны в т.ч. и в случае отрицательных величин
virtual bool GetHex(float x, float y, int &hexX, int &hexY);
//Получение локальных координат центра определённого хекса
virtual void GetHexCenter(unsigned short hexX, unsigned short hexY, float &x, float &y);
//Перегружаемые функции для визуализации
//Прорисовка экрана
virtual void Draw() {}
//Преобразование экранных координат в локальные и обратно
virtual void ScreenToLocal(float screenX, float screenY, float &localX, float &localY) {}
virtual void LocalToScreen(float localX, float localY, float &screenX, float &screenY) {}
virtual void ScreenToLocal(float &X, float &Y) {}
virtual void LocalToScreen(float &X, float &Y) {}
//Двигает камеру на определённое смещение
virtual void MoveCamera(float dx, float dy) {}
//Перемещает камеру в опр. координаты
virtual void SetCameraPosition(float x, float y) {}
};
#endif
Файл реализации
hexmap.cpp:
Скрытый текст:
Код:
#include <stdlib.h>
#include <windows.h>
#include <queue>
#include <math.h>
#include "HexMap.h"
using namespace std;
#define round(X) (((X) - floor(X)) < 0.5f ? floor(X) : ceil(X))
HexCosts::HexCosts(unsigned char number) {
n = number;
costs = new unsigned char [n];
}
HexCosts::~HexCosts() {
if (costs) delete [n] costs;
costs = NULL;
}
//==============================================================================
HexMap::HexMap() {
m = NULL;
w = h = 0;
}
HexMap::HexMap(unsigned short width, unsigned short height) {
m = NULL;
Create(width, height);
}
HexMap::~HexMap() {
Clear();
m = NULL; w = h = 0;
}
void HexMap::Create(unsigned short width, unsigned short height) {
Clear();
w = width; h = height;
m = new HEX_BASE_TYPE *[w];
for (unsigned short i = 0; i < w; i++) {
m[i] = new HEX_BASE_TYPE[h];
}
}
void HexMap::Clear() {
if (m) {
for (unsigned short i = 0; i < w; i++) {
delete [h] m[i];
}
delete [w] m;
}
}
void HexMap::BuildMoveZone(unsigned short startX, unsigned short startY, unsigned short maxCost, const HexCosts *hexCosts) {
for (unsigned short i = 0; i < w; i++) {
for (unsigned short j = 0; j < h; j++) {
m[i][j].move_cost = 0xFFFFu;
}
}
queue<__HM_POINT> way;
m[startX][startY].move_cost = 0;
way.push(__HM_POINT(startX, startY, 0));
unsigned short move_cost, cur_move_cost;
unsigned char hmc;
unsigned short x, y, it = 0;
while (!way.empty()) {
it++;
x = way.front().x; y = way.front().y;
cur_move_cost = m[x][y].move_cost;
if (cur_move_cost != way.front().c) {way.pop(); continue;}
way.pop();
#define SET_CELL(X,Y) \
HEX_BASE_TYPE *cur_m = &m[X][Y]; \
hmc = hexCosts->GetCost(cur_m->hex_type); \
if (hmc < 0xFFu) { \
move_cost = cur_move_cost + hmc; \
if ((move_cost < cur_m->move_cost)&&(move_cost <= maxCost)) {\
cur_m->move_cost = move_cost; \
way.push(__HM_POINT(X, Y, move_cost)); \
} \
}
if (y > 0) {
//Верхний [0, -1]
SET_CELL(x, y - 1);
if (!(x & 1)) {
if (x > 0) {
//Левый в -1 ур. [-1, -1]
SET_CELL(x - 1, y - 1);
}
if (x < (w - 1)) {
//Правый в -1 ур. [+1, -1]
SET_CELL(x + 1, y - 1);
}
}
}
if (y < (h -1)) {
//Нижний [0, +1]
SET_CELL(x, y + 1);
if ((x & 1)) {
if (x > 0) {
//Левый в +1 ур. [-1, +1]
SET_CELL(x - 1, y + 1);
}
if (x < (w - 1)) {
//Правый в +1 ур. [+1, +1]
SET_CELL(x + 1, y + 1);
}
}
}
if (x > 0) {
//Левый в этом ур. [-1, 0]
SET_CELL(x - 1, y);
}
if (x < (w - 1)) {
//Правый в этом ур. [+1, 0]
SET_CELL(x + 1, y);
}
#undef SET_CELL
}
}
//==============================================================================
GameHexMap::GameHexMap(unsigned short width, unsigned short height, float hexWidth, float hexMiddle, float hexHeight, bool isLastRow) : HexMap(width, height) {
k_w = hexWidth;
k_m = hexMiddle;
k_h = hexHeight;
is_last_row = isLastRow;
if (w % 2) {//нечётное
map_width = ((float)((w >> 1) + 1)) * (k_w + k_m) - k_m;
} else {//чётное
map_width = ((float)(w >> 1)) * (k_w + k_m) + (k_w - k_m) / 2.0f;
}
if (is_last_row) {
map_height = (((float)h) + 0.5f) * k_h;
} else {
map_height = ((float)h) * k_h;
}
}
bool GameHexMap::GetHex(float x, float y, int &hexX, int &hexY) {
x = (x + k_m / 2.0f) / ((k_m + k_w) / 2.0f) - 1.0f;//Модифицируем x
y /= k_h;//Модифицируем y
//Теперь x - между хексом floor(x) и ceil(x)
float x1 = floor(x); float x2 = ceil(x);
//Теперь y - между хексом floor(y) и floor(y - 0.5)
float y1, y2;
if (((int)x2) & 1) {
y1 = floor(y) + 0.5;
y2 = floor(y - 0.5f) + 1.0f;
} else {
y2 = floor(y) + 0.5;
y1 = floor(y - 0.5f) + 1.0f;
}
if (((x - x1) * (x - x1) + (y - y1) * (y - y1)) < ((x - x2) * (x - x2) + (y - y2) * (y - y2))) {
hexX = x1;
if (hexX & 1) {
y1 -= 1.5;
} else {
y1 -= 0.5;
}
hexY = round(y1);
} else {
hexX = x2;
if (hexX & 1) {
y2 -= 1.5;
} else {
y2 -= 0.5;
}
hexY = round(y2);
}
if ((hexX < 0)||(hexY < 0)||(hexX >= w)||(hexY >= h)) return false;
if ((!is_last_row)&&(hexX & 1)&&(hexY == (h - 1))) return false;
return true;
}
void GameHexMap::GetHexCenter(unsigned short hexX, unsigned short hexY, float &x, float &y) {
x = ((float)hexX) * (k_w + k_m) / 2.0f + k_w / 2.0f;
y = ((float)hexY) * k_h + (hexX & 1 ? k_h : k_h / 2.0f);
}
Значит, что здесь к чему:
HEX_BASE_TYPE - это тип каждого хекса, который Вы можете изменить под свои нужды. В моём простейшем варианте там всего два поля:
hex_type - тип хекса как некое число от 0 до 255
move_cost - цена попадания в этот хекс, отдана под построение зоны хода
HexCosts - класс для задание соответствия, передвижение по какому хексу сколько стоит в неких очках хода. 0 считается за непроходимость хекса. Соответственно, в конструкторе задаётся количество типов хексов, затем SetCost для каждого типа изменяется цена хода по этому типу
P.S. А, нет, кажется, за непроходимость считается значение 255...
HexMap - базовый класс для карты. В конструкторе задаётся её ширина и высота (выше показана система координат), хранится карта как двумерный массив. Крайние клетки, если необходимо, всегда можно сделать абсолютно непроходимыми.
При помощи
BuildMoveZone строится зона хода, то есть рассчитываются цены попадания из определённой точки в окрестные до некого предела. Почему не ищется путь из одной в другую? Потому, что делалось это для пошаговой игры "героеподобного" типа, где как раз строить зону хода и надо на хексовом поле боя, а вовсе не искать путь. Но если Вам охота искать путь, то разобравшись в алгоритме построении зоны хода, Вы спокойно напишите поиск пути желаемым методом. Однако очень вероятно, что построения зоны хода Вам хватит.
Итак:
startX, startY - начальная точка
maxCost - до достижения какой стоимости всего пути строить зону
hexCosts - цены движений по определённым хексам. Обращаю внимание, что шлются они именно при построении зоны хода, т.е. у разных юнитов может быть своя стоимость движения по хексам.
В результате поля
move_cost клеток заполняются стоимостями движения в них. Как это преобразуется в путь? Очень просто. Мы берём конечную точку пути и ищем среди её соседей хекс, стоимость достижения которого
минимальна. Затем по тому же принципу его соседа, и так далее. Принцип определения соседей см. в коде
BuildMoveZone, там всё ясно.
Класс
GameHexMap, наследующий от
HexMax - это проецирование хексовой карты на плоскость. Из рисунка должно быть ясно, как эта карта предполагается на плоскости расположившейся.
В конструкторе width и height - ширина и высота карты в хексах,
hexWidth, hexMiddle и hexHeight - длины w, m и h соответственно в единицах измерения плоскости, isLastRow - присутствует ли на карте самый нижний ряд хексов.
GetHex - позволяет определить, какому хексу принадлежат соответствующие координаты на плоскости (например, в которых щёлкнул мышкой пользователь).
GetHexCenter наоборот, возвращает координаты цента определённого хекса в единицах измерения плоскости.
Остальные методы абстрактные и вставлены для дальнейшего наследования.
Ну и читайте комменты

Удачи, если есть вопросы - задавайте.