using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System;
using System.Text.RegularExpressions;
using UnityEngine.Rendering;
using LitJson;
namespace WeChatWASM
{
///
/// 脚本调用的话可修改 Assets/WX-WASM-SDK/Editor/MiniGameConfig.asset配置,然后调用WXEditorWindow 的 DoExport方法导出小游戏
///
public class WXEditorWindow : EditorWindow
{
string projectName = "";
string appid = "";
string cdn = "";
string videoUrl = "";
public static string dst = "";
string streamCDN = "";
int bundleHashLength = 32;
string bundlePathIdentifier = "StreamingAssets;";
string bundleExcludeExtensions = "json;";
string preloadFiles = ""; // 预下载文件名, 以,分隔文件
// string audioPrefix = "";
bool useAudioApi = false;
bool useFriendRelation = false;
bool developBuild = false;
bool autoProfile = false;
bool scriptOnly = false;
bool profilingFuncs = false;
bool profilingMemory = false;
bool deleteStreamingAssets = true;
int assetLoadType = 0; // 首包资源加载方式
bool webgl2 = false;
int orientation = 0;
static string SDKFilePath = "";
public static WXEditorScriptObject config;
public static string webglDir = "webgl"; //导出的webgl目录
public static string miniGameDir = "minigame"; // 生成小游戏的目录
public static string audioDir = "Assets"; //音频资源目录
public string codeMd5 = "";
public string dataMd5 = "";
public string dataFileSize = "";
public Texture tex;
public string defaultImgSrc = "Assets/WX-WASM-SDK/wechat-default/images/background.jpg";
public string bgImageSrc = "";
public int memorySize = 0;
public bool hideAfterCallMain = true;
public string dataFileSubPrefix = "";
public int maxStorage = 200;
public int defaultReleaseSize = 31457280;
public int texturesHashLength = 8;
public string texturesPath = "Assets/Textures";
public bool needCacheTextures = false;
public int loadingBarWidth = 240;
public bool needCheckUpdate = false;
[MenuItem("微信小游戏 / 转换小游戏", false, 1)]
public static void Open()
{
#if !(UNITY_2018_1_OR_NEWER)
UnityEngine.Debug.LogError("目前仅支持 Unity2018及以上的版本!");
#endif
var win = GetWindow(typeof(WXEditorWindow), false, "转换微信小游戏", true);//创建窗口
win.minSize = new Vector2(650, 800);
win.maxSize = new Vector2(1600, 950);
win.Show();
// 打开面板时自动检查更新
PluginUpdateManager.CheckUpdte();
Init();
}
public static void Init() {
PlayerSettings.WebGL.threadsSupport = false;
PlayerSettings.runInBackground = false;
PlayerSettings.WebGL.compressionFormat = WebGLCompressionFormat.Disabled;
#if UNITY_2020_1_OR_NEWER
PlayerSettings.WebGL.template = "PROJECT:WXTemplate2020";
#else
PlayerSettings.WebGL.template = "PROJECT:WXTemplate";
#endif
PlayerSettings.WebGL.linkerTarget = WebGLLinkerTarget.Wasm;
PlayerSettings.WebGL.dataCaching = false;
#if UNITY_2021_2_OR_NEWER
PlayerSettings.WebGL.debugSymbolMode = WebGLDebugSymbolMode.Embedded;
#else
PlayerSettings.WebGL.debugSymbols = true;
#endif
EditorSettings.spritePackerMode = SpritePackerMode.AlwaysOnAtlas;
}
public void OnEnable()
{
Init();
LoadData();
UpdateGraphicAPI();
}
public void LoadData() {
SDKFilePath = Path.Combine(Application.dataPath, "WX-WASM-SDK", "wechat-default", "unity-sdk", "index.js");
config = UnityUtil.GetEditorConf();
projectName = config.ProjectConf.projectName;
appid = config.ProjectConf.Appid;
cdn = config.ProjectConf.CDN;
assetLoadType = config.ProjectConf.assetLoadType;
videoUrl = config.ProjectConf.VideoUrl;
orientation = (int)config.ProjectConf.Orientation;
dst = config.ProjectConf.DST;
// streamCDN = config.ProjectConf.StreamCDN;
bundleHashLength = config.ProjectConf.bundleHashLength;
bundlePathIdentifier = config.ProjectConf.bundlePathIdentifier;
bundleExcludeExtensions = config.ProjectConf.bundleExcludeExtensions;
preloadFiles = config.ProjectConf.preloadFiles;
developBuild = config.CompileOptions.DevelopBuild;
autoProfile = config.CompileOptions.AutoProfile;
scriptOnly = config.CompileOptions.ScriptOnly;
profilingFuncs = config.CompileOptions.profilingFuncs;
profilingMemory = config.CompileOptions.ProfilingMemory;
deleteStreamingAssets = config.CompileOptions.DeleteStreamingAssets;
webgl2 = config.CompileOptions.Webgl2;
useAudioApi = config.SDKOptions.UseAudioApi;
// audioPrefix = config.ProjectConf.AssetsUrl;
useFriendRelation = config.SDKOptions.UseFriendRelation;
bgImageSrc = config.ProjectConf.bgImageSrc;
tex = AssetDatabase.LoadAssetAtPath(bgImageSrc);
memorySize = config.ProjectConf.MemorySize;
hideAfterCallMain = config.ProjectConf.HideAfterCallMain;
// 不常用配置,先只通过MiniGameConfig.assets修改
dataFileSubPrefix = config.ProjectConf.dataFileSubPrefix;
maxStorage = config.ProjectConf.maxStorage;
defaultReleaseSize = config.ProjectConf.defaultReleaseSize;
texturesHashLength = config.ProjectConf.texturesHashLength;
texturesPath = config.ProjectConf.texturesPath;
needCacheTextures = config.ProjectConf.needCacheTextures;
loadingBarWidth = config.ProjectConf.loadingBarWidth;
needCheckUpdate = config.ProjectConf.needCheckUpdate;
}
private void OnFocus()
{
LoadData();
}
private void OnDisable()
{
EditorUtility.SetDirty(config);
}
private void OnLostFocus() {
config.ProjectConf.projectName = projectName;
config.ProjectConf.Appid = appid;
config.ProjectConf.CDN = cdn;
config.ProjectConf.assetLoadType = assetLoadType;
config.ProjectConf.VideoUrl = videoUrl;
config.ProjectConf.Orientation = (WXScreenOritation)orientation;
config.ProjectConf.DST = dst;
// config.ProjectConf.StreamCDN = streamCDN;
config.ProjectConf.bundleHashLength = bundleHashLength;
config.ProjectConf.bundlePathIdentifier = bundlePathIdentifier;
config.ProjectConf.bundleExcludeExtensions = bundleExcludeExtensions;
config.ProjectConf.preloadFiles = preloadFiles;
config.CompileOptions.DevelopBuild = developBuild;
config.CompileOptions.AutoProfile = autoProfile;
config.CompileOptions.ScriptOnly = scriptOnly;
config.CompileOptions.profilingFuncs = profilingFuncs;
config.CompileOptions.ProfilingMemory = profilingMemory;
config.CompileOptions.DeleteStreamingAssets = deleteStreamingAssets;
config.CompileOptions.Webgl2 = webgl2;
config.SDKOptions.UseAudioApi = useAudioApi;
// config.ProjectConf.AssetsUrl = audioPrefix;
config.SDKOptions.UseFriendRelation = useFriendRelation;
config.ProjectConf.bgImageSrc = bgImageSrc;
config.ProjectConf.MemorySize = memorySize;
config.ProjectConf.HideAfterCallMain = hideAfterCallMain;
config.ProjectConf.dataFileSubPrefix = dataFileSubPrefix;
config.ProjectConf.maxStorage = maxStorage;
config.ProjectConf.defaultReleaseSize = defaultReleaseSize;
config.ProjectConf.texturesHashLength = texturesHashLength;
config.ProjectConf.texturesPath = texturesPath;
config.ProjectConf.needCacheTextures = needCacheTextures;
config.ProjectConf.loadingBarWidth = loadingBarWidth;
config.ProjectConf.needCheckUpdate = needCheckUpdate;
}
static string[] GetScenePaths()
{
List scenes = new List();
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();
}
private int Build()
{
PlayerSettings.WebGL.emscriptenArgs = "";
PlayerSettings.runInBackground = false;
if (memorySize != 0)
{
if (memorySize >= 1024)
{
UnityEngine.Debug.LogErrorFormat($"memorySize必须小于1024,请查看GIT文档优化Unity WebGL的内存");
return -1;
}
PlayerSettings.WebGL.emscriptenArgs += $" -s TOTAL_MEMORY={memorySize}MB";
}
if (profilingMemory)
{
PlayerSettings.WebGL.emscriptenArgs += " --memoryprofiler ";
}
if (profilingFuncs)
{
#if !(UNITY_2021_2_OR_NEWER)
PlayerSettings.WebGL.emscriptenArgs += " --profiling-funcs";
#else
PlayerSettings.WebGL.debugSymbolMode = WebGLDebugSymbolMode.Embedded;
#endif
}
UnityEngine.Debug.Log("[Builder] Starting to build WebGL project ... ");
UnityEngine.Debug.Log("PlayerSettings.WebGL.emscriptenArgs : "+ PlayerSettings.WebGL.emscriptenArgs);
// PlayerSettings.WebGL.memorySize = memorySize;
BuildOptions option = BuildOptions.None;
if (developBuild)
{
option |= BuildOptions.Development;
}
if (autoProfile)
{
option |= BuildOptions.ConnectWithProfiler;
}
if (scriptOnly)
{
option |= BuildOptions.BuildScriptsOnly;
}
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(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:%s", PlayerSettings.WebGL.emscriptenArgs);
return -1;
}
UnityEngine.Debug.LogFormat("[Builder] Done: " + projDir);
return 0;
}
private static bool CopyDirectory(string SourcePath, string DestinationPath, bool overwriteexisting)
{
bool ret = false;
var separator = Path.DirectorySeparatorChar;
var ignoreFiles = new List() { "unityNamespace.js" };
// eventEmitter - 改名为event-emitter
// loading和libs 是可交互视频用到的文件,先下掉可交互方案
var ignoreDirs = new List() { "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;
}
private static bool CopyMusicDirectory(string SourcePath, string DestinationPath, bool overwriteexisting)
{
bool ret = false;
try
{
if (Directory.Exists(SourcePath))
{
foreach (string fls in Directory.GetFiles(SourcePath))
{
FileInfo flinfo = new FileInfo(fls);
string[] suffix = { ".wav",".mp3", "m4a", "aac", "mp4" };
if (Array.IndexOf(suffix, flinfo.Extension.ToLower())>-1) {
UnityUtil.CreateDir(DestinationPath);
flinfo.CopyTo(Path.Combine(DestinationPath, flinfo.Name), overwriteexisting);
}
}
foreach (string drs in Directory.GetDirectories(SourcePath))
{
DirectoryInfo drinfo = new DirectoryInfo(drs);
if (CopyMusicDirectory(drs, Path.Combine(DestinationPath, drinfo.Name), overwriteexisting) == false)
ret = false;
}
}
ret = true;
}
catch (Exception ex)
{
ret = false;
UnityEngine.Debug.LogError(ex);
}
return ret;
}
private void ConvertCode()
{
UnityEngine.Debug.LogFormat("[Converter] Starting to adapt framewor. Dst: " + dst);
UnityUtil.DelectDir(Path.Combine(dst, miniGameDir));
#if UNITY_2020_1_OR_NEWER
string text = File.ReadAllText(Path.Combine(dst, webglDir, "Build", "webgl.framework.js"), Encoding.UTF8);
#else
string text = File.ReadAllText(Path.Combine(dst, webglDir, "Build", "webgl.wasm.framework.unityweb"), Encoding.UTF8);
#endif
int i;
for (i = 0; i < ReplaceRules.rules.Length; i++)
{
var rule = ReplaceRules.rules[i];
text = Regex.Replace(text, rule.old, rule.newStr);
}
if (PlayerSettings.WebGL.exceptionSupport == WebGLExceptionSupport.None)
{
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.StartsWith("(") && text.EndsWith(")"))
{
text = text.Substring(1, text.Length - 2);
}
text = "GameGlobal.unityNamespace.UnityModule = " + text;
}
if (!Directory.Exists(Path.Combine(dst, miniGameDir)))
{
Directory.CreateDirectory(Path.Combine(dst, miniGameDir));
}
File.WriteAllText(Path.Combine(dst, miniGameDir, "webgl.wasm.framework.unityweb.js"), text, new UTF8Encoding(false));
UnityEngine.Debug.LogFormat("[Converter] adapt framework done! ");
}
///
/// 删掉导出目录webgl目录下旧资源包
///
private 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"))
{
File.Delete(fileInfo.FullName);
}
}
}
}
catch (Exception ex)
{
UnityEngine.Debug.LogError(ex);
}
}
///
/// 等brotli之后,统计下资源包加brotli压缩后代码包是否超过了20M(小游戏代码分包总大小限制)
///
private void checkNeedCopyDataPackage(bool brotliError)
{
// 如果brotli失败,使用CDN加载
if (brotliError) {
// brotli失败后,因为无法知道wasmcode大小,则得不到最终小游戏总包体大小。不能使用小游戏分包加载资源,还原成cdn的方式。
if (assetLoadType == 1) {
UnityEngine.Debug.LogWarning("brotli失败,无法检测文件大小,请上传资源文件到CDN");
assetLoadType = 0;
}
ShowNotification(new GUIContent("Brotli压缩失败,请到转出目录手动压缩!!!"));
}
if ((assetLoadType == 1))
{
#if UNITY_2020_1_OR_NEWER
var dataPath = Path.Combine(dst, webglDir, "Build", "webgl.data");
#else
var dataPath = Path.Combine(dst, webglDir, "Build", "webgl.data.unityweb");
#endif
var brcodePath = Path.Combine(dst, miniGameDir, "wasmcode", codeMd5 + ".webgl.wasm.code.unityweb.wasm.br");
var brcodeInfo = new FileInfo(brcodePath);
var brcodeSize = brcodeInfo.Length;
if (brcodeSize + int.Parse(dataFileSize) > 20971520)
{
ShowNotification(new GUIContent("资源文件过大,不适宜用代码分包加载"));
throw new Exception("资源文件过大,不适宜用代码分包加载");
} else
{
File.Copy(dataPath, Path.Combine(dst, miniGameDir, "data-package", dataMd5 + ".webgl.data.unityweb.bin.txt"), true);
}
}
var loadDataFromCdn = assetLoadType == 0;
Rule[] rules =
{
new Rule()
{
old="$DEPLOY_URL",
newStr= cdn
},
new Rule()
{
old="$LOAD_DATA_FROM_SUBPACKAGE",
newStr = loadDataFromCdn ? "false" : "true"
}
};
string[] files = { "game.js", "game.json", "project.config.json" };
ReplaceFileContent(files, rules);
}
public int GenerateBinFile(bool isFromConvert = false)
{
UnityEngine.Debug.LogFormat("[Converter] Starting to genarate md5 and copy files");
#if UNITY_2020_1_OR_NEWER
var codePath = Path.Combine(dst, webglDir, "Build", "webgl.wasm");
#else
var codePath = Path.Combine(dst, webglDir, "Build", "webgl.wasm.code.unityweb");
#endif
codeMd5 = UnityUtil.BuildFileMd5(codePath);
#if UNITY_2020_1_OR_NEWER
var dataPath = Path.Combine(dst, webglDir, "Build", "webgl.data");
#else
var dataPath = Path.Combine(dst, webglDir, "Build", "webgl.data.unityweb");
#endif
dataMd5 = UnityUtil.BuildFileMd5(dataPath);
#if UNITY_2020_1_OR_NEWER
var symbolPath = Path.Combine(dst, webglDir, "Build", "webgl.symbols.json");
#else
var symbolPath = Path.Combine(dst, webglDir, "Build", "webgl.wasm.symbols.unityweb");
#endif
RemoveOldAssetPackage(Path.Combine(dst, webglDir));
RemoveOldAssetPackage(Path.Combine(dst, webglDir+"-min"));
File.Copy(dataPath, Path.Combine(dst, webglDir, dataMd5 + ".webgl.data.unityweb.bin.txt"), true);
CopyDirectory(Path.Combine(Application.dataPath, "WX-WASM-SDK", "wechat-default"), Path.Combine(dst, miniGameDir), true);
// FIX: 2021.2版本生成symbol有bug,导出时生成symbol报错,有symbol才copy
// 代码分包需要symbol文件以进行增量更新
if (File.Exists(symbolPath)) {
File.Copy(symbolPath, Path.Combine(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();
if (useAudioApi) {
CopyMusicDirectory(Application.dataPath, Path.Combine(dst, webglDir ,audioDir), true);
}
// 如果没有StreamingAssets目录,默认生成
if (!Directory.Exists(Path.Combine(dst, webglDir, "StreamingAssets"))) {
Directory.CreateDirectory(Path.Combine(dst, webglDir, "StreamingAssets"));
}
return Brotlib(codePath);
}
private void ModifySDKFile()
{
var config = UnityUtil.GetEditorConf();
string content = File.ReadAllText(SDKFilePath, Encoding.UTF8);
content = content.Replace("$unityVersion$", Application.unityVersion);
File.WriteAllText(Path.Combine(dst, miniGameDir, "unity-sdk", "index.js"), content, Encoding.UTF8);
content = File.ReadAllText(Path.Combine(Application.dataPath, "WX-WASM-SDK", "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(dst, miniGameDir, "unity-sdk", "storage.js"), content, Encoding.UTF8);
//修改纹理dxt
content = File.ReadAllText(Path.Combine(Application.dataPath, "WX-WASM-SDK", "wechat-default", "unity-sdk", "texture.js"), Encoding.UTF8);
content = content.Replace("\"$UseDXT5$\"", config.CompressTexture.useDXT5 ? "true" : "false");
File.WriteAllText(Path.Combine(dst, miniGameDir, "unity-sdk", "texture.js"), content, Encoding.UTF8);
}
class PreloadFile
{
public PreloadFile(string fn, string rp)
{
fileName = fn;
relativePath = rp;
}
public string fileName;
public string relativePath;
};
///
/// 从webgl目录模糊搜索preloadfiles中的文件,作为预下载的列表
///
private string GetPreloadList(string strPreloadfiles)
{
if (strPreloadfiles == "")
{
return "";
}
string preloadList = "";
var streamingAssetsPath = Path.Combine(dst, webglDir + "/StreamingAssets");
var fileNames = strPreloadfiles.Split(new char[] { ';' });
List preloadFiles = new List();
foreach(var fileName in fileNames)
{
if (fileName.Trim() == "") continue;
preloadFiles.Add(new PreloadFile(fileName, ""));
}
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 == "")
{
UnityEngine.Debug.LogError($"并非所有预下载的文件都被找到,剩余:{preloadFile.fileName}");
continue;
}
preloadList += ("\"" + preloadFile.relativePath + "\", \r");
}
return preloadList;
}
public string HandleLoadingImage()
{
var info = AssetDatabase.LoadAssetAtPath(bgImageSrc);
var oldFilename = Path.GetFileName(defaultImgSrc);
var newFilename = Path.GetFileName(bgImageSrc);
if (bgImageSrc != defaultImgSrc)
{
// 图片宽高不能超过2048
if (info.width > 2048 || info.height > 2048)
{
throw new Exception("封面图宽高不可超过2048");
}
File.Delete(Path.Combine(dst, miniGameDir, "images", oldFilename));
File.Copy(bgImageSrc, Path.Combine(dst, miniGameDir, "images", newFilename), true);
return "images/" + Path.GetFileName(bgImageSrc);
} else
{
return "images/" + Path.GetFileName(defaultImgSrc);
}
}
public void ModifyWeChatConfigs(bool isFromConvert = false)
{
UnityEngine.Debug.LogFormat("[Converter] Starting to modify configs");
var config = UnityUtil.GetEditorConf();
var PRELOAD_LIST = GetPreloadList(preloadFiles);
var imgSrc = HandleLoadingImage();
var bundlePathIdentifierStr = GetArrayString(bundlePathIdentifier);
var excludeFileExtensionsStr = GetArrayString(bundleExcludeExtensions);
var screenOrientation = new List(){"portrait", "landscape", "landscapeLeft", "landscapeRight"}[orientation];
Rule[] replaceArrayList =
{
new Rule()
{
old="$GAME_NAME",
newStr="webgl"
},
new Rule()
{
old="$PROJECT_NAME",
newStr=projectName == "" ? "webgl" : projectName,
},
new Rule()
{
old="$APP_ID",
newStr=appid
},
new Rule()
{
old="$ORIENTATION",
newStr=screenOrientation
},
new Rule()
{
old="$LOADING_VIDEO_URL",
newStr=videoUrl
},
new Rule()
{
old="$CODE_MD5",
newStr=codeMd5
},
new Rule()
{
old="$DATA_MD5",
newStr=dataMd5
},
// new Rule()
// {
// old="$DATA_FILE_SIZE",
// newStr=dataFileSize
// },
new Rule()
{
old="$STREAM_CDN",
newStr=streamCDN
},
new Rule()
{
old="$AUDIO_PREFIX",
newStr=cdn + "/Assets"
},
new Rule()
{
old="\"$PRELOAD_LIST\"",
newStr=PRELOAD_LIST
},
new Rule()
{
old="$BACKGROUND_IMAGE",
newStr=imgSrc
},
new Rule()
{
old="$HIDE_AFTER_CALLMAIN",
newStr = hideAfterCallMain ? "true" : "false"
},
new Rule()
{
old="$BUNDLE_HASH_LENGTH",
newStr=bundleHashLength.ToString()
},
new Rule()
{
old="$BUNDLE_PATH_IDENTIFIER",
newStr=bundlePathIdentifierStr
},
new Rule()
{
old="$EXCLUDE_FILE_EXTENSIONS",
newStr=excludeFileExtensionsStr
},
new Rule()
{
old="$WEBGL_VERSION",
newStr=webgl2? "2" : "1"
},
new Rule()
{
old="$UNITY_VERSION",
newStr=Application.unityVersion
},
new Rule()
{
old="$PLUGIN_VERSION",
newStr=WXPluginVersion.pluginVersion
},
new Rule()
{
old="$DATA_FILE_SUB_PREFIX",
newStr=dataFileSubPrefix
},
new Rule()
{
old="$MAX_STORAGE_SIZE",
newStr=maxStorage.ToString()
},
new Rule()
{
old="$DEFAULT_RELEASE_SIZE",
newStr=defaultReleaseSize.ToString()
},
new Rule()
{
old="$TEXTURE_HASH_LENGTH",
newStr=texturesHashLength.ToString()
},
new Rule()
{
old="$TEXTURES_PATH",
newStr=texturesPath
},
new Rule()
{
old="$NEED_CACHE_TEXTURES",
newStr=needCacheTextures ? "true" : "false"
},
new Rule()
{
old="$LOADING_BAR_WIDTH",
newStr=loadingBarWidth.ToString()
},
new Rule()
{
old="$NEED_CHECK_UPDATE",
newStr=needCheckUpdate ? "true" : "false"
}
};
List replaceList = new List(replaceArrayList);
List files = new List { "game.js", "game.json", "project.config.json", "unity-namespace.js" };
ReplaceFileContent(files.ToArray(), replaceList.ToArray());
UnityEngine.Debug.LogFormat("[Converter] that to modify configs ended");
}
///
/// 对文件做内容替换
///
///
///
public 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(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));
}
}
}
///
/// 按;分隔字符串,将分隔后每一项作为字符串用,连接
/// eg: input "i1;i2;i3" => output: `"i1", "i2", "i3"`
///
///
public string GetArrayString(string inp) {
var result = "";
var iterms = new List(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 int Brotlib(string filePath)
{
UnityEngine.Debug.LogFormat("[Converter] Starting to generate Brotlib file");
var exePath = "";
var path = "";
#if UNITY_EDITOR_OSX
exePath = Path.Combine(Application.dataPath, "WX-WASM-SDK/Editor/Brotli/macos/brotli");
#else
exePath = Path.Combine(Application.dataPath, "WX-WASM-SDK/Editor/Brotli/win_x86_64/brotli.exe");
#endif
var dstPath = Path.Combine(dst, miniGameDir, "wasmcode", codeMd5 + ".webgl.wasm.code.unityweb.wasm.br");
WeChatWASM.UnityUtil.RunCmd(exePath, string.Format($" --force --quality 11" +
$" --input {filePath}" +
$" --output {dstPath}"), path);
return 0;
}
///
/// 如果没有使用好友关系链的话,自动删掉无用代码
///
private void ClearFriendRelationCode()
{
if (!useFriendRelation)
{
var filePath = Path.Combine(dst, miniGameDir, "game.json");
string content = File.ReadAllText(filePath, Encoding.UTF8);
content = content.Replace("\"openDataContext\": \"open-data\",", "");
File.WriteAllText(filePath, content);
string openDataDir = Path.Combine(dst, miniGameDir, "open-data");
UnityUtil.DelectDir(openDataDir);
Directory.Delete(openDataDir, true);
}
}
public static void DrawProObjectField(
GUIContent label,
SerializedProperty value,
Type objType,
GUIStyle style,
bool allowSceneObjects,
Texture objIcon = null) where T : UnityEngine.Object
{
T tObj = value.objectReferenceValue as T;
if (objIcon == null)
{
objIcon = EditorGUIUtility.FindTexture("PrefabNormal Icon");
}
style.imagePosition = ImagePosition.ImageLeft;
int pickerID = 455454425;
if (tObj != null)
{
EditorGUILayout.LabelField(label,
new GUIContent(tObj.name, objIcon), style);
}
if (GUILayout.Button("Select"))
{
EditorGUIUtility.ShowObjectPicker(
tObj, allowSceneObjects, "", pickerID);
}
if (Event.current.commandName == "ObjectSelectorUpdated")
{
if (EditorGUIUtility.GetObjectPickerControlID() == pickerID)
{
tObj = EditorGUIUtility.GetObjectPickerObject() as T;
value.objectReferenceValue = tObj;
}
}
}
private void OnGUI()
{
var labelStyle = new GUIStyle(EditorStyles.boldLabel);
labelStyle.fontSize = 14;
labelStyle.margin.left = 20;
labelStyle.margin.top = 10;
labelStyle.margin.bottom = 10;
GUILayout.Label("基本设置", labelStyle);
var inputStyle = new GUIStyle(EditorStyles.textField);
inputStyle.fontSize = 14;
inputStyle.margin.left = 20;
inputStyle.margin.bottom = 10;
inputStyle.margin.right = 20;
var intPopupStyle = new GUIStyle(EditorStyles.popup);
intPopupStyle.fontSize = 14;
intPopupStyle.margin.left = 20;
intPopupStyle.margin.bottom = 15;
intPopupStyle.margin.right = 20;
appid = EditorGUILayout.TextField("游戏appid", appid, inputStyle);
cdn = EditorGUILayout.TextField("游戏资源CDN", cdn, inputStyle);
projectName = EditorGUILayout.TextField("小游戏项目名", projectName, inputStyle);
orientation = EditorGUILayout.IntPopup("游戏方向", orientation, new[] { "Portrait", "Landscape", "LandscapeLeft", "LandscapeRight" }, new[] { 0, 1, 2, 3 }, intPopupStyle);
var totalMemoryFieldDesc = new GUIContent("最大内存(MB)", "预留的初始内存值,需评估游戏最大内存峰值进行设置,消除内存自动增长带来的峰值尖刺。请查看GIT文档<优化Unity WebGL的内存>");
memorySize = EditorGUILayout.IntField(totalMemoryFieldDesc, memorySize, inputStyle);
GUILayout.Label("导出路径", labelStyle);
var choosePathButtonClicked = false;
var openTargetButtonClicked = false;
var resetButtonClicked = false;
if (dst == "")
{
GUIStyle pathButtonStyle = new GUIStyle(GUI.skin.button);
pathButtonStyle.fontSize = 12;
pathButtonStyle.margin.left = 20;
choosePathButtonClicked = GUILayout.Button("选择导出路径", pathButtonStyle, GUILayout.Height(30), GUILayout.Width(200));
}
else
{
int pathButtonHeight = 28;
GUIStyle pathLabelStyle = new GUIStyle(GUI.skin.textField);
pathLabelStyle.fontSize = 12;
pathLabelStyle.alignment = TextAnchor.MiddleLeft;
pathLabelStyle.margin.top = 6;
pathLabelStyle.margin.bottom = 6;
pathLabelStyle.margin.left = 20;
GUILayout.BeginHorizontal();
// 路径框
GUILayout.Label(dst, pathLabelStyle, GUILayout.Height(pathButtonHeight - 6), GUILayout.ExpandWidth(true), GUILayout.MaxWidth(EditorGUIUtility.currentViewWidth - 126));
openTargetButtonClicked = GUILayout.Button("打开", GUILayout.Height(pathButtonHeight), GUILayout.Width(40));
resetButtonClicked = GUILayout.Button("重选", GUILayout.Height(pathButtonHeight), GUILayout.Width(40));
GUILayout.EndHorizontal();
}
EditorGUILayout.Space();
GUILayout.Label("启动Loader设置", labelStyle);
EditorGUILayout.BeginHorizontal();
GUILayout.Space(20);
tex = (Texture)EditorGUILayout.ObjectField("启动背景图/视频封面", tex, typeof(Texture2D), false);
var currentBgSrc = AssetDatabase.GetAssetPath(tex);
TextureImporter texInfo = (TextureImporter)AssetImporter.GetAtPath(currentBgSrc);
if (!string.IsNullOrEmpty(currentBgSrc) && currentBgSrc != bgImageSrc)
{
bgImageSrc = currentBgSrc;
var config = UnityUtil.GetEditorConf();
config.ProjectConf.bgImageSrc = bgImageSrc;
EditorUtility.SetDirty(config);
AssetDatabase.SaveAssets();
}
GUILayout.Space(20);
EditorGUILayout.EndHorizontal();
videoUrl = EditorGUILayout.TextField("加载阶段视频url", videoUrl, inputStyle);
var optionsList = new List();
optionsList.Add(new GUIContent("CDN"));
optionsList.Add(new GUIContent("小游戏分包"));
GUIContent assetLoadTypeLabel = new GUIContent("首包资源加载方式", "选择'CDN'通过传统CDN加载资源。选择'小游戏分包'通过小游戏代码分包加载资源。小游戏分包有总大小20M限制,若资源加代码总大小超过20M,会自动切换为传统CDN加载");
assetLoadType = EditorGUILayout.IntPopup(assetLoadTypeLabel, assetLoadType, optionsList.ToArray(), new[] { 0, 1 }, intPopupStyle);
// audioPrefix = EditorGUILayout.TextField("Assets目录对应CDN地址", audioPrefix, inputStyle);
// memorySize = EditorGUILayout.IntField("游戏内存大小(MB)", memorySize, inputStyle);
// EditorGUILayout.Space();
// GUILayout.Label("AssetBundle缓存配置", labelStyle);
// streamCDN = EditorGUILayout.TextField("Bundle包CDN地址", streamCDN, inputStyle);
// var bundlePathFieldDesc = new GUIContent("路径标识符(;分隔)", "下载路径中包含标识符可判定为下载Bundle,可自动缓存。默认值StreamingAssets");
// bundlePathIdentifier = EditorGUILayout.TextField(bundlePathFieldDesc, bundlePathIdentifier, inputStyle);
var bundleExcludeExtensionsFieldDesc = new GUIContent("不自动缓存文件类型(;分隔)", "当请求url包含资源'cdn+StreamingAssets'时会自动缓存,但StreamingAssets目录下不是所有文件都需缓存,此选项配置不需要自动缓存的文件拓展名。默认值json");
bundleExcludeExtensions = EditorGUILayout.TextField(bundleExcludeExtensionsFieldDesc, bundleExcludeExtensions, inputStyle);
var bundleHashLengthFieldDesc = new GUIContent("Bundle名中Hash长度", "自定义Bundle文件名中hash部分长度,默认值32,用于缓存控制。");
bundleHashLength = EditorGUILayout.IntField(bundleHashLengthFieldDesc, bundleHashLength, inputStyle);
EditorGUILayout.Space();
GUILayout.Label("预下载选项", labelStyle);
GUILayout.BeginHorizontal();
preloadFiles = EditorGUILayout.TextField("文件列表(;间隔,模糊匹配)", preloadFiles, inputStyle);
GUILayout.EndHorizontal();
EditorGUILayout.Space();
GUIStyle toggleStyle = new GUIStyle(GUI.skin.toggle);
toggleStyle.margin.left = 20;
toggleStyle.margin.right = 20;
GUILayout.Label("SDK功能选项", labelStyle);
GUILayout.BeginHorizontal();
useFriendRelation = GUILayout.Toggle(useFriendRelation, "使用好友关系链", toggleStyle);
useAudioApi = GUILayout.Toggle(useAudioApi, "使用微信音频API", toggleStyle);
GUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.Space();
GUILayout.Label("调试编译选项", labelStyle);
GUILayout.BeginHorizontal();
developBuild = GUILayout.Toggle(developBuild, "Development Build",toggleStyle);
autoProfile = GUILayout.Toggle(autoProfile, "Autoconnect Profiler", toggleStyle);
scriptOnly = GUILayout.Toggle(scriptOnly, "Scripts Only Build", toggleStyle);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
profilingFuncs = GUILayout.Toggle(profilingFuncs, "Profiling Funcs ", toggleStyle);
profilingMemory = GUILayout.Toggle(profilingMemory, "Profiling Memory ", toggleStyle);
var oldwebgl2 = webgl2;
webgl2 = GUILayout.Toggle(webgl2, "WebGL2.0(beta)", toggleStyle);
if (oldwebgl2 != webgl2) UpdateGraphicAPI();
GUILayout.EndHorizontal();
EditorGUILayout.Space();
deleteStreamingAssets = GUILayout.Toggle(deleteStreamingAssets, "ClearStreamingAssets", toggleStyle);
GUIStyle exportButtonStyle = new GUIStyle(GUI.skin.button);
exportButtonStyle.fontSize = 14;
exportButtonStyle.margin.left = 20;
exportButtonStyle.margin.top = 40;
EditorGUILayout.BeginHorizontal();
var isExportBtnPressed = GUILayout.Button("导出WEBGL并转换为小游戏(常用)", exportButtonStyle, GUILayout.Height(40), GUILayout.Width(EditorGUIUtility.currentViewWidth - 270));
var isConvertBtnPressed = GUILayout.Button("将WEBGL转为小游戏(不常用)", exportButtonStyle, GUILayout.Height(40), GUILayout.Width(210));
EditorGUILayout.EndHorizontal();
if (isExportBtnPressed)
{
DoExport(true);
}
if (isConvertBtnPressed)
{
DoExport(false);
}
if (choosePathButtonClicked)
{
// 弹出选目录窗口
var dstPath = EditorUtility.SaveFolderPanel("选择你的游戏导出目录", "", "");
if (dstPath != "")
{
dst = dstPath;
OnLostFocus();
}
}
if (openTargetButtonClicked)
{
UnityUtil.ShowInExplorer(dst);
}
if (resetButtonClicked)
{
dst = "";
}
EditorGUILayout.Space();
}
private void UpdateGraphicAPI()
{
PlayerSettings.SetUseDefaultGraphicsAPIs(BuildTarget.WebGL, false);
GraphicsDeviceType[] targets = new GraphicsDeviceType[] { };
if (webgl2)
{
PlayerSettings.SetGraphicsAPIs(BuildTarget.WebGL, new GraphicsDeviceType[] { GraphicsDeviceType.OpenGLES3 });
}
else
{
PlayerSettings.SetGraphicsAPIs(BuildTarget.WebGL, new GraphicsDeviceType[] { GraphicsDeviceType.OpenGLES2 });
}
}
// 可以调用这个来集成
public void DoExport(bool buildWebGL) {
OnLostFocus();
EditorUtility.SetDirty(config);
AssetDatabase.SaveAssets();
if (dst == "")
{
ShowNotification(new GUIContent("请先选择游戏导出路径"));
}
else
{
#if UNITY_EDITOR_OSX
// MacSetAuth();
#endif
//仅删除StreamingAssets目录
if (deleteStreamingAssets)
{
UnityUtil.DelectDir(Path.Combine(dst, webglDir + "/StreamingAssets"));
}
if (buildWebGL && Build() != 0)
{
return;
}
ConvertCode();
int res = GenerateBinFile();
if (res == 0)
{
checkNeedCopyDataPackage(false);
UnityEngine.Debug.LogFormat("[Converter] All done!");
ShowNotification(new GUIContent("转换完成"));
}
else
{
checkNeedCopyDataPackage(true);
}
// 如果是2021版本,官方symbols产生有BUG,这里需要用工具将embedded的函数名提取出来
#if UNITY_2021_2_OR_NEWER
var path = "Assets/WX-WASM-SDK/Editor/Node";
var nodePath = "node";
#if UNITY_EDITOR_OSX
nodePath = "/usr/local/bin/node";
#endif
WeChatWASM.UnityUtil.RunCmd(nodePath, string.Format($"--experimental-modules dump_wasm_symbol.mjs {dst}"), path);
UnityEngine.Debug.LogError($"Unity 2021版本使用Embeded Symbols, 代码包中含有函数名体积较大, 发布前使用代码分包工具进行优化");
#endif
}
}
}
}