最近项目内测出现了许多问题,但是由于项目纯C#开发。导致客户端这边的bug需要重新出包才能解决,看着bug在那毫无办法。项目已经到了中后期使用Lua重写游戏几乎不可能,而且工程量巨大。看了XLua相关介绍,觉得是一个不错的解决方案。所以基于这个问题引入了XLua来临时解决部分线上bug。
XLua简介
此次引用XLua官方说明。XLua (Github地址)
C#下Lua编程支持
xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用。
xLua的突破
xLua在功能、性能、易用性都有不少突破,这几方面分别最具代表性的是:
- 可以运行时把C#实现(方法,操作符,属性,事件等等)替换成lua实现;
- 出色的GC优化,自定义struct,枚举在Lua和C#间传递无C# gc alloc;
- 编辑器下无需生成代码,开发更轻量;
- 更详细的特性、平台支持介绍请看这里。
引入XLua
1、下载XLua工程把Assets和Tools拷贝到工程目录里面。
2、创建一个LuaManager.cs 代码如下:
using UnityEngine;
using XLua;
using System.IO;
public class LuaManager{
private static LuaManager uniqueLuaManager;
private static LuaEnv luaState;
private LuaManager(){
}
public static LuaManager GetInstance(){
if (uniqueLuaManager == null)
{
uniqueLuaManager = new LuaManager();
luaState = new LuaEnv();
}
return uniqueLuaManager;
}
public static void Init(){
LuaManager.GetInstance();
luaState.AddLoader(CustomLoader);
luaState.DoString("require 'Main'");
}
public static void DoString(string str){
if (luaState == null)
{
Init();
}
object[] rets = luaState.DoString(str);
foreach(var o in rets){
Debug.Log(o);
}
}
static byte[] CustomLoader(ref string fileName){
string luaPath = Application.dataPath + "/Resources/Lua/" + fileName + ".lua";
string strLuaContent = File.ReadAllText(luaPath);
byte[] block = null;
block = System.Text.Encoding.UTF8.GetBytes(strLuaContent);
return block;
}
public static void Destory(){
luaState.Dispose();
}
}
调用LuaManager.Init()初始化。这个初始化可以放到刚进入游戏的时候初始化的时候。"Assets/Resources/"下创建Lua文件夹,里面新建Main.lua这个为Lua启动的第一个文件。后续Lua相关的代码可以从这里开始。这之后Lua就可以正常使用了。在其他地方也可以使用LuaManager.DoString()来运行一段Lua代码。
3、由于我们游戏内是需要使用热更CS功能。在上面之前CS已经可以运行一段Lua代码。所以采用的方案是把需要热更的代码放到CDN上。每次进入游戏的时候去CDN拉取热更的Lua代码,通过Lua代码来HotFix我们的CS代码。
Lua Hotfix Cs
1、在XLua Github上,作者已经写了关于如何引入Hotfix(热补丁操作指南)。大体照着这个步骤来即可。这里需要特别注意的一个地方是,在有任何修改CS代码导致重新编译会使得之前Lua里面的xlua.hotfix失效。所以,每次有修改的时候都需要重新generate Code和hotfix inject in Editor。建议把这两个步骤放到每次出包的时候主动调用这两个,重新注入一下代码。
2、关于热更新文档里面说的标识要热更新的类型有两种方式,建议大家用第二种。第一种的话需要对要热更新的类都做[Hotfix]标签。在开发阶段我们并不知道哪些类会出现bug。所以采用第二种之后不需要对类做Hotfix标签了。第二种需要在Assets/Editor文件夹下创建HotfixCfg.cs(注意,一定要在Editor目录下)。代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using UnityEngine;
using XLua;
public static class HotfixCfg
{
[Hotfix]
public static List by_property
{
get
{
var allTypes = Assembly.Load("Assembly-CSharp").GetTypes();
var nameSpace = new List();
foreach (var t in allTypes)
{
if (t.Namespace != null && (t.Namespace.StartsWith("这里填你的命名空间", StringComparison.CurrentCulture)))
{
if (!nameSpace.Contains(t.Namespace))
{
nameSpace.Add(t.Namespace);
}
}
}
var retList = new List();
var sb = new StringBuilder();
foreach (var t in allTypes)
{
if (nameSpace.Contains(t.Namespace))
{
retList.Add(t);
sb.AppendLine(t.FullName);
}
}
File.WriteAllText(Path.Combine(Application.dataPath, "../HotTypes.txt"), sb.ToString());
return retList;
}
}
}
3、最后,每次热更之前可以把代码先放到Main.Lua测试其正确性,测试无误之后放到CDN上的Hotfix.lua里面。进游戏的时候加载Hotfix.lua里面的内容,调用LuaManager.DoString()运行这段热更代码即可。
这里只用到了Lua热更Cs代码。个人还是比较建议项目前期的时候规划这些。用了几年的CS+Lua的方式开发项目,个人认为用Lua来写界面逻辑比较适合游戏这种版本迭代快的项目。至于性能方面不管是XLua、Slua和Tolua都已经优化的不错了,而且现在手机的性能一直攀升。这方面其实问题不是特别大,有些特别耗性能的可以放到CS那边做,这些部分也不会经常改动。其他UI相关的还是用Lua比较好,而且可以小版本迭代,不需要更新整包。
-------------------------------------------------分割线------------------------------------------------------------------------
看了下XLua的配置,他们已经给出了CS热更的推荐配置。如下xLua的配置:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using UnityEngine;
using XLua;
using System.Linq;
public static class HotfixCfg
{
/***************热补丁可以参考这份自动化配置***************/
[Hotfix]
static IEnumerable HotfixInject
{
get
{
return (from type in Assembly.Load("Assembly-CSharp").GetExportedTypes()
where type.Namespace == null || !type.Namespace.StartsWith("XLua")
select type);
}
}
//--------------begin 热补丁自动化配置-------------------------
static bool hasGenericParameter(Type type)
{
if (type.IsGenericTypeDefinition) return true;
if (type.IsGenericParameter) return true;
if (type.IsByRef || type.IsArray)
{
return hasGenericParameter(type.GetElementType());
}
if (type.IsGenericType)
{
foreach (var typeArg in type.GetGenericArguments())
{
if (hasGenericParameter(typeArg))
{
return true;
}
}
}
return false;
}
static bool typeHasEditorRef(Type type)
{
if (type.Namespace != null && (type.Namespace == "UnityEditor" || type.Namespace.StartsWith("UnityEditor.")))
{
return true;
}
if (type.IsNested)
{
return typeHasEditorRef(type.DeclaringType);
}
if (type.IsByRef || type.IsArray)
{
return typeHasEditorRef(type.GetElementType());
}
if (type.IsGenericType)
{
foreach (var typeArg in type.GetGenericArguments())
{
if (typeHasEditorRef(typeArg))
{
return true;
}
}
}
return false;
}
static bool delegateHasEditorRef(Type delegateType)
{
if (typeHasEditorRef(delegateType)) return true;
var method = delegateType.GetMethod("Invoke");
if (method == null)
{
return false;
}
if (typeHasEditorRef(method.ReturnType)) return true;
return method.GetParameters().Any(pinfo => typeHasEditorRef(pinfo.ParameterType));
}
// 配置某Assembly下所有涉及到的delegate到CSharpCallLua下,Hotfix下拿不准那些delegate需要适配到lua function可以这么配置
[CSharpCallLua]
static IEnumerable AllDelegate
{
get
{
BindingFlags flag = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
List allTypes = new List();
var allAssemblys = new Assembly[]
{
Assembly.Load("Assembly-CSharp")
};
foreach (var t in (from assembly in allAssemblys from type in assembly.GetTypes() select type))
{
var p = t;
while (p != null)
{
allTypes.Add(p);
p = p.BaseType;
}
}
allTypes = allTypes.Distinct().ToList();
var allMethods = from type in allTypes
from method in type.GetMethods(flag)
select method;
var returnTypes = from method in allMethods
select method.ReturnType;
var paramTypes = allMethods.SelectMany(m => m.GetParameters()).Select(pinfo => pinfo.ParameterType.IsByRef ? pinfo.ParameterType.GetElementType() : pinfo.ParameterType);
var fieldTypes = from type in allTypes
from field in type.GetFields(flag)
select field.FieldType;
return (returnTypes.Concat(paramTypes).Concat(fieldTypes)).Where(t => t.BaseType == typeof(MulticastDelegate) && !hasGenericParameter(t) && !delegateHasEditorRef(t)).Distinct();
}
}
//--------------end 热补丁自动化配置-------------------------
}