前言
在现在商业项目中,Lua被使用的很多,不光是他可以很快的书写逻辑,而且能够方便的进行热更新等,也被证实了在很多成功项目案例中。
同样前几年NGUI被应用与Unity很多,随着Unity对自己原生UI的完善,UGUI也越来越容易被大家接受,也便于被大家使用。这篇就介绍UGUI+XLua的UI框架。
还有FairyGUI也是很不错的,以后有时间再做介绍。
搭建框架
-
添加两个空的场景:驱动场景 Drivers和UI面板UGameStart场景
-
将Drivers和UGameStart场景添加到Build Settings中
-
编写代码Drivers.cs(主要用于设备的适配等,还要包括一些更新判断等)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Reflection;
using UnityEngine.SceneManagement;
public class Drivers : MonoBehaviour
{
//主程序逻辑入口类
public string MainProc = "UMain";
private Assembly assembly;
private void Start()
{
//防止加载场景后该脚本失效,加载完成后再销毁
DontDestroyOnLoad(gameObject);
//设配相关操作
//加载场景,开始游戏主逻辑
StartCoroutine(OnStart());
}
private IEnumerator OnStart()
{
assembly = Assembly.Load("Assembly-CSharp");
yield return StartCoroutine(LoadingScene());
GameObject camera = Camera.main.gameObject;
DontDestroyOnLoad(camera);
//主相机上添加主逻辑脚本
camera.AddComponent(assembly.GetType(MainProc));
Destroy(gameObject);
}
//加载游戏逻辑场景UGameStart
private IEnumerator LoadingScene()
{
AsyncOperation ao = SceneManager.LoadSceneAsync("UGameStart");
yield return ao;
}
}
- 编写UMain.cs,UMainModule.cs,AppBase.cs,CAppModule.cs
(主要用于启动游戏的主逻辑,控制游戏模块,以及Update等方法的驱动)
UMain:AppBase 挂在MainCamera上,进行驱动
AppBase对CAppModule进行管理,继承于MonoBehaviour
UMainModule中添加相关模块 其中的模块都继承与CAppModule
CAppModule提供几个需要实现的方法
Init Destroy Update LateUpdate FixedUpdate
- 编辑场景UGameStart
- 修改MainCamera的Culling Mask 去掉UI可见
- 创建EmptyGameObject 归位0,0,0 ,命名为UIRoot
- 在UIRoot下创建Camera(UICamera),设置Culling Mask为只可见UI,并添加设置 Tag:UICamera,Layer:UI
- UIRoot下添加Canvas,并设置Canvas组件的Render Mode为Screen Space-Camera,将UICamera赋值
- 设置Canvas Scaler组件 UI Scale Mode为:Scale With Screen Size:设置分辨率为750x1344(这里需要后期读取设备属性进行动态修改)
- 设置Graphic RayCaster组件 Blocking Mask为:UI 和Ignore Raycast
- 新建几个UI显示的层级:CloseLayer,LowLayer,MiddleLayer,HighLayer,TipLayer等,显示层级逐一提高。
- CloseLayer 设置RectTranform Left Top Right Bottom 分为-5000,5000,5000,-5000 为不见层
- LowLayer设置PosZ为-500 MiddleLayer:-1000 HighLayer:-1500 TipLayer:-2000,并设置他们的Canvas组件:勾选Override Sorting,SortingLayer分别设置为LowLayer,MiddleLayer,HighLayer,TipLayer(需要添加上上面4个层)
- 以上完成UGameStart编辑,可自行扩展,保存场景
- 接入XLua
- 在UMainModule.cs中添加LuaModule:CAppModule
using System;
using System.IO;
using UnityEngine;
using XLua;
//UMain的模块类
/// <summary>
/// Lua模块
/// </summary>
public class LuaModule : CAppModule
{
private static LuaEnv luaEnv;
public static LuaTable CurEnvGlobal()
{
if (luaEnv != null)
{
return luaEnv.Global;
}
return null;
}
[CSharpCallLua]
private delegate void UpdateD(float time, float unscaleTime, float deltaTime, float unscaleDeltaTime, float realtimeSinceStartUp);
[CSharpCallLua]
private delegate void LateUpdateD();
[CSharpCallLua]
private delegate void FixedUpdateD(float fixedDeltaTime);
private UpdateD updateD = null;
private LateUpdateD lateUpdateD = null;
private FixedUpdateD fixedUpdateD = null;
public LuaModule()
{
}
private Byte[] MyLoader(ref string filepath)
{
filepath = Application.dataPath + "/Lua/" + filepath.Replace('.', '/') + ".lua";
if(File.Exists(filepath))
{
return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(filepath));
}
return null;
}
public override void Init()
{
name = "LuaModule";
luaEnv = new LuaEnv();
luaEnv.AddLoader(MyLoader);
try
{
//该LuaStart 为调用的第一个LuaStart.lua文件
luaEnv.DoString("require 'LuaStart'");
}
catch(Exception e)
{
Debug.Log(e);
return;
}
updateD = luaEnv.Global.GetInPath<UpdateD>("Update");
fixedUpdateD = luaEnv.Global.GetInPath<FixedUpdateD>("FixedUpdate");
lateUpdateD = luaEnv.Global.GetInPath<LateUpdateD>("LateUpdate");
}
public override void Update()
{
updateD?.Invoke(Time.time,Time.unscaledTime,Time.deltaTime,Time.unscaledDeltaTime,Time.realtimeSinceStartup);
if(luaEnv!=null)
{
luaEnv.Tick();
}
}
public override void LateUpdate()
{
lateUpdateD?.Invoke();
}
public override void FixedUpdate()
{
fixedUpdateD?.Invoke(Time.fixedDeltaTime);
}
public override void Destroy()
{
updateD = null;
fixedUpdateD = null;
lateUpdateD = null;
luaEnv.Dispose();
luaEnv = null;
}
public void StartGame()
{
luaEnv.Global.Get<LuaFunction>("StartGame").Call();
}
public void ExitGame()
{
luaEnv.Global.Get<LuaFunction>("ExitGame").Call();
}
}
在UMain.cs中初始化,调用StartGame即可开始Lua游戏逻辑
至此C#层的启动逻辑书写完毕。
扩展:C#层给与了充足的模块扩展空间,之后还需要封装网络层
- 编写Lua相关文件
- LuaStart.lua
--设置垃圾回收的参数 --其他参数 print("加载成功") require("Main")
- Main.lua
function Update(time,unscaledTime,deltaTime,unscaledDeltaTime,realtimeSinceStartup) end function LateUpdate() end function FixedUpdate(fixedDeltaTime) end function Init() end function StartGame() print("StartGame") end function ExitGame() print("ExitGame") end
完成以上:如果出现InvalidCastExcaption:This type must add to CsharpCallLua...错误:1.File->Build Settings->Player Setting->OtherSettings->Api Compatibility Level*改为 .NET 4.x;2.XLua->Clear Generated Code即可。
运行,即可加载LuaStart,以及使用Lua中的一些函数。
LuaUI框架封装
- 在Lua文件夹下创建Core->UI文件夹,存放UI框架核心类
- 在Lua下创建LuaInclude.lua与CSInclude.lua分别用于Lua的载入与CS类的引用
- 在Lua->Core文件夹下创建Class.lua (lua面向对象Class相关方法)和CoreInclude (用于引用Core文件夹中的Lua文件)
- Lua->Core->UI目录下创建InterfaceView.lua(UI类接口),BaseView.lua(基础UI类) ,UIClassHelper.lua(UI类助手类),UIManager(UI的创建,销毁等)
- 在Lua下创建Module文件夹,用于存放UI模块。
- 在Lua->Module 下创建ModuleInclude.lua(用于模块的导入),再创建LoginView文件夹(用登录页面作为范例)
- 再Lua->Module->LoginView下创建:LoginControl.lua,LoginDefine.lua,LoginEvent.lua,LoginModel.lua,LoginView.lua,RegisterView.lua,LoginViewInclude.lua
框架核心:每个模块应该有以下文件
1.xxxControl.lua:该lua用于与后端进行交互,书写该模块网络协议的监听
2.xxxModel.lua:该lua用于该页面的动态数据存储,例如红点数据,同时提供其他页面从该页面数据中获取数据的方法
3.xxxDefine.lua:该lua用于该页面的静态数据定义,例如子弹的上限值(策划后期可能会更改)
4.xxxEvent.lua:该lua用于该页面的相关事件声明,例如LoginEvent.LoginFinish(登录完成事件)
5.xxxView.lua:该lua用于每个模块下相关的页面定义,主要书写页面的具体业务逻辑
6.xxxInclude.lua:该lua用于引入该文件夹下的lua文件,便于管理
具体项目结构如下
简单封装资源加载工具(ResourceManager)和UGUI工具(UGUITools)
- ResourceManager.cs
using UnityEngine;
public static class ResourceManager
{
public static Transform LoadUIPrefab(string name)
{
return Resources.Load<Transform>(name);
}
public static Sprite LoadSprite(string path)
{
return Resources.Load<Sprite>(path);
}
}
- UGUITools.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public static class UGUITools
{
public static GameObject FindChild(GameObject obj,string path)
{
if(obj!=null)
{
Transform tf = obj.transform.Find(path);
if (tf != null)
return tf.gameObject;
}
return null;
}
public static Component FindInChild(string componentTypeName,GameObject gameObject,string path="")
{
Transform tf;
Component comp;
if(string.IsNullOrEmpty(path))
{
tf = gameObject.transform;
}
else
{
tf = gameObject.transform.Find(path);
}
if (tf!=null)
{
comp = tf.GetComponent(componentTypeName);
if (comp != null)
return comp;
}
return null;
}
public static bool IsNil(object obj)
{
if(obj!=null)
{
if(obj.Equals(null))
{
return true;
}
return false;
}
return true;
}
public static GameObject SimpleClone(GameObject obj)
{
if(obj!=null)
{
Transform tf = obj.transform.parent;
if(tf!=null)
{
return GameObject.Instantiate(obj, tf);
}
else
{
return GameObject.Instantiate(obj);
}
}
return null;
}
public static Image CreateImage(string name,Transform parent=null)
{
GameObject imgGo = new GameObject(name);
if(parent!=null)
{
imgGo.transform.SetParent(parent,false);
}
Image img = imgGo.AddComponent<Image>();
return img;
}
}
完成以上辅助工具后,便可开始第一个案例:登录与注册页面
XLua需要调用C#或者引擎等相关代码,需要再Editor目录下创建一个GenConfig.cs进行配置