minigame-tuanjie-transform-sdk/Editor/WXConvertCore.cs
2024-03-05 15:27:49 +08:00

1243 lines
52 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using LitJson;
using UnityEditor.Build;
using System.Linq;
namespace WeChatWASM
{
public class WXConvertCore
{
static WXConvertCore()
{
Init();
}
public static void Init()
{
config = UnityUtil.GetEditorConf();
// SDKFilePath = Path.Combine(Application.dataPath, "WX-WASM-SDK-V2", "Runtime", "wechat-default", "unity-sdk", "index.js");
SDKFilePath = Path.Combine(UnityUtil.GetWxSDKRootPath(), "Runtime", "wechat-default", "unity-sdk", "index.js");
// string templateHeader = (UnityUtil.GetSDKMode() == UnityUtil.SDKMode.Package && UnityUtil.GetEngineVersion() >= UnityUtil.EngineVersion.Tuanjie) ? "PACKAGE:com.qq.wx.minigame:" : "PROJECT:";
string templateHeader = "PROJECT:";
#if WEIXINMINIGAME
PlayerSettings.WeixinMiniGame.threadsSupport = false;
PlayerSettings.runInBackground = false;
PlayerSettings.WeixinMiniGame.compressionFormat = WeixinMiniGameCompressionFormat.Disabled;
#if UNITY_2022_3_OR_NEWER
PlayerSettings.WeixinMiniGame.template = $"{templateHeader}WXTemplate2022TJ";
#elif UNITY_2020_1_OR_NEWER
PlayerSettings.WeixinMiniGame.template = $"{templateHeader}WXTemplate2020";
#else
PlayerSettings.WeixinMiniGame.template = $"{templateHeader}WXTemplate";
#endif
PlayerSettings.WeixinMiniGame.linkerTarget = WeixinMiniGameLinkerTarget.Wasm;
PlayerSettings.WeixinMiniGame.dataCaching = false;
#if UNITY_2021_2_OR_NEWER
PlayerSettings.WeixinMiniGame.debugSymbolMode = WeixinMiniGameDebugSymbolMode.External;
#else
PlayerSettings.WeixinMiniGame.debugSymbols = true;
#endif
#else
PlayerSettings.WebGL.threadsSupport = false;
PlayerSettings.runInBackground = false;
PlayerSettings.WebGL.compressionFormat = WebGLCompressionFormat.Disabled;
#if UNITY_2022_3_OR_NEWER
PlayerSettings.WebGL.template = $"{templateHeader}WXTemplate2022";
#elif UNITY_2020_1_OR_NEWER
PlayerSettings.WebGL.template = $"{templateHeader}WXTemplate2020";
#else
PlayerSettings.WebGL.template = $"{templateHeader}WXTemplate";
#endif
PlayerSettings.WebGL.linkerTarget = WebGLLinkerTarget.Wasm;
PlayerSettings.WebGL.dataCaching = false;
#if UNITY_2021_2_OR_NEWER
PlayerSettings.WebGL.debugSymbolMode = WebGLDebugSymbolMode.External;
#else
PlayerSettings.WebGL.debugSymbols = true;
#endif
#endif
EditorSettings.spritePackerMode = SpritePackerMode.AlwaysOnAtlas;
}
public enum WXExportError
{
SUCCEED = 0,
NODE_NOT_FOUND = 1,
BUILD_WEBGL_FAILED = 2,
}
public static WXEditorScriptObject config;
public static string webglDir = "webgl"; // 导出的webgl目录
public static string miniGameDir = "minigame"; // 生成小游戏的目录
public static string audioDir = "Assets"; // 音频资源目录
public static string dataFileSize = string.Empty;
public static string codeMd5 = string.Empty;
public static string dataMd5 = string.Empty;
private static string SDKFilePath = string.Empty;
public static string defaultImgSrc = "Assets/WX-WASM-SDK-V2/Runtime/wechat-default/images/background.jpg";
// 可以调用这个来集成
public static WXExportError DoExport(bool buildWebGL = true)
{
if (!CheckSDK())
{
Debug.LogError("若游戏曾使用旧版本微信SDK需删除 Assets/WX-WASM-SDK 文件夹后再导入最新工具包。");
return WXExportError.BUILD_WEBGL_FAILED;
}
CheckBuildTarget();
Init();
// JSLib
SettingWXTextureMinJSLib();
UpdateGraphicAPI();
EditorUtility.SetDirty(config);
AssetDatabase.SaveAssets();
if (config.ProjectConf.DST == string.Empty)
{
Debug.LogError("请先配置游戏导出路径");
return WXExportError.BUILD_WEBGL_FAILED;
}
else
{
// 仅删除StreamingAssets目录
if (config.CompileOptions.DeleteStreamingAssets)
{
UnityUtil.DelectDir(Path.Combine(config.ProjectConf.DST, webglDir + "/StreamingAssets"));
}
if (buildWebGL && Build() != 0)
{
return WXExportError.BUILD_WEBGL_FAILED;
}
if (WXExtEnvDef.GETDEF("UNITY_2021_2_OR_NEWER") && !config.CompileOptions.DevelopBuild)
{
// 如果是2021版本官方symbols产生有BUG这里需要用工具将函数名提取出来
WeChatWASM.UnityUtil.preprocessSymbols(GetWebGLSymbolPath());
}
ConvertCode();
string dataFilePath = GetWebGLDataPath();
string wxTextDataDir = WXAssetsTextTools.GetTextMinDataDir();
string dataFilePathBackupDir = $"{wxTextDataDir}{WXAssetsTextTools.DS}slim";
string dataFilePathBackupPath = $"{dataFilePathBackupDir}{WXAssetsTextTools.DS}backup.txt";
if (!Directory.Exists(dataFilePathBackupDir))
{
Directory.CreateDirectory(dataFilePathBackupDir);
}
if (File.Exists(dataFilePathBackupPath))
{
File.Delete(dataFilePathBackupPath);
}
File.Copy(dataFilePath, dataFilePathBackupPath);
if (config.CompileOptions.fbslim && !IsInstantGameAutoStreaming())
{
WXAssetsTextTools.FirstBundleSlim(dataFilePath, (result, info) =>
{
if (!result)
{
Debug.LogError("【首包资源优化异常】:" + info);
}
finishExport();
});
}
else
{
finishExport();
}
}
return WXExportError.SUCCEED;
}
private static void CheckBuildTarget()
{
if (UnityUtil.GetEngineVersion() == UnityUtil.EngineVersion.Unity)
{
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.WebGL, BuildTarget.WebGL);
}
else
{
#if TUANJIE_2022_3_OR_NEWER
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.WeixinMiniGame, BuildTarget.WeixinMiniGame);
#endif
}
}
public static void UpdateGraphicAPI()
{
GraphicsDeviceType[] targets = new GraphicsDeviceType[] { };
#if WEIXINMINIGAME
PlayerSettings.SetUseDefaultGraphicsAPIs(BuildTarget.WeixinMiniGame, false);
if (config.CompileOptions.Webgl2)
{
PlayerSettings.SetGraphicsAPIs(BuildTarget.WeixinMiniGame, new GraphicsDeviceType[] { GraphicsDeviceType.OpenGLES3 });
}
else
{
PlayerSettings.SetGraphicsAPIs(BuildTarget.WeixinMiniGame, new GraphicsDeviceType[] { GraphicsDeviceType.OpenGLES2 });
}
#else
PlayerSettings.SetUseDefaultGraphicsAPIs(BuildTarget.WebGL, false);
if (config.CompileOptions.Webgl2)
{
PlayerSettings.SetGraphicsAPIs(BuildTarget.WebGL, new GraphicsDeviceType[] { GraphicsDeviceType.OpenGLES3 });
}
else
{
PlayerSettings.SetGraphicsAPIs(BuildTarget.WebGL, new GraphicsDeviceType[] { GraphicsDeviceType.OpenGLES2 });
}
#endif
}
/// <summary>
/// 移除输入js代码字符串中所有以prefix为前缀的函数的函数体function与函数名之间仅允许有一个空格
/// </summary>
/// <param name="input">输入字符串</param>
/// <param name="prefix">函数前缀</param>
/// <returns>处理后的字符串</returns>
public static string RemoveFunctionsWithPrefix(string input, string prefix)
{
StringBuilder output = new StringBuilder();
int braceCount = 0;
int lastIndex = 0;
int index = input.IndexOf("function " + prefix);
while (index != -1)
{
output.Append(input, lastIndex, index - lastIndex);
lastIndex = index;
while (input[lastIndex] != '{')
{
lastIndex++;
}
braceCount = 1;
++lastIndex;
while (braceCount > 0)
{
if (input[lastIndex] == '{')
{
++braceCount;
}
else if (input[lastIndex] == '}')
{
--braceCount;
}
++lastIndex;
}
index = input.IndexOf("function " + prefix, lastIndex);
}
output.Append(input, lastIndex, input.Length - lastIndex);
return output.ToString();
}
private static void ConvertCode()
{
UnityEngine.Debug.LogFormat("[Converter] Starting to adapt framewor. Dst: " + config.ProjectConf.DST);
UnityUtil.DelectDir(Path.Combine(config.ProjectConf.DST, miniGameDir));
string text = String.Empty;
if (WXExtEnvDef.GETDEF("UNITY_2020_1_OR_NEWER"))
{
text = File.ReadAllText(Path.Combine(config.ProjectConf.DST, webglDir, "Build", "webgl.framework.js"), Encoding.UTF8);
}
else
{
text = File.ReadAllText(Path.Combine(config.ProjectConf.DST, webglDir, "Build", "webgl.wasm.framework.unityweb"), Encoding.UTF8);
}
int i;
for (i = 0; i < ReplaceRules.rules.Length; i++)
{
var rule = ReplaceRules.rules[i];
text = Regex.Replace(text, rule.old, rule.newStr);
}
string[] prefixs =
{
"_JS_Video_",
//"jsVideo",
"_JS_Sound_",
"jsAudio",
"_JS_MobileKeyboard_",
"_JS_MobileKeybard_"
};
foreach (var prefix in prefixs)
{
text = RemoveFunctionsWithPrefix(text, prefix);
}
#if WEIXINMINIGAME
if (PlayerSettings.WeixinMiniGame.exceptionSupport == WeixinMiniGameExceptionSupport.None)
#else
if (PlayerSettings.WebGL.exceptionSupport == WebGLExceptionSupport.None)
#endif
{
Rule[] rules =
{
new Rule()
{
old = "console.log\\(\"Exception at",
newStr = "if(Module.IsWxGame);console.log(\"Exception at",
},
new Rule()
{
old = "throw ptr",
newStr = "if(Module.IsWxGame)window.WXWASMSDK.WXUncaughtException(true);else throw ptr",
},
};
foreach (var rule in rules)
{
text = Regex.Replace(text, rule.old, rule.newStr);
}
}
if (text.Contains("UnityModule"))
{
text += ";GameGlobal.unityNamespace.UnityModule = UnityModule;";
}
else if (text.Contains("unityFramework"))
{
text += ";GameGlobal.unityNamespace.UnityModule = unityFramework;";
}
else if (text.Contains("tuanjieFramework"))
{
text += ";GameGlobal.unityNamespace.UnityModule = tuanjieFramework;";
}
else
{
if (text.StartsWith("(") && text.EndsWith(")"))
{
text = text.Substring(1, text.Length - 2);
}
text = "GameGlobal.unityNamespace.UnityModule = " + text;
}
if (!Directory.Exists(Path.Combine(config.ProjectConf.DST, miniGameDir)))
{
Directory.CreateDirectory(Path.Combine(config.ProjectConf.DST, miniGameDir));
}
var header = "function createWebAudio(){window.AudioContext=window.AudioContext||window.webkitAudioContext;if(window.AudioContext){return new AudioContext();}return wx.createWebAudioContext();}\n";
if (config.CompileOptions.DevelopBuild)
{
header = header + RenderAnalysisRules.header;
for (i = 0; i < RenderAnalysisRules.rules.Length; i++)
{
var rule = RenderAnalysisRules.rules[i];
text = Regex.Replace(text, rule.old, rule.newStr);
}
}
text = header + text;
File.WriteAllText(Path.Combine(config.ProjectConf.DST, miniGameDir, "webgl.wasm.framework.unityweb.js"), text, new UTF8Encoding(false));
UnityEngine.Debug.LogFormat("[Converter] adapt framework done! ");
}
private static int Build()
{
#if WEIXINMINIGAME
PlayerSettings.WeixinMiniGame.emscriptenArgs = string.Empty;
if (WXExtEnvDef.GETDEF("UNITY_2021_2_OR_NEWER"))
{
PlayerSettings.WeixinMiniGame.emscriptenArgs += " -s EXPORTED_FUNCTIONS=_main,_sbrk,_emscripten_stack_get_base,_emscripten_stack_get_end";
}
#else
PlayerSettings.WebGL.emscriptenArgs = string.Empty;
if (WXExtEnvDef.GETDEF("UNITY_2021_2_OR_NEWER"))
{
PlayerSettings.WebGL.emscriptenArgs += " -s EXPORTED_FUNCTIONS=_sbrk,_emscripten_stack_get_base,_emscripten_stack_get_end";
#if UNITY_2021_2_5
PlayerSettings.WebGL.emscriptenArgs += ",_main";
#endif
}
#endif
PlayerSettings.runInBackground = false;
if (config.ProjectConf.MemorySize != 0)
{
if (config.ProjectConf.MemorySize >= 1024)
{
UnityEngine.Debug.LogErrorFormat($"UnityHeap必须小于1024请查看GIT文档<a href=\"https://github.com/wechat-miniprogram/minigame-unity-webgl-transform/blob/main/Design/OptimizationMemory.md\">优化Unity WebGL的内存</a>");
return -1;
}
else if (config.ProjectConf.MemorySize >= 500)
{
UnityEngine.Debug.LogWarningFormat($"UnityHeap大于500M时32位Android与iOS普通模式较大概率启动失败中轻度游戏建议小于该值。请查看GIT文档<a href=\"https://github.com/wechat-miniprogram/minigame-unity-webgl-transform/blob/main/Design/OptimizationMemory.md\">优化Unity WebGL的内存</a>");
}
#if WEIXINMINIGAME
PlayerSettings.WeixinMiniGame.emscriptenArgs += $" -s TOTAL_MEMORY={config.ProjectConf.MemorySize}MB";
#else
PlayerSettings.WebGL.emscriptenArgs += $" -s TOTAL_MEMORY={config.ProjectConf.MemorySize}MB";
#endif
}
#if WEIXINMINIGAME
if (config.CompileOptions.ProfilingMemory)
{
PlayerSettings.WeixinMiniGame.emscriptenArgs += " --memoryprofiler ";
}
if (config.CompileOptions.profilingFuncs)
{
PlayerSettings.WeixinMiniGame.emscriptenArgs += " --profiling-funcs ";
}
#if UNITY_2021_2_OR_NEWER
#if UNITY_2022_1_OR_NEWER
// 默认更改为OptimizeSize减少代码包体积
PlayerSettings.SetIl2CppCodeGeneration(NamedBuildTarget.WeixinMiniGame, config.CompileOptions.Il2CppOptimizeSize ? Il2CppCodeGeneration.OptimizeSize : Il2CppCodeGeneration.OptimizeSpeed);
#else
EditorUserBuildSettings.il2CppCodeGeneration = config.CompileOptions.Il2CppOptimizeSize ? Il2CppCodeGeneration.OptimizeSize : Il2CppCodeGeneration.OptimizeSpeed;
#endif
#endif
UnityEngine.Debug.Log("[Builder] Starting to build WeixinMiniGame project ... ");
UnityEngine.Debug.Log("PlayerSettings.WeixinMiniGame.emscriptenArgs : " + PlayerSettings.WeixinMiniGame.emscriptenArgs);
#else
if (config.CompileOptions.ProfilingMemory)
{
PlayerSettings.WebGL.emscriptenArgs += " --memoryprofiler ";
}
if (config.CompileOptions.profilingFuncs)
{
PlayerSettings.WebGL.emscriptenArgs += " --profiling-funcs ";
}
string original_EXPORTED_RUNTIME_METHODS = "\"ccall\",\"cwrap\",\"stackTrace\",\"addRunDependency\",\"removeRunDependency\",\"FS_createPath\",\"FS_createDataFile\",\"stackTrace\",\"writeStackCookie\",\"checkStackCookie\"";
// 添加额外的EXPORTED_RUNTIME_METHODS
string additional_EXPORTED_RUNTIME_METHODS = ",\"lengthBytesUTF8\",\"stringToUTF8\"";
PlayerSettings.WebGL.emscriptenArgs += " -s EXPORTED_RUNTIME_METHODS='[" + original_EXPORTED_RUNTIME_METHODS + additional_EXPORTED_RUNTIME_METHODS + "]'";
#if UNITY_2021_2_OR_NEWER
#if UNITY_2022_1_OR_NEWER
// 默认更改为OptimizeSize减少代码包体积
PlayerSettings.SetIl2CppCodeGeneration(NamedBuildTarget.WebGL, config.CompileOptions.Il2CppOptimizeSize ? Il2CppCodeGeneration.OptimizeSize : Il2CppCodeGeneration.OptimizeSpeed);
#else
EditorUserBuildSettings.il2CppCodeGeneration = config.CompileOptions.Il2CppOptimizeSize ? Il2CppCodeGeneration.OptimizeSize : Il2CppCodeGeneration.OptimizeSpeed;
#endif
#endif
UnityEngine.Debug.Log("[Builder] Starting to build WebGL project ... ");
UnityEngine.Debug.Log("PlayerSettings.WebGL.emscriptenArgs : " + PlayerSettings.WebGL.emscriptenArgs);
#endif
// PlayerSettings.WebGL.memorySize = memorySize;
BuildOptions option = BuildOptions.None;
if (config.CompileOptions.DevelopBuild)
{
option |= BuildOptions.Development;
}
if (config.CompileOptions.AutoProfile)
{
option |= BuildOptions.ConnectWithProfiler;
}
if (config.CompileOptions.ScriptOnly)
{
option |= BuildOptions.BuildScriptsOnly;
}
#if UNITY_2021_2_OR_NEWER
if (config.CompileOptions.CleanBuild)
{
option |= BuildOptions.CleanBuildCache;
}
#endif
#if TUANJIE_2022_3_OR_NEWER
if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.WeixinMiniGame)
{
UnityEngine.Debug.LogFormat("[Builder] Current target is: {0}, switching to: {1}", EditorUserBuildSettings.activeBuildTarget, BuildTarget.WeixinMiniGame);
if (!EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.WeixinMiniGame, BuildTarget.WeixinMiniGame))
{
UnityEngine.Debug.LogFormat("[Builder] Switching to {0}/{1} failed!", BuildTargetGroup.WeixinMiniGame, BuildTarget.WeixinMiniGame);
return -1;
}
}
var projDir = Path.Combine(config.ProjectConf.DST, webglDir);
var result = BuildPipeline.BuildPlayer(GetScenePaths(), projDir, BuildTarget.WeixinMiniGame, option);
if (result.summary.result != UnityEditor.Build.Reporting.BuildResult.Succeeded)
{
UnityEngine.Debug.LogFormat("[Builder] BuildPlayer failed. emscriptenArgs:{0}", PlayerSettings.WeixinMiniGame.emscriptenArgs);
return -1;
}
#else
if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.WebGL)
{
UnityEngine.Debug.LogFormat("[Builder] Current target is: {0}, switching to: {1}", EditorUserBuildSettings.activeBuildTarget, BuildTarget.WebGL);
if (!EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.WebGL, BuildTarget.WebGL))
{
UnityEngine.Debug.LogFormat("[Builder] Switching to {0}/{1} failed!", BuildTargetGroup.WebGL, BuildTarget.WebGL);
return -1;
}
}
var projDir = Path.Combine(config.ProjectConf.DST, webglDir);
var result = BuildPipeline.BuildPlayer(GetScenePaths(), projDir, BuildTarget.WebGL, option);
if (result.summary.result != UnityEditor.Build.Reporting.BuildResult.Succeeded)
{
UnityEngine.Debug.LogFormat("[Builder] BuildPlayer failed. emscriptenArgs:{0}", PlayerSettings.WebGL.emscriptenArgs);
return -1;
}
#endif
UnityEngine.Debug.LogFormat("[Builder] Done: " + projDir);
return 0;
}
private static string GetWebGLDataPath()
{
if (WXExtEnvDef.GETDEF("UNITY_2020_1_OR_NEWER"))
{
return Path.Combine(config.ProjectConf.DST, webglDir, "Build", "webgl.data");
}
else
{
return Path.Combine(config.ProjectConf.DST, webglDir, "Build", "webgl.data.unityweb");
}
}
private static void finishExport()
{
int code = GenerateBinFile();
if (code == 0)
{
convertDataPackage(false);
UnityEngine.Debug.LogFormat("[Converter] All done!");
//ShowNotification(new GUIContent("转换完成"));
}
else
{
convertDataPackage(true);
}
}
/// <summary>
/// 等brotli之后统计下资源包加brotli压缩后代码包是否超过了20M小游戏代码分包总大小限制
/// </summary>
private static void convertDataPackage(bool brotliError)
{
var baseDataFilename = dataMd5 + ".webgl.data.unityweb.bin";
var webglDirPath = Path.Combine(config.ProjectConf.DST, webglDir);
var minigameDirPath = Path.Combine(config.ProjectConf.DST, miniGameDir);
var minigameDataPath = Path.Combine(minigameDirPath, "data-package");
// 未压缩的包名
var originDataFilename = baseDataFilename + ".txt";
var originMinigameDataPath = Path.Combine(minigameDataPath, originDataFilename);
var originTempDataPath = Path.Combine(webglDirPath, originDataFilename);
// br压缩的资源包名
var brDataFilename = baseDataFilename + ".br";
var brMinigameDataPath = Path.Combine(minigameDataPath, brDataFilename);
var tempDataBrPath = Path.Combine(webglDirPath, brDataFilename);
// 资源文件名
var dataFilename = originDataFilename;
// 原始webgl的资源路径即webgl/build目录下的资源名
var sourceDataPath = GetWebGLDataPath();
// webgl目录下的资源路径
var tempDataPath = originTempDataPath;
var dataPackageBrotliRet = 0;
// 如果brotli失败使用CDN加载
if (brotliError)
{
// brotli失败后因为无法知道wasmcode大小则得不到最终小游戏总包体大小。不能使用小游戏分包加载资源还原成cdn的方式。
if (config.ProjectConf.assetLoadType == 1)
{
UnityEngine.Debug.LogWarning("brotli失败无法检测文件大小请上传资源文件到CDN");
config.ProjectConf.assetLoadType = 0;
}
// ShowNotification(new GUIContent("Brotli压缩失败请到转出目录手动压缩"));
Debug.LogError("Brotli压缩失败请到转出目录手动压缩");
}
// 需要压缩资源包
if (!!config.ProjectConf.compressDataPackage)
{
dataFilename = brDataFilename;
tempDataPath = tempDataBrPath;
UnityEngine.Debug.LogFormat("[Compressing] Starting to compress datapackage");
dataPackageBrotliRet = Brotlib(dataFilename, sourceDataPath, tempDataPath);
Debug.Log("[Compressing] compress ret = " + dataPackageBrotliRet);
// 若压缩资源包失败,回退未压缩状态
if (dataPackageBrotliRet != 0)
{
config.ProjectConf.compressDataPackage = false;
dataFilename = originDataFilename;
tempDataPath = originTempDataPath;
}
}
// 不需要压缩资源包或压缩失败
if (!config.ProjectConf.compressDataPackage || dataPackageBrotliRet != 0)
{
// 将资源包从Build目录复制一份作为未压缩资源
File.Copy(sourceDataPath, tempDataPath, true);
}
// 用小游戏分包加载时需要计算是否未超过20M
if (config.ProjectConf.assetLoadType == 1)
{
// 计算wasm包大小
var brcodePath = Path.Combine(minigameDirPath, "wasmcode", codeMd5 + ".webgl.wasm.code.unityweb.wasm.br");
var brcodeInfo = new FileInfo(brcodePath);
var brcodeSize = brcodeInfo.Length;
// 计算首资源包大小
var tempDataInfo = new FileInfo(tempDataPath);
var tempFileSize = tempDataInfo.Length.ToString();
// 胶水层及sdk可能占一定大小粗略按照1M来算则剩余19M
if (brcodeSize + int.Parse(tempFileSize) > (20 - 1) * 1024 * 1024)
{
config.ProjectConf.assetLoadType = 0;
Debug.LogError("资源文件过大不适宜用放小游戏包内加载请上传资源文件到CDN");
}
else
{
// 小游戏分包加载时压缩成功且总大小符合要求将br文件copy到小游戏目录
File.Copy(tempDataPath, config.ProjectConf.compressDataPackage ? brMinigameDataPath : originMinigameDataPath, true);
}
}
checkNeedRmovePackageParallelPreload();
// 设置InstantGame的首资源包路径上传用
FirstBundlePath = tempDataPath;
var loadDataFromCdn = config.ProjectConf.assetLoadType == 0;
Rule[] rules =
{
new Rule()
{
old = "$DEPLOY_URL",
newStr = config.ProjectConf.CDN,
},
new Rule()
{
old = "$LOAD_DATA_FROM_SUBPACKAGE",
newStr = loadDataFromCdn ? "false" : "true",
},
new Rule()
{
old = "$COMPRESS_DATA_PACKAGE",
newStr = config.ProjectConf.compressDataPackage ? "true" : "false",
}
};
string[] files = { "game.js", "game.json", "project.config.json", "check-version.js" };
ReplaceFileContent(files, rules);
}
private static void checkNeedRmovePackageParallelPreload()
{
// cdn下载时不需要填写并行下载配置
if (config.ProjectConf.assetLoadType == 0)
{
var filePath = Path.Combine(config.ProjectConf.DST, miniGameDir, "game.json");
string content = File.ReadAllText(filePath, Encoding.UTF8);
JsonData gameJson = JsonMapper.ToObject(content);
JsonWriter writer = new JsonWriter();
writer.IndentValue = 2;
writer.PrettyPrint = true;
gameJson["parallelPreloadSubpackages"].Remove(gameJson["parallelPreloadSubpackages"][1]);
// 将配置写回到文件夹
gameJson.ToJson(writer);
File.WriteAllText(filePath, writer.TextWriter.ToString());
}
}
/// <summary>
/// 对文件做内容替换
/// </summary>
/// <param name="files"></param>
/// <param name="replaceList"></param>
public static void ReplaceFileContent(string[] files, Rule[] replaceList)
{
if (files.Length != 0 && replaceList.Length != 0)
{
for (int i = 0; i < files.Length; i++)
{
var filePath = Path.Combine(config.ProjectConf.DST, miniGameDir, files[i]);
string text = File.ReadAllText(filePath, Encoding.UTF8);
for (int j = 0; j < replaceList.Length; j++)
{
var rule = replaceList[j];
text = text.Replace(rule.old, rule.newStr);
}
File.WriteAllText(filePath, text, new UTF8Encoding(false));
}
}
}
private static string GetWebGLCodePath()
{
if (WXExtEnvDef.GETDEF("UNITY_2020_1_OR_NEWER"))
{
return Path.Combine(config.ProjectConf.DST, webglDir, "Build", "webgl.wasm");
}
else
{
return Path.Combine(config.ProjectConf.DST, webglDir, "Build", "webgl.wasm.code.unityweb");
}
}
private static bool CopyDirectory(string SourcePath, string DestinationPath, bool overwriteexisting)
{
bool ret = false;
var separator = Path.DirectorySeparatorChar;
var ignoreFiles = new List<string>() { "unityNamespace.js" };
// eventEmitter - 改名为event-emitter
// loading和libs 是可交互视频用到的文件,先下掉可交互方案
var ignoreDirs = new List<string>() { "eventEmitter", "loading", "libs" };
try
{
if (Directory.Exists(SourcePath))
{
if (Directory.Exists(DestinationPath) == false)
{
Directory.CreateDirectory(DestinationPath);
}
else
{
// 已经存在,删掉目录下无用的文件
foreach (string filename in ignoreFiles)
{
var filepath = Path.Combine(DestinationPath, filename);
if (File.Exists(filepath))
{
File.Delete(filepath);
}
}
foreach (string dir in ignoreDirs)
{
var dirpath = Path.Combine(DestinationPath, dir);
if (Directory.Exists(dirpath))
{
Directory.Delete(dirpath);
}
}
}
foreach (string fls in Directory.GetFiles(SourcePath))
{
FileInfo flinfo = new FileInfo(fls);
if (flinfo.Extension == ".meta" || ignoreFiles.Contains(flinfo.Name))
{
continue;
}
flinfo.CopyTo(Path.Combine(DestinationPath, flinfo.Name), overwriteexisting);
}
foreach (string drs in Directory.GetDirectories(SourcePath))
{
DirectoryInfo drinfo = new DirectoryInfo(drs);
if (ignoreDirs.Contains(drinfo.Name))
{
continue;
}
if (CopyDirectory(drs, Path.Combine(DestinationPath, drinfo.Name), overwriteexisting) == false)
{
ret = false;
}
}
}
ret = true;
}
catch (Exception ex)
{
ret = false;
UnityEngine.Debug.LogError(ex);
}
return ret;
}
public static string FirstBundlePath = "";
public static int GenerateBinFile(bool isFromConvert = false)
{
UnityEngine.Debug.LogFormat("[Converter] Starting to genarate md5 and copy files");
var codePath = GetWebGLCodePath();
codeMd5 = UnityUtil.BuildFileMd5(codePath);
var dataPath = GetWebGLDataPath();
dataMd5 = UnityUtil.BuildFileMd5(dataPath);
var symbolPath = GetWebGLSymbolPath();
RemoveOldAssetPackage(Path.Combine(config.ProjectConf.DST, webglDir));
RemoveOldAssetPackage(Path.Combine(config.ProjectConf.DST, webglDir + "-min"));
// CopyDirectory(Path.Combine(Application.dataPath, "WX-WASM-SDK-V2", "Runtime", "wechat-default"), Path.Combine(config.ProjectConf.DST, miniGameDir), true);
CopyDirectory(Path.Combine(UnityUtil.GetWxSDKRootPath(), "Runtime", "wechat-default"), Path.Combine(config.ProjectConf.DST, miniGameDir), true);
// FIX: 2021.2版本生成symbol有bug导出时生成symbol报错有symbol才copy
// 代码分包需要symbol文件以进行增量更新
if (File.Exists(symbolPath))
{
File.Copy(symbolPath, Path.Combine(config.ProjectConf.DST, miniGameDir, "webgl.wasm.symbols.unityweb"), true);
}
var info = new FileInfo(dataPath);
dataFileSize = info.Length.ToString();
UnityEngine.Debug.LogFormat("[Converter] that to genarate md5 and copy files ended");
ModifyWeChatConfigs(isFromConvert);
ModifySDKFile();
ClearFriendRelationCode();
// 如果没有StreamingAssets目录默认生成
if (!Directory.Exists(Path.Combine(config.ProjectConf.DST, webglDir, "StreamingAssets")))
{
Directory.CreateDirectory(Path.Combine(config.ProjectConf.DST, webglDir, "StreamingAssets"));
}
return Brotlib(codeMd5 + ".webgl.wasm.code.unityweb.wasm.br", codePath, Path.Combine(config.ProjectConf.DST, miniGameDir, "wasmcode", codeMd5 + ".webgl.wasm.code.unityweb.wasm.br"));
}
private static int Brotlib(string filename, string sourcePath, string targetPath)
{
UnityEngine.Debug.LogFormat("[Converter] Starting to generate Brotlib file");
var cachePath = Path.Combine(config.ProjectConf.DST, webglDir, filename);
var shortFilename = filename.Substring(filename.IndexOf('.') + 1);
// 如果code没有发生过变化则不再进行br压缩
if (File.Exists(cachePath))
{
File.Copy(cachePath, targetPath, true);
return 0;
}
// 删除旧的br压缩文件
if (Directory.Exists(Path.Combine(config.ProjectConf.DST, webglDir)))
{
foreach (string path in Directory.GetFiles(Path.Combine(config.ProjectConf.DST, webglDir)))
{
FileInfo fileInfo = new FileInfo(path);
if (fileInfo.Name.Contains(shortFilename))
{
File.Delete(fileInfo.FullName);
}
}
}
UnityUtil.brotli(sourcePath, targetPath);
if (targetPath != cachePath)
{
File.Copy(targetPath, cachePath, true);
}
return 0;
}
/// <summary>
/// 如果没有使用好友关系链的话,自动删掉无用代码
/// </summary>
private static void ClearFriendRelationCode()
{
var filePath = Path.Combine(config.ProjectConf.DST, miniGameDir, "game.json");
string content = File.ReadAllText(filePath, Encoding.UTF8);
JsonData gameJson = JsonMapper.ToObject(content);
if (!config.SDKOptions.UseFriendRelation || !config.SDKOptions.UseMiniGameChat)
{
JsonWriter writer = new JsonWriter();
writer.IndentValue = 2;
writer.PrettyPrint = true;
// 将 game.json 里面关系链相关的配置删除
if (!config.SDKOptions.UseFriendRelation)
{
gameJson.Remove("openDataContext");
gameJson["plugins"].Remove("Layout");
// 删除 open-data 相应的文件夹
string openDataDir = Path.Combine(config.ProjectConf.DST, miniGameDir, "open-data");
UnityUtil.DelectDir(openDataDir);
Directory.Delete(openDataDir, true);
}
if (!config.SDKOptions.UseMiniGameChat)
{
gameJson["plugins"].Remove("MiniGameChat");
UnityEngine.Debug.Log(gameJson["plugins"]);
}
// 将配置写回到文件夹
gameJson.ToJson(writer);
File.WriteAllText(filePath, writer.TextWriter.ToString());
}
}
private static void ModifySDKFile()
{
var config = UnityUtil.GetEditorConf();
string content = File.ReadAllText(SDKFilePath, Encoding.UTF8);
content = content.Replace("$unityVersion$", Application.unityVersion);
File.WriteAllText(Path.Combine(config.ProjectConf.DST, miniGameDir, "unity-sdk", "index.js"), content, Encoding.UTF8);
// content = File.ReadAllText(Path.Combine(Application.dataPath, "WX-WASM-SDK-V2", "Runtime", "wechat-default", "unity-sdk", "storage.js"), Encoding.UTF8);
content = File.ReadAllText(Path.Combine(UnityUtil.GetWxSDKRootPath(), "Runtime", "wechat-default", "unity-sdk", "storage.js"), Encoding.UTF8);
var PreLoadKeys = config.PlayerPrefsKeys.Count > 0 ? JsonMapper.ToJson(config.PlayerPrefsKeys) : "[]";
content = content.Replace("'$PreLoadKeys'", PreLoadKeys);
File.WriteAllText(Path.Combine(config.ProjectConf.DST, miniGameDir, "unity-sdk", "storage.js"), content, Encoding.UTF8);
// 修改纹理dxt
// content = File.ReadAllText(Path.Combine(Application.dataPath, "WX-WASM-SDK-V2", "Runtime", "wechat-default", "unity-sdk", "texture.js"), Encoding.UTF8);
content = File.ReadAllText(Path.Combine(UnityUtil.GetWxSDKRootPath(), "Runtime", "wechat-default", "unity-sdk", "texture.js"), Encoding.UTF8);
File.WriteAllText(Path.Combine(config.ProjectConf.DST, miniGameDir, "unity-sdk", "texture.js"), content, Encoding.UTF8);
}
public static string HandleLoadingImage()
{
var info = AssetDatabase.LoadAssetAtPath<Texture>(config.ProjectConf.bgImageSrc);
var oldFilename = Path.GetFileName(defaultImgSrc);
var newFilename = Path.GetFileName(config.ProjectConf.bgImageSrc);
if (config.ProjectConf.bgImageSrc != defaultImgSrc)
{
// 图片宽高不能超过2048
if (info.width > 2048 || info.height > 2048)
{
throw new Exception("封面图宽高不可超过2048");
}
File.Delete(Path.Combine(config.ProjectConf.DST, miniGameDir, "images", oldFilename));
File.Copy(config.ProjectConf.bgImageSrc, Path.Combine(config.ProjectConf.DST, miniGameDir, "images", newFilename), true);
return "images/" + Path.GetFileName(config.ProjectConf.bgImageSrc);
}
else
{
return "images/" + Path.GetFileName(defaultImgSrc);
}
}
/// <summary>
/// 按;分隔字符串,将分隔后每一项作为字符串用,连接
/// eg: input "i1;i2;i3" => output: `"i1", "i2", "i3"`
/// </summary>
/// <param name="inp"></param>
/// <returns></returns>
public static string GetArrayString(string inp)
{
var result = string.Empty;
var iterms = new List<string>(inp.Split(new char[] { ';' }));
iterms.ForEach((iterm) =>
{
if (!string.IsNullOrEmpty(iterm.Trim()))
{
result += "\"" + iterm.Trim() + "\", ";
}
});
if (!string.IsNullOrEmpty(result))
{
result = result.Substring(0, result.Length - 2);
}
return result;
}
private class PreloadFile
{
public PreloadFile(string fn, string rp)
{
fileName = fn;
relativePath = rp;
}
public string fileName;
public string relativePath;
}
/// <summary>
/// 从webgl目录模糊搜索preloadfiles中的文件作为预下载的列表
/// </summary>
private static string GetPreloadList(string strPreloadfiles)
{
if (strPreloadfiles == string.Empty)
{
return string.Empty;
}
string preloadList = string.Empty;
var streamingAssetsPath = Path.Combine(config.ProjectConf.DST, webglDir + "/StreamingAssets");
var fileNames = strPreloadfiles.Split(new char[] { ';' });
List<PreloadFile> preloadFiles = new List<PreloadFile>();
foreach (var fileName in fileNames)
{
if (fileName.Trim() == string.Empty)
{
continue;
}
preloadFiles.Add(new PreloadFile(fileName, string.Empty));
}
if (Directory.Exists(streamingAssetsPath))
{
foreach (string path in Directory.GetFiles(streamingAssetsPath, "*", SearchOption.AllDirectories))
{
FileInfo fileInfo = new FileInfo(path);
foreach (var preloadFile in preloadFiles)
{
if (fileInfo.Name.Contains(preloadFile.fileName))
{
// 相对于StreamingAssets的路径
var relativePath = path.Substring(streamingAssetsPath.Length + 1).Replace('\\', '/');
preloadFile.relativePath = relativePath;
break;
}
}
}
}
else
{
UnityEngine.Debug.LogError("没有找到StreamingAssets目录 无法生成预下载列表");
}
foreach (var preloadFile in preloadFiles)
{
if (preloadFile.relativePath == string.Empty)
{
UnityEngine.Debug.LogError($"并非所有预下载的文件都被找到,剩余:{preloadFile.fileName}");
continue;
}
preloadList += "\"" + preloadFile.relativePath + "\", \r";
}
return preloadList;
}
public static void ModifyWeChatConfigs(bool isFromConvert = false)
{
UnityEngine.Debug.LogFormat("[Converter] Starting to modify configs");
var config = UnityUtil.GetEditorConf();
var PRELOAD_LIST = GetPreloadList(config.ProjectConf.preloadFiles);
var imgSrc = HandleLoadingImage();
var bundlePathIdentifierStr = GetArrayString(config.ProjectConf.bundlePathIdentifier);
var excludeFileExtensionsStr = GetArrayString(config.ProjectConf.bundleExcludeExtensions);
var screenOrientation = new List<string>() { "portrait", "landscape", "landscapeLeft", "landscapeRight" }[(int)config.ProjectConf.Orientation];
Rule[] replaceArrayList = ReplaceRules.GenRules(new string[] {
config.ProjectConf.projectName == string.Empty ? "webgl" : config.ProjectConf.projectName,
config.ProjectConf.Appid,
screenOrientation,
config.CompileOptions.enableIOSPerformancePlus ? "true" : "false",
config.ProjectConf.VideoUrl,
codeMd5,
dataMd5,
config.ProjectConf.StreamCDN,
config.ProjectConf.CDN + "/Assets",
PRELOAD_LIST,
imgSrc,
config.ProjectConf.HideAfterCallMain ? "true" : "false",
config.ProjectConf.bundleHashLength.ToString(),
bundlePathIdentifierStr,
excludeFileExtensionsStr,
config.CompileOptions.Webgl2 ? "2" : "1",
Application.unityVersion,
WXExtEnvDef.pluginVersion,
config.ProjectConf.dataFileSubPrefix,
config.ProjectConf.maxStorage.ToString(),
config.ProjectConf.defaultReleaseSize.ToString(),
config.ProjectConf.texturesHashLength.ToString(),
config.ProjectConf.texturesPath,
config.ProjectConf.needCacheTextures ? "true" : "false",
config.ProjectConf.loadingBarWidth.ToString(),
config.ProjectConf.needCheckUpdate ? "true" : "false",
GetColorSpace(),
config.ProjectConf.disableHighPerformanceFallback ? "true" : "false",
config.SDKOptions.PreloadWXFont ? "true" : "false",
config.CompileOptions.showMonitorSuggestModal ? "true" : "false",
config.CompileOptions.enableProfileStats ? "true" : "false",
config.CompileOptions.iOSAutoGCInterval.ToString(),
dataFileSize,
IsInstantGameAutoStreaming() ? "true" : "false",
(config.CompileOptions.DevelopBuild && config.CompileOptions.enableRenderAnalysis) ? "true" : "false",
config.ProjectConf.IOSDevicePixelRatio.ToString(),
});
List<Rule> replaceList = new List<Rule>(replaceArrayList);
List<string> files = new List<string> { "game.js", "game.json", "project.config.json", "unity-namespace.js", "check-version.js" };
ReplaceFileContent(files.ToArray(), replaceList.ToArray());
UnityEngine.Debug.LogFormat("[Converter] that to modify configs ended");
}
/// <summary>
/// 获取当前工程颜色空间
/// </summary>
/// <returns></returns>
private static string GetColorSpace()
{
switch (PlayerSettings.colorSpace)
{
case ColorSpace.Gamma:
return "Gamma";
case ColorSpace.Linear:
return "Linear";
case ColorSpace.Uninitialized:
return "Uninitialized";
default:
return "Unknow";
}
}
/// <summary>
/// 删掉导出目录webgl目录下旧资源包
/// </summary>
private static void RemoveOldAssetPackage(string dstDir)
{
try
{
if (Directory.Exists(dstDir))
{
foreach (string path in Directory.GetFiles(dstDir))
{
FileInfo fileInfo = new FileInfo(path);
if (fileInfo.Name.Contains("webgl.data.unityweb.bin.txt") || fileInfo.Name.Contains("webgl.data.unityweb.bin.br"))
{
File.Delete(fileInfo.FullName);
}
}
}
}
catch (Exception ex)
{
UnityEngine.Debug.LogError(ex);
}
}
private static string GetWebGLSymbolPath()
{
if (WXExtEnvDef.GETDEF("UNITY_2020_1_OR_NEWER"))
{
return Path.Combine(config.ProjectConf.DST, webglDir, "Build", "webgl.symbols.json");
}
else
{
return Path.Combine(config.ProjectConf.DST, webglDir, "Build", "webgl.wasm.symbols.unityweb");
}
}
private static string[] GetScenePaths()
{
List<string> scenes = new List<string>();
for (int i = 0; i < EditorBuildSettings.scenes.Length; i++)
{
var scene = EditorBuildSettings.scenes[i];
UnityEngine.Debug.LogFormat("[Builder] Scenes [{0}]: {1}, [{2}]", i, scene.path, scene.enabled ? "x" : " ");
if (scene.enabled)
{
scenes.Add(scene.path);
}
}
return scenes.ToArray();
}
/// <summary>
/// 兼容 WebGL1 WebGL2 Linear Gamma 配置 Assets/WX-WASM-SDK/Plugins
/// </summary>
private static void SettingWXTextureMinJSLib()
{
string[] jsLibs;
string DS = WXAssetsTextTools.DS;
if (UnityUtil.GetSDKMode() == UnityUtil.SDKMode.Package)
{
jsLibs = new string[]
{
$"Packages{DS}com.qq.weixin.minigame{DS}Runtime{DS}Plugins{DS}SDK-WX-TextureMin-JS-WEBGL1.jslib",
$"Packages{DS}com.qq.weixin.minigame{DS}Runtime{DS}Plugins{DS}SDK-WX-TextureMin-JS-WEBGL2.jslib",
$"Packages{DS}com.qq.weixin.minigame{DS}Runtime{DS}Plugins{DS}SDK-WX-TextureMin-JS-WEBGL2-Linear.jslib",
};
}
else
{
string jsLibRootDir = $"Assets{DS}WX-WASM-SDK-V2{DS}Runtime{DS}Plugins{DS}";
// 下方顺序不可变动
jsLibs = new string[]
{
$"{jsLibRootDir}SDK-WX-TextureMin-JS-WEBGL1.jslib",
$"{jsLibRootDir}SDK-WX-TextureMin-JS-WEBGL2.jslib",
$"{jsLibRootDir}SDK-WX-TextureMin-JS-WEBGL2-Linear.jslib",
};
}
int index = 0;
if (config.CompileOptions.Webgl2)
{
if (PlayerSettings.colorSpace == ColorSpace.Linear)
{
index = 2;
}
else
{
index = 1;
}
}
for (int i = 0; i < jsLibs.Length; i++)
{
var importer = AssetImporter.GetAtPath(jsLibs[i]) as PluginImporter;
bool value = i == index;
#if WEIXINMINIGAME
importer.SetCompatibleWithPlatform(BuildTarget.WeixinMiniGame, value);
#else
importer.SetCompatibleWithPlatform(BuildTarget.WebGL, value);
#endif
importer.SaveAndReimport();
}
}
public static bool IsInstantGameAutoStreaming()
{
if (string.IsNullOrEmpty(GetInstantGameAutoStreamingCDN()))
{
return false;
}
return true;
}
public static bool CheckSDK()
{
string dir = Path.Combine(Application.dataPath, "WX-WASM-SDK");
if (Directory.Exists(dir))
{
return false;
}
return true;
}
public static string GetInstantGameAutoStreamingCDN()
{
#if UNITY_INSTANTGAME
string cdn = Unity.InstantGame.IGBuildPipeline.GetInstantGameCDNRoot();
return cdn;
#else
return "";
#endif
}
}
}