1. 程式人生 > >用ECMAScript4 ( ActionScript3) 實現Unity的熱更新 -- 使用FairyGUI (二)

用ECMAScript4 ( ActionScript3) 實現Unity的熱更新 -- 使用FairyGUI (二)

src class 測試 資源 isp ola 物體 ddp onclick

上次講解了FairyGUI的最簡單的熱更新辦法,並對其中一個Demo進行了修改並做成了熱更新的方式。

這次我們來一個更加復雜一些的情況:Emoji.

FairyGUI的 Example 04 - Emoji

場景是一個聊天對話框。玩家可以輸入文本和表情,對面的機器人還會回復一句話。回復的對話中還附帶一個表情。

Demo分析

FairyGUI的這個Demo展示了如下技巧:

  • 繼承UBBParser ,在Demo中使用自定義的表情。
  • 編寫了一個MonoBehaviour,管理所有的邏輯。
    1. 動態添加UI項目的各按鈕的點擊事件
    2. 保存對話內容
    3. 根據對話的類型(發送方和接收方),可以給對話項提供不同的展示資源
    4. 腳本中解析表情,並展示出來。
    5. 當對話條數超過一定數目時,會自動從頭部移除最早的對話。

修改為熱更新

本次熱更新的嘗試目標,所有邏輯全部移動到熱更新代碼中執行。

  1. 首先我們將這個場景另存為F_Emoji。
  2. 導入ActionScript3 虛擬機的unity插件包,並生成熱更新項目。如果您不知道怎麽操作,可以查看這裏
  3. ActionScript3插件包已更新,請下載最新的插件包 v0.96f6 以及以後版本。

現在準備工作已完成。

我們將場景中的UIPanel直接刪除。我們全部使用腳本來創建這個Panel。

  1. 進入Assets->ASRuntimePlayer,將 AS3Player預設和AS3StartupProgress預設拖到場景上。
  2. 將AS3Player物件下的Action Script Start Up 腳本組件的Document Class 設置為EmojiTest。
    (這一步的作用表示指示腳本解釋器啟動時,具體創建一個哪種類型的實例)
  3. 現在打開熱更新項目,新建一個 EmojiTest 的類。

現在我們首先來說明,如何使用熱更新腳本來創建UIPanel。

根據FairyGUI的說明,要從腳本創建UIPanel,則一定要先註冊UI所在的包,否則會提示創建失敗

原C# Demo中,註冊代碼是在Awake事件中,並且設計時就拖動到場景上的,而我們這裏需要熱更新,我們可以選擇在EmojiTest的構造函數中,或者直接在包外代碼中寫入註冊代碼,如下:

//務必先加入package
UIPackage.addPackage______("UI/Emoji");

var go:GameObject = new GameObject("uip");

var uip:UIPanel = go.addComponent(UIPanel) as UIPanel;

uip.gameObject.layer = LayerMask.nameToLayer("UI");
uip.packageName = "Emoji";
uip.componentName = "Main";

將上面的代碼寫到包外代碼中。點擊編譯,然後在Unity中點擊播放:

技術分享圖片

如圖,這裏的uip物體就是通過熱更新腳本創建的,然後FairyGUI根據我們配置的package和componet,創建了對話的UI。

編寫熱更新邏輯

現在我們來編寫熱更新邏輯。但是這次有些不同,我們需要對UBBParser這個類進行一些修改。

FairyGUI的Demo中,為了解析UBB表情,定義了EmojiParser ,繼承自UBBParser。並且EmojiParser中,訪問了基類的受保護的對象handlers。

然而,我們的ActionScript3腳本如果繼承C#類庫,是無非直接訪問受保護的字段的。因此,我們需要先在Unity工程中,將UBBParser的handlers改為public的成員.

修改完成後,待Unity重新編譯項目後,在熱更新項目中,使用 bat/CreateUnityAPI.bat 這個批處理文件重新生成一下ActionScript3的API代碼。

確保以上步驟完成後,我們著手將C#代碼改寫為ActionScript3代碼.

我們同樣可以將邏輯繼承自MonoBehaviour,並且也創建一個繼承UBBParser的類,由於handlers字段已改為公開成員,因此可以在腳本中訪問。

package 
{
    /**
     * ...
     * @author 
     */
    public class EmojiTest 
    {
        
        public function EmojiTest() 
        {
            
        }
        
    }

}
import fairygui.Emoji;
import fairygui.EventContext;
import fairygui.FitScreen;
import fairygui.GButton;
import fairygui.GComponent;
import fairygui.GList;
import fairygui.GObject;
import fairygui.GRichTextField;
import fairygui.GRoot;
import fairygui.GTextInput;
import fairygui.ScrollBarDisplayType;
import fairygui.UIConfig;
import fairygui.UIPackage;
import fairygui.UIPanel;
import fairygui.utils.UBBParser;
import system.Char;
import system._Object_;
import system.collections.generic.Dictionary_Of_UInt32_Emoji;
import unityengine.Application;
import unityengine.GameObject;
import unityengine.KeyCode;
import unityengine.LayerMask;
import unityengine.MonoBehaviour;
import unityengine.Random;

