Добавить в корзинуПозвонить
Найти в Дзене
ZDG

Пишем платформер на C и JavaScript #3: Внезапные сложности

Мы научились свободно перемещаться по пространству, и теперь задача – исключить пересечение персонажа с элементами уровня. Хотя пространство сейчас полностью свободно для перемещений по любой траектории, оно имеет незримые границы клеток. Клетки могут быть пустые и заполненные.
Стало быть, проверка относительно проста. Надо только вычислить, в какой клетке

Предыдущие части: Гравитация, Введение

Мы научились свободно перемещаться по пространству, и теперь задача – исключить пересечение персонажа с элементами уровня. Хотя пространство сейчас полностью свободно для перемещений по любой траектории, оно имеет незримые границы клеток. Клетки могут быть пустые и заполненные.

Стало быть, проверка относительно проста. Надо только вычислить, в какой клетке пространства находится персонаж, и проверить, занята эта клетка или нет. Чтобы узнать координаты клетки в матрице, нужно координаты персонажа разделить на размер клетки без остатка:

var cell_x = Math.floor(body.x / cell_size);
var cell_x = Math.floor(body.y / cell_size);

Однако наш персонаж теперь не занимает ровно одну клетку, как раньше. Он может присутствовать сразу в четырёх клетках:

Задача усложняется, но несущественно: вместо одной клетки проверим четыре.

Потеря памяти

Нет, речь не об утечке компьютерной памяти. Это я потерял память. Несколько лет назад я делал платформеры на Flash. Сейчас Flash уже мёртв, поэтому я когда-то удалил его с машины, а сейчас исходный код не посмотришь (всё было в файлах .fla). Но приступая к написанию данного цикла материалов, я не особо парился. Что сделано один раз, можно сделать и второй.

Однако, дойдя до точки, когда нужно вычислять столкновения с клетками, я вдруг понял, что это сложнее, чем представлялось. Но этого не может быть, ведь все предыдущие платформеры работали, и там не было ничего сложного. Я потратил два дня, чтобы это осмыслить и найти ошибку в рассуждениях, но нет – это действительно сложнее, и я пока не вижу никаких других вариантов.

Наш персонаж описывается прямоугольником. На пересечения мы проверяем именно этот прямоугольник. При движении в какую-либо сторону нужно проверять три угла прямоугольника, которые соответствуют направлению движения. Например, при движении вправо и вниз:

-2

Персонаж может задеть за препятствие: правым верхним углом, правым нижним углом, и левым нижним углом.

При движении вправо и вверх:

-3

То есть, во-первых, в зависимости от направления движения нужно выбрать комбинацию трёх углов. Чего я совершенно не помню, но может быть и делал.

Но это не страшно. Для решения задачи нужно к координатам угла прямоугольника прибавить вектор движения (красную стрелку) и посмотреть, находится ли конечная точка внутри клетки.

Но тут же нас ждёт вторая проблема:

-4

На картинке ситуация такая, что линия движения пересекает либо верхнюю сторону клетки, либо левую. Пока не вычислим точку пересечения линии и клетки, не узнаем. А вычислять её надо, потому что:

Если мы пересеклись с верхней стороной квадрата, то блокируется движение по вертикали, но не по горизонтали. Если же пересеклись с левой стороной, то блокируется движение по горизонтали, но не по вертикали. Путать эти варианты нельзя.

Кроме того, для правильного позиционирования персонажа нужно отсекать ту часть траектории, которая попадает внутрь квадрата, но оставлять ту часть, которая находится снаружи, чтобы персонаж мог продолжать движение до самого упора.

-5

Наконец, возможен вариант, когда линия движения проскакивает через клетку и её конечные координаты находятся в свободном квадрате, но персонажа всё равно нужно блокировать:

-6

Значит, и здесь нужно вычислять пересечение линии с квадратом. Получается, что вычислять пересечение нужно для разных случаев, и все они жизненно необходимы для корректной работы.

Но и это ещё не всё. На картинке выше персонаж занимает 4 клетки, но чёрную клетку, через которую проходит траектория, он не занимает. Эта клетка вне наших радаров. Её нельзя вычислить из координат персонажа или линии движения. Нужно буквально сканировать все клетки вдоль линии движения, чтобы найти пересечения. То есть мы здесь приходим к алгоритму бросания лучей!

Масштабы проблемы меня просто поразили. Такой сложности в прошлом явно не было, но сейчас она есть, и от неё никуда не деться. Единственный вариант, который мне приходит в голову – прошлая реализация не учитывала каких-то нюансов и следовательно работала на честном слове.

Теперь нужно сделать более качественную реализацию. У меня была заготовка статьи про оптимизацию метода бросания лучей, и вот он нам понадобился. Поэтому я сначала допишу ту статью и почитаю источники на предмет альтернативных решений, а потом продолжим.

Наука
7 млн интересуются