Automatically bind cocostudioUI controls with events in cocos2d-js
I. Cause
The most disgusting job in client game development is UI-related things. Although visual tools like cocostudio are available, there are a lot of controls on the interface that need to be accessed by code, you need to write too much repeated code, for example:
// Load the UI configuration file var root = ccs. uiReader. widgetFromJsonFile ("res/cocosui/UIEditorTest/UIButton_Editor/UIButton_Editor_1.json"); this. _ mainNode. addChild (root); // query the back control and add the Event Response var back_label = ccui. helper. seekWidgetByName (root, "back"); back_label.addTouchEventListener (this. backEvent, this); // query the Button_123 control and add the Event Response var button = root. getChildByName (root, "Button_123"); button. addTouchEventListener (this. touchEvent, this );
The method above is the most direct access to the control. The problem is that if there are 10, 20, or more UI controls in a UI, do we still have a good time to play with the UI and logic development? Is there any way to access the UI component and receive the UI Event Response without having to write the code by hand?
Ii. Thinking
70% ~ for mobile games, especially card games ~ 80% of client workload is in the UI layout and logic. The above seekWidgetByName, getChildByName, and addTouchEventListener functions will flood a lot of client code and overwrite the number of lines of our code.
I have experienced Qt development. Qt also has its own ui design tool to generate xml ui configuration files. there are two ways to use this xml: the first method: Use Qt's Own compilation tool to generate a c ++ code file for xml translation, the Code content is to create various controls based on the information in xml, and set coordinates, attributes, and events. Method 2: In a program, load the xml file into a node using the UILoader tool class. Then, call the Qt function to automatically associate the signal/slot. The principle of automatic binding of signals/slots (events) is to write an event processing function in the format:
Void on _ control name_signal name (parameter );
I will not elaborate on how to use it. If you are interested, you can check it yourself. According to Qt this function prompts, we can not automatically bind the coccostudio output in the cocos2d-js ui file?
Iii. Naming Conventions
1. code naming conventions
According to the cocos2d-js code style, we agree: (1) class member variables starting with the underscore "_" followed by an English word in the camper name format. For example, private functions in the _ loginButton, _ closeButton, and _ nameLabel (2) classes also use the same method. Example: _ onLoginButtonTouchBegan: function (){...}
2. UI naming conventions
In the cocostudioUI Editor, we follow the naming rules of member variables in the above Code. Start the control that needs to be accessed by code with the name "_", followed by an English word in the camper name format. See:
3. Name the ccui control event
Ccui. there are two types of Widget event registration: 1 ). common touch events include: ccui. widget. TOUCH_BEGAN touch start (Press) ccui. widget. TOUCH_MOVED touch mobile (mobile) ccui. widget. TOUCH_ENDED touch end (LIFT)
Ccui. Widget. TOUCH_CANCELED touch cancellation (usually useless) we use the widget. addTouchEventListener (selector, target) to register a touch event for the control and set the callback function.
2 ). control special events: for example, CheckBox: ccui. checkBox. EVENT_SELECTEDccui.CheckBox.EVENT_UNSELECTED, for example, TextField: ccui. textField. widgets are required for events such as logging. addEventListener (selector, target) to register.
The selector here is our callback function. We need to name it and implement this function. The event type is identified by parameters:
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;}}
This "_ onButtonEvent" is the name of the event function. If we press :【
Prefix + control name (underline removed) + event name]For example, the control event function name is: _ button event name: _ onButtonTouchBegan, _ onButtonTouchMoved, and _ onButtonTouchEnded.
Iv. Code Implementation
With the above conventions, we can begin to bind the UI. 1. Define a list of automatically bound controls. Common Control types and event names are listed here.
// Touch the event sz. UILoader. touchEvents = ["TouchBegan", "TouchMoved", "TouchEnded"]; // control event list 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];
The sz. UILoader. widgetEvents array can add components to be bound as needed.
2. logical process 1). Use loader to load the ui file and input target as the current layer. All events and control variables are bound to the target. 2) traverse the loaded child node and check whether the name prefix starts. And whether the node type is in the widgetEvents array.
3) bind the childNode to the target.
4) extract the childNode event function name and check whether the target has these functions.
5) register the Event Response for widgetNode. 6) load the class to receive the Event Response and forward the event to the corresponding target event processing function.
3. Implementation of the UI loading class
Sz. UILoader = cc. class. extend ({_ eventPrefix: null, _ memberPrefix: null, /*** load the UI file ** @ param target binds the node loaded by jsonFile to the target * @ param jsonFile the json file generated by cocostudio UI editor */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) ;},/*** recursively bind Members to subnodes under the rootWidget * @ param target * @ private */_ bindMenbers: function (FIG, target) {var widgetName, children = fig. getChildren (); var self = this; children. forEach (function (widget) {widgetName = widget. getName (); // The control name exists and is bound to var prefix = widgetName on the target. substr (0, self. _ memberPrefix. length); if (prefix = self. _ memberPrefix) {target [widgetName] = widget; self. _ registerWidgetEvent (target, widget);} // bind a child control to implement. _ B. _ c. _ d access the sub-control if (! RootWidget [widgetName]) {rootWidget [widgetName] = widget;} // if there are child nodes, recursively go to if (widget. getChildrenCount () {self. _ bindMenbers (widget, target) ;}}) ;},/*** get control event * @ 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 bindW IdgetEvent. widgetType) {break;} return bindWidgetEvent;},/*** register a control event * @ param target * @ param widget * @ private */_ registerWidgetEvent: function (target, widget) {var name = widget. getName (); // intercept the prefix. The first letter is uppercase 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 {// retrieve the widget name var widgetEvent = this. _ getWidgetEvent (widget); if (! WidgetEvent) {return ;}// check the event function to generate the event name array 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 ;}}// Event Response function 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) ;}}; // register the event listener if (isBindEvent) {widget. setTouchEnabled (true); if (widget. addEventListener) {widget. addEventListener (eventFunc, target);} else {widget. addTouchEventListener (eventFunc, target) ;}}}); sz. uiloader = new sz. UILoader ();
Everything is ready. Let's see how to use it in the specific code:
GameLayer = cc. layer. extend ({ctor: function () {this. _ super (); // load the UI file, bind controls and events to thissz. uiloader. widgetFromJsonFile (this, "res/DemoLogin. exportJson "); // you can immediately access the control's attribute method cc. log (this. _ closeButton. getName () ;},/** _ closeButton's TouchBegan event processing function */_ onCloseButtonTouchBegan: function (sender) {cc. log ("_ onCloseButtonTouchBegan ");},});
Now let's see if my client code is much more concise than before!
5. I have spent a lot of text explaining the issue of naming. Some people may think that using this UILoader will rape his code. Because it does not use "_" as the prefix of the member variable, or the prefix of the member variable is not "_" but "m _". In order not to rape others' code, the following options are provided:
sz.uiloader.widgetFromJsonFile(this, "res/DemoLogin.ExportJson", {eventPerfix:"on", memberPrefix:"m_"} );
The last optional parameter options object has two attributes: eventPerfix and memberPrefix, which are used to configure the event prefix and the member variable prefix.
6. prefix + control name + Event
In some cases, you do not want to separate TouchBegan, TouchMoved, and TouchEnded into three response functions for writing, but use the original event parameters to determine the event type. In this case, you only need to implement the function name with the prefix + control name + Event, for example, the control name _ loginButton, and define a function such:
_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;}},
At this time, UILoader will give priority to this event handler function. If a "_ onLoginButtonTouchBegan" is also implemented, it will not be executed. In this way, the sz. UILoader rape event is blocked again, and your original code is compatible.
Complete code can be downloaded to githut: https://github.com/ShawnZhang2015/UILoader