//在腳本中繼承UBBParser。邏輯照搬C# Demo
//給每個表情提供一個handler,這個handler處理此表情的資源。
class EmojiParser extends UBBParser
{
    private static var  _instance:EmojiParser;
    public static function get inst():EmojiParser
    {        
        if (_instance == null)
            _instance = new EmojiParser();
        return _instance;
        
    }

    private static var TAGS:Vector.<String> =Vector.<String>([
         "88","am","bs","bz","ch","cool","dhq","dn","fd","gz","han","hx","hxiao","hxiu" ]);
    public function EmojiParser ()
    {
        for each (var ss in TAGS)
        {
            this.handlers[":"+ss] = OnTag_Emoji;
        }
    }

    private function OnTag_Emoji( tagName:String,  end:Boolean,  attr:String):String
    {
        return "<img src=‘" + UIPackage.getItemURL("Emoji", tagName.substr(1).toLowerCase()) + "‘/>";
    }
}

/**
 * 對話消息,不必多說
 */
class Message
{
    public var sender:String;
    public var senderIcon:String;
    public var msg:String;
    public var fromMe:Boolean;
}

/**
 * 將Demo中的邏輯用熱更新腳本改寫。
 * 我們同樣可以繼承自MonoBehaviour
 */
class EmojiMain extends MonoBehaviour
{
    var _mainView:GComponent;
    var _list:GList;
    var _input1:GTextInput;
    var _input2:GTextInput;
    var _emojiSelectUI1:GComponent;
    var _emojiSelectUI2:GComponent;

    
    var _messages:Vector.<Message>;

    var _emojies:Dictionary_Of_UInt32_Emoji;

    function Awake()
    {
        //UIPackage.AddPackage("UI/Emoji");

        UIConfig.verticalScrollBar = "ui://Emoji/ScrollBar_VT";
        UIConfig.defaultScrollBarDisplay = ScrollBarDisplayType.Auto;
    }

    function Start()
    {
        Application.targetFrameRate = 60;
        
        _messages = new Vector.<Message>();

        _mainView = UIPanel( this.getComponent(UIPanel)).ui;

        _list = _mainView.getChild("list").asList;
        _list.setVirtual();
        _list.itemProvider = GetListItemResource;
        _list.itemRenderer = RenderListItem;

        //給按鈕添加處理事件。
        _input1 = _mainView.getChild("input1").asTextInput;
        _input1.onKeyDown.add(__inputKeyDown1);

        _input2 = _mainView.getChild("input2").asTextInput;
        _input2.onKeyDown.add(__inputKeyDown2);

        //作為demo,這裏只添加了部分表情素材
        _emojies = new Dictionary_Of_UInt32_Emoji();
        for (var i:uint = 0x1f600; i < 0x1f637; i++)
        {
            var url:String = UIPackage.getItemURL("Emoji", i.toString(16));
            if (url != null)
                _emojies.add(i,  Emoji.constructor_(url));
        }
        _input2.emojies = _emojies;

        _mainView.getChild("btnSend1").onClick.add(__clickSendBtn1);
        _mainView.getChild("btnSend2").onClick.add(__clickSendBtn2);

        //添加發送表情按鈕的事件
        _mainView.getChild("btnEmoji1").onClick.add(__clickEmojiBtn1);
        _mainView.getChild("btnEmoji2").onClick.add(__clickEmojiBtn2);

        _emojiSelectUI1 = UIPackage.createObject("Emoji", "EmojiSelectUI").asCom;
        _emojiSelectUI1.fairyBatching = true;
        _emojiSelectUI1.getChild("list").asList.onClickItem.add(__clickEmoji1);

        _emojiSelectUI2 = UIPackage.createObject("Emoji", "EmojiSelectUI_ios").asCom;
        _emojiSelectUI2.fairyBatching = true;
        _emojiSelectUI2.getChild("list").asList.onClickItem.add(__clickEmoji2);
    }

