Внимание! Данная статья рассчитана на людей, хотя бы на начальном (повыше Hello world'овца) уровне Love2D и Lua в целом. Остальных же прошу посетить
этот и
этот сайты
Всем привет. Хочу рассказать о важной вещи для создателей клонов Minecraft и Terraria (Ну или для тех, кто хочет использовать для карты тайлы, а не объекты) - это столкновения игрока с массивом. За чистоту кода не отвечаю, так что, если у вас есть свои более оптимальные варианты реализации, то буду рад =)
Сразу для сокращения кода введу пару переменных, которые являются ссылками на модули Love. Также подготовлю главные 3 функции любого проекта.
gr = love.graphics
kb = love.keyboard
m = math
function love.load()
end
function love.update(dt)
end
function love.draw()
end
function sign(n)
if n == 0 then
return 0
end
return n / math.abs(n)
end
function div(a, b)
return m.floor(a / b)
end
Для тех, кто сидит на GM или другом языке, но не на Lua функции sign и div (В GM это оператор, но в Lua его нет, поэтому функция) понятны. А для остальных поясню:
sign
возвращает знак числа (или как он верно называется) – если число меньше 0, то получаем -1, если больше, то 1, если 0, то его и получаем.
div
это целочисленное деление, т.е. без дробной части. Так как нет стандартного оператора типа div, а он тут потребуется, то я решил написать свой, но в виде функции.
Сначала сделаем поле, по которому будет бегать наш “игрок”. генерация простая – сверху-донизу поле забивается нулями, но если высота ниже заданной, то забиваются в случайном порядке единицы. Так же зададим переменную размера блока
Все это в love.load
field = {}
for x = 0, 20 do
field[x] = {}
for y = 0, 20 do
field[x][y] = 0
if y > 10 and math.random(3) < 3 then
field[x][y] = 1
end
end
end
bsize = 32
Далее создадим самого игрока и зададим ему переменные координат, ширины и высоты, скорости
bsize = 32
player = {}
player.x = 300
player.y = 100
player.w = 20
player.h = 30
player.vsp = 0
player.hsp = 0
Так как мой способ не идеальный (есть 1 баг), то лучше игрока сделать меньше блока, а то он может пролететь сквозь него.
C load закончили. Переходим к update. Для начала просто зададим увеличение вертикальной скорости игрока, смены горизонтальной скорости по нажатию “A” и “D” и рестарта по нажатию “R”:
if love.keyboard.isDown('r') then love.load() end
player.vsp = player.vsp + 500 * dt
if kb.isDown('a') then
player.hsp = player.hsp - 500 * dt
elseif kb.isDown('d') then
player.hsp = player.hsp + 500 * dt
end
А дальше самое главное – сами движения с столкновения. Сначала я опишу принцип работы:
Что нам нужно, чтобы столкнуть 2 объекта? Проверить их коордитаты. Тоже самое используется для столкновение “объект – массив”, но тут это чуть сложнее, так как нам нужно преобразовывать координаты игрока, чтобы можно было их сравнить с определенной ячейкой нашей карты. На картинке приблизительный вариант:

Как, надеюсь, видно на картинке, мы проверяем 4 крайние точки игрока.
Проверяемая точка, координаты которой нацело поделены на размер блока, равна индексу блока, в котором находится эта уже поделенная координата. Надеюсь, понятно объяснил =)
Теперь же, нам надо реализовать это в коде. Но мы же хотим сделать еще и перемещение, а не только столкновения. Причем желательно эти движения сделать точными, без заездов в блоки. Это можно сделать следующим способом: Ставим цикл, на количество шагов, равное модулю скорости. И уже в этом цикле, проверять столкновения и в зависимости от них смещать игрока на dt (так как если смещать на 1, то это будет слишком быстро). Добавляем в love.update
for i = 1, m.abs(player.vsp) do
pvsp = sign(player.vsp)
px = div(player.x, bsize)
pxw = div(player.x + player.w, bsize)
py = div(player.y + pvsp, bsize)
pyw = div(player.y + player.h + pvsp, bsize)
if field[ px ][ pyw ] == 0 and
field[ pxw ][ pyw ] == 0 and
field[ px ][ py ] == 0 and
field[ pxw ][ py ] == 0 then
player.y = player.y + pvsp * dt
else
player.vsp = 0
if kb.isDown('w') then player.vsp = -200 end
end
end
for i = 1, m.abs(player.hsp) do
phsp = sign(player.hsp)
px = div(player.x + phsp, bsize)
pxw = div(player.x + player.w + phsp, bsize)
py = div(player.y, bsize)
pyw = div(player.y + player.h, bsize)
if field[ px ][ py ] == 0 and
field[ pxw ][ py ] == 0 and
field[ px ][ pyw ] == 0 and
field[ pxw ][ pyw ] == 0 then
player.x = player.x + phsp * dt
else
player.hsp = 0
end
end
Как вы видите, тут мы и использовали наши функции sign и div. Объясняю.
Вертикальное перемещение: Мы проверяем 4 вершины игрока со смещением в 1 пиксель в сторону вертикальной скорости, и если на деленных координатах вершины чисто, то двигаем игрока, если нет – останавливаем его и даем ему возможность прыгнуть при нажатии “W”.
Горизонтальное: Все тоже самое, только горизонтально и без прыжка.
Забыл сказать – если выберетесь за пределы массива, то игра вылетит с ошибкой. Тут надо делать либо проверки, либо ставить стенки.
Ну вот и все, наш герой бегает и прыгает по массиву! Только.. с прыжком есть 1 баг, вернее, недочет: Даже если игрок столкнулся с блоком сверху, а не снизу, то он может прыгнуть. Это дает возможность лазать по потолкам. Давайте считать это фичей =D
А… забыл еще одно: рисование =) тут все просто. Рисуем массив и игрока:
function love.draw()
for x = 0, 20 do
for y = 0, 20 do
if field[x][y] == 1 then
gr.rectangle('line', x * bsize, y * bsize, bsize, bsize)
end
end
end
gr.rectangle('fill', player.x, player.y, player.w, player.h)
end
Вот и все! Вот полный листинг кода статьи:
gr = love.graphics
kb = love.keyboard
m = math
function love.load()
field = {}
for x = 0, 20 do
field[x] = {}
for y = 0, 20 do
field[x][y] = 0
if y > 10 and math.random(3) < 3 then
field[x][y] = 1
end
end
end
bsize = 32
player = {}
player.x = 300
player.y = 100
player.w = 20
player.h = 30
player.vsp = 0
player.hsp = 0
end
function love.update(dt)
if love.keyboard.isDown('r') then love.load() end
player.vsp = player.vsp + 500 * dt
if kb.isDown('a') then
player.hsp = player.hsp - 500 * dt
elseif kb.isDown('d') then
player.hsp = player.hsp + 500 * dt
end
for i = 1, m.abs(player.vsp) do
pvsp = sign(player.vsp)
px = div(player.x, bsize)
pxw = div(player.x + player.w, bsize)
py = div(player.y + pvsp, bsize)
pyw = div(player.y + player.h + pvsp, bsize)
if field[ px ][ pyw ] == 0 and
field[ pxw ][ pyw ] == 0 and
field[ px ][ py ] == 0 and
field[ pxw ][ py ] == 0 then
player.y = player.y + pvsp * dt
else
player.vsp = 0
if kb.isDown('w') then player.vsp = -200 end
end
end
for i = 1, m.abs(player.hsp) do
phsp = sign(player.hsp)
px = div(player.x + phsp, bsize)
pxw = div(player.x + player.w + phsp, bsize)
py = div(player.y, bsize)
pyw = div(player.y + player.h, bsize)
if field[ px ][ py ] == 0 and
field[ pxw ][ py ] == 0 and
field[ px ][ pyw ] == 0 and
field[ pxw ][ pyw ] == 0 then
player.x = player.x + phsp * dt
else
player.hsp = 0
end
end
end
function love.draw()
for x = 0, 20 do
for y = 0, 20 do
if field[x][y] == 1 then
gr.rectangle('line', x * bsize, y * bsize, bsize, bsize)
end
end
end
gr.rectangle('fill', player.x, player.y, player.w, player.h)
end
function sign(n)
if n == 0 then
return 0
elseif n < 0 then
return -1
elseif n > 0 then
return 1
end
end
function div(a, b)
return m.floor(a / b)
end
Ну и, конечно, вы можете скачать
.love примерВсем спасибо за внимание.