热更 UI

界面系统在游戏中占据重要地位。游戏界面是否友好,很大程度上决定了玩家的体验;界面开发是否便利,也影响着游戏的开发进度。Unity3D 的UGUI系统,使用户可以“可视化地”开发界面,那么怎样用Lua去调用UGUI呢?

一、显示 UI 界面

下面演示如何显示一个UI界面。由于UI界面也是一种资源,使用第二篇“资源热更新”的方法即可。这个例子中,制作一个含有按钮的界面,然后组成名为Panel1的UI预设,存放到Tank目录下。

前面(第二篇)已在Packager类HandleExampleBundle方法中添加了一句“AddBuildMap("tank" + AppConst.ExtName, "*.prefab", "Assets/Tank");”(当然也可以添加到其他地方),它会把Tank目录下的所有预设打包成名为tank的资源包。故而点击“Build xxx Resource”后,Panel1也会被打包到tank资源包中。

修改Lua入口函数Main.lua中的Main方法,在加载资源后把panel1放到Canvas下(需要在场景中添加画布),然后调整它的位置和大小。

  1. --主入口函数。从这里开始lua逻辑
  2. function Main()
  3. LuaHelper = LuaFramework.LuaHelper;
  4. resMgr = LuaHelper.GetResManager();
  5. resMgr:LoadPrefab('tank', { 'Panel1' }, OnLoadFinish);
  6. end
  7. --加载完成后的回调--
  8. function OnLoadFinish(objs)
  9. --显示面板
  10. go = UnityEngine.GameObject.Instantiate(objs[0]);
  11. local parent = UnityEngine.GameObject.Find("Canvas")
  12. go.transform:SetParent(parent.transform);
  13. go.transform.localScale = Vector3.one;
  14. go.transform.localPosition = Vector3.zero;
  15. end

运行游戏,即可看到加载出来的界面。

二、事件响应

c#中可以使用事件监听的方法给UI组件添加事件。例如,添加按钮点击事件的方法如下:

  1. Button btn = go.GetComponent ();
  2. btn.onClick.AddListener(()=>{this.OnClick(go);});

然而在LuaFramework的API中,没能找到合适的方法,只能根据第三篇中“自定义API”的方法,自己编写一套了。编写UIEvent类,它包含用于添加监听事件的AdonClick和清除监听事件的ClearButtonClick方法,代码如下所示(完成后记得要“修改CustomSetting”和“生成wrap文件”)。

  1. using UnityEngine;
  2. using System.Collections;
  3. using LuaInterface;
  4. using UnityEngine.UI;
  5. public class UIEvent
  6. {
  7. //添加监听
  8. public static void AdonClick(GameObject go, LuaFunction luafunc)
  9. {
  10. if (go == null || luafunc == null) return;
  11. Button btn = go.GetComponent<Button> ();
  12. if (btn == null) return;
  13. btn.onClick.AddListener(()=>{luafunc.Call(go);});
  14. }
  15. //清除监听
  16. public static void ClearButtonClick(GameObject go)
  17. {
  18. if (go == null) return;
  19. Button btn = go.GetComponent<Button> ();
  20. if (btn == null) return;
  21. btn.onClick.RemoveAllListeners();
  22. }
  23. }

接下来测试下这套API,修改Main.lua,代码如下:

  1. --主入口函数。从这里开始lua逻辑
  2. function Main()
  3. end
  4. --加载完成后的回调--
  5. function OnLoadFinish(objs)
  6. --显示面板
  7. --事件处理
  8. local btn = go.transform:Find("Button").gameObject
  9. UIEvent.AdonClick(btn, OnClick)
  10. end
  11. function OnClick()
  12. print("触发按钮事件")
  13. end

运行游戏,点击按钮,OnClick方法即被调用。

三、界面管理器

