以下内容是根据Unity 2020.1.0f1版本进行编写的


1、前言

我自己有个独游项目,一开始是用C#写的,后面接入了tolua框架改用lua写,有后端是手游,但是发现手游上线TapTap十分麻烦,并且对于有后端的游戏更麻烦,思考了很久,决定改成单机游戏上微信小游戏。

微信小游戏是基于webGL的,我打算打一个整包,不用cdn,试了好久,这个情况下,对于lua代码文件和一些动态加载的资源,无法找到路径加载,是因为没有被直接引用的资源不会被打进包里,因此需要改一下项目框架。

2、目的

因为没有直接引用的资源在打包时不会被打进整包里,因此不能像DoFile那样按路径查找然后按路径加载lua代码文件。

但是原本的资源加载模块就是按路径加载的,并且已经做了不少功能了,tolua框架加载模块(module)也是通过DoFile实现的,本质上也是加载对应路径的lua文件,将其作为module(模块)存储在已加载的模块列表中。

所以目的就是希望修改加载模块,在尽可能小改动的情况下,修改框架希望加载整包的情况下保留资源路径引用。并且修改tolua框架的加载模块逻辑,看能通过什么方法把所需的lua代码加载进来。

3、思考

微信小游戏是支持Resources加载的,一开始是想着把资源都放在Resources文件夹下,但是这样的话,其实改动挺大的了,很多路径的前缀都要改成Resources开头,并且tolua框架的DoFile方法是通过File. ReadAllBytes来加载lua文件的,改成Resources也很麻烦。

那么,还有没有更简单的办法呢?

最后我想到,可以弄一个映射关系的脚本,将脚本挂载到场景上,然后通过工具将项目全部的lua文件和资源文件都添加引用,添加引用时将unity路径作为key,资源作为value,这样加载时仅需改成从该脚本上根据key获取对应value就行。

对于lua文件,修改资源导入后处理,将lua文件识别问TextAsset文件,使用时,先加载到lua的TextAsset文件,然后读取其中的text文本,接着将tolua框架中读取框架lua文件和读取自己写的lua文件都改成使用DoFile实现。

4、实现

首先,将项目转换成WebGL项目是必不可少的,可以参考下面几个大佬的步骤:

unity webgl Tolua/Ulua:https://zhuanlan.zhihu.com/p/486826570
U3d WebGL使用ToLua:https://blog.csdn.net/qq_35267906/article/details/89376798
还有一些相关的文档:
小游戏微信官方文档:https://developers.weixin.qq.com/minigame/dev/guide/
小程序微信官方文档:
https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/basic.html
了解Unity WebGL中的内存:
https://unity.com/cn/blog/engine-platform/understanding-memory-in-unity-webgl

接着,实现上述的映射脚本:
实现lua脚本的引用映射:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LuaScriptRef : MonoBehaviour
{
    public List<string> luaFramework_modnames = new List<string>();
    public List<TextAsset> luaFramework_mod_textAssets = new List<TextAsset>();
    public Dictionary<string, TextAsset> luaFrameworkDic = new Dictionary<string, TextAsset>();

    public List<string> luaLogic_modnames = new List<string>();
    public List<TextAsset> luaLogic_mod_textAssets = new List<TextAsset>();
    public Dictionary<string, TextAsset> luaLogicDic = new Dictionary<string, TextAsset>();

    private void Awake()
    {
        luaFrameworkDic.Clear();
        luaLogicDic.Clear();
        for (int i = 0;i < luaFramework_modnames.Count;i++)
        {
            luaFrameworkDic.Add(luaFramework_modnames[i], luaFramework_mod_textAssets[i]);
        }
        for (int i = 0; i < luaLogic_modnames.Count; i++)
        {
            luaFrameworkDic.Add(luaLogic_modnames[i], luaLogic_mod_textAssets[i]);
        }
    }

    public TextAsset GetLuaTextAsset(string modname)
    {
        if (modname.EndsWith(".lua"))
        {
            modname = modname.Substring(0, modname.Length - 4);
        }
        if (modname.Contains("."))
        {
            modname = modname.Split('.')[modname.Split('.').Length - 1];
        }
        if (modname.Contains("/"))
        {
            modname = modname.Split('/')[modname.Split('/').Length - 1];
        }
        if (modname.Contains("\\"))
        {
            modname = modname.Split('\\')[modname.Split('\\').Length - 1];
        }
        //Debug.Log("GetLuaTextAsset:" + modname);
        if (luaFrameworkDic.ContainsKey(modname))
        {
            return luaFrameworkDic[modname];
        }
        if (luaLogicDic.ContainsKey(modname))
        {
            return luaLogicDic[modname];
        }
        Debug.LogError("找不到模块:" + modname);
        return null;
    }
}

