1706 lines
71 KiB
C#
Raw Normal View History

2024-10-23 09:14:01 +08:00
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;
using System.Net;
using static WeChatWASM.LifeCycleEvent;
namespace WeChatWASM
{
public class WXConvertCore
{
static WXConvertCore()
{
}
public static void Init()
{
SDKFilePath = Path.Combine(UnityUtil.GetWxSDKRootPath(), "Runtime", "wechat-default", "unity-sdk", "index.js");
string templateHeader = "PROJECT:";
#if TUANJIE_2022_3_OR_NEWER
PlayerSettings.WeixinMiniGame.threadsSupport = false;
PlayerSettings.runInBackground = false;
PlayerSettings.WeixinMiniGame.compressionFormat = WeixinMiniGameCompressionFormat.Disabled;
if(UnityUtil.GetEngineVersion() == UnityUtil.EngineVersion.Tuanjie)
{
var absolutePath = Path.GetFullPath("Packages/com.qq.weixin.minigame/WebGLTemplates/WXTemplate2022TJ");
PlayerSettings.WeixinMiniGame.template = $"PATH:{absolutePath}";
}
else
{
PlayerSettings.WeixinMiniGame.template = $"{templateHeader}WXTemplate2022TJ";
}
PlayerSettings.WeixinMiniGame.linkerTarget = WeixinMiniGameLinkerTarget.Wasm;
PlayerSettings.WeixinMiniGame.dataCaching = false;
PlayerSettings.WeixinMiniGame.debugSymbolMode = WeixinMiniGameDebugSymbolMode.External;
#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
}
public enum WXExportError
{
SUCCEED = 0,
NODE_NOT_FOUND = 1,
BUILD_WEBGL_FAILED = 2,
}
public static WXEditorScriptObject config => UnityUtil.GetEditorConf();
public static string webglDir = "webgl"; // 导出的webgl目录
public static string miniGameDir = "minigame"; // 生成小游戏的目录
public static string audioDir = "Assets"; // 音频资源目录
public static string frameworkDir = "framework";
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";
private static bool lastBrotliType = false;
public static bool UseIL2CPP
{
get
{
#if TUANJIE_2022_3_OR_NEWER
return PlayerSettings.GetScriptingBackend(BuildTargetGroup.WeixinMiniGame) == ScriptingImplementation.IL2CPP;
#else
return true;
#endif
}
}
// 可以调用这个来集成
public static WXExportError DoExport(bool buildWebGL = true)
{
LifeCycleEvent.Init();
Emit(LifeCycle.beforeExport);
if (!CheckSDK())
{
Debug.LogError("若游戏曾使用旧版本微信SDK需删除 Assets/WX-WASM-SDK 文件夹后再导入最新工具包。");
return WXExportError.BUILD_WEBGL_FAILED;
}
if (!CheckBuildTemplate())
{
Debug.LogError("因构建模板检查失败终止导出。");
return WXExportError.BUILD_WEBGL_FAILED;
}
2024-10-25 18:09:59 +08:00
if (CheckInvalidPerfIntegration())
{
Debug.LogError("性能分析工具只能用于Development Build, 终止导出! ");
return WXExportError.BUILD_WEBGL_FAILED;
}
2024-10-23 09:14:01 +08:00
CheckBuildTarget();
Init();
2024-10-25 18:09:59 +08:00
ProcessWxPerfBinaries();
2024-10-23 09:14:01 +08:00
// JSLib
SettingWXTextureMinJSLib();
UpdateGraphicAPI();
EditorUtility.SetDirty(config);
AssetDatabase.SaveAssets();
// 记录上次导出的brotliType
{
var filePath = Path.Combine(config.ProjectConf.DST, miniGameDir, "unity-namespace.js");
string content = string.Empty;
if (File.Exists(filePath))
{
content = File.ReadAllText(filePath, Encoding.UTF8);
}
Regex regex = new Regex("brotliMT\\s*:\\s*(true|false)", RegexOptions.IgnoreCase);
Match match = regex.Match(content);
if (match.Success)
{
lastBrotliType = match.Groups[1].Value == "true";
}
}
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这里需要用工具将函数名提取出来
var symFile1 = "";
if (!UseIL2CPP)
{
symFile1 = Path.Combine(config.ProjectConf.DST, webglDir, "Code", "wwwroot", "_framework", "dotnet.native.js.symbols");
}
else
{
var rootPath = Directory.GetParent(Application.dataPath).FullName;
string webglDir = WXExtEnvDef.GETDEF("WEIXINMINIGAME") ? "WeixinMiniGame" : "WebGL";
symFile1 = Path.Combine(rootPath, "Library", "Bee", "artifacts", webglDir, "build", "debug_WebGL_wasm", "build.js.symbols");
}
WeChatWASM.UnityUtil.preprocessSymbols(symFile1, GetWebGLSymbolPath());
// WeChatWASM.UnityUtil.preprocessSymbols(GetWebGLSymbolPath());
}
ConvertCode();
if (!UseIL2CPP)
{
ConvertDotnetCode();
}
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 (UnityUtil.GetEngineVersion() == 0 && config.CompileOptions.fbslim && !IsInstantGameAutoStreaming())
{
WXAssetsTextTools.FirstBundleSlim(dataFilePath, (result, info) =>
{
if (!result)
{
Debug.LogWarning("[首资源包跳过优化]:因处理失败自动跳过" + info);
}
finishExport();
});
}
else
{
finishExport();
}
}
return WXExportError.SUCCEED;
}
2024-10-25 18:09:59 +08:00
private static void ProcessWxPerfBinaries()
{
string[] wxPerfPlugins;
string DS = WXAssetsTextTools.DS;
if (UnityUtil.GetSDKMode() == UnityUtil.SDKMode.Package)
{
wxPerfPlugins = new string[]
{
$"Packages{DS}com.qq.weixin.minigame{DS}Runtime{DS}Plugins{DS}WxPerfJsBridge.jslib",
$"Packages{DS}com.qq.weixin.minigame{DS}Runtime{DS}Plugins{DS}wx_perf_2022.a",
$"Packages{DS}com.qq.weixin.minigame{DS}Runtime{DS}Plugins{DS}wx_perf_2021.a",
};
}
else
{
string jsLibRootDir = $"Assets{DS}WX-WASM-SDK-V2{DS}Runtime{DS}Plugins{DS}";
// 下方顺序不可变动
wxPerfPlugins = new string[]
{
$"{jsLibRootDir}WxPerfJsBridge.jslib",
$"{jsLibRootDir}wx_perf_2022.a",
$"{jsLibRootDir}wx_perf_2021.a",
};
}
{
// WxPerfJsBridge.jslib
var wxPerfJSBridgeImporter = AssetImporter.GetAtPath(wxPerfPlugins[0]) as PluginImporter;
#if PLATFORM_WEIXINMINIGAME
wxPerfJSBridgeImporter.SetCompatibleWithPlatform(BuildTarget.WeixinMiniGame, config.CompileOptions.enablePerfAnalysis);
#else
wxPerfJSBridgeImporter.SetCompatibleWithPlatform(BuildTarget.WebGL, config.CompileOptions.enablePerfAnalysis);
#endif
}
{
// wx_perf_2022.a
bool bShouldEnablePerf2022Plugin = config.CompileOptions.enablePerfAnalysis && IsCompatibleWithUnity202203OrNewer();
var wxPerf2022Importer = AssetImporter.GetAtPath(wxPerfPlugins[1]) as PluginImporter;
#if PLATFORM_WEIXINMINIGAME
wxPerf2022Importer.SetCompatibleWithPlatform(BuildTarget.WeixinMiniGame, bShouldEnablePerf2022Plugin);
#else
wxPerf2022Importer.SetCompatibleWithPlatform(BuildTarget.WebGL, bShouldEnablePerf2022Plugin);
#endif
}
{
// wx_perf_2021.a
bool bShouldEnablePerf2021Plugin = config.CompileOptions.enablePerfAnalysis && IsCompatibleWithUnity202103To202203();
var wxPerf2021Importer = AssetImporter.GetAtPath(wxPerfPlugins[2]) as PluginImporter;
#if PLATFORM_WEIXINMINIGAME
wxPerf2021Importer.SetCompatibleWithPlatform(BuildTarget.WeixinMiniGame, bShouldEnablePerf2021Plugin);
#else
wxPerf2021Importer.SetCompatibleWithPlatform(BuildTarget.WebGL, bShouldEnablePerf2021Plugin);
#endif
}
for (int i = 0; i < wxPerfPlugins.Length; i++)
{
var importer = AssetImporter.GetAtPath(wxPerfPlugins[i]) as PluginImporter;
importer.SaveAndReimport();
AssetDatabase.WriteImportSettingsIfDirty(wxPerfPlugins[i]);
AssetDatabase.Refresh();
}
}
private static bool IsCompatibleWithUnity202203OrNewer()
{
#if UNITY_2022_3_OR_NEWER
return true;
#endif
return false;
}
static bool IsCompatibleWithUnity202103To202203()
{
#if UNITY_2022_3_OR_NEWER
return false;
#endif
#if !UNITY_2021_3_OR_NEWER
return false;
#endif
return true;
}
2024-10-23 09:14:01 +08:00
private static void CheckBuildTarget()
{
Emit(LifeCycle.beforeSwitchActiveBuildTarget);
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
}
Emit(LifeCycle.afterSwitchActiveBuildTarget);
}
public static void UpdateGraphicAPI()
{
GraphicsDeviceType[] targets = new GraphicsDeviceType[] { };
#if PLATFORM_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 bool CheckBuildTemplate()
{
string[] res = BuildTemplate.CheckCustomCoverBaseConflict(
Path.Combine(UnityUtil.GetWxSDKRootPath(), "Runtime", "wechat-default"),
Path.Combine(Application.dataPath, "WX-WASM-SDK-V2", "Editor", "template"),
new string[] { @"\.(js|ts|json)$" }
);
if (res.Length != 0)
{
Debug.LogError("系统发现自定义构建模板中存在以下文件对应的基础模板已被更新,为确保游戏导出正常工作请自行解决可能存在的冲突:");
for (int i = 0; i < res.Length; i++)
{
Debug.LogError($"自定义模板文件 [{i}]: [ {res[i]} ]");
}
Debug.LogError("有关上述警告产生原因及处理办法请阅读https://wechat-miniprogram.github.io/minigame-unity-webgl-transform/Design/BuildTemplate.html#%E6%96%B0%E7%89%88%E6%9C%ACsdk%E5%BC%95%E8%B5%B7%E7%9A%84%E5%86%B2%E7%AA%81%E6%8F%90%E9%86%92");
return false;
}
return true;
}
2024-10-25 18:09:59 +08:00
// Assert when release + Perf-feature
private static bool CheckInvalidPerfIntegration()
{
const string MACRO_ENABLE_WX_PERF_FEATURE = "ENABLE_WX_PERF_FEATURE";
string defineSymbols = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
return (!config.CompileOptions.DevelopBuild) && (defineSymbols.IndexOf(MACRO_ENABLE_WX_PERF_FEATURE) != -1);
}
2024-10-23 09:14:01 +08:00
private static void ConvertDotnetCode()
{
CompressAssemblyBrotli();
ConvertDotnetRuntimeCode();
ConvertDotnetFrameworkCode();
}
private static void ConvertDotnetRuntimeCode()
{
var runtimePath = GetWeixinMiniGameFilePath("jsModuleRuntime")[0];
var dotnetJs = File.ReadAllText(runtimePath, Encoding.UTF8);
Rule[] rules =
{
new Rule()
{
old = "await *WebAssembly\\.instantiate\\(\\w*,",
newStr = $"await WebAssembly.instantiate(Module[\"wasmPath\"],",
},
new Rule()
{
old = "['\"]Expected methodFullName if trace is instrumented['\"]\\);?",
newStr = "'Expected methodFullName if trace is instrumented'); return;",
}
};
foreach (var rule in rules)
{
if (ShowMatchFailedWarning(dotnetJs, rule.old, "runtime") == false)
{
dotnetJs = Regex.Replace(dotnetJs, rule.old, rule.newStr);
}
}
File.WriteAllText(Path.Combine(config.ProjectConf.DST, miniGameDir, frameworkDir, Path.GetFileName(runtimePath)), dotnetJs, new UTF8Encoding(false));
}
private static void CompressAssemblyBrotli()
{
GetWeixinMiniGameFilePath("assembly").ToList().ForEach(assembly => UnityUtil.brotli(assembly, assembly + ".br", "8"));
}
private static void ConvertDotnetFrameworkCode()
{
var target = "webgl.wasm.framework.unityweb.js";
var dotnetJsPath =
Path.Combine(config.ProjectConf.DST, webglDir, "Code", "wwwroot", "_framework", "dotnet.js");
var dotnetJs = File.ReadAllText(dotnetJsPath, Encoding.UTF8);
// todo: handle dotnet js
foreach (var rule in ReplaceRules.DoenetRules(new string[] { frameworkDir,
Path.GetFileName(GetWeixinMiniGameFilePath("jsModuleRuntime")[0]),
Path.GetFileName(GetWeixinMiniGameFilePath("jsModuleNative")[0]),
}))
{
if (ShowMatchFailedWarning(dotnetJs, rule.old, "dotnet") == false)
{
dotnetJs = Regex.Replace(dotnetJs, rule.old, rule.newStr);
}
}
File.WriteAllText(Path.Combine(config.ProjectConf.DST, miniGameDir, frameworkDir, target), ReplaceRules.DotnetHeader + dotnetJs + ReplaceRules.DotnetFooter, new UTF8Encoding(false));
}
private static void ConvertCode()
{
UnityEngine.Debug.LogFormat("[Converter] Starting to adapt framework. Dst: " + config.ProjectConf.DST);
UnityUtil.DelectDir(Path.Combine(config.ProjectConf.DST, miniGameDir));
string text = String.Empty;
var target = "webgl.wasm.framework.unityweb.js";
if (WXExtEnvDef.GETDEF("UNITY_2020_1_OR_NEWER"))
{
if (UseIL2CPP)
{
text = File.ReadAllText(Path.Combine(config.ProjectConf.DST, webglDir, "Build", "webgl.framework.js"), Encoding.UTF8);
}
else
{
var frameworkPath = GetWeixinMiniGameFilePath("jsModuleNative")[0];
target = Path.GetFileName(frameworkPath);
text = File.ReadAllText(frameworkPath, 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 current = i + 1;
var total = ReplaceRules.rules.Length;
EditorUtility.DisplayProgressBar($"Converting...{current}/{total}", "Replace holder...", current * 1.0f / total);
var rule = ReplaceRules.rules[i];
// text = Regex.Replace(text, rule.old, rule.newStr);
if (ShowMatchFailedWarning(text, rule.old, "WXReplaceRules") == false)
{
text = Regex.Replace(text, rule.old, rule.newStr);
}
}
EditorUtility.ClearProgressBar();
string[] prefixs =
{
"_JS_Video_",
//"jsVideo",
"_JS_Sound_",
"jsAudio",
"_JS_MobileKeyboard_",
"_JS_MobileKeybard_"
};
foreach (var prefix in prefixs)
{
text = RemoveFunctionsWithPrefix(text, prefix);
}
#if PLATFORM_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 (UseIL2CPP)
{
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));
}
if (!Directory.Exists(Path.Combine(config.ProjectConf.DST, miniGameDir, frameworkDir)))
{
Directory.CreateDirectory(Path.Combine(config.ProjectConf.DST, miniGameDir, frameworkDir));
}
var header = "var OriginalAudioContext = window.AudioContext || window.webkitAudioContext;window.AudioContext = function() {if (this instanceof window.AudioContext) {return wx.createWebAudioContext();} else {return new OriginalAudioContext();}};";
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;
var targetPath = Path.Combine(config.ProjectConf.DST, miniGameDir, target);
if (!UseIL2CPP)
{
targetPath = Path.Combine(config.ProjectConf.DST, miniGameDir, frameworkDir, target);
foreach (var rule in ReplaceRules.NativeRules)
{
if (ShowMatchFailedWarning(text, rule.old, "native") == false)
{
text = Regex.Replace(text, rule.old, rule.newStr);
}
}
}
File.WriteAllText(targetPath, text, new UTF8Encoding(false));
UnityEngine.Debug.LogFormat("[Converter] adapt framework done! ");
}
private static int Build()
{
#if PLATFORM_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";
PlayerSettings.WeixinMiniGame.emscriptenArgs += " -s EXPORTED_FUNCTIONS=_sbrk,_emscripten_stack_get_base,_emscripten_stack_get_end -s ERROR_ON_UNDEFINED_SYMBOLS=0";
}
#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 -s ERROR_ON_UNDEFINED_SYMBOLS=0";
#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 PLATFORM_WEIXINMINIGAME
PlayerSettings.WeixinMiniGame.emscriptenArgs += $" -s TOTAL_MEMORY={config.ProjectConf.MemorySize}MB";
#else
PlayerSettings.WebGL.emscriptenArgs += $" -s TOTAL_MEMORY={config.ProjectConf.MemorySize}MB";
#endif
}
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\"";
#if PLATFORM_WEIXINMINIGAME
PlayerSettings.WeixinMiniGame.emscriptenArgs += " -s EXPORTED_RUNTIME_METHODS='[" + original_EXPORTED_RUNTIME_METHODS + additional_EXPORTED_RUNTIME_METHODS + "]'";
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
PlayerSettings.WebGL.emscriptenArgs += " -s EXPORTED_RUNTIME_METHODS='[" + original_EXPORTED_RUNTIME_METHODS + additional_EXPORTED_RUNTIME_METHODS + "]'";
if (config.CompileOptions.ProfilingMemory)
{
PlayerSettings.WebGL.emscriptenArgs += " --memoryprofiler ";
}
if (config.CompileOptions.profilingFuncs)
{
PlayerSettings.WebGL.emscriptenArgs += " --profiling-funcs ";
}
#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 string[] GetWeixinMiniGameFilePath(string key)
{
var bootJson = Path.Combine(config.ProjectConf.DST, webglDir, "Code", "wwwroot", "_framework", "blazor.boot.json");
var boot = JsonMapper.ToObject(File.ReadAllText(bootJson, Encoding.UTF8));
// Disable jiterpreter if haven't set
if (!boot.ContainsKey("environmentVariables"))
{
var jd = new JsonData();
jd["INTERP_OPTS"] = "-jiterp";
boot["environmentVariables"] = jd;
JsonWriter writer = new JsonWriter();
boot.ToJson(writer);
File.WriteAllText(bootJson, writer.TextWriter.ToString());
Debug.Log("Env INTERP_OPTS added to blazor.boot.json");
}
else if (!boot["environmentVariables"].ContainsKey("INTERP_OPTS"))
{
boot["environmentVariables"]["INTERP_OPTS"] = "-jiterp";
JsonWriter writer = new JsonWriter();
boot.ToJson(writer);
File.WriteAllText(bootJson, writer.TextWriter.ToString());
Debug.Log("Env INTERP_OPTS added to blazor.boot.json");
}
return boot["resources"][key].Keys.Select(file => Path.Combine(config.ProjectConf.DST, webglDir, "Code", "wwwroot", "_framework", file)).ToArray();
}
private static void finishExport()
{
int code = GenerateBinFile();
if (code == 0)
{
convertDataPackage(false);
UnityEngine.Debug.LogFormat("[Converter] All done!");
//ShowNotification(new GUIContent("转换完成"));
Emit(LifeCycle.exportDone);
}
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"))
{
if (UseIL2CPP)
{
return Path.Combine(config.ProjectConf.DST, webglDir, "Build", "webgl.wasm");
}
else
{
return GetWeixinMiniGameFilePath("wasmNative")[0];
}
}
else
{
return Path.Combine(config.ProjectConf.DST, webglDir, "Build", "webgl.wasm.code.unityweb");
}
}
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"));
var buildTemplate = new BuildTemplate(
Path.Combine(UnityUtil.GetWxSDKRootPath(), "Runtime", "wechat-default"),
Path.Combine(Application.dataPath, "WX-WASM-SDK-V2", "Editor", "template"),
Path.Combine(config.ProjectConf.DST, miniGameDir)
);
buildTemplate.start();
// 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();
GameJsPlugins();
// 如果没有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) && lastBrotliType == config.CompileOptions.brotliMT)
{
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);
}
}
}
if (config.CompileOptions.brotliMT)
{
MultiThreadBrotliCompress(sourcePath, targetPath);
}
else
{
UnityUtil.brotli(sourcePath, targetPath);
}
if (targetPath != cachePath)
{
File.Copy(targetPath, cachePath, true);
}
return 0;
}
public static bool MultiThreadBrotliCompress(string sourcePath, string dstPath, int quality = 11, int window = 21, int maxCpuThreads = 0)
{
if (maxCpuThreads == 0) maxCpuThreads = Environment.ProcessorCount;
var sourceBuffer = File.ReadAllBytes(sourcePath);
byte[] outputBuffer = new byte[0];
int ret = 0;
if (sourceBuffer.Length > 50 * 1024 * 1024 && Path.GetExtension(sourcePath) == ".wasm") // 50MB以上的wasm压缩率低了可能导致小游戏包超过20MB需提高压缩率
{
ret = BrotliEnc.CompressWasmMT(sourceBuffer, ref outputBuffer, quality, window, maxCpuThreads);
}
else
{
ret = BrotliEnc.CompressBufferMT(sourceBuffer, ref outputBuffer, quality, window, maxCpuThreads);
}
if (ret == 0)
{
using (FileStream fileStream = new FileStream(dstPath, FileMode.Create, FileAccess.Write))
{
fileStream.Write(outputBuffer, 0, outputBuffer.Length);
}
return true;
}
else
{
Debug.LogError("CompressWasmMT failed");
return false;
}
}
/// <summary>
/// 更新game.json
/// </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 || config.CompileOptions.autoAdaptScreen)
{
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"]);
}
if (config.CompileOptions.autoAdaptScreen)
{
gameJson["displayMode"] = "desktop";
}
// 将配置写回到文件夹
gameJson.ToJson(writer);
File.WriteAllText(filePath, writer.TextWriter.ToString());
}
}
/// <summary>
/// 更新game.js
/// </summary>
private static void GameJsPlugins()
{
var filePath = Path.Combine(config.ProjectConf.DST, miniGameDir, "game.js");
string content = File.ReadAllText(filePath, Encoding.UTF8);
Regex regex = new Regex(@"^import .*;$", RegexOptions.Multiline);
MatchCollection matches = regex.Matches(content);
int lastIndex = 0;
if (matches.Count > 0)
{
lastIndex = matches[matches.Count - 1].Index + matches[matches.Count - 1].Length;
}
bool changed = false;
StringBuilder sb = new StringBuilder(content);
if (config.ProjectConf.needCheckUpdate)
{
sb.Insert(lastIndex, Environment.NewLine + "import './plugins/check-update';");
changed = true;
}
else
{
File.Delete(Path.Combine(config.ProjectConf.DST, miniGameDir, "plugins", "check-update.js"));
}
if (config.CompileOptions.autoAdaptScreen)
{
sb.Insert(lastIndex, Environment.NewLine + "import './plugins/screen-adapter';");
changed = true;
}
else
{
File.Delete(Path.Combine(config.ProjectConf.DST, miniGameDir, "plugins", "screen-adapter.js"));
}
if (changed)
{
File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);
}
else
{
Directory.Delete(Path.Combine(config.ProjectConf.DST, miniGameDir, "plugins"), true);
}
}
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;
}
private static string GetCustomUnicodeRange(string customUnicode)
{
if (customUnicode == string.Empty)
{
return "[]";
}
List<int> unicodeCodes = new List<int>();
// 将字符串中的每个字符转换为Unicode编码并存储在数组中
foreach (char c in customUnicode)
{
unicodeCodes.Add(char.ConvertToUtf32(c.ToString(), 0));
}
// 对数组进行排序
unicodeCodes.Sort();
// 将连续的编码合并为范围
List<Tuple<int, int>> ranges = new List<Tuple<int, int>>();
int startRange = unicodeCodes[0];
int endRange = unicodeCodes[0];
for (int i = 1; i < unicodeCodes.Count; i++)
{
if (unicodeCodes[i] == endRange)
{
continue;
}
else if (unicodeCodes[i] == endRange + 1)
{
endRange = unicodeCodes[i];
}
else
{
ranges.Add(Tuple.Create(startRange, endRange));
startRange = endRange = unicodeCodes[i];
}
}
ranges.Add(Tuple.Create(startRange, endRange));
StringBuilder ret = new StringBuilder();
// 输出范围
foreach (var range in ranges)
{
ret.AppendFormat("[0x{0:X}, 0x{1:X}], ", range.Item1, range.Item2);
}
// 移除字符串末尾的多余", "
ret.Length -= 2;
ret.Insert(0, "[");
ret.Append("]");
return ret.ToString();
}
/// <summary>
/// 生成Unitynamespace下的bootconfig
/// </summary>
private static string GenerateBootInfo()
{
StringBuilder sb = new StringBuilder();
// 添加player-connection-ip信息
2024-10-25 18:09:59 +08:00
try
2024-10-23 09:14:01 +08:00
{
2024-10-25 18:09:59 +08:00
var host = Dns.GetHostEntry("");
foreach (var ip in host.AddressList)
2024-10-23 09:14:01 +08:00
{
2024-10-25 18:09:59 +08:00
if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
sb.Append($"player-connection-ip={ip.ToString()}");
break;
}
2024-10-23 09:14:01 +08:00
}
}
2024-10-25 18:09:59 +08:00
catch (Exception e)
{
Debug.LogWarning("[可选]生成Boot info 失败!错误:" + e.Message);
}
2024-10-23 09:14:01 +08:00
return sb.ToString();
}
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];
var customUnicodeRange = GetCustomUnicodeRange(config.FontOptions.CustomUnicode);
Debug.Log("customUnicodeRange: " + customUnicodeRange);
var boolConfigInfo = GenerateBootInfo();
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(),
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(),
UseIL2CPP ? "" : "/framework",
UseIL2CPP ? "false" : "true",
config.CompileOptions.brotliMT ? "true" : "false",
// FontOptions
config.FontOptions.CJK_Unified_Ideographs ? "true" : "false",
config.FontOptions.C0_Controls_and_Basic_Latin ? "true" : "false",
config.FontOptions.CJK_Symbols_and_Punctuation ? "true" : "false",
config.FontOptions.General_Punctuation ? "true" : "false",
config.FontOptions.Enclosed_CJK_Letters_and_Months ? "true" : "false",
config.FontOptions.Vertical_Forms ? "true" : "false",
config.FontOptions.CJK_Compatibility_Forms ? "true" : "false",
config.FontOptions.Miscellaneous_Symbols ? "true" : "false",
config.FontOptions.CJK_Compatibility ? "true" : "false",
config.FontOptions.Halfwidth_and_Fullwidth_Forms ? "true" : "false",
config.FontOptions.Dingbats ? "true" : "false",
config.FontOptions.Letterlike_Symbols ? "true" : "false",
config.FontOptions.Enclosed_Alphanumerics ? "true" : "false",
config.FontOptions.Number_Forms ? "true" : "false",
config.FontOptions.Currency_Symbols ? "true" : "false",
config.FontOptions.Arrows ? "true" : "false",
config.FontOptions.Geometric_Shapes ? "true" : "false",
config.FontOptions.Mathematical_Operators ? "true" : "false",
customUnicodeRange,
boolConfigInfo,
2024-10-25 18:09:59 +08:00
config.CompileOptions.DevelopBuild ? "true" : "false",
config.CompileOptions.enablePerfAnalysis ? "true" : "false",
config.ProjectConf.MemorySize.ToString(),
2024-10-23 09:14:01 +08:00
});
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", "unity-sdk/font/index.js" };
ReplaceFileContent(files.ToArray(), replaceList.ToArray());
BuildTemplate.mergeJSON(
Path.Combine(Application.dataPath, "WX-WASM-SDK-V2", "Editor", "template", "minigame"),
Path.Combine(config.ProjectConf.DST, miniGameDir)
);
Emit(LifeCycle.afterBuildTemplate);
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 PLATFORM_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
}
public static bool ShowMatchFailedWarning(string text, string rule, string file)
{
if (Regex.IsMatch(text, rule) == false)
{
Debug.Log($"UnMatched {file} rule: {rule}");
return true;
}
return false;
}
}
}