一文搞懂Unity接入toLua框架全流程
前言
最近打算做个微信小游戏上架,正好熟悉一下好久未使用过的lua语法,目前市面上还是很多厂家因为历史技术栈的原因,仍然在使用tolua,xlua等框架,而非这几年较为流行的C#热更框架,ilruntime或HybridCLR,所以对lua语法的熟悉和掌握还是非常有必要的。废话不多讲,开始介绍Unity接入tolua框架全流程。
下载tolua框架
点击链接:https://github.com/topameng/tolua?tab=readme-ov-file ,下载tolua框架,将它整个拉入到你新建的Unity工程中去
拉完后,目录结构如下:
因为套了一层tolua-master目录,所以要稍微调整下CustomSetting的路径
生成Wrap文件
点击菜单栏Lua/Generate All生成warp文件。
生成完成后可能报错,The type'ReadOnlySpan<int>'may not be used as a type argument:
解决方法是在修改ToLuaExport.cs中的IsObsolete方法,添加代码段:
public static bool IsObsolete(MemberInfo mb)
{
// 新增
if (mb is MethodBase method)
{
ParameterInfo[] parameters = method.GetParameters();
foreach (ParameterInfo parameter in parameters)
{
Type parameterType = parameter.ParameterType;
if (parameterType.IsGenericType)
{
Type genericTypeDefinition = parameterType.GetGenericTypeDefinition();
if (genericTypeDefinition == typeof(System.Span<>) ||
genericTypeDefinition == typeof(System.ReadOnlySpan<>))
return true;
}
}
}
// 新增
object[] attrs = mb.GetCustomAttributes(true);
for (int j = 0; j < attrs.Length; j++)
{
Type t = attrs[j].GetType() ;
if (t == typeof(System.ObsoleteAttribute) || t == typeof(NoToLuaAttribute) || t == typeof(MonoPInvokeCallbackAttribute) ||
t.Name == "MonoNotSupportedAttribute" || t.Name == "MonoTODOAttribute") // || t.ToString() == "UnityEngine.WrapperlessIcall")
{
return true;
}
}
if (IsMemberFilter(mb))
{
return true;
}
return false;
}
然后重新生成wrap文件即可。
在Unity2022版本下,导出可能还有这种报错,DelegateFactory.cs(764,60): error CS1661: Cannot convert anonymous method to type 'Application.MemoryUsageChangedCallback' because the parameter types do not match the delegate parameter types
解决方法是在DelegateFactory.cs文件中找到报错绑定的方法,我这里对应的方法是UnityEngine.Application.MemoryUsageChangedCallback,在ToLuaExport.cs的memberFilter添加“UnityEngine.Application.MemoryUsageChangedCallback”后,重新导出即可修复。
添加Lua脚本
找一个目录用作Lua脚本目录,我选择的路径是:
注意,在LuaConst.cs也要进行修改:
main.lua脚本展示
Unity主工程添加启动脚本
添加一个脚本用来启动Lua虚拟机,将它挂到一个GameObject上
using System;
using LuaInterface;
using UnityEngine;
namespace XiaYun.Core
{
public class GameLaunch : MonoBehaviour
{
private LuaState luaState;
private void Awake()
{
DontDestroyOnLoad(this);
luaState = new LuaState();
luaState.Start();
luaState.DoFile("Main");
LuaFunction main = luaState.GetFunction("Main.main");
main.Call();
main.Dispose();
}
}
}
启动
启动运行,顺利输出:Hello World
运行后可能会碰到这种报错:DllNotFoundException: tolua assembly:<unknown assembly> type:<unknown type> member:(null)
不要慌,重启一下unity即可(反正我是这么解决的,真他娘玄学)
接入断点
我使用的Lua编辑器是Idea,安装插件EmmyLua
将Lua关联这2个文件后缀,此时就能够高亮Lua语法了
然后按照如下顺序,添加Debug配置:
添加好后,将文本框内的内容复制到mian.lua文件头后即可。
点亮这个以后,即可断点lua代码:
接入Unity端API高亮
Idea编辑器安装插件EmmyLua-Unity:
在Unity端新增编辑器脚本:
using System;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;
namespace EmmyLua
{
enum TypeKind
{
Class,
Array,
}
enum Proto
{
Lib,
Ping
}
[InitializeOnLoad]
class EmmyLuaService
{
private static Socket socket;
private static Thread receiveThread;
private static int PORT = 996;
private static bool doTryLater;
private static DateTime lastTime;
private static bool connected;
private static bool running;
private const string KEY_EMMY_SERVICE_ENABLE = "emmy.service.enable";
private const string KEY_EMMY_SERVICE_DEBUG_ENABLE = "emmy.service.debug.enable";
private const int ERROR_CODE_ABORT = 10053;
private const int ERROR_CODE_ACTIVELY_REJECT = 10061;
private static bool IsEnable
{
get { return EditorPrefs.GetBool(KEY_EMMY_SERVICE_ENABLE); }
set { EditorPrefs.SetBool(KEY_EMMY_SERVICE_ENABLE, value); }
}
[MenuItem("Lua/EmmyLua/Enable")]
static void EnableService()
{
IsEnable = true;
StartConnect();
}
[MenuItem("Lua/EmmyLua/Enable", true)]
static bool EnableServiceCheck()
{
return !IsEnable;
}
[MenuItem("Lua/EmmyLua/Disable")]
static void DisableService()
{
IsEnable = false;
Stop();
}
[MenuItem("Lua/EmmyLua/Disable", true)]
static bool DisableServiceCheck()
{
return IsEnable;
}
private static bool IsDebugEnable
{
get { return PlayerPrefs.GetInt(KEY_EMMY_SERVICE_DEBUG_ENABLE) == 1; }
set { PlayerPrefs.SetInt(KEY_EMMY_SERVICE_DEBUG_ENABLE, value ? 1 : 0); }
}
[MenuItem("Lua/调试开启")]
static void EnableDebug()
{
IsDebugEnable = true;
}
[MenuItem("Lua/调试开启", true)]
static bool EnableDebugCheck()
{
return !IsDebugEnable;
}
[MenuItem("Lua/调试关闭")]
static void DisableDebug()
{
IsDebugEnable = false;
}
[MenuItem("Lua/调试关闭", true)]
static bool DisableDebugCheck()
{
return IsDebugEnable;
}
static EmmyLuaService()
{
EditorApplication.update += Update;
StartConnect();
}
static void StartConnect()
{
if (running || !IsEnable)
return;
running = true;
connected = false;
doTryLater = false;
try
{
if (socket != null)
socket.Close();
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.BeginConnect("127.0.0.1", PORT, OnConnect, socket);
}
catch (Exception e)
{
TryLater();
}
}
private static void OnConnect(IAsyncResult ar)
{
try
{
socket.EndConnect(ar);
connected = true;
SendData(socket);
}
catch (Exception e)
{
System.ComponentModel.Win32Exception ee = e as System.ComponentModel.Win32Exception;
if (ee == null) ee = e.InnerException as System.ComponentModel.Win32Exception;
if (ee.ErrorCode != ERROR_CODE_ABORT && ee.ErrorCode != ERROR_CODE_ACTIVELY_REJECT)
{
UnityEngine.Debug.LogError(ee.ErrorCode);
UnityEngine.Debug.LogError($"ee.Message--->{ee.Message}");
}
TryLater();
}
}
private static void TryLater()
{
running = false;
connected = false;
doTryLater = true;
lastTime = DateTime.Now;
}
private static void Stop()
{
if (running)
{
running = false;
connected = false;
doTryLater = false;
if (socket != null)
socket.Close();
}
}
private static void Update()
{
if (!IsEnable)
return;
var sp = DateTime.Now - lastTime;
if (sp.TotalSeconds > 5)
{
if (connected)
{
Ping();
}
else if (doTryLater)
{
StartConnect();
}
}
}
[DidReloadScripts]
static void UpdateScripts()
{
StartConnect();
}
private static void WriteString(BinaryWriter writer, string value)
{
var encoding = Encoding.UTF8;
var chars = encoding.GetBytes(value);
writer.Write(chars.Length);
writer.Write(chars);
}
private static void WriteType(BinaryWriter write, Type type)
{
if (type.IsArray)
{
write.Write((byte) TypeKind.Array);
WriteType(write, type.GetElementType());
}
else
{
write.Write((byte) TypeKind.Class);
WriteString(write, type.FullName ?? "any");
}
}
private static void Ping()
{
using (var buf = new MemoryStream())
{
var writer = new BinaryWriter(buf);
writer.Write(8);
writer.Write((int) Proto.Ping);
try
{
var bytes = buf.GetBuffer();
socket.Send(bytes, 8, SocketFlags.None);
}
catch (Exception e)
{
TryLater();
}
}
}
private static void SendData(Socket client)
{
var buf = new MemoryStream();
var writer = new BinaryWriter(buf);
writer.Seek(8, SeekOrigin.Begin);
var types = GetTypes();
foreach (var type in types)
{
WriteTypeData(type, writer);
}
writer.Flush();
// write size and proto
var len = (int) buf.Length;
writer.Seek(0, SeekOrigin.Begin);
writer.Write(len);
writer.Write((int) Proto.Lib);
writer.Flush();
// send
client.Send(buf.GetBuffer(), len, SocketFlags.None);
writer.Close();
}
private static void WriteTypeData(Type type, BinaryWriter writer)
{
var fullName = type.FullName;
if (!string.IsNullOrEmpty(fullName))
{
// full name
WriteString(writer, fullName);
// base type full name
{
string baseTypeFullName = null;
if (type.BaseType != null)
baseTypeFullName = type.BaseType.FullName;
writer.Write(baseTypeFullName != null);
if (baseTypeFullName != null)
WriteString(writer, baseTypeFullName);
}
// fields
var fields =
type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
writer.Write(fields.Length);
foreach (var fi in fields)
{
WriteString(writer, fi.Name);
WriteType(writer, fi.FieldType);
}
// properties
var properties =
type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static| BindingFlags.DeclaredOnly);
writer.Write(properties.Length);
foreach (var pi in properties)
{
WriteString(writer, pi.Name);
WriteType(writer, pi.PropertyType);
}
// methods
var methods =
(from mi in type.GetMethods(BindingFlags.Public | BindingFlags.Instance| BindingFlags.Static | BindingFlags.DeclaredOnly)
where !mi.Name.StartsWith("get_") && !mi.Name.StartsWith("set_")
select mi).ToArray();
writer.Write(methods.Count());
foreach (var mi in methods)
{
// name
WriteString(writer, mi.Name);
// is static
writer.Write(mi.IsStatic);
// parameters
var parameterInfos = mi.GetParameters();
writer.Write(parameterInfos.Length);
foreach (var pi in parameterInfos)
{
WriteString(writer, pi.Name);
WriteType(writer, pi.ParameterType);
}
// returns
WriteType(writer, mi.ReturnType);
}
}
}
private static Type[] GetTypes()
{
var unityTypes = from assembly in AppDomain.CurrentDomain.GetAssemblies()
where !(assembly.ManifestModule is ModuleBuilder)
&& !IsExcludedAssembly(assembly)
from type in assembly.GetExportedTypes()
where type.BaseType != typeof(MulticastDelegate)
&& !type.IsInterface
&& !type.IsEnum
//&& !type.IsGenericType
//&& !type.IsGenericTypeDefinition
&& !type.IsNested
&& !IsExcluded(type)
select type;
var arr = unityTypes.ToArray();
return arr;
}
private static bool IsExcluded(Type type)
{
return false;
}
private static readonly System.Collections.Generic.Dictionary<string, bool> excludedAssembly = new System.Collections.Generic.Dictionary<string, bool>
{
["DOTweenPro.dll"] = true,
["UnityEditor.dll"] = true,
["DOTweenEditor.dll"] = true,
["DOTweenProEditor.dll"] = true,
["UnityEditor.dll"] = true
};
private static bool IsExcludedAssembly(Assembly assembly)
{
return (excludedAssembly.ContainsKey(assembly.ManifestModule.Name));
}
}
}
开启这个后:
就能顺利在Idea编辑器下看到自动提示Unity端Api了:
本文作者:陈侠云
本文链接:https://www.cnblogs.com/chenxiayun/p/18748041
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步