实现lua脚本映射类的Editor类,增加自动添加引用的逻辑:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;

[CustomEditor(typeof(LuaScriptRef))]
public class AutoSetLuaScriptRef : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        LuaScriptRef targetScript = (LuaScriptRef)target;
        List<string> frameworkNameList = targetScript.luaFramework_modnames;
        List<TextAsset> frameworkTextAssetList = targetScript.luaFramework_mod_textAssets;
        List<string> logicNameList = targetScript.luaLogic_modnames;
        List<TextAsset> logicTextAssetList = targetScript.luaLogic_mod_textAssets;

        if (GUILayout.Button("添加数据"))
        {
            frameworkNameList.Clear();
            frameworkTextAssetList.Clear();

            logicNameList.Clear();
            logicTextAssetList.Clear();

            string luaFrameworkPath = "/Framework/LuaFramework";
            FileInfo[] fileInfos = new DirectoryInfo(Application.dataPath + luaFrameworkPath).GetFiles("*.lua", SearchOption.AllDirectories);
            foreach(var fileInfo in fileInfos)
            {
                TextAsset t = AssetDatabase.LoadAssetAtPath<TextAsset>(AppConst.UnifyPath(fileInfo.FullName));
                frameworkNameList.Add(fileInfo.Name.Split('.')[0]);
                frameworkTextAssetList.Add(t);
            }

            string luaLogicPath = "/Scripts/Lua/logic";
            FileInfo[] fileInfos1 = new DirectoryInfo(Application.dataPath + luaLogicPath).GetFiles("*.lua", SearchOption.AllDirectories);
            foreach(var fileInfo in fileInfos1)
            {
                TextAsset t = AssetDatabase.LoadAssetAtPath<TextAsset>(AppConst.UnifyPath(fileInfo.FullName));
                string[] lineStrs = t.text.Split(new string[] { "\n" }, System.StringSplitOptions.RemoveEmptyEntries);
                string moduleStr = "";
                string moduleName = "";
                foreach (var line in lineStrs)
                {
                    if (line.StartsWith("module"))
                    {
                        moduleStr = line;
                    }
                }
                if (!string.IsNullOrEmpty(moduleStr))
                {
                    string modulePath = moduleStr.Split('\"')[1];
                    string[] moduleNameInfo = modulePath.Split('.');
                    moduleName = moduleNameInfo[moduleNameInfo.Length - 1];
                }
                else
                {
                    moduleName = fileInfo.Name.Split('.')[0];
                }
                logicNameList.Add(moduleName);
                logicTextAssetList.Add(t);
            }

            EditorUtility.SetDirty(targetScript);
        }
    }
}

实现资源的引用映射:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ResourceRef : MonoBehaviour
{
    public List<string> resource_sprite_paths = new List<string>();
    public List<Sprite> resource_sprites = new List<Sprite>();
    public List<string> resource_prefab_paths = new List<string>();
    public List<GameObject> resource_prefabs = new List<GameObject>();
    public Dictionary<string, Sprite> resourceImageDic = new Dictionary<string, Sprite>();
    public Dictionary<string, GameObject> resourcePrefabDic = new Dictionary<string, GameObject>();

    private void Awake()
    {
        resourceImageDic.Clear();
        resourcePrefabDic.Clear();
        for (int i = 0; i < resource_sprite_paths.Count; i++)
        {
            resourceImageDic.Add(resource_sprite_paths[i], resource_sprites[i]);
        }
        for (int i = 0; i < resource_prefab_paths.Count; i++)
        {
            resourcePrefabDic.Add(resource_prefab_paths[i], resource_prefabs[i]);
        }
    }

    public Sprite GetSprite(string path)
    {
        if (resourceImageDic.ContainsKey(path))
        {
            return resourceImageDic[path];
        }
        Debug.LogError("ResourceRef 找不到图片资源,path:" + path);
        return null;
    }

    public GameObject GetPrefab(string path)
    {
        if (resourcePrefabDic.ContainsKey(path))
        {
            return resourcePrefabDic[path];
        }
        Debug.LogError("ResourceRef 找不到预制资源,path:" + path);
        return null;
    }
}

