如果|

陈侠云

园龄:2年10个月粉丝:1关注:1

一文搞懂Unity接入toLua框架全流程

前言

最近打算做个微信小游戏上架,正好熟悉一下好久未使用过的lua语法,目前市面上还是很多厂家因为历史技术栈的原因,仍然在使用tolua,xlua等框架,而非这几年较为流行的C#热更框架,ilruntime或HybridCLR,所以对lua语法的熟悉和掌握还是非常有必要的。废话不多讲,开始介绍Unity接入tolua框架全流程。

下载tolua框架

点击链接:https://github.com/topameng/tolua?tab=readme-ov-file ,下载tolua框架,将它整个拉入到你新建的Unity工程中去
image
拉完后,目录结构如下:
image
因为套了一层tolua-master目录,所以要稍微调整下CustomSetting的路径
image

生成Wrap文件

点击菜单栏Lua/Generate All生成warp文件。
生成完成后可能报错,The type'ReadOnlySpan<int>'may not be used as a type argument:
image
解决方法是在修改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
image
解决方法是在DelegateFactory.cs文件中找到报错绑定的方法,我这里对应的方法是UnityEngine.Application.MemoryUsageChangedCallback,在ToLuaExport.cs的memberFilter添加“UnityEngine.Application.MemoryUsageChangedCallback”后,重新导出即可修复。

添加Lua脚本

找一个目录用作Lua脚本目录,我选择的路径是:
image
注意,在LuaConst.cs也要进行修改:
image

main.lua脚本展示

image

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
image
运行后可能会碰到这种报错:DllNotFoundException: tolua assembly:<unknown assembly> type:<unknown type> member:(null)
不要慌,重启一下unity即可(反正我是这么解决的,真他娘玄学)

接入断点

我使用的Lua编辑器是Idea,安装插件EmmyLua
image
将Lua关联这2个文件后缀,此时就能够高亮Lua语法了
image
然后按照如下顺序,添加Debug配置:
image
添加好后,将文本框内的内容复制到mian.lua文件头后即可。
点亮这个以后,即可断点lua代码:
image

接入Unity端API高亮

Idea编辑器安装插件EmmyLua-Unity:
image
在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));
		}
	}
}

开启这个后:
image
就能顺利在Idea编辑器下看到自动提示Unity端Api了:
image

本文作者:陈侠云

本文链接:https://www.cnblogs.com/chenxiayun/p/18748041

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   陈侠云  阅读(11)  评论(0编辑  收藏  举报
//雪花飘落效果
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起