Tabrakan
Kita harus bisa menabrak dinding

Yang bergerak akan bertabrakan
Sekarang setelah ada entity yang bergerak, muncul sebuah ‘masalah’. Mereka berjalan menembus satu sama lain seperti hantu 👻.
Ada beberapa cara untuk mengatasinya, dan mungkin di sini saya salah menimbang, yaitu soal ‘kecepatan’. Setiap perhitungan butuh waktu dan kamu ingin menekannya serendah mungkin. Keputusan pertama yang harus saya ambil adalah memakai perhitungan poligon atau persegi sederhana (rect).
Update 2025: dengan pengalaman yang saya punya sekarang, saya hanya akan memakai poligon.
Pilihan saya jatuh pada rect, karena saya hanya punya bentuk sederhana. Kalau saya punya gambar transparan sebuah karakter, gambar itu punya tinggi dan lebar maksimal, dan itulah seluruh persegi yang tidak boleh ditembus.
Engine3 diperluas dengan fungsi dasar berikut
---Check if a point is inside the rect.
-- This function will include the border
---@param x integer
---@param y integer
---@param rect Rect The you want to use for the collision check
local function pointInRect(x, y, rect)
return x >= rect.x1 and x <= rect.x2 and y >= rect.y1 and y <= rect.y2
end
---@param x integer
---@param y integer
---@param rects table<Rect> The (collidable)Rects you want to use for the collision check
local function pointInRects(x, y, rects)
for _, rect in pairs(rects) do
if pointInRect(x, y, rect) then
return true
end
end
return false
end
--- Check if a point is inside the rect
-- https://2dengine.com/?p=intersections
---@param x integer X point coordinate
---@param y integer Y point coordinate
---@param l integer X left corner coordinate
---@param t integer Y top corner coordinate
---@param r integer X right corner coordinate
---@param b integer Y bottom corner coordinate
local function pointInAABB(x, y, l, t, r, b)
if x < l or x > r then
return false
end
if y < t or y > b then
return false
end
return true
end
---Check if two boxes overlap
-- https://love2d.org/wiki/BoundingBox.lua
---@param x1 integer X left corner coordinate box 1
---@param y1 integer Y top corner coordinate box 1
---@param w1 integer Width box 1
---@param h1 integer Height box 1
---@param x2 integer X left corner coordinate box 2
---@param y2 integer Y top corner coordinate box 2
---@param w2 integer Width box 2
---@param h2 integer Height box 2
local function rectsOverlap(x1, y1, w1, h1, x2, y2, w2, h2)
return x1 < x2 + w2 and x2 < x1 + w1 and y1 < y2 + h2 and y2 < y1 + h1
end
---Check if two boxes overlap using AABB
---@param x1a integer X left corner coordinate box 1
---@param y1a integer Y top corner coordinate box 1
---@param x2a integer X right corner coordinate box 1
---@param y2a integer Y bottom corner coordinate box 1
---@param x1b integer X left corner coordinate box 2
---@param y1b integer Y top corner coordinate box 2
---@param x2b integer X right corner coordinate box 2
---@param y2b integer Y bottom corner coordinate box 2
local function AABBVsAABB(x1a, y1a, x2a, y2a, x1b, y1b, x2b, y2b)
if x1a > x2b or x1b > x2a then
return false
end
if y1a > y2b or y1b > y2a then
return false
end
return true
end
---Check if two boxes overlap using AABB using width and height
---@param o1x integer
---@param o1y integer
---@param o1w integer
---@param o1h integer
---@param o2x integer
---@param o2y integer
---@param o2w integer
---@param o2h integer
local function AABBWHVsAABBWH(o1x, o1y, o1w, o1h, o2x, o2y, o2w, o2h)
return not ((o2x > o1x + o1w) or (o2x + o2w < o1x) or (o2y > o1y + o1h) or (o2y + o2h < o1y))
end
Lalu sebuah entity mendapat informasi tentang bagaimana harus berinteraksi dengan entity lain.
Lucunya, saya cepat sadar bahwa cara ini memang sangat cepat, tapi juga terlalu terbatas. Karena kamu juga bisa punya bentuk lingkaran atau bentuk ‘aneh’ lainnya. Kalau kamu misalnya mengarahkan kursor mouse ke objek seperti itu, di luar lingkarannya (yang dikelilingi rect tak terlihat) sudah muncul keterangan benda apa itu.
Karena itu sekarang sebuah entity memakai poligon untuk tabrakan dengan kursor dan rect untuk gerakan.
local Collision = require "lib.engine3.src.framework.helper.math.collision"
---@field collidableA? table {1[opt](left),2([opt]right),3([opt]top),4([opt]bottom)}
---@field collidableB? table {1[opt](left),2([opt]right),3([opt]top),4([opt]bottom)}
---@field collidableRects? table Collidable rects [{x1,y1,x2,y2}]
function Frameset:new(data)
Ikuti jalurnya
Sekarang semua entity punya data yang dibutuhkan, jadi kita bisa menentukan dengan aStar ke mana entity boleh berjalan.
function PathFinding:isPointInCollision(collisionRectsTable, x, y)
for _, collisionRectList in pairs(collisionRectsTable) do
for _, rect in pairs(collisionRectList) do
if Collision.pointInRect(x, y, rect) then
return true
end
end
end
return false
end
Saat kamu mengklik di suatu tempat di layar, akan dicek di peta ke mana pemain boleh berjalan. Peta memakai fungsi di atas dan sebagian disimpan di cache.
local path = aStar()
if path then
path = self:optimizePath(path)
path = self:smoothPath(path, map)
return {targetX = targetX, targetY = targetY, path = path}
end
Karena saya tidak mau semua koordinat x,y, melainkan hanya saat-saat ketika entity harus pindah posisi, saya juga memakai optimizePath dan smoothPath, seperti yang bisa kamu lihat di bawah.
path {
x = 1, y = 1
x = 2, y = 1
x = 3, y = 1
x = 4, y = 1
x = 5, y = 1
x = 6, y = 1
}
path {
x = 1, y = 1
x = 6, y = 1
}
Penutup
Satu kesalahan yang pernah saya buat adalah ingin menangani ini di Love2d lewat thread di latar belakang. Pikir saya, dengan begitu akan lebih cepat. Hasilnya memang lebih cepat, tapi tidak lagi sesuai dengan yang terlihat di layar, justru yang terlihat adalah ‘keterlambatan’.
Update 2025 Meskipun di atas terlihat sederhana, sistem ini akan mengalami beberapa refactor dan sebagian akan saya ganti dengan walkmap. Itu adalah layer sederhana di Photoshop berwarna hitam dan putih, di mana hitam bisa dilewati dan putih tidak.