Сегодня будет немного технических деталей. Получилось выделить немного времени и поработать над генерацией поля и чуть-чуть оптимизацией поиска гекса по позиции мышки.
Про генерацию поля и мою борьбу с ChatGPT я расскажу завтра, а пока про небольшой лайвхак, который позволяет обойти ограничение на форму коллайдеров в GameMaker.
В GameMaker достаточно ограниченный набор формы коллайдеров, это грустно. И если мне просто надо определить попадает ли точка на какой-то регион, например, на контурной карте, то можно использовать другой подход.
В моем случае у меня есть гексы (шестиугольники), и мне хотелось бы понять на какой из 300+ гексов сейчас попадает мышка.
Вариант 1.
Мы просто перебираем каждый гекс из массива и проверяет попадает ли точка на поверхность фигуры построенной по его 6 точкам. Вот пример такой функции для GML.
/// is_point_in_hexagon(px, py, hex)
/// Проверяет, находится ли точка (px, py) внутри шестиугольника hex.
/// hex - массив с координатами шестиугольника в формате [x1, y1, x2, y2, ..., x6, y6].
/// Возвращает true, если точка внутри, иначе false.
function is_point_in_hexagon(px, py, hex) {
var count = array_length(hex) div 2;
var inside = false;
var j = count - 1;
for (var i = 0; i < count; i++) {
var xi = hex[i * 2], yi = hex[i * 2 + 1];
var xj = hex[j * 2], yj = hex[j * 2 + 1];
// Проверка пересечения луча, выходящего из точки, с ребром полигона
var intersect = ((yi > py) != (yj > py)) &&
(px < (xj - xi) * (py - yi) / (yj - yi) + xi);
if (intersect) {
inside = !inside;
}
j = i;
}
return inside;
}
Минусы - много математики, и каждый кадр нужен обход почти всего массива гексов (пока не найдем нужный), и чем гексов больше - тем больше нагрузка. Плюс еще на каждый гекс нам надо проверить вхождение туда нужной точки по формуле. К тому же, в случае гексов это работает, а если это контур страны, то такой формулы просто не существует.
Вариант 2.
Когда мы делали “Don Duality” у нас как раз возникла проблема с картой и выбором там регионов. Не помню как я пришел к этому методу, сам дошел или где-то прочитал, но суть такая.
В GameMaker отрисовка объектов происходит не сразу на экран, а на так называемые поверхности (Surface). По-умолчанию, все draw попадают на основной Application Surface, но никто не запрещает создать отдельный и манипулировать им как захочется.
И суть метода была в том, чтобы создать отдельную поверхность по размеру экрана, на которой отрисовать регионы, но “залить” каждый регион цветом равным его порядковому номеру в массиве. Цвет же кодируется через RGB, а это нам дает 256*256*256 чисел. И отрисовать эту поверхность где-то за пределами видимой области.
// Create
clickSurf = surface_create(room_width, room_height);
// Draw
surface_set_target( clickSurf );
for (var _i=0; _i < ds_list_size(my_field_list); _i++) {
var _hex_key = ds_list_find_value(my_field_list, _i);
var _hex = ds_map_find_value(field_map, _hex_key);
var _corners = polygon_corners(_hex, hex_layout);
var _clr = make_color_rgb(0, 0, _i+1);
draw_set_color(_clr); // задаем цвет гексу
draw_primitive_begin(pr_trianglefan); // рисуем гекс
for (var _k=0; _k < 6; _k++) {
var _corner = _corners[_k]
var _x1 = _corner[XX];
var _y1 = _corner[YY];
draw_vertex(_x1, _y1);
}
draw_primitive_end();
}
draw_set_color(c_white);
surface_reset_target();
draw_surface(clickSurf, room_width + 10, room_height);
// Step
if ( surface_exists(clickSurf) ) {
var _col = surface_getpixel(clickSurf, mouse_x, mouse_y);
var _number = colour_get_blue(_col);
var _hex_key = ds_list_find_value(my_field_list, _number);
}
После этого мы просто смотрим какой цвет у пикселя, в нужной нам точке и получаем номер элемента массива, который хранит нужную нам информацию.
Если контуры регионов не динамические, то можно вообще сохранить отрисованную один раз поверхность и использовать данные из буфера, чтобы не гонять постоянно циклы в draw. ⌨️