LuaFramework提供了一套简单的(不完善的)界面管理器,具体代码请参见PanelManager类。PanelManager类的CreatePanel方法完成异步加载资源,在加载完成后,会设置面板的大小和位置,然后调用回调函数。与上面用lua加载界面的方法完全一样。

  1. public void CreatePanel(string name, LuaFunction func = null) {
  2. string assetName = name + "Panel";
  3. string abName = name.ToLower() + AppConst.ExtName;
  4. if (Parent.Find(name) != null) return;
  5. #if ASYNC_MODE
  6. ResManager.LoadPrefab(abName, assetName, delegate(UnityEngine.Object[] objs) {
  7. if (objs.Length == 0) return;
  8. GameObject prefab = objs[0] as GameObject;
  9. if (prefab == null) return;
  10. GameObject go = Instantiate(prefab) as GameObject;
  11. go.name = assetName;
  12. go.layer = LayerMask.NameToLayer("Default");
  13. go.transform.SetParent(Parent);
  14. go.transform.localScale = Vector3.one;
  15. go.transform.localPosition = Vector3.zero;
  16. go.AddComponent<LuaBehaviour>();
  17. if (func != null) func.Call(go);
  18. Debug.LogWarning("CreatePanel::>> " + name + " " + prefab);
  19. });
  20. #else
  21. GameObject prefab = ResManager.LoadAsset<GameObject>(name, assetName);
  22. if (prefab == null) return;
  23. GameObject go = Instantiate(prefab) as GameObject;
  24. go.name = assetName;
  25. go.layer = LayerMask.NameToLayer("Default");
  26. go.transform.SetParent(Parent);
  27. go.transform.localScale = Vector3.one;
  28. go.transform.localPosition = Vector3.zero;
  29. go.AddComponent<LuaBehaviour>();
  30. if (func != null) func.Call(go);
  31. Debug.LogWarning("CreatePanel::>> " + name + " " + prefab);
  32. #endif
  33. }

LuaFramework会给每个界面添加名为LuaBehaviour的组件,它拥有用于添加按钮监听的AddClick方法,相关代码如下,与UIEvent的AdonClick方法相似。

  1. using UnityEngine;
  2. using LuaInterface;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using System;
  6. using UnityEngine.UI;
  7. namespace LuaFramework {
  8. public class LuaBehaviour : View {
  9. private string data = null;
  10. private Dictionary<string, LuaFunction> buttons = new Dictionary<string, LuaFunction>();
  11. protected void Awake() {
  12. Util.CallMethod(name, "Awake", gameObject);
  13. }
  14. protected void Start() {
  15. Util.CallMethod(name, "Start");
  16. }
  17. protected void OnClick() {
  18. Util.CallMethod(name, "OnClick");
  19. }
  20. protected void OnClickEvent(GameObject go) {
  21. Util.CallMethod(name, "OnClick", go);
  22. }
  23. /// <summary>
  24. /// 添加单击事件
  25. /// </summary>
  26. public void AddClick(GameObject go, LuaFunction luafunc) {
  27. if (go == null || luafunc == null) return;
  28. buttons.Add(go.name, luafunc);
  29. go.GetComponent<Button>().onClick.AddListener(
  30. delegate() {
  31. luafunc.Call(go);
  32. }
  33. );
  34. }
  35. /// <summary>
  36. /// 删除单击事件
  37. /// </summary>
  38. /// <param name="go"></param>
  39. public void RemoveClick(GameObject go) {
  40. if (go == null) return;
  41. LuaFunction luafunc = null;
  42. if (buttons.TryGetValue(go.name, out luafunc)) {
  43. luafunc.Dispose();
  44. luafunc = null;
  45. buttons.Remove(go.name);
  46. }
  47. }
  48. /// <summary>
  49. /// 清除单击事件
  50. /// </summary>
  51. public void ClearClick() {
  52. foreach (var de in buttons) {
  53. if (de.Value != null) {
  54. de.Value.Dispose();
  55. }
  56. }
  57. buttons.Clear();
  58. }
  59. //-----------------------------------------------------------------
  60. protected void OnDestroy() {
  61. ClearClick();
  62. #if ASYNC_MODE
  63. string abName = name.ToLower().Replace("panel", "");
  64. ResManager.UnloadAssetBundle(abName + AppConst.ExtName);
  65. #endif
  66. Util.ClearMemory();
  67. Debug.Log("~" + name + " was destroy!");
  68. }
  69. }
  70. }

在LuaFramework的PureMVC架构中,如果要添加一个界面,需要编写对应的Controller、View,以及修改3个框架自带的lua文件,比较繁琐。因此在实际项目中有必要重写PanelManager,由它实现界面的加载及事件处理。

?