Skip to content

Instantly share code, notes, and snippets.

@redheadgektor
Created March 4, 2023 14:35
Show Gist options
  • Save redheadgektor/2a393806e62869485a91568049b9afef to your computer and use it in GitHub Desktop.
Save redheadgektor/2a393806e62869485a91568049b9afef to your computer and use it in GitHub Desktop.
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