Created
March 4, 2023 14:35
-
-
Save redheadgektor/2a393806e62869485a91568049b9afef to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.IO; | |
using System.Linq; | |
using System.Runtime.InteropServices; | |
using System.Threading.Tasks; | |
using UnityEditor; | |
using UnityEditor.Build; | |
using UnityEditor.Build.Reporting; | |
using UnityEditor.Compilation; | |
using UnityEditor.Rendering; | |
using UnityEngine; | |
using Debug = UnityEngine.Debug; | |
public class GameBuilderSettings | |
{ | |
public string buildName = "build"; | |
public string buildDir = ""; | |
public string buildPath = ""; | |
public BuildTarget buildTarget = BuildTarget.StandaloneWindows64; | |
public bool compressWithLz4 = false; | |
public bool compressWithLz4HC = false; | |
/* DEVELOPMENT BUILD */ | |
public bool development = false; | |
public bool enableDeepProfilingSupport = false; | |
public bool connectWithProfiler = false; | |
/* DEVELOPMENT BUILD END */ | |
public bool CompileOnlyScripts = false; | |
public bool useAssetBundleManifest = false; | |
public string AssetBundleManifestPath = ""; | |
public bool removeCrashHandler = false; | |
public bool compile2cpp = false; | |
public Il2CppCodeGeneration il2cppCodeGen = Il2CppCodeGeneration.OptimizeSpeed; | |
public bool generateBatchmodeBat = false; | |
public string batchmodeBat = "-batchmode -nographics -logFile engine.log"; | |
public bool generateDebugBat = false; | |
public string debugBat = "-logFile engine.log"; | |
public bool stripShaders = false; | |
/* SCENES */ | |
public List<string> scenes = new List<string>(); | |
/* IL2CPP Additional args */ | |
public List<string> il2cpp_add_args = new List<string>(); | |
} | |
public class GameBuilder : EditorWindow, IPreprocessShaders, IPreprocessComputeShaders | |
{ | |
public static GameBuilder Instance; | |
public GameBuilderSettings settings = new GameBuilderSettings(); | |
[MenuItem("Game/Unload Bundles", priority = 2056)] | |
static void BuildBundles() | |
{ | |
AssetBundle.UnloadAllAssetBundles(true); | |
} | |
[MenuItem("Game/Build", priority = 2056)] | |
static void ShowWindow() | |
{ | |
Instance = (GameBuilder)GetWindow(typeof(GameBuilder), true); | |
Instance.Show(); | |
Instance.titleContent = new GUIContent("Game Builder"); | |
Instance.minSize = new Vector2(640, 380); | |
CompilationPipeline.compilationStarted += delegate { Save(Instance.settings); Instance.Close(); }; | |
} | |
Vector2 scrollPosition; | |
Vector2 il2cpp_args_scrollPosition; | |
private void OnGUI() | |
{ | |
GUILayout.BeginVertical(); | |
/* SCROLL START */ | |
scrollPosition = GUILayout.BeginScrollView(scrollPosition); | |
/* BUILD NAME */ | |
GUILayout.BeginHorizontal("box"); | |
GUILayout.Label("Build Name"); | |
settings.buildName = EditorGUILayout.TextField(settings.buildName); | |
GUILayout.EndHorizontal(); | |
/* BUILD FOLDER */ | |
GUILayout.BeginHorizontal("box"); | |
GUILayout.Label("Build Folder"); | |
settings.buildDir = EditorGUILayout.TextField(settings.buildDir); | |
if (GUILayout.Button("Select Dir")) | |
{ | |
settings.buildDir = EditorUtility.OpenFolderPanel("Select build folder", Environment.CurrentDirectory, "build"); | |
settings.buildPath = settings.buildDir + "/" + settings.buildName + ".exe"; | |
} | |
GUILayout.EndHorizontal(); | |
/* EXE PATH */ | |
GUILayout.BeginHorizontal("box"); | |
GUILayout.Label("Exe path: " + settings.buildPath); | |
GUILayout.EndHorizontal(); | |
/* BUILD TARGET/OPTIONS */ | |
GUILayout.BeginVertical("box"); | |
settings.buildTarget = (BuildTarget)EditorGUILayout.EnumPopup("Build Target:", settings.buildTarget); | |
if (!settings.compressWithLz4HC) | |
{ | |
settings.compressWithLz4 = GUILayout.Toggle(settings.compressWithLz4, "Compress with Lz4"); | |
} | |
if (!settings.compressWithLz4) | |
{ | |
settings.compressWithLz4HC = GUILayout.Toggle(settings.compressWithLz4HC, "Compress with Lz4 (High Compression)"); | |
} | |
/* DEVELOPMENT BUILD */ | |
settings.development = GUILayout.Toggle(settings.development, "Development"); | |
if (settings.development) | |
{ | |
GUILayout.BeginVertical("box"); | |
EditorGUILayout.HelpBox("Use these parameters only for debugging!", MessageType.Warning, true); | |
settings.enableDeepProfilingSupport = GUILayout.Toggle(settings.enableDeepProfilingSupport, "Deep Profiling"); | |
settings.connectWithProfiler = GUILayout.Toggle(settings.connectWithProfiler, "Connect to Profiller"); | |
GUILayout.EndVertical(); | |
} | |
/* DEVELOPMENT BUILD END */ | |
settings.removeCrashHandler = GUILayout.Toggle(settings.removeCrashHandler, new GUIContent(removeCrashHandlerTitle, removeCrashHandlerTooltip)); | |
settings.compile2cpp = GUILayout.Toggle(settings.compile2cpp, "Compile code to C++"); | |
if (settings.compile2cpp) | |
{ | |
PlayerSettings.SetScriptingBackend(BuildTargetGroup.Standalone, ScriptingImplementation.IL2CPP); | |
GUILayout.BeginHorizontal("box"); | |
GUILayout.Label("Code Generation: "); | |
settings.il2cppCodeGen = (Il2CppCodeGeneration)EditorGUILayout.EnumPopup(settings.il2cppCodeGen); | |
GUILayout.EndHorizontal(); | |
var target = EditorUserBuildSettings.activeBuildTarget; | |
var group = BuildPipeline.GetBuildTargetGroup(target); | |
var namedBuildTarget = NamedBuildTarget.FromBuildTargetGroup(group); | |
PlayerSettings.SetIl2CppCodeGeneration(namedBuildTarget, settings.il2cppCodeGen); | |
GUILayout.BeginVertical("box"); | |
il2cpp_args_scrollPosition = GUILayout.BeginScrollView(il2cpp_args_scrollPosition); | |
for (var i = 0; i < settings.il2cpp_add_args.Count; i++) | |
{ | |
GUILayout.BeginHorizontal("box"); | |
GUILayout.Label($"Argument {i+1}"); | |
settings.il2cpp_add_args[i] = EditorGUILayout.TextField(settings.il2cpp_add_args[i]); | |
if (GUILayout.Button("X")) { settings.il2cpp_add_args.RemoveAt(i); } | |
GUILayout.EndHorizontal(); | |
} | |
if (GUILayout.Button("Add new IL2CPP Argument")) | |
{ | |
settings.il2cpp_add_args.Add(string.Empty); | |
} | |
GUILayout.EndScrollView(); | |
GUILayout.EndVertical(); | |
} | |
else | |
{ | |
PlayerSettings.SetScriptingBackend(BuildTargetGroup.Standalone, ScriptingImplementation.Mono2x); | |
} | |
settings.stripShaders = GUILayout.Toggle(settings.stripShaders, "Strip all shaders"); | |
settings.CompileOnlyScripts = GUILayout.Toggle(settings.CompileOnlyScripts, new GUIContent(CompileOnlyScriptsTitle)); | |
settings.useAssetBundleManifest = GUILayout.Toggle(settings.useAssetBundleManifest, new GUIContent(AssetBundleManifestTitle, AssetBundleManifestTooltip)); | |
if (settings.useAssetBundleManifest) | |
{ | |
GUILayout.BeginHorizontal("box"); | |
settings.AssetBundleManifestPath = GUILayout.TextField(settings.AssetBundleManifestPath); | |
if (GUILayout.Button("Select Manifest")) | |
{ | |
settings.AssetBundleManifestPath = EditorUtility.OpenFilePanelWithFilters("Select asset bundle manifest", settings.buildDir, new string[] { "Manifest File", "manifest", "All files", "*" }); | |
} | |
GUILayout.EndHorizontal(); | |
} | |
settings.generateBatchmodeBat = GUILayout.Toggle(settings.generateBatchmodeBat, "Generate bat for run in batchmode"); | |
if (settings.generateBatchmodeBat) { settings.batchmodeBat = GUILayout.TextField(settings.batchmodeBat); } | |
settings.generateDebugBat = GUILayout.Toggle(settings.generateDebugBat, "Generate bat for run debug"); | |
if (settings.generateDebugBat) { settings.debugBat = GUILayout.TextField(settings.debugBat); } | |
GUILayout.BeginVertical("box"); | |
GUILayout.Label("Scenes"); | |
for(int i = 0; i < settings.scenes.Count; i++) | |
{ | |
GUILayout.BeginHorizontal("box"); | |
settings.scenes[i] = EditorGUILayout.TextField(settings.scenes[i]); | |
if (GUILayout.Button("X")) { settings.scenes.RemoveAt(i); } | |
GUILayout.EndHorizontal(); | |
} | |
if(GUILayout.Button("Find scenes")) | |
{ | |
string[] guids = AssetDatabase.FindAssets("t:SceneAsset"); | |
for(int j = 0; j < guids.Length; j++) | |
{ | |
string path = AssetDatabase.GUIDToAssetPath(guids[j]); | |
if (!settings.scenes.Contains(path) && path.StartsWith("assets", StringComparison.OrdinalIgnoreCase)) | |
{ | |
settings.scenes.Add(path); | |
} | |
} | |
} | |
GUILayout.EndVertical(); | |
GUILayout.EndVertical(); | |
/* SCROLL END */ | |
GUILayout.EndScrollView(); | |
GUILayout.EndVertical(); | |
if (GUILayout.Button("Build")) | |
{ | |
StartBuild(settings); | |
} | |
} | |
public static void StartBuild() | |
{ | |
GameBuilderSettings settings = new GameBuilderSettings(); | |
Load(settings); | |
StartBuild(settings); | |
} | |
public async static void StartBuild(GameBuilderSettings settings) | |
{ | |
Save(settings); | |
BuildPlayerOptions options = new BuildPlayerOptions(); | |
options.locationPathName = settings.buildPath; | |
options.target = settings.buildTarget; | |
var old_il2cpp_args = PlayerSettings.GetAdditionalIl2CppArgs(); | |
var il2cpp_args = string.Empty; | |
foreach (var i in settings.il2cpp_add_args) | |
{ | |
if (!string.IsNullOrEmpty(i) || !string.IsNullOrWhiteSpace(i)) | |
{ | |
il2cpp_args += i + " "; | |
} | |
} | |
PlayerSettings.SetAdditionalIl2CppArgs(il2cpp_args); | |
if (settings.compressWithLz4) | |
{ | |
options.options |= BuildOptions.CompressWithLz4; | |
} | |
if (settings.compressWithLz4HC) | |
{ | |
options.options |= BuildOptions.CompressWithLz4HC; | |
} | |
/* DEVELOPMENT BUILD */ | |
if (settings.development) | |
{ | |
options.options |= BuildOptions.Development; | |
if (settings.connectWithProfiler) | |
{ | |
options.options |= BuildOptions.ConnectWithProfiler; | |
} | |
if (settings.enableDeepProfilingSupport) | |
{ | |
options.options |= BuildOptions.EnableDeepProfilingSupport; | |
} | |
} | |
/* DEVELOPMENT BUILD END */ | |
if (settings.CompileOnlyScripts) | |
{ | |
options.options |= BuildOptions.BuildScriptsOnly; | |
} | |
if (settings.useAssetBundleManifest) | |
{ | |
options.assetBundleManifestPath = settings.AssetBundleManifestPath; | |
} | |
options.options |= BuildOptions.DetailedBuildReport; | |
options.scenes = settings.scenes.ToArray(); | |
DateTime time = DateTime.Now; | |
PlayerSettings.bundleVersion = $"build_{time.Date.Day}.{time.Month}.{time.Year} | {time.Hour}:{time.Minute}:{time.Second}"; | |
BuildReport report = null; | |
report = BuildPipeline.BuildPlayer(options); | |
/* BUILD SUCCESS */ | |
if (report.summary.result == BuildResult.Succeeded) | |
{ | |
string p = Path.Combine(settings.buildDir, "Data"); | |
if (Directory.Exists(p)) | |
{ | |
DeleteDirectory(p); | |
} | |
if (Directory.Exists(Path.Combine(settings.buildDir, settings.buildName + "_Data"))) | |
{ | |
Directory.Move(Path.Combine(settings.buildDir, settings.buildName + "_Data"), p); | |
} | |
//remove crashHandler if checked | |
if (settings.removeCrashHandler) | |
{ | |
if (File.Exists(Path.Combine(settings.buildDir, "UnityCrashHandler64.exe"))) | |
{ | |
File.Delete(Path.Combine(settings.buildDir, "UnityCrashHandler64.exe")); | |
} | |
if (File.Exists(Path.Combine(settings.buildDir, "UnityCrashHandler32.exe"))) | |
{ | |
File.Delete(Path.Combine(settings.buildDir, "UnityCrashHandler32.exe")); | |
} | |
} | |
if (settings.generateBatchmodeBat) | |
{ | |
string commandline1 = "@echo off\necho Starting engine...\nstart " + settings.buildName + ".exe "+settings.batchmodeBat; | |
File.WriteAllText(settings.buildDir + "/batchmode.bat", commandline1); | |
string commandline2 = "@echo off\necho Starting engine...\nstart " + settings.buildName + ".exe "+settings.debugBat; | |
File.WriteAllText(settings.buildDir + "/debug.bat", commandline2); | |
} | |
if(Directory.Exists(Path.Combine(settings.buildDir, "build_BurstDebugInformation_DoNotShip"))) | |
{ | |
DeleteDirectory(Path.Combine(settings.buildDir, "build_BurstDebugInformation_DoNotShip")); | |
} | |
var files = report.GetFiles().OrderByDescending(x => x.size); | |
PackedAssets[] assets = report.packedAssets; | |
var scene_Assets = report.scenesUsingAssets; | |
int total_size = (int)report.summary.totalSize; | |
TimeSpan build_time = report.summary.totalTime; | |
EditorUtility.DisplayProgressBar("Build Report","Starting...", 0); | |
using (StringWriter sw = new StringWriter()) | |
{ | |
sw.WriteLine("Build version (engine/game): " + Application.unityVersion + "/" + Application.version + " (Platform: " + (report.summary.platform + ")")); | |
sw.WriteLine("Build date: " + report.summary.buildEndedAt); | |
sw.WriteLine("Build GUID: " + report.summary.guid); | |
sw.WriteLine("Total files: " + files.Count()); | |
sw.WriteLine("Total build size: " + ((total_size / 1024f) / 1024f).ToString("f2") + " MB"); | |
sw.WriteLine("\nFiles used by build:"); | |
for (int i = 0; i < files.Count(); i++) | |
{ | |
EditorUtility.DisplayProgressBar("Build Report", "Collecting files information", (float)i/(float)files.Count()); | |
BuildFile _f = files.ElementAt(i); | |
if (_f.role == "DebugInfo" || _f.role == "MonoConfig") | |
continue; | |
ulong sz = _f.size; | |
if (sz <= 1024) | |
{ | |
sw.WriteLine("[" + _f.role + "] " + Path.GetFileName(_f.path) + " [" + sz + " bytes]"); | |
} | |
if (sz > 1024 && sz <= 1024 * 1024) | |
{ | |
sw.WriteLine("[" + _f.role + "] " + Path.GetFileName(_f.path) + " [" + sz / 1024 + " kb]"); | |
} | |
if (sz > 1024 * 1024 && sz <= 1024 * 1024 * 1024) | |
{ | |
sw.WriteLine("[" + _f.role + "] " + Path.GetFileName(_f.path) + " [" + sz / 1024f / 1024f + " mb]"); | |
} | |
await Task.Yield(); | |
} | |
sw.WriteLine("\nAssets used by build:"); | |
for (int i = 0; i < assets.Length; i++) | |
{ | |
EditorUtility.DisplayProgressBar("Build Report", "Collecting bundles information", (float)i / (float)assets.Length); | |
PackedAssets _f = assets[i]; | |
var contents = _f.contents.OrderByDescending(x => x.packedSize); | |
ulong main_sz = 0; | |
for (int j = 0; j < _f.contents.Length; j++) | |
{ | |
main_sz += _f.contents[j].packedSize; | |
} | |
ulong sz = main_sz; | |
if (sz < 1024) | |
{ | |
sw.WriteLine("======= " + _f.shortPath + " ======= (Files " + contents.Count() + ") (Size " + main_sz + " bytes)"); | |
} | |
if (sz >= 1024 && sz <= 1024 * 1024) | |
{ | |
sw.WriteLine("======= " + _f.shortPath + " ======= (Files " + contents.Count() + ") (Size " + (main_sz / 1024f).ToString("f2") + " kb)"); | |
} | |
if (sz > 1024 * 1024 && sz <= 1024 * 1024 * 1024) | |
{ | |
sw.WriteLine("======= " + _f.shortPath + " ======= (Files " + contents.Count() + ") (Size " + (main_sz / 1024f / 1024f).ToString("f2") + " mb)"); | |
} | |
for (int j = 0; j < contents.Count(); j++) | |
{ | |
ulong sz2 = contents.ElementAt(j).packedSize; | |
if (sz2 < 1024) | |
{ | |
sw.WriteLine(" - [" + contents.ElementAt(j).type.Name + "] " + contents.ElementAt(j).sourceAssetPath + " [" + sz2 + " bytes]"); | |
} | |
if (sz2 >= 1024 && sz2 <= 1024 * 1024) | |
{ | |
sw.WriteLine(" - [" + contents.ElementAt(j).type.Name + "] " + contents.ElementAt(j).sourceAssetPath + " [" + (sz2 / 1024f).ToString("f2") + " kb]"); | |
} | |
if (sz2 > 1024 * 1024 && sz2 <= 1024 * 1024 * 1024) | |
{ | |
sw.WriteLine(" - [" + contents.ElementAt(j).type.Name + "] " + contents.ElementAt(j).sourceAssetPath + " [" + (sz2 / 1024f / 1024f).ToString("f2") + " mb]"); | |
} | |
} | |
await Task.Yield(); | |
} | |
sw.WriteLine("\nScenes using assets:"); | |
for (int i = 0; i < scene_Assets.Length; i++) | |
{ | |
sw.WriteLine($"Scene {scene_Assets[i].name} | Assets: {scene_Assets[i].list.Length}"); | |
for (int j = 0; j < scene_Assets[i].list.Length; j++) | |
{ | |
sw.WriteLine($" - {scene_Assets[i].list[j].assetPath}"); | |
} | |
} | |
sw.Close(); | |
sw.Flush(); | |
EditorUtility.ClearProgressBar(); | |
File.WriteAllText(Path.Combine(settings.buildDir, settings.buildName + ".buildreport"), sw.ToString()); | |
Save(settings); | |
} | |
int dialog_result = EditorUtility.DisplayDialogComplex("Build complete!", "Path: " + report.summary.outputPath + "\n" | |
+ "Files: " + files.Count() + "\n" | |
+ "Total Size: " + (total_size / 1024) / 1024 + " MB\n" | |
+ "Building time: " + (int)build_time.TotalHours + ":" + (int)build_time.TotalMinutes + ":" + (int)build_time.TotalSeconds, "Open build directory", "Run game", "Close"); | |
if (dialog_result == 0) | |
{ | |
EditorUtility.RevealInFinder(report.summary.outputPath); | |
} | |
if (dialog_result == 1) | |
{ | |
Process.Start(settings.buildPath); | |
} | |
} | |
else if (report.summary.result == BuildResult.Failed) | |
{ | |
EditorUtility.DisplayDialog("Building error!", $"Has errors while building player. See in console", "Close"); | |
} | |
PlayerSettings.SetAdditionalIl2CppArgs(old_il2cpp_args); | |
} | |
const string CompileOnlyScriptsTitle = "Compile Only Scripts"; | |
const string AssetBundleManifestTitle = "Use Asset Bundle Manifest"; | |
const string AssetBundleManifestTooltip = "In build will not include files related to bundles that are specified in the manifest"; | |
const string removeCrashHandlerTitle = "Remove CrashHandler"; | |
const string removeCrashHandlerTooltip = "After the build is complete, the roof handler will be removed from the game folder"; | |
private void OnEnable() | |
{ | |
settings.buildDir = Path.Combine(Environment.CurrentDirectory, "Build"); | |
settings.buildPath = settings.buildDir + "/" + settings.buildName + ".exe"; | |
for (var i = 0; i < EditorBuildSettings.scenes.Length; i++) | |
{ | |
settings.scenes.Add(EditorBuildSettings.scenes[i].path); | |
} | |
Load(settings); | |
} | |
private void OnDestroy() | |
{ | |
Save(settings); | |
} | |
/* SETTINGS SAVE/LOAD */ | |
public const string GameBuilderSettingsFile = "GameBuilderSettings.json"; | |
static string SettingsPath = Path.Combine(Environment.CurrentDirectory, "ProjectSettings", GameBuilderSettingsFile); | |
private static void Load(GameBuilderSettings settings) | |
{ | |
if (File.Exists(SettingsPath)) | |
{ | |
try | |
{ | |
JsonUtility.FromJsonOverwrite(File.ReadAllText(SettingsPath), settings); | |
} | |
catch (Exception ex) { Debug.LogError("[GameBuilder] Error while load settings -> ex:" + ex.Message); return; } | |
} | |
else | |
{ | |
Save(settings); | |
} | |
} | |
private static void Save(GameBuilderSettings settings) | |
{ | |
try | |
{ | |
File.WriteAllText(SettingsPath, JsonUtility.ToJson(settings, true)); | |
} | |
catch(Exception ex) { Debug.LogError("[GameBuilder] Error while saving settings -> ex:"+ex.Message); } | |
} | |
public static void DeleteDirectory(string target_dir) | |
{ | |
string[] files = Directory.GetFiles(target_dir); | |
string[] dirs = Directory.GetDirectories(target_dir); | |
foreach (string file in files) | |
{ | |
File.SetAttributes(file, FileAttributes.Normal); | |
File.Delete(file); | |
} | |
foreach (string dir in dirs) | |
{ | |
DeleteDirectory(dir); | |
} | |
Directory.Delete(target_dir, false); | |
} | |
int IOrderedCallback.callbackOrder => int.MaxValue; | |
void IPreprocessShaders.OnProcessShader(Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> data) | |
{ | |
if (settings.stripShaders) | |
{ | |
data.Clear(); | |
} | |
} | |
void IPreprocessComputeShaders.OnProcessComputeShader(ComputeShader shader, string kernelName, IList<ShaderCompilerData> data) | |
{ | |
if (settings.stripShaders) | |
{ | |
data.Clear(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment