Va chạm
Chúng ta phải đụng vào tường được

Thứ gì di chuyển thì sẽ va chạm
Giờ đã có những entity di chuyển, và một ‘vấn đề’ xuất hiện. Chúng đi xuyên qua nhau như một bóng ma 👻.
Có nhiều cách để xử lý việc này, và có lẽ ở đây tôi đã cân nhắc sai, đó là về ’tốc độ’. Mỗi phép tính đều tốn thời gian và bạn muốn giữ nó ở mức thấp nhất. Quyết định đầu tiên tôi phải đưa ra là dùng phép tính đa giác hay hình chữ nhật đơn giản (rect).
Cập nhật 2025: với kinh nghiệm hiện giờ, tôi sẽ chỉ dùng đa giác.
Tôi chọn rect, vì tôi chỉ có những hình dạng đơn giản. Khi tôi có một ảnh trong suốt của một nhân vật, nó có chiều cao và chiều rộng tối đa, và đó là toàn bộ hình chữ nhật mà không ai được đi xuyên qua.
Engine3 đã được mở rộng với các hàm cơ bản sau
---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
Sau đó một entity sẽ nhận thông tin về cách xử lý với các entity khác.
Điều buồn cười là tôi nhanh chóng nhận ra rằng cách này đúng là rất nhanh, nhưng cũng quá hạn chế. Vì bạn còn có thể có hình tròn hoặc những hình ‘kỳ quặc’ khác. Ví dụ khi bạn di chuột qua một vật như vậy, nó đã hiện ra đó là gì ngay cả khi con trỏ còn ở ngoài hình tròn (bên ngoài có rect vô hình bao quanh).
Vì vậy giờ đây một entity dùng đa giác cho va chạm với con trỏ và rect cho chuyển động.
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)
Đi theo đường
Giờ mọi entity đã có dữ liệu cần thiết, ta có thể dùng aStar để xác định entity được phép đi đâu.
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
Khi bạn nhấp vào đâu đó trên màn hình, bản đồ sẽ được kiểm tra xem người chơi được đi đâu. Bản đồ dùng hàm ở trên và một phần được lưu trong bộ nhớ đệm.
local path = aStar()
if path then
path = self:optimizePath(path)
path = self:smoothPath(path, map)
return {targetX = targetX, targetY = targetY, path = path}
end
Vì tôi không muốn tất cả tọa độ x,y mà chỉ những thời điểm entity cần đổi vị trí, tôi còn dùng optimizePath và smoothPath, như bạn thấy bên dưới.
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
}
Lời kết
Một sai lầm tôi từng mắc là muốn xử lý việc này trong Love2d bằng một luồng chạy nền. Tôi nghĩ như vậy sẽ nhanh hơn. Kết quả là nó đúng là nhanh hơn, nhưng không còn khớp với những gì bạn thấy trên màn hình, mà ngược lại bạn còn thấy một độ ’trễ’.
Cập nhật 2025 Dù ở trên trông có vẻ đơn giản, hệ thống này sẽ còn được tái cấu trúc nhiều lần và tôi sẽ chuyển một phần sang walkmap. Đó là một lớp đơn giản trong Photoshop với màu đen và trắng, trong đó màu đen là đi được và màu trắng thì không.