HitBox — это чаще всего невидимая область или группа областей, которая помогает обнаруживать коснулся ли объект другого объекта, у которого тоже есть свой хитбокс.
В HTML страницах изначально нет понятие хитбокса у блоков, поэтому в этом посте мы сами получим и обработаем их с помощью JS.
Для чего нам нужны хитбоксы?
Чаще всего для каких-нибудь игрушек или же симуляция, где важно определить касания блоков.
1 пример: block1 коснулся block2 и произошло какое-нибудь действие.
2 пример: player стоял на block1, как только он отошел с него, сразу же упал
Почему мы не можем просто сравнить координаты?
Мы можем сравнить координаты X=left и Y=top, но наше условие будет выполняться только, если block1 идеально встанет на место block2, что будет выглядеть странно, да и если вы делается какую-нибудь игру/симуляцию, это будет неправильным решением
Универсальность и размер нашей функции
Наша функция JS будет работать со всеми блоками, если мы дадим доступ к ним.
Размер функции будет зависеть от кол-ва видов ваших блоков, но по стандарту она занимает не больше 30 строк кода
Все действия будут проходить через 2 цикла for():
1 цикл: Для определения углов блоков.
2 цикл: Определения касания блоков между собой.
Логика получения координат:
Допустим у нас есть 3 блока разного размера и разных цветов (красный, зеленый, синий) с начальными позициями:
У каждого блока есть координата X и Y, которые находятся в левом углу самого блока:
Но у блоков помимо координат, есть высота и ширина (height, width):
Прибавляя эти значения между собой, мы уже можем получить все нужные нам координаты: x1, x2, y1, y2.
Приступим к коду
Для начала в .html файле напишем базовый код и наш главный блок:
<body>
<main class="mainBox">
</main>
<script src="script.js"></script> <!-- Заранее подключаем скрипт -->
</body>
Внутри mainBox засунем наши 3 блока и дадим им классы
<main class="mainBox">
<div class="block red"></div> <!-- Обращаться ко всем в JS будем через класс -->
<div class="block green"></div>
<div class="block blue"></div>
</main>
Добавим кнопку, чтобы мы могли определять будущие хитбоксы по нажатию.
Сразу же даем класс и onclick:
<main class="mainBox">
<div class="block red"></div>
<div class="block green"></div>
<div class="block blue"></div>
</main>
<button class="btn" onclick="getHitboxTouchResult()">Проверить</button>
Переходим в CSS и даем следующие стили:
body {
margin: 0; /* Чтобы по краям не было отступов */
background-color: rgb(60, 60, 60); /* Серый цвет приятнее глазу :D */
}
.mainBox {
position: relative; /* Чтобы блоки могли использовать left, top в род. блоке */
}
.block {
position: absolute; /* Возможность использования left, top */
}
/* Размеры и цвета доч. блоко */
.red {
background-color: rgba(255, 0, 0, 0.6);
width: 200px;
height: 200px;
}
.blue {
background-color: rgb(0, 0, 255, 0.6);
width: 250px;
height: 250px;
}
.green {
background-color: rgba(0, 255, 0, 0.6);
width: 100px;
height: 100px;
}
.btn {
position: absolute; /* Крепим кнопку снизу справа */
right: 0;
bottom: 0;
width: 100px; /* Даем размер */
height: 40px;
}
Получаем такой результат:
Все блоки слева сверху, давайте переместим их дав left, top параметры:
.red {
background-color: rgba(255, 0, 0, 0.6);
width: 200px;
height: 200px;
left: 920px;
top: 320px;
}
.blue {
background-color: rgb(0, 0, 255, 0.6);
width: 250px;
height: 250px;
left: 690px;
top: 190px;
}
.green {
background-color: rgba(0, 255, 0, 0.6);
width: 100px;
height: 100px;
left: 640px;
top: 420px;
}
Теперь они у нас в таком положении по центру страницы:
Переходим в JS и обозначаем нашу главную функцию определения хитбоксов:
function setHitboxesAndCheck() {}
В нее записываем получения всех доч. блоков через класс block:
blocks = document.querySelectorAll(".block")
Все блоки у нас, теперь определим их координаты:
for (var i = 0; i < Object.keys(blocks).length; i++) { // повторяем цикл пока i не достигнет количества всех блоков
}
Определим координаты X:
for (var i = 0; i < Object.keys(blocks).length; i++) {
// параметры offset могут записывать данные блока, в нашем случае мы используем offsetLeft и offsetWidth
// за счет того, что blocks является массивом, мы можем каждому блоку присвоить переменные
blocks[i].x1 = blocks[i].offsetLeft // первая точка X1
blocks[i].x2 = blocks[i].offsetLeft + blocks[i].offsetWidth // вторая точка X2 за счет прибавления ширины
}
Не забываем про Y:
for (var i = 0; i < Object.keys(blocks).length; i++) {
blocks[i].x1 = blocks[i].offsetLeft
blocks[i].x2 = blocks[i].offsetLeft + blocks[i].offsetWidth
blocks[i].y1 = blocks[i].offsetTop
blocks[i].y2 = blocks[i].offsetTop + blocks[i].offsetHeight // тут уже прибавляем высоту
}
Координаты наших 4 точек уже готовы, выглядят они так:
Проверяем касания наших точек
Для начала поймем как мы будем это делать.
Представим 2 блока, у которых есть свои точки:
Мы видим, что blocks[0].y2 находится в blocks[1].
Вначале будем проверять координаты X:
if (blocks[1].x1 <= blocks[0].x2) { // Если координата x1 второго блока меньше или равна координате x2 второго блока
if (blocks[0].x2 >= blocks[1].x1) { // Если координата x1 первого блока больше или равна координате x2 второго блока
// В этих условия мы проверяем блоки именно касаются друг друга или просто находятся рядом
console.log(true) // Если условия верны и блоки касаются по горизонтали, то мы выводим true
}
}
Подключим циклы для проверок всех блоков:
for (var i = 0; i < Object.keys(blocks).length; i++) {
for (var o = 0; o < Object.keys(blocks).length; o++) { // чтобы проверять блоки между собой
if (i != o) { // даем условия, чтобы блоки не сравнивали свои же координаты
if (blocks[i].x1 <= blocks[o].x2) { // наши условия
if (blocks[i].x2 >= blocks[o].x1) { // заменяем цифры на переменные
console.log(true)
}
}
}
}
}
Создаем еще одну функцию к которой подключим кнопку:
function getHitboxTouchResult() { // onclick нашей кнопки
setHitboxesAndCheck()
}
Мы видим, что в консоли (F12) выводится true если они касаются по координате X.
Если мы подставим красный блоки ниже синего, в таком случае все равно будет выводится true, надо исправлять.
Для этого дадим еще пару условий по координатам:
if (blocks[i].x1 <= blocks[o].x2) {
if (blocks[i].x2 >= blocks[o].x1) {
if (blocks[i].y1 <= blocks[o].y1) { // тут тоже самое, но в первом условии проверяем левые верхние углы между собой
if (blocks[i].y2 >= blocks[o].y1) {
console.log(true) // выводим true
}
}
}
}
Теперь мы можем полностью определить хитбокс по горизонтали и вертикали.
Чуть изменим нашу функцию, чтобы мы могли использовать ее в сравнениях.
Заменим:
console.log(true)
На:
return true
Также заменим в функции кнопки, простой вызов на:
console.log(setHitboxesAndCheck())
Для использования нескольких видов блоков, нужно будет лишь их указывать в сравнениях.
Весь код:
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hitboxes</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="mainBox">
<div class="block red"></div>
<div class="block green"></div>
<div class="block blue"></div>
</main>
<button class="btn" onclick="getHitboxTouchResult()">Check</button>
<script src="script.js"></script>
</body>
</html>
CSS
body {
margin: 0;
background-color: rgb(60, 60, 60);
}
.mainBox {
position: relative;
}
.block {
position: absolute;
}
.red {
background-color: rgba(255, 0, 0, 0.6);
width: 200px;
height: 200px;
left: 920px;
top: 320px;
}
.blue {
background-color: rgb(0, 0, 255, 0.6);
width: 250px;
height: 250px;
left: 690px;
top: 190px;
}
.green {
background-color: rgba(0, 255, 0, 0.6);
width: 100px;
height: 100px;
left: 640px;
top: 420px;
display: none;
}
.btn {
position: absolute;
right: 0;
bottom: 0;
width: 100px;
height: 40px;
}
JS
function setHitboxesAndCheck() {
blocks = document.querySelectorAll(".block")
for (var i = 0; i < Object.keys(blocks).length; i++) {
blocks[i].x1 = blocks[i].offsetLeft
blocks[i].x2 = blocks[i].offsetLeft + blocks[i].offsetWidth
blocks[i].y1 = blocks[i].offsetTop
blocks[i].y2 = blocks[i].offsetTop + blocks[i].offsetHeight
}
for (var i = 0; i < Object.keys(blocks).length; i++) {
for (var o = 0; o < Object.keys(blocks).length; o++) {
if (i != o) {
if (blocks[i].x1 <= blocks[o].x2) {
if (blocks[i].x2 >= blocks[o].x1) {
if(blocks[i].y1 <= blocks[o].y1) {
if(blocks[i].y2 >= blocks[o].y1) {
return true
}
}
}
}
}
}
}
}
function getHitboxTouchResult() {
console.log(setHitboxesAndCheck())
}
Спасибо за прочтение данного поста.
Буду благодарен, если посетите мой телеграм канал: https://t.me/blg_projects
Комментарии (9)
k12th
27.09.2022 14:27А чего не через getBoundingClientRect?
Samogon4ik Автор
27.09.2022 14:48-2Можно и через него.Тут уже сам выбираешь, через что получать значения
Metotron0
27.09.2022 14:50Ожидал, что будет учтён transform: rotate, потому что без него тут пробле не видно. Понятно, что и с поворотами давно уже всё решено, но хотя бы на школьном уровне решение можно было представить.
wdhappyk
27.09.2022 22:19+3Данный код не будет работать корректно, если элементы будут находится в разных контейнерах с position != static, т.к. координаты будут рассчитываться уже относительно родителя, а не документа.
https://stackblitz.com/edit/angular-ivy-hbzpmj?file=src%2Fapp%2Fapp.component.html,src%2Fapp%2Fapp.component.css,src%2Fstyles.css
Посмотрите на данный пример. Изменение у .item position со static на relative изменяет offset-ы дочернего элемента.Samogon4ik Автор
27.09.2022 22:22+2К сожалению я это не учел.Как я понимаю стоило использовать getBoundingClientRect
Закреплю дабы люди знали о подобной проблеме
bazrik
27.09.2022 22:20+1А через Intersection Observer разве нельзя это сделать?
Metotron0
28.09.2022 05:32По-моему, там есть какое-то условие по вложенности пересекающихся элементов. То ли у них общий родитель, то ли что-то похожее. Пишут, что элемент, за которым следим, должен лежать в root, означает ли это что можно отслеживать только пересечение элементов, один из которых потомок другого?
wdhappyk
Данный код не будет работать корректно, если элементы будут находится в разных контейнерах с position != static, т.к. координаты будут рассчитываться уже относительно родителя, а не документа.
https://stackblitz.com/edit/angular-ivy-hbzpmj?file=src%2Fapp%2Fapp.component.html,src%2Fapp%2Fapp.component.css,src%2Fstyles.css
Посмотрите на данный пример. Изменение у .item position со static на relative изменяет offset-ы дочернего элемента.