第一次遇見組件系統,我想大家基本都是跟着教程學習中提到的狀態機吧!
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,應該是爲了後續的擴展做好準備,所以就沒再糾結下去了!
-- 將該組件某些的方法導出到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()。
暫時就這麼多了,以後如果對組件還有深入研究會來補充的!
如有不足之處,請大家指出,一起交流成長!