quick-cocos2dx 組件管系統初探

    第一次遇見組件系統,我想大家基本都是跟着教程學習中提到的狀態機吧!

cc.GameObject.extend(self.fsm)
	:addComponent("components.behavior.StateMachine")
	:exportMethods()

    作者剛看到這種使用方式,覺得很困惑,今天終於去走了一遍源碼。

    在此先感謝這個大神寫的文章:quick-cocos2dx 組件管理器

    接觸過unity3D的都知道,unity裏的對象,都是由一個空的gameObject附加上相應的組件構成的。quick-lua裏也有這種功能。它是由Registry.lua,GameObject.lua和Component.lua協調完成的。如果想使用這套機制,只需要調一個函數:GameObject.extend(obj),這樣obj就能附加組件了。如果相用自定義組件,則要自定義Component的派生類。

    下面的代碼註釋,以添加狀態機組件爲例,假設target爲精靈,component爲狀態機。這樣描述相對形象點,沒有那麼抽象,方便理解。

    GameObject:負責爲target對象拓展功能,使其能夠附加各種組件 

function GameObject.extend(target)
    target.components_ = {}	--精靈添加一個組件集合表

    function target:checkComponent(name)
        return self.components_[name] ~= nil
    end

    function target:addComponent(name)
        local component = Registry.newObject(name)  --創建一個狀態機組件
        self.components_[name] = component	    --精靈的組件表保存狀態機組件
        component:bind_(self)                       --將狀態機綁到精靈上 
        return component
    end

    function target:removeComponent(name)
        local component = self.components_[name]
        if component then component:unbind_() end
        self.components_[name] = nil
    end

    function target:getComponent(name)
        return self.components_[name]
    end

    return target
end

    Registry:此類用於管理所有組件類。相當於一張註冊表,所有的組件都要在這張表中註冊。當然

不用去顯示地註冊,newObject會根據組件的名字,自動加載並註冊組件

function Registry.newObject(name, ...)
    local cls = Registry.classes_[name]
    if not cls then
        -- auto load
        pcall(function()
            cls = require(name)	        --加載狀態機模塊
            Registry.add(cls, name)	--對狀態機組件進行保存記錄
        end)
    end
    return cls.new(...)	--創建一個狀態機對象
end
function Registry.add(cls, name)
    if not name then name = cls.__cname end
    Registry.classes_[name] = cls
end

    Component:組件基類,所有組件都要派生自它

-- 構造函數,
-- name爲組件名稱
-- depends爲該組件需要依賴於哪些組件,
-- 當精靈附加狀態機時,會自動爲且附加所有的depends組件  
function Component:ctor(name, depends)
    self.name_ = name
    self.depends_ = checktable(depends)
end

-- 將狀態機綁到精靈上 
function Component:bind_(target)
    self.target_ = target
    for _, name in ipairs(self.depends_) do
        if not target:checkComponent(name) then
            target:addComponent(name)
        end
    end
    self:onBind_(target)
end

     我挖到這裏,對這個depends很有興趣,然後輸出了下,結果發現目前depends目前爲nil,應該是爲了後續的擴展做好準備,所以就沒再糾結下去了!

    wKioL1RtYWah9Hh1AAAvpIwpfT8215.jpg  

-- 將該組件某些的方法導出到target上
-- methods爲字符串數組,字符串名字就是函數名。
-- 有了這套機制,在調用addcomponent後接着調用此函數,
-- 則以後想使用該組件的功能,直接通過target就能調用,無需先獲取組件,再調用函數 
function Component:exportMethods_(methods)
    self.exportedMethods_ = methods    
    local target = self.target_
    local com = self
    for _, key in ipairs(methods) do    -- 遍歷函數名集合
        if not target[key] then         -- 如果精靈對象有了該函數,就不精靈進行添加
            local m = com[key]
            target[key] = function(__, ...)
                return m(com, ...)
            end
        end
    end
    return self
end

    這個exportMethods_()函數是我之前一直不明白的,現在我解釋下。函數中的self是狀態機,還記得在addComponent函數裏有一句component:bind_(self)嗎?在bind_()函數裏self.target_ = target這句就把狀態機綁定到了精靈上。

    在exportMethods_()裏,target = self.target_這句就獲取到了精靈對象,com = self之後,com就爲狀態機。在遍歷判斷,說明精靈對象可以覆蓋掉狀態機裏函數(不是真正意義上的覆蓋,只是無法直接調用到狀態機的,可以通過getComponent()函數獲取狀態機,然後調用)。

target[key] = function(__, ...)
    return m(com, ...)
end

    以上代碼就把狀態機裏的函數導出到了精靈上,以後使用狀態機的函數,就不要獲取狀態機了,可以直接調用。大家可能對m(com, ...)這句有點遺憾,這是lua的語法。如果在函數內用到self,那函數參數要傳入調用函數的對象實例。這裏m是類成員函數,所以需要傳入對象實例,因爲函數裏沒用到self傳入self也不影響。大家可以試驗下,以下代碼把函數前2句註釋和不註釋看下效果就明白了!

a = {}
function a:say()
	self.x = 5
	print(self.x)
	print("Hello")
end

b = a["say"]
b(a)
b()

    有時候大家可能看到以下代碼:

cc(self):addComponent("components.behavior.StateMachine")
	:exportMethods()

    這個效果一樣的,因爲quick把在初始化的時候 cc = cc.GameObject.extend()。

    暫時就這麼多了,以後如果對組件還有深入研究會來補充的!

    如有不足之處,請大家指出,一起交流成長!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章