    function AddMsg(sender:String,  senderIcon:String,  msg:String,  fromMe:Boolean):void
    {
        var isScrollBottom:Boolean = _list.scrollPane.isBottomMost;

        var newMessage:Message = new Message();
        newMessage.sender = sender;
        newMessage.senderIcon = senderIcon;
        newMessage.msg = msg;
        newMessage.fromMe = fromMe;
        _messages.push(newMessage);

        if (newMessage.fromMe)
        {
            if (_messages.length == 1 || Random.range(0, 1) < 0.5)
            {
                var replyMessage:Message = new Message();
                replyMessage.sender = "FairyGUI";
                replyMessage.senderIcon = "r1";
                replyMessage.msg = "Today is a good day. " + Char.convertFromUtf32( 0x0001f600 ).toString();
                replyMessage.fromMe = false;
                _messages.push(replyMessage);
            }
        }

        if (_messages.length > 100)
        {
            _messages.splice(0, _messages.length - 100);
        }
            //_messages.RemoveRange(0, _messages.Count - 100);

        _list.numItems = _messages.length;

        if (isScrollBottom)
            _list.scrollPane.scrollBottom();
    }

    function GetListItemResource( index:int):String
    {
        var msg: Message= _messages[index];
        if (msg.fromMe)
            return "ui://Emoji/chatRight";
        else
            return "ui://Emoji/chatLeft";
    }

    function RenderListItem( index:int,  obj:GObject):void
    {
        var item:GButton = GButton(obj);
        var msg:Message= _messages[index];
        if (!msg.fromMe)
            item.getChild("name").text = msg.sender;
        item.icon = UIPackage.getItemURL("Emoji", msg.senderIcon);


        //Recaculate the text width
        var tf:GRichTextField = item.getChild("msg").asRichTextField;
        tf.emojies = _emojies;
        tf.width = tf.initWidth;
        tf.text = EmojiParser.inst.parse(msg.msg);
        tf.width = tf.textWidth;
        
    }

    function __clickSendBtn1( context:EventContext):void
    {
        var msg:String = _input1.text;
        if (msg.length == 0)
            return;

        AddMsg("Unity", "r0", msg, true);
        _input1.text = "";
    }

    function __clickSendBtn2( context:EventContext):void
    {
        var msg:String = _input2.text;
        if (msg.length == 0)
            return;

        AddMsg("Unity", "r0", msg, true);
        _input2.text = "";
    }

    function __clickEmojiBtn1( context:EventContext):void
    {
        GRoot.inst.showPopup__(_emojiSelectUI1, GObject(context.sender), _Object_( false));
    }

    function __clickEmojiBtn2( context:EventContext):void
    {
        GRoot.inst.showPopup__(_emojiSelectUI2, GObject(context.sender), _Object_( false));
    }

    function __clickEmoji1( context:EventContext):void
    {
        var item:GButton= GButton(context.data);
        _input1.replaceSelection("[:" + item.text + "]");
    }

    function __clickEmoji2( context:EventContext):void
    {
        var item:GButton = GButton(context.data);
        _input2.replaceSelection(Char.convertFromUtf32( parseInt(UIPackage.getItemByURL(item.icon).name,16) ));
    }

    function __inputKeyDown1( context:EventContext):void
    {
        if (context.inputEvent.keyCode == KeyCode.Return)
            _mainView.getChild("btnSend1").onClick.call();
    }

    function __inputKeyDown2( context:EventContext):void
    {
        if (context.inputEvent.keyCode == KeyCode.Return)
            _mainView.getChild("btnSend2").onClick.call();
    }

    
}



//務必先加入package
UIPackage.addPackage______("UI/Emoji");

var go:GameObject = new GameObject("uip");

var uip:UIPanel = go.addComponent(UIPanel) as UIPanel;

uip.gameObject.layer = LayerMask.nameToLayer("UI");
uip.packageName = "Emoji";
uip.componentName = "Main";

//將邏輯代碼掛載到UIPanel上。
go.addComponent(EmojiMain);

可以將如上代碼直接寫到熱更新腳本中,編譯。

然後在Unity中點擊播放,我們看到我們的熱更已經生效!

技術分享圖片

如此,我們即可完全使用熱更代碼處理FairyGUI的對話聊天模塊。

打包到安卓手機

您可以將這個場景導出到手機上測試。

當打包時,有可能會遇到腳本錯誤。這是因為FairyGUI的某些代碼使用了宏編譯,在windows狀態下有這個類,而安卓狀態下確沒有。因此我們可以將這些類型配置到導出API的工具中,

聲明它不導出。以這個案例而言,則是將如下配置寫入genapi.config.xml的<notcreatetypes>配置節中:

     <item value="FairyGUI.CopyPastePatch"></item>
      

然後再次導出API,編譯腳本,生成安卓包:

技術分享圖片

如此我們就看到了手機上運行的效果。

用ECMAScript4 ( ActionScript3) 實現Unity的熱更新 -- 使用FairyGUI (二)