实现资源映射类的Editor类,增加自动添加引用的逻辑:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;

[CustomEditor(typeof(ResourceRef))]
public class AutoSetResourceRef : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        ResourceRef targetScript = (ResourceRef)target;
        List<string> spriteNameList = targetScript.resource_sprite_paths;
        List<Sprite> spriteList = targetScript.resource_sprites;
        List<string> prefabNameList = targetScript.resource_prefab_paths;
        List<GameObject> prefabList = targetScript.resource_prefabs;

        if (GUILayout.Button("添加数据"))
        {
            spriteNameList.Clear();
            spriteList.Clear();

            prefabNameList.Clear();
            prefabList.Clear();

            string path2 = "/prefabs";
            string path3 = "/ui";
            string path4 = "/scene";
            string path5 = "/image";
            AddRes(path2, ref spriteNameList, ref spriteList, ref prefabNameList, ref prefabList);
            AddRes(path3, ref spriteNameList, ref spriteList, ref prefabNameList, ref prefabList);
            AddRes(path4, ref spriteNameList, ref spriteList, ref prefabNameList, ref prefabList);
            AddRes(path5, ref spriteNameList, ref spriteList, ref prefabNameList, ref prefabList);
            EditorUtility.SetDirty(targetScript);
        }
    }

    private static void AddRes(string path, ref List<string> spriteNameList, ref List<Sprite> spriteList, ref List<string> prefabNameList, ref List<GameObject> prefabList)
    {
        FileInfo[] pngFiles = new DirectoryInfo(Application.dataPath + path).GetFiles("*.png", SearchOption.AllDirectories);
        FileInfo[] prefabInfos = new DirectoryInfo(Application.dataPath + path).GetFiles("*.prefab", SearchOption.AllDirectories);
        AddSpriteToList(pngFiles, ref spriteNameList, ref spriteList);
        AddPrefabToList(prefabInfos, ref prefabNameList, ref prefabList);
    }

    private static void AddSpriteToList(FileInfo[] fileInfos, ref List<string> spriteNameList, ref List<Sprite> spriteList)
    {
        foreach (var fileInfo in fileInfos)
        {
            string unityPath = AppConst.UnifyPath(fileInfo.FullName);
            Sprite s = AssetDatabase.LoadAssetAtPath<Sprite>(unityPath);
            spriteNameList.Add(unityPath);
            spriteList.Add(s);
        }
    }

    private static void AddPrefabToList(FileInfo[] fileInfos, ref List<string> prefabNameList, ref List<GameObject> prefabList)
    {
        foreach (var fileInfo in fileInfos)
        {
            string unityPath = AppConst.UnifyPath(fileInfo.FullName);
            GameObject go = AssetDatabase.LoadAssetAtPath<GameObject>(unityPath);
            prefabNameList.Add(unityPath);
            prefabList.Add(go);
        }
    }
}

因为我这个项目的资源只有预制、ui预制、场景、png图片4种资源,所以只需要按目录将引用映射到脚本就行。如果还有别的资源需要引用,可以继续增加需要支持的资源类型。

注意:如果有打SpriteAtlas图集的话,打了图集会自动将图集打进整包,无需将图集引用也添加进映射中

最后改lua框架:

首先,改下LuaState中的OpenBaseLuaLibs方法,DoFile改成DoString:

image

然后,改下tolua框架中加载lua文件的方式,改成用DoString替代:

-- 允许重复加载
function webGLRequire(modname)
	local luaTable = package.loaded[modname]
	if luaTable and type(luaTable) ~= "table" then
		return luaTable
	end
	local err
	local textAsset = LuaScriptRef:GetLuaTextAsset(modname)
	if textAsset then
		local func = loadstring(textAsset.text, modname)
		local isOk, msg = pcall(func)
		if not isOk then
			print('代码执行失败,错误信息:'..msg) -- 捕获错误,避免崩溃
		else
			print('代码执行成功')
			luaTable = msg -- 执行成功时,返回的就是luaTable
		end
		package.loaded[modname] = msg
	end
	return luaTable
end

最后的最后,lua脚本中全部通过require方法加载lua脚本的都需要改成webGLRequire方法加载(这里仅指出几个例子):

tolua.lua:
image

socket.lua:
image

functions.lua:
image

5、结果

image

游戏能正常运行了,并且在微信开发者工具上也能正常运行



大佬们发现有错误欢迎拍砖~


 posted on 2026-02-05 16:28  chj一诺千金  阅读(6)  评论(0)    收藏  举报