Always thankful for your support, Lotte💖
Last active
April 28, 2023 09:49
-
-
Save LotteMakesStuff/1d01621393e2c36f96ba13128c726588 to your computer and use it in GitHub Desktop.
Since Unity 2018.3 we've had this cool new tool called [SettingsProvider] that we can use to add custom settings menu into Unitys settings screens. This short example shows how i use [SettingsProvider] and some [MenuItems] to help build and run test clients when im developing a multiplayer game. My usecase is that i want a quick way to build and…
This file contains hidden or 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.Collections.Generic; | |
using System.Diagnostics; | |
using UnityEditor; | |
using UnityEditor.Build.Reporting; | |
using UnityEngine; | |
using Debug = UnityEngine.Debug; | |
public static class NetworkingTools | |
{ | |
private static string multiplayerBuildSettingsPath = "Project/MultiplayerBuildSetting"; | |
private static string multiplayerBuildPathKey = "MultiplayerBuildLoc"; | |
private static string multplayerBuildSecenesKey = "MultiplayerBuildScenes"; | |
private static List<SceneAsset> sceneAssets; | |
// Add a Multiplayer menu to the Unity menu bar with shortcuts to open our custom build setting and run builds... | |
// NOTE we have such a high priority value to make sure its at the bottom of the Multiplayer menu! the Unity.Transport | |
// package also uses this menu, we want our stuff at the bottom to make it easier to find~ | |
[MenuItem("Multiplayer/Open Multiplayer Build Settings", false, 2999)] | |
public static void OpenBuildSettings() | |
{ | |
SettingsService.OpenProjectSettings(multiplayerBuildSettingsPath); | |
} | |
[MenuItem("Multiplayer/Build Test Client", false, 3000)] | |
public static void BuildTestClient() | |
{ | |
// Ask unity to do a custom build for us.. first we need to pull the settings out of editor preferences... | |
string buildPath = EditorPrefs.GetString(multiplayerBuildPathKey); | |
string scenes = EditorPrefs.GetString(multplayerBuildSecenesKey); | |
if (string.IsNullOrWhiteSpace(buildPath) || string.IsNullOrWhiteSpace(scenes)) | |
{ | |
// if either of the settings strings are not filled in, ask unity to open the settings panel for us | |
SettingsService.OpenProjectSettings(multiplayerBuildSettingsPath); | |
return; | |
} | |
// plug in our build settings | |
BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions(); | |
buildPlayerOptions.scenes = scenes.Split(';'); | |
buildPlayerOptions.locationPathName = buildPath + "/MultiplayerTest.exe"; | |
buildPlayerOptions.target = BuildTarget.StandaloneWindows64; | |
buildPlayerOptions.options = BuildOptions.None; | |
// actually run the build and store off the results! | |
BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions); | |
BuildSummary summary = report.summary; | |
if (summary.result == BuildResult.Succeeded) | |
{ | |
Debug.Log("Build succeeded: " + summary.totalTime); | |
// ask unity to open a file exporer window with the client exe selected | |
EditorUtility.RevealInFinder(buildPath + "/MultiplayerTest.exe"); | |
} | |
else if (summary.result == BuildResult.Failed) | |
{ | |
Debug.Log("Build failed"); | |
} | |
} | |
[MenuItem("Multiplayer/Run Latest Client", false, 3001)] | |
public static void RunTestClient() | |
{ | |
string buildPath = EditorPrefs.GetString(multiplayerBuildPathKey); | |
Process process = new Process | |
{ | |
StartInfo = {FileName = buildPath + "/MultiplayerTest.exe"} | |
}; | |
process.Start(); | |
} | |
[MenuItem("Multiplayer/Run Latest Client", true, 3001)] | |
public static bool CanRunTestClient() | |
{ | |
// validation function for the "Run Latest Client" menu entry. Lets check if MultiplayerTest.exe exists | |
// if it dosnt that means we dont have a build to run, so we return false so the run menu item is grayed out | |
string buildPath = EditorPrefs.GetString(multiplayerBuildPathKey); | |
return System.IO.File.Exists(buildPath + "/MultiplayerTest.exe"); | |
} | |
[SettingsProvider] | |
public static SettingsProvider CreateMultiplayerBuildLocationSettingsProvider() | |
{ | |
var provider = new SettingsProvider(multiplayerBuildSettingsPath, SettingsScope.Project) | |
{ | |
label = "Multiplayer Build", | |
deactivateHandler = () => | |
{ | |
// get the list of scenes in the build... | |
sceneAssets = null; | |
}, | |
guiHandler = (searchContext) => | |
{ | |
// Lets actually draw our settings window! | |
// First off were going to draw a textbox for the build path. | |
using (var check = new EditorGUI.ChangeCheckScope()) | |
{ | |
string loc = EditorPrefs.GetString(multiplayerBuildPathKey); | |
loc = EditorGUILayout.TextField("Build folder", loc); | |
// add in a button for opening a folder picker... | |
if (GUILayout.Button("Pick folder...")) | |
{ | |
loc = EditorUtility.OpenFolderPanel("Multiplayer Build Location", "", ""); | |
} | |
if (check.changed) | |
{ | |
// if the path string was saved, lets store it off into editor prefrences, so we can easily | |
// query for it in the build process. | |
EditorPrefs.SetString(multiplayerBuildPathKey, loc); | |
} | |
} | |
GUILayout.Space(5); | |
// Next, lets draw a list of scenes we want to be included in the buld | |
// first, check if the static scene asset list is null... if its null that means this is the first time | |
// were drawing the settings UI, and we need to load up any previously saved list of scenes | |
if (sceneAssets == null) | |
{ | |
sceneAssets = new List<SceneAsset>(); | |
// the list is stored in editor prefs - | |
// each scene path in the string is separated with a ; so lets split with that | |
var scenes = EditorPrefs.GetString(multplayerBuildSecenesKey); | |
var scenePaths = scenes.Split(';'); | |
foreach (var scenePath in scenePaths) | |
{ | |
// load each scene asset up from the asset database and add it to the list | |
sceneAssets.Add(AssetDatabase.LoadAssetAtPath<SceneAsset>(scenePath)); | |
} | |
} | |
using (var check = new EditorGUI.ChangeCheckScope()) | |
{ | |
EditorGUILayout.LabelField("Scenes included in build", EditorStyles.boldLabel); | |
int deleteItem = -1; | |
for (int i = 0; i < sceneAssets.Count; ++i) | |
{ | |
EditorGUILayout.BeginHorizontal(); | |
sceneAssets[i] = | |
(SceneAsset) EditorGUILayout.ObjectField(sceneAssets[i], typeof(SceneAsset), false); | |
if (GUILayout.Button("X", GUILayout.Width(30))) | |
{ | |
// draw a button for removing the scene from the list. if its clicked, lets store of its | |
// index so we can remove it after we finish itterating over the scene asset list | |
deleteItem = i; | |
} | |
EditorGUILayout.EndHorizontal(); | |
} | |
if (deleteItem != -1) | |
{ | |
// we can now remove the scene safely | |
sceneAssets.RemoveAt(deleteItem); | |
} | |
EditorGUILayout.BeginHorizontal(); | |
if (GUILayout.Button("Clone main build list", GUILayout.Width(140))) | |
{ | |
sceneAssets = new List<SceneAsset>(); | |
foreach (var scene in EditorBuildSettings.scenes) | |
{ | |
sceneAssets.Add(AssetDatabase.LoadAssetAtPath<SceneAsset>(scene.path)); | |
} | |
} | |
if (GUILayout.Button("Add")) | |
{ | |
sceneAssets.Add(null); | |
} | |
EditorGUILayout.EndHorizontal(); | |
// if the list has changed at all, save it out | |
if (check.changed) | |
{ | |
Debug.Log("Changed"); | |
string scenes = ""; | |
bool first = true; | |
foreach (var asset in sceneAssets) | |
{ | |
if (!first) | |
scenes += ";"; | |
scenes += AssetDatabase.GetAssetPath(asset); | |
first = false; | |
} | |
EditorPrefs.SetString(multplayerBuildSecenesKey, scenes); | |
} | |
EditorGUILayout.Space(5); | |
if (GUILayout.Button("Build...")) | |
{ | |
BuildTestClient(); | |
} | |
} | |
} | |
}; | |
return provider; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment