1. 程式人生 > >獨孤九劍與乾坤大挪移—uikiller外掛系統

獨孤九劍與乾坤大挪移—uikiller外掛系統

上篇《雷神之錘》介紹了uikiller的基本用法,有人說長按功能可以取名為蓄力攻擊重擊,我覺得還真是可以的,但就是感覺招數名字不夠大氣。在這裡還要給大家道個歉,上篇中我說了這一樣句話:

uikiller只有一行需要要被主動呼叫的函式:uikiller.bindComponent

不好意思,在這裡我可能隱瞞了大家,除了uikiller.bindComponent以外,還有一行可以被主動呼叫的函式

uikiller.bindNode(node, target)

這次希望我能把uikiller全部祕密給抖動出來,請各位不要太過驚訝!

一、獨孤九劍

之前的篇幅主要介紹了uikiller的一系的招數、連擊等,接下來講講uikiller的另一種絕技,它是爭對敵人不同的招數一一破解的功法:獨孤九劍

(外掛系統)

1. 總決式

總決式中體現外掛類的寫法,主要的屬性和事件函式名

let UIKillerPlugin = {
    name: '外掛名字',
    onRegister() {
        //當外掛註冊時響應
    },

    onCheckNode(node, target) {
        //當一個node將要被繫結時響應,返回false將不會繫結該節點下的子節點
    },

    onBeforeHandleEvent(node, event, hasEventFunc) {
       //當一個節點的事件處理函式執行前響應
    },

    onAfterHandleEvent(node, event, hasEventFunc, eventResult) {
       //當一個節點的事件處理函式執行後響應 
    }
};

//註冊外掛
uikiller.registerPlugin(UIKillerPlugin);

uikiller的外掛是一組事件勾子函式,這些勾子函式不一定都要實現,但一般至少要實現一個才有意義,瞭解了總決式,我們來看看能做些什麼。

2. 破劍式—過濾節點

在一些戰鬥中,英雄與魔靈只需要站在那裡,擺擺pose就夠了,不需要被指揮(無需呼叫節點或元件上的方面)。可以將這些英雄們請到VIP席上就坐。

let UIKillerBindFilter = {
    name: 'UIKillerBindFilter',
    onCheckNode(node, target) {
        if (node === target.node) {
            return true;
        }
        //過濾掉首字母為“@”的節點下的子節點
        if (node.name[0] === '@') {
            return false;
        }

        /*在bindNode\bindComponent的最後一個引數是一個options物件*/
        /*呼叫options上的過濾函式,進行節點過濾*/
        let options = target.$options;
        if (uikiller.isFunction(options.filter)) {
            if (options.filter(node)) {
                return false;
            }
        }        
    }
};
uikiller.registerPlugin(UIKillerBindFilter);

在編輯器中,當節點以名“@”開頭,他下面的子節點都不能會被綁定了。如果你還想複雜點你可以嘗試用正則表示式過濾節點名。

遇到複雜的情況可以使用bindNode或bindComponent上的最後一個引數,設定一個filter函式實現動態過濾。

//元件程式碼
onLoad() {
    uikiller.bindComponent(this, { filter: (node) => {
        //在此可以根據node名字、型別等屬性進行過濾    
    }})
}

3. 破刀式—Label的問題

Label是一個作戰頻次特別高的魔靈(component),但是問題也多,比如:字型、多語言特別的煩人,就像插在心口上的一把刀,這裡介紹一種破多語言的方法。

const UIKillerLabelLanguage = {  
    name: 'UIKillerLabelLanguage',
    onCheckNode(node, target) {
        //檢查節點是否有label元件 
        let label = node.getComponent(cc.Label);
        if (!label) {
            return;
        }
        //以節點名上的$為多語言key,如果不存在以name為多語言key
        let key = node.$ || node.name;
        //使用i18n獲取多語言文字 
        label.string = i18n.t(key);
    }
};

這種方法不用為裝備有Label的英雄(node)部署一個魔靈(component),直接在英雄的名字上做文章是不是更直接一些呢?除了Label外,還可以應用到EditBox\RichText之上,更多招數自己去發明創造!

4. 破槍式—音效控制

英雄、魔靈們奮勇作戰,在戰場上各種機槍、手槍、來福槍此起彼伏,喊殺聲中還有帶上背景音樂,可是在奎特爾上英雄是不能直接發出聲音的,那是怎麼辦的呢?看下面最常見的一種做法

onButtonTouchEnd() {
     //處理一堆業務
     ...
     //播放音效
     cc.audioEngine.play(url);
}

這種做法是到處埋地雷,稍微不注意把自己給炸了,還有可能地雷弄錯了或要換地雷時,就慘了。

看看uikiller的破槍式,先定義一個音效配置

//用英雄節點的名字作key或者$名字也行
 const SOUND_CONFIG = {
    _attack: '3002.mp3',
    _expedition: '3006.mp3', 
    click: 'click.mp3',      
}

破槍式心法

const UIKillerTouchSound = {
    name:'UIKillerTouchSound',
    /**
     * 事件處理函式執行完畢後觸發此函式
     * @param {*} node         觸控事件節點
     * @param {*} event        觸控事件物件
     * @param {*} hasEventFunc  是否有事件處理函式 
     * @param {*} eventResult   事件處理函式返回值
     */
    onAfterHandleEvent(node, event, hasEventFunc, eventResult) {
        //在TouchEnd事件時,而且事件處理函式返回值不能為false
        if (event.type !== cc.Node.EventType.TOUCH_END || eventResult === false) {
            return;
        }
        //獲取聲音檔名,沒有的話統一的click聲音
        let soundName =   SOUND_CONFIG[eventResult] || //優先取事件返回值
                          SOUND_CONFIG[node.$]      ||  //其次取$名字
                          SOUND_CONFIG[node.name]   || //再次為節點名
                          SOUND_CONFIG.click;          //沒有統一的click聲音
        //播放音效
        let url = cc.url.raw(`resources/sound/${soundName}`);
        cc.audioEngine.play(url);
    }
};

戰鬥實操,領略一下如此整潔的戰場,地雷沒有了,只有動聽的音效!

onLoad() {
    this.bindComponent(this);
},

//有個_button節點,且寫了這個函式,不做事也會有預設音效播放
_onButtonTouchEnd() {
},

//有個_button1節點,事件函式返回了false,不會有聲音出現
_onButton1TouchEnd() {
  return false; 
},

//有個_attack節點,音效配置中對應“3002.mp3”,將自動播放此音效,這裡不用再費心了
_onAttackTouchEnd() {
    //如果返回一個音效配置字串,將會改變聲音的播放
    //return "_expedition";
},

破刀式可以作用於任何節點,不僅僅是Button,再此招式啟示下,你可以可以推演出點選時的動畫播放,比如爆炸一個粒子等等。

5. 無招勝有招

獨孤九劍以無招勝有招,是敵強我愈強,使用“獨孤九劍”,除了精熟javascript和奎特爾的世界外,還需要依賴使劍者的靈悟,一到自由揮灑、更無規範的境界,便如是大詩人靈感到來,作出了一首好詩一般。

二、乾坤大挪移

在講解乾坤大挪移之前,需要做一點點鋪墊,那就是在創造奎特爾星球上除了創世之主(程式設計師)以還,還存在兩類物種分別是:

  • 神機軍師(策劃)

  • 幻靈法師(美術)

1. 謀士與法師的煩惱

謀士與法師也是構建奎特爾不可缺少的人物,他們通常都是高輸出、低防禦的特質,同時謀士與法師大多都不會使用咒語(編寫程式碼)與英雄(node)和魔靈(component)做心靈溝通。


但有時謀士需要嘗試下派兵佈陣(UI佈局),法師想預覽下剛研製出的幻術(美術效果),在沒有創世之主的幫助下,經常是做的得過且過,如何解決這個問題呢?

不使用任何一句咒語(程式碼),就能讓謀士和法師任意放出或收回召喚獸(prefab)

經過我對javascript咒語和奎特爾的潛心研究,創造了新的招式:乾坤大挪移心法

2. 第一層

「乾坤大挪移」第一層心法,是對javascript運用,利用原型給魔靈們增加一個createNode、destroyNode法門。

//召喚一個英雄
cc.Component.prototype.createNode = function() {
...
}

//給英雄放個假
cc.Component.prototype.destroyNode = function() {
}

這裡就不帖那麼多咒語(程式碼)了,請上github領取你的武器。

3. 第二層

第二層心法是利用cc.Button的ClickEvents事件屬性佈置召喚陣法,請看下圖:

  1. 給Button英雄新增一個點選處理

  2. 給事件cc.Node屬性設定為將要召喚出的神獸(prefab)的落腳點(父節點)

  3. 隨便指定一個元件

  4. 元件方法上選擇createNode(這是召喚核心)

  5. CustomEventData設定為召喚獸的名字(prefab的路徑字串)

需要注意,召喚獸(prefab)需要放到resources目錄中。點選這個Button,就可以將召喚獸(prefab)釋放出來了。

4. 第三層

有召喚必有降伏,將不需要的英雄(node)或召喚獸(prefab)叫回去睡覺覺。

  1. 給Button英雄新增一個點選處理

  2. cc.Node設定為想要休假的節點

  3. 隨便指定一個元件即可

  4. 選擇destroyNode方法

點選這個Button,節點就自己乖乖回家了!
乾坤大挪移的一大特點就是「激發最大潛力」,有此功夫謀士(策劃)與法師(美術)的小宇宙也可以被激發起來,同時也激發了創世之主(程式設計師)做更有創意的事情。

5. 第四層

很輕鬆的就來到練乾坤大挪移的第四層,這一層不再是謀士(策劃)和法師(美術)能輕易習得的了!前面我提到了uikiller中還有一個主動呼叫的函式: bindNode,它是修習後面幾層的基礎,功能非同小可,先看看最簡單的演示:

onLoad() {
    //定義一個target物件,交UI編輯器上的節點和元件挪移到它上面去
    this.target = {
        //處理節點的觸控事件
        _onLabelTouchEnd() {
           //_label補繫結到target物件上了,可以用this直接訪問 
           this._label.$Label.string = 'yyy';     
        }
    };
    uikiller.bindNode(this.node, this.target);
    //_label屬性已經被挪移到this.target上了,在target變數上訪問
    this.target._label.x = 100;
    this.target._label.$Label.string = 'xxx';
}

其實上面的演示中bindNode並不能展示他的威力,不過它體現了乾坤大挪移的精髓之一:「牽引挪移敵勁」,將一個元件上的節點和事件牽引到別的物件上。

6. 第五層

第五層了,我們來看看乾坤大挪移的「轉換陰陽二氣」的能力,接下來越下越複雜了,要細心哦,小心走火入魔。

  • 建立一個傀儡魔靈,並繫結到一個prefab的根節點上

------------------XXXComponent.js--------------------
let Ctrl = require('Ctrl');
cc.Class({
    extends: cc.Component,

    onLoad() {
        //建立一個控制器
        this.ctrl = new Ctrl(this.node);
        //使用bindComponent注入乾坤大挪移的陽剛之氣
        uikiller.bindComponent(this); 
    },

    /**
    * 在此控制介面邏輯
    */ 
    _onButtonTouchEnd(sender) {
        //360度旋轉起來
        this._button.runAction(cc.rotateBy(3, 360).repeatForever()); 
    }
});
  • 再製造一個幕後黑手一起操縱英雄(node)

let uikiller = require('uikiller');

function Ctrl(node) {
    //使用bindNode注入乾坤大挪移的陰柔之力
    uikiller.bindNode(node, this); 
    //同樣方式訪問節點與元件
    this._label.$Label.string = 'xxx';
};

//定義成員函式
Ctrl.prototype = {
    /**
    * 在此控制業務邏輯
    */
    _onButtonTouchEnd(
        //處理業務邏輯
        ...
        //發起網路請求
        net.request(url, param, (data) => {
            //資料來了,停止旋轉
            this._button.stopAllActions();
            this._button.rotation = 0;
        });
    }
}

上面的演示,相當於兩個人同時一起駕駛了一部坦克,一起協作,他們之間遙相呼應,發動了一個大必殺技:激無雙亂舞


看到這裡,你可能對曾經的信仰有些懷疑了,因為有這樣一個古老的傳說

在奎特爾星球上一切皆魔靈(一切皆元件)

此刻,這個傳說被打uikiller給打破了。

7. 第六層

第六層已經是很高的境界了,既然一切皆魔靈(一切皆元件)的傳說被打破了,那利用bindNode的複製對手武功、牽引挪移敵勁的能力,就不一定要構造一個魔靈(component)控制戰鬥。同時結合PureMVC + uikiller.bindNode將prefab做為mvc中的view,將view中的節點和元件挪移到meidator中,那將是另一種全新的戰鬥模式!

7. 第七層

第七層MVVM,比第六層深了數倍,不好意思我也不得要領,還得繼續修練,為了不至於走火入魔,我就不再介紹了!

四、寫在最後

uikiller中關於上述大招的使用都放到github上了,歡迎把玩
https://github.com/ShawnZhang2015/uikiller

歡迎關注「奎特爾星球」微信公眾號,有程式碼、有教程、有視訊、有故事,一起來玩吧!