Semuanya ialah objek

Semuanya hidup dalam bentuk objek.

Sangat banyak objek

Objek

Saya suka bekerja dengan objek, anda boleh menyimpan semua maklumat yang diperlukan kemudian di dalamnya. Lua tidak mempunyai sistem kelas seperti yang saya kenal dalam bahasa lain. Apa yang saya perlukan ialah sesuatu yang membolehkan kelas ditindih atau diperluas kemudian. Maksud saya dari sudut permainan yang menggunakan engine3.

Idea di sebaliknya ialah engine3 boleh dilancarkan supaya semua orang boleh menggunakannya. Permainan itu sendiri boleh kekal peribadi. Kedua-duanya wujud bersebelahan dan saling melengkapi. Selepas beberapa ujian, ia berjaya, dan kini objek boleh dicipta berdasarkan kelas.

Contoh kelas, engine3/core/camera.lua (versi terhad).

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

Katakan kamera dalam permainan perlu berkelakuan sedikit berbeza daripada kamera lalai engine3. Maka saya membuat fail game/framework/core/camera.lua dan merujuk kepada kelas camera engine3. Kemudian saya membuat perubahan saya.

---@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

Seronok ada semua objek yang berbeza ini (mulai sekarang saya panggil objek sebagai entity), tetapi semuanya perlu boleh dicari dan digunakan di seluruh kod. Jadi perlu dibuat sesuatu untuk mengurus semuanya.

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

Ini sebenarnya jantung permainan, atau salah satu daripada banyak bahagian penting. Jika ada yang tidak kena di sini, anda ada masalah…

Apabila sesuatu entity dicipta, ia akan ditambah ke Entity Manager dan mungkin dibuang semula. Dalam kelas camera di atas anda sudah nampak bahawa ini dilakukan dengan baris di bawah.

entityManager:add(self)

Setiap kelas yang saya tulis ada baris itu, supaya ia menambah dirinya sendiri melalui entityManager.

Dalam kelas anda juga boleh membuat fungsi release, yang dijalankan secara automatik apabila entityManager membuang objek itu. Ini sangat penting untuk mengawal memori. Anda tidak mahu menyimpan semua aset seperti imej, tetapi mengeluarkannya daripada memori pada satu ketika.

function Camera:release()
    -- do things when the entityManger delete this object
end

Penggunaan

Apabila saya ada kelas baharu di mana-mana dan memerlukan entity, contohnya kamera, ia boleh dilakukan dengan beberapa cara.

local cameras = entityManager:find(nil,"Camera")

Namun ini akan memulangkan jadual dengan semua Entity yang dijumpai jenis Camera (kelas). Ini tidak praktikal kerana dalam kes ini sentiasa ada 1 kamera, dan saya mahu 100% pasti bahawa Camera memang wujud.

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

Ini ialah pembalut di sekeliling find, dan memberi ralat apabila objek tidak wujud.

Beberapa contoh lain ialah

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})

Akhir kata

Semua perkara yang saya cari berkaitan kelas dan objek kini sudah dipenuhi

  • kelas
  • kelas boleh diperluas
  • kelas boleh ditindih
  • entity dikawal oleh manager
  • entity boleh saya gunakan di mana-mana

Sangat penting untuk membuat pilihan ini secepat mungkin dan menjangka bahawa kod akhir masih akan banyak berubah. Jika anda menyusun semuanya secara berstruktur, anda akan mendapat banyak manfaat kemudian.

Jika Lua tidak lagi mampu memproses bilangan entity, bahagian ini boleh ditukar kepada C!