Perlanggaran
Kita perlu boleh melanggar dinding

Yang bergerak akan berlanggar
Sekarang setelah ada entity yang bergerak, timbul satu ‘masalah’. Mereka berjalan menembusi satu sama lain seperti hantu 👻.
Ada pelbagai cara untuk melakukannya, dan mungkin di sini saya tersilap menimbang, iaitu tentang ‘kelajuan’. Setiap pengiraan mengambil masa dan anda mahu ia serendah mungkin. Keputusan pertama yang perlu saya buat ialah sama ada menggunakan pengiraan poligon atau segi empat ringkas (rect).
Kemas kini 2025: dengan pengalaman yang saya ada sekarang, saya hanya akan menggunakan poligon.
Pilihan saya ialah rect, kerana saya hanya ada bentuk yang ringkas. Apabila saya ada imej lutsinar sesuatu watak, ia mempunyai ketinggian dan lebar maksimum, dan itulah keseluruhan segi empat yang tidak boleh ditembusi.
Engine3 telah diperluas dengan fungsi asas 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
Kemudian sesuatu entity menerima maklumat tentang cara berinteraksi dengan entity lain.
Yang lucunya, saya cepat sedar bahawa ia memang sangat pantas, tetapi juga terlalu terhad. Kerana anda juga boleh mempunyai bentuk bulatan atau bentuk ‘pelik’ lain. Jika anda contohnya menggerakkan kursor tetikus di atas objek sebegitu, ia sudah menunjukkan apa objek itu walaupun di luar bulatan (yang dikelilingi rect tidak kelihatan).
Oleh itu sesuatu entity kini menggunakan poligon untuk perlanggaran dengan kursor dan rect untuk pergerakan.
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)
Ikut laluan
Sekarang semua entity mempunyai data yang diperlukan, kita boleh 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
Apabila anda mengklik di mana-mana pada skrin, peta akan disemak untuk melihat ke mana pemain boleh berjalan. Peta menggunakan fungsi di atas dan sebahagiannya disimpan dalam cache.
local path = aStar()
if path then
path = self:optimizePath(path)
path = self:smoothPath(path, map)
return {targetX = targetX, targetY = targetY, path = path}
end
Oleh sebab saya tidak mahu semua koordinat x,y, tetapi hanya saat-saat apabila entity perlu bertukar kedudukan, saya juga menggunakan optimizePath dan smoothPath, seperti yang anda 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
}
Akhir kata
Satu kesilapan yang pernah saya buat ialah mahu mengendalikan ini dalam Love2d melalui thread latar belakang. Idea saya ialah ia akan jadi lebih pantas. Hasilnya memang lebih pantas, tetapi tidak lagi sepadan dengan apa yang anda nampak pada skrin, malah anda nampak ‘kelewatan’.
Kemas kini 2025 Walaupun di atas nampak ringkas, sistem ini akan melalui beberapa refactor dan saya akan beralih sebahagiannya kepada walkmap. Ia lapisan ringkas dalam Photoshop berwarna hitam dan putih, di mana hitam boleh dilalui dan putih tidak.