Mọi thứ đều là đối tượng
Mọi thứ trở nên sống động dưới dạng đối tượng.

Đối tượng
Tôi thích làm việc với đối tượng, bạn có thể đặt vào đó mọi thông tin mình sẽ cần sau này. Lua không có hệ thống lớp như tôi biết ở các ngôn ngữ khác. Tôi cần một thứ cho phép sau này ghi đè hoặc mở rộng các lớp. Ý tôi là nhìn từ phía trò chơi sử dụng engine3.
Ý tưởng đằng sau là engine3 có thể được phát hành để ai cũng dùng được. Còn trò chơi thì có thể giữ riêng tư. Hai thứ tồn tại song song và bổ sung cho nhau. Sau vài lần thử nghiệm, tôi đã làm được, và giờ có thể tạo đối tượng từ các lớp.
Một ví dụ về lớp, engine3/core/camera.lua (phiên bản rút gọn).
local Callback = require "lib.engine3.src.framework.core.callback"
---@class Camera: Object
---@field config Config
---@field gameCanvas Canvas
---@field x integer
---@field y integer
---@field width integer
---@field height integer
---@field followSpeed integer
---@field targetX integer
---@field targetY integer
---@field bound boolean
---@field bounds_min_x integer
---@field bounds_min_y integer
---@field bounds_max_x integer
---@field bounds_max_y integer
---@field callbackAfterStop function
---@field callback Callback
---@field super self
---@overload fun(data: CameraOptions):Camera
local Camera = Object:extend("Camera")
Camera:implement(Callback)
---@class CameraOptions
---@field x? integer Default 0
---@field y? integer Default 0
---@field width? integer Default canvas width
---@field height? integer Default canvas height
---@field followSpeed integer Default 1
function Camera:new(data)
if data == nil then
data = {}
end
self.config = entityManager:findOne(nil, "Config")
self.gameCanvas = entityManager:findOne({name = "gameCanvas"}, "Canvas")
self.x = data.x or 0
self.y = data.y or 0
self.width = data.width or self.gameCanvas:getWidth()
self.height = data.height or self.gameCanvas:getHeight()
self.followSpeed = data.followSpeed or 1
self.targetX = nil
self.targetY = nil
self.bound = true
self.bounds_min_x = nil
self.bounds_min_y = nil
self.bounds_max_x = nil
self.bounds_max_y = nil
self.callbackAfterStop = {}
entityManager:add(self)
end
---@param x integer
---@param y integer
---@param w integer
---@param h integer
function Camera:setBounds(x, y, w, h)
self.bound = true
self.bounds_min_x = x
self.bounds_min_y = y
self.bounds_max_x = x + w
self.bounds_max_y = y + h
end
---@param dt number
function Camera:update(dt)
-- core things
end
function Camera:setBounds(x, y, w, h)
self.bound = true
self.bounds_min_x = x
self.bounds_min_y = y
self.bounds_max_x = x + w
self.bounds_max_y = y + h
end
Giả sử camera trong trò chơi cần hoạt động hơi khác camera mặc định của engine3. Khi đó tôi tạo tệp game/framework/core/camera.lua và tham chiếu tới lớp camera của engine3. Sau đó tôi thực hiện những thay đổi của mình.
---@class Camera: Object
---@field followEntity Actor|Animation
---@overload fun():Camera
local Camera = require("lib.engine3.src.framework.core.camera"):extend("Camera")
-- extra new function
---@return Actor|Animation
function Camera:getFollowEntity()
-- do things
return self.followEntity
end
-- overwrite the original
---@param dt number
function Camera:update(dt)
if self.followEntity then
-- do something
end
-- optional but possible, call the original camera update
Camera.super.update(self, dt)
end
Entity Manager
Có nhiều đối tượng khác nhau thì hay đấy (từ giờ tôi gọi đối tượng là entity), nhưng chúng phải được tìm thấy và sử dụng trong toàn bộ mã. Vì vậy cần có thứ gì đó để quản lý tất cả.
EntityManager:implement(Callback)
function EntityManager:new()
self.entities = {}
self.lastEntityId = 0
self.callbackAfterAdd = {}
self.callbackAfterDelete = {}
end
function EntityManager:createId()
end
function EntityManager:add(entities)
end
function EntityManager:del(entity, args)
...
if entity.release then
entity:release(args)
end
...
end
function EntityManager:delAll(args)
end
function EntityManager:getEntityIndex(entity)
end
function EntityManager:find(filter, className, enitities)
end
function EntityManager:findOne(filter, className, entities)
end
function EntityManager:filter(data, filter)
end
Đây thực ra là trái tim của trò chơi, hoặc một trong nhiều phần quan trọng. Nếu có gì sai ở đây, bạn sẽ gặp rắc rối…
Khi một entity được tạo, nó sẽ được thêm vào Entity Manager và có thể bị xóa sau đó. Trong lớp camera ở trên, bạn đã thấy việc này được làm bằng dòng bên dưới.
entityManager:add(self)
Mọi lớp tôi viết đều có dòng đó, để nó tự thêm mình qua entityManager.
Trong lớp, bạn cũng có thể tạo hàm release, được chạy tự động khi entityManager xóa đối tượng. Điều này rất quan trọng để kiểm soát bộ nhớ. Bạn không muốn giữ lại mọi tài nguyên như hình ảnh, mà đến lúc nào đó phải giải phóng chúng khỏi bộ nhớ.
function Camera:release()
-- do things when the entityManger delete this object
end
Cách dùng
Khi tôi có một lớp mới ở đâu đó và cần một entity, ví dụ camera, có thể làm theo nhiều cách.
local cameras = entityManager:find(nil,"Camera")
Tuy nhiên cách này trả về một bảng chứa mọi Entity tìm thấy thuộc loại Camera (lớp). Như vậy không tiện, vì trong trường hợp này luôn chỉ có 1 camera, và tôi muốn chắc chắn 100% là Camera có tồn tại.
local camera = entityManager:findOne(nil, "Camera")
function EntityManager:findOne(filter, className, entities)
local objects = self:find(filter, className, entities)
if #objects == 1 then
return objects[1]
elseif #objects > 1 then
error("Cannot get exactly one object, because it was found (" .. #objects .. ") times !")
end
if className ~= nil then
error("findOne() object does not exist :" .. className)
else
dump(filter)
error("findOne() Cannot find object")
end
end
Đây là lớp bao quanh find, và sẽ báo lỗi khi đối tượng không tồn tại.
Một vài ví dụ khác là
local entities = entityManager:find({name = "bird", visible = true}, "Frameset")
local entities = entityManager:find({name = "bird", visible = true}, "Frameset", customEntities)
-- special magic
local entities = entityManager:find({createdBy = Item}, "Frameset")
-- special magic
local entities = entityManager:find({collidableA = "CONTAINS_DATA"}, "Frameset")
-- special magic
local entities = entityManager:find({isObject = require "lib.engine3.src.framework.core.canvas", excludeEntities = self.canvases})
Lời kết
Mọi điểm tôi tìm kiếm về lớp và đối tượng giờ đã được đáp ứng
- lớp
- lớp có thể mở rộng
- lớp có thể ghi đè
- entity được một manager kiểm soát
- entity có thể dùng ở mọi nơi
Rất quan trọng là phải đưa ra lựa chọn này càng sớm càng tốt và chấp nhận rằng mã cuối cùng sẽ còn thay đổi nhiều. Nếu bạn thiết lập mọi thứ có cấu trúc, sau này bạn sẽ được lợi rất nhiều.
Nếu Lua không còn xử lý nổi số lượng entity, có thể chuyển phần này sang C!