在cocos2d-js實現自動綁定cocostudioUI控制項與事件,cocos2djs事件

來源:互聯網
上載者:User

在cocos2d-js實現自動綁定cocostudioUI控制項與事件,cocos2djs事件

一.起因

        在用戶端遊戲開發中最讓人噁心的工作就是UI相關的東西,雖然有了像cocostudio這樣的視覺化檢視,但介面中有大量需要由代碼訪問的控制項的時候,需要寫太多重複的代碼例如:

    //載入UI設定檔    var root = ccs.uiReader.widgetFromJsonFile("res/cocosui/UIEditorTest/UIButton_Editor/UIButton_Editor_1.json");    this._mainNode.addChild(root);    //查詢back控制項,並添事件響應    var back_label = ccui.helper.seekWidgetByName(root, "back");    back_label.addTouchEventListener(this.backEvent,this);    //查詢Button_123控制項,並添事件響應    var button = root.getChildByName(root, "Button_123");    button.addTouchEventListener(this.touchEvent,this);</span>
                   上面是最為直接存取控制項的方法。問題在於如果一個UI介面中有十個、二十個甚至更多的UI控制項需要操作的是候,我們做UI的介面、邏輯開發的同學們是否還有時間愉快的玩耍?有沒有什麼方法不需要去手寫這些代碼,就能愉快的訪問UI組件與接收UI事件響應呢?
二. 思考
        對於手機遊戲,特別是卡牌類的遊戲來說70%~80%的用戶端工作量是在UI布局與邏輯上。       上面的seekWidgetByName、getChildByName、addTouchEventListener函數將大量充斥在用戶端代碼中,濫竽充數著我們的程式碼數。
       自己曾經有過Qt的開發經驗。Qt中也有自己的UI設計工具,產生xml的ui設定檔. 對於這個xml有兩種使用方式:       第一種方式: 使用Qt自己的編譯工具,將xml翻譯產生一個c++代碼檔案,代碼內容就是根據xml中的資訊建立各種控制項,設定座標\屬性\事件等。       第二種方式: 在程式中,將xml檔案使用UILoader工具類載入進來,成為一個節點。然後調用Qt的函數實現訊號/槽的自動關聯。實現訊號/槽(事件)自動綁定的原理,是要求寫一個事件處理函數,格式為:  
    void on_控制項名_訊號名(參數);  
      具體如何使用就不細說了,有興趣的朋友可以自己去看看。根據Qt的這個功能提示,我們何嘗不可以在cocos2d-js自動綁定coccostudio輸出的ui檔案呢?
三. 名命約定
1. 代碼名命約定
根據cocos2d-js代碼風格,我們約定:        (1)類成員變數以底線 "_"開頭後面接以駝峰名命格式的英文單詞。例如:_loginButton 、 _closeButton 、 _nameLabel        (2)類中的私人函數也使用同樣的方式。        例如:_onLoginButtonTouchBegan: function() { ...}
2. UI命名規範
       在cocostudioUI編輯器中,我們遵循上述代碼中成員變數的命名規範。將需要由代碼訪問的控制項取名為"_"開頭,後面接以駝峰名命格式的英文單詞。 請看:

3. ccui控制項事件命名
ccui.Widget事件註冊有兩種:1). 常規的touch事件有:ccui.Widget.TOUCH_BEGAN   觸摸開始 (按下)ccui.Widget.TOUCH_MOVED  觸摸移動 (移動)ccui.Widget.TOUCH_ENDED  觸摸結束 (抬起)
ccui.Widget.TOUCH_CANCELED  觸摸取消 (一般沒用)我們使用widget.addTouchEventListener(selector, target)給控制項註冊觸摸事件,設定回呼函數.
2). 控制項特殊事件:比如CheckBox的:ccui.CheckBox.EVENT_SELECTEDccui.CheckBox.EVENT_UNSELECTED又如:TextField的:ccui.TextField.EVENT_ATTACH_WITH_IMEccui.TextField.EVENT_DETACH_WITH_IMEccui.TextField.EVENT_INSERT_TEXTccui.TextField.EVENT_DELETE_BACKWARD這類事件需要使用widget.addEventListener(selector, target)來註冊。
這裡的selector就是我們的回呼函數,需要我們取名並實現這個函數,事件類型是通過參數來識別的:
ctor: function() {this._super();...button.addTouchEventListener(this._onButtonEvent, this);},_onButtonEvent: function(sender, type) {switch(type) {case ccui.Widget.TOUCH_BEGAN:...;return true;case ccui.Widget.TOUCH_MOVED:...;break;case ccui.Widget.TOUCH_ENDED:...;break;}}

這個"_onButtonEvent"就是我們為事件函數取的名字,如果我們按:【 首碼+控制項名(取掉底線)+事件名】給控制項事件函數取名舉例說明:控制項名字為:_button事件名則為:_onButtonTouchBegan、_onButtonTouchMoved、_onButtonTouchEnded
四. 代碼實現
有了上面的約定,我們可以開始實現UI的綁定了。1. 定義一個自動綁定的控制項列表,我們這裡列出了常用的控制項類型與事件名字。
//觸摸事件sz.UILoader.touchEvents = ["TouchBegan", "TouchMoved", "TouchEnded"];//控制項事件列表sz.UILoader.widgetEvents = [    //Button    {widgetType: ccui.Button, events: sz.UILoader.touchEvents},    //ImageView    {widgetType: ccui.ImageView, events: sz.UILoader.touchEvents},    //TextFiled    {widgetType: ccui.TextField, events: ["AttachWithIME", "DetachWithIME", "InsertText", "DeleteBackward"]},    //CheckBox    {widgetType: ccui.CheckBox, events: ["Selected", "Unselected"]},    //ListView    {widgetType: ccui.ListView, events:["SelectedItem"]},    //Panel    {widgetType: ccui.Layout, events: sz.UILoader.touchEvents},    //BMFont    {widgetType: ccui.TextBMFont, events: sz.UILoader.touchEvents},    //last must null    null];
這個sz.UILoader.widgetEvents數組可以根據需要自己添加需要綁定的組件。
2.邏輯流程1). 使用loader載入ui檔案並傳入target為當前layer。所有事件和控制項變數都將綁定到target上。2). 遍曆載入後的子節點(childNode),檢查名字首碼是否以”_”開頭。並且該節點類型是否在widgetEvents數組中。
3). 將childNode綁定到target上。
4). 提取childNode事件函數名,檢查target是否有這些函數存在。
5). 為widgetNode註冊事件響應。6). 載入類接收到事件響應,轉寄事件到對應的target的事件處理函數上。
3.UI載入類的具體實現
sz.UILoader = cc.Class.extend({    _eventPrefix: null,    _memberPrefix: null,    /**     * 載入UI檔案     * @param target將  jsonFile載入出的節點綁定到的目標     * @param jsonFile  cocostudio UI編輯器產生的json檔案     */    widgetFromJsonFile: function(target, jsonFile, options) {        cc.assert(target && jsonFile);        if (!options) {            options = {};        }        this._eventPrefix  =  options.eventPrefix || sz.UILoader.DEFAULT_EVENT_PREFIX;        this._memberPrefix = options.memberPrefix || sz.UILoader.DEFAULT_MEMBER_PREFIX;        var rootNode = ccs.uiReader.widgetFromJsonFile(jsonFile);        if (!rootNode) {            cc.log("Load json file failed");        }        target.rootNode = rootNode;        target.addChild(rootNode);        this._bindMenbers(rootNode, target);    },    /**     * 遞迴對rootWidget下的子節點進行成員綁定     * @param rootWidget     * @param target     * @private     */    _bindMenbers: function(rootWidget, target) {        var widgetName,            children = rootWidget.getChildren();        var self = this;        children.forEach(function(widget) {            widgetName = widget.getName();            //控制項名存在,綁定到target上            var prefix = widgetName.substr(0, self._memberPrefix.length);            if (prefix === self._memberPrefix) {                target[widgetName] = widget;                self._registerWidgetEvent(target, widget);            }            //綁定子控制項,可以實現: a._b._c._d 訪問子控制項            if (!rootWidget[widgetName]) {                rootWidget[widgetName] = widget;            }            //如果還有子節點,遞迴進去            if (widget.getChildrenCount()) {                self._bindMenbers(widget, target);            }        });    },    /**     * 擷取控制項事件     * @param widget     * @returns {*}     */    _getWidgetEvent: function(widget) {        var bindWidgetEvent = null;        var events = sz.UILoader.widgetEvents;        for (var i = 0; i < events.length; i++) {            bindWidgetEvent = events[i];            if (widget instanceof bindWidgetEvent.widgetType) {                break;            }        }        return bindWidgetEvent;    },    /**     * 註冊控制項事件     * @param target     * @param widget     * @private     */    _registerWidgetEvent: function(target, widget) {        var name = widget.getName();        //截取首碼,首字母大寫        var newName = name[this._memberPrefix.length].toUpperCase() + name.slice(this._memberPrefix.length + 1);        var eventName = this._eventPrefix + newName + "Event";        var isBindEvent = false;        if (target[eventName]) {            isBindEvent = true;        } else {            //取事控制項件名            var widgetEvent = this._getWidgetEvent(widget);            if (!widgetEvent) {                return;            }            //檢查事函數,建置事件名數組            var eventNameArray = [];            for (var i = 0; i < widgetEvent.events.length; i++) {                eventName = this._eventPrefix + newName + widgetEvent.events[i];                eventNameArray.push(eventName);                if (cc.isFunction(target[eventName])) {                    isBindEvent = true;                }            }        }        //事件響應函數        var self = this;        var eventFunc = function(sender, type) {            var callBack;            if (eventNameArray) {                var funcName = eventNameArray[type];                callBack = target[funcName];            } else {                callBack = target[eventName];            }            if (self._onWidgetEvent) {                self._onWidgetEvent(sender, type);            }            if (callBack) {                return callBack.call(target, sender, type);            }        };        //註冊事件監聽        if (isBindEvent) {            widget.setTouchEnabled(true);            if (widget.addEventListener) {                widget.addEventListener(eventFunc, target);            } else {                widget.addTouchEventListener(eventFunc, target);            }        }    }     });sz.uiloader = new sz.UILoader();

所有東西都已經準備好了, 我們看看怎麼在具體的代碼中使用:
GameLayer = cc.Layer.extend({ctor: function() {this._super();//載入UI檔案,繫結控制項和事件到thissz.uiloader.widgetFromJsonFile(this, "res/DemoLogin.ExportJson");//立即可以訪問控制項的屬性方法了cc.log(this._closeButton.getName());},/* * _closeButton的TouchBegan事件處理函數 */_onCloseButtonTouchBegan: function(sender) {cc.log("_onCloseButtonTouchBegan");},});


現在看看我的用戶端代碼是不是比之前的簡潔多了!
五.首碼的問題上面花了大量文字講解了關於命名的問題,有人可能會覺得使用這個UILoader會強姦他的代碼。因為他沒有使用“_”做為成員變數首碼,或是成員變數首碼不是“_”而是“m_”。為了不強姦別人的代碼,提供了下面的選項:
sz.uiloader.widgetFromJsonFile(this, "res/DemoLogin.ExportJson", {eventPerfix:"on", memberPrefix:"m_"} );
最後一個選擇性參數options對象,有兩個屬性eventPerfix 、memberPrefix用於配置事件首碼和成員變數首碼
六. 首碼+控制項名+Event
  有些時候,不想將TouchBegan、TouchMoved、TouchEnded分成三個響應函數分別來寫,而是使用原來事件參數來判斷事件類型。這時你只需要實現以【首碼+控制項名+Event】的函數名例如:控制項名為_loginButton, 定義一個函數如:
_onLoginButtonEvent: function(sender, type) {switch (type) {case 0:cc.log("_onLoginButtonEvent: began");break;case 1:cc.log("_onLoginButtonEvent: move");break;case 2:cc.log("_onLoginButtonEvent: end");break;}},
這時UILoader會優先使用這個事件處理函數,如果同時也實現了一個"_onLoginButtonTouchBegan",它將不會被執行。這樣再次阻止sz.UILoader強姦事件的發生,可以相容你原來的代碼。
完整代碼可以到githut下載: https://github.com/ShawnZhang2015/UILoader


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.