以下内容是根据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:

然后,改下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:

socket.lua:

functions.lua:

5、结果

游戏能正常运行了,并且在微信开发者工具上也能正常运行
大佬们发现有错误欢迎拍砖~
posted on
浙公网安备 33010602011771号