This is a script I actively use in the development of Dinosaur Island. For example, I use it to make sure all of my pickup objects start at the same height from the ground without having to calculate that at runtime.
using System.Collections;
using System.Collections.Generic;
#if (UNITY_EDITOR)
using UnityEngine;
using UnityEditor;
public class PlatformerUtils : EditorWindow
{
int layerOffsetLayer;
Vector3 layerOffsetVector;
bool layerOffsetGroundCheck;
string tagOffsetTag;
Vector3 tagOffsetVector;
bool tagOffsetGroundCheck;
string setRotationTag;
Vector3 setRotationVector;
RotationType rotationType;
[MenuItem("Tools/PlatformerUtils")]
private static void OpenUtils() {
new PlatformerUtils().Show();
}
public enum RotationType {
Global,
Local
}
void OnGUI() {
LoadPrefs();
EditorGUILayout.LabelField("Offset Objects in Layer", EditorStyles.boldLabel);
layerOffsetLayer = EditorGUILayout.LayerField("Layer for Offsetting:", layerOffsetLayer);
layerOffsetVector = EditorGUILayout.Vector3Field("Offset Vector", layerOffsetVector);
layerOffsetGroundCheck = EditorGUILayout.Toggle("Check for Ground", layerOffsetGroundCheck);
if (GUILayout.Button("Offset by Layer")) {
GameObject[] toOffset = FindObjectsByLayer(layerOffsetLayer);
foreach(GameObject obj in toOffset){
OffsetObject(obj, layerOffsetVector, layerOffsetGroundCheck);
}
}
EditorGUILayout.Space();
EditorGUILayout.LabelField("Offset Objects with Tag", EditorStyles.boldLabel);
tagOffsetTag = EditorGUILayout.TagField("Tag for Offsetting", tagOffsetTag);
tagOffsetVector = EditorGUILayout.Vector3Field("Offset Vector", tagOffsetVector);
tagOffsetGroundCheck = EditorGUILayout.Toggle("Check for Ground", tagOffsetGroundCheck);
if (GUILayout.Button("Offset by Tag")) {
GameObject[] toOffset = GameObject.FindGameObjectsWithTag(tagOffsetTag);
foreach(GameObject obj in toOffset){
OffsetObject(obj, tagOffsetVector, tagOffsetGroundCheck);
}
}
EditorGUILayout.Space();
EditorGUILayout.LabelField("Set Rotation by Tag", EditorStyles.boldLabel);
setRotationTag = EditorGUILayout.TagField("Tag for Rotation", setRotationTag);
setRotationVector = EditorGUILayout.Vector3Field("Rotation Vector", setRotationVector);
rotationType = (RotationType)EditorGUILayout.EnumPopup("Locality", rotationType);
if (GUILayout.Button("Rotate by Tag")) {
GameObject[] toOffset = GameObject.FindGameObjectsWithTag(setRotationTag);
foreach(GameObject obj in toOffset){
switch(rotationType){
case RotationType.Global:
obj.transform.eulerAngles = setRotationVector;
break;
case RotationType.Local:
obj.transform.localRotation = Quaternion.Euler(setRotationVector.x, setRotationVector.y, setRotationVector.z);
break;
}
}
}
SavePrefs();
}
GameObject[] FindObjectsByLayer(int layer)
{
List validTransforms = new List();
Transform[] objs = Resources.FindObjectsOfTypeAll() as Transform[];
for (int i = 0; i < objs.Length; i++)
{
if (objs[i].hideFlags == HideFlags.None)
{
if (objs[i].gameObject.layer == layer)
{
validTransforms.Add(objs[i].gameObject);
}
}
}
return validTransforms.ToArray();
}
void OffsetObject(GameObject obj, Vector3 offsetVector, bool checkGround){
if(checkGround){
RaycastHit hit;
if(Physics.Raycast(obj.transform.position,
-obj.transform.up,
out hit,
Mathf.Infinity,
CONSTANTS.GROUND_MASK)){
obj.transform.position = obj.transform.position + offsetVector;
obj.transform.position = new Vector3(obj.transform.position.x,
obj.transform.position.y - hit.distance,
obj.transform.position.z);
Debug.Log("Moved object " + obj.name);
} else {
Debug.Log("No ground detected underneath " + obj.name);
}
} else {
obj.transform.position += offsetVector;
Debug.Log("Moved object " + obj.name + " without checking for ground");
}
}
void LoadPrefs(){
layerOffsetLayer = EditorPrefs.GetInt("PlatformerUtils_layerOffsetLayer");
layerOffsetVector = new Vector3(EditorPrefs.GetFloat("PlatformerUtils_layerOffsetVectorX"),
EditorPrefs.GetFloat("PlatformerUtils_layerOffsetVectorY"),
EditorPrefs.GetFloat("PlatformerUtils_layerOffsetVectorZ"));
}
void SavePrefs(){
EditorPrefs.SetInt("PlatformerUtils_layerOffsetLayer", layerOffsetLayer);
EditorPrefs.SetFloat("PlatformerUtils_layerOffsetVectorX", layerOffsetVector.x);
EditorPrefs.SetFloat("PlatformerUtils_layerOffsetVectorY", layerOffsetVector.y);
EditorPrefs.SetFloat("PlatformerUtils_layerOffsetVectorZ", layerOffsetVector.z);
}
}
#endif
I really hate the Unity build menu. It's got too many options and it isn't easily navigatable via keyboard. This is a script I wrote to build multiple platform standalones in quick succession, and I use it all the time
using UnityEngine;
using UnityEditor;
#if (UNITY_EDITOR)
using UnityEditor.Build.Reporting;
using System.Collections.Generic;
public class BuildPlayer : EditorWindow {
enum BuildVersion{
Windows,
OSX,
Linux
}
int nScenes;
static string[] sceneNames;
bool showScenes;
bool buildWin;
bool buildMac;
bool buildLin;
static string buildPath;
[MenuItem("Tools/BuildPlayer")]
private static void OpenBuilder() {
buildPath = Application.dataPath.Substring(0, Application.dataPath.Length - 6) + "/";
new BuildPlayer().Show();
}
BuildPlayerOptions getBuildOptions(BuildVersion bv){
BuildPlayerOptions options = new BuildPlayerOptions();
string[] formattedSceneNames = new string[nScenes];
for(int i = 0; i < nScenes; i++){
formattedSceneNames[i] = "Assets/Scenes/" + sceneNames[i] + ".unity";
}
options.scenes = formattedSceneNames;
options.options = BuildOptions.None;
switch(bv){
case BuildVersion.Windows:
options.locationPathName = buildPath + "Builds/Windows/DinosaurIsland.exe";
options.target = BuildTarget.StandaloneWindows64;
break;
case BuildVersion.OSX:
options.locationPathName = buildPath + "Builds/Mac/DinosaurIsland.app";
options.target = BuildTarget.StandaloneOSX;
break;
case BuildVersion.Linux:
options.locationPathName = buildPath + "Builds/Linux/DinosaurIsland.x86_64";
options.target = BuildTarget.StandaloneLinux64;
break;
default:
Debug.Log("BuildVersion " + bv + " not recognized");
break;
}
return options;
}
void OnGUI() {
LoadPrefs();
EditorGUILayout.BeginHorizontal();
nScenes = EditorGUILayout.DelayedIntField("Number of scenes in build:", nScenes);
showScenes = EditorGUILayout.Toggle("Show scenes:", showScenes);
EditorGUILayout.EndHorizontal();
if(sceneNames != null){
List sceneNamesList = new List(sceneNames);
int difference = nScenes - sceneNames.Length;
if(difference > 0){
sceneNamesList.AddRange(new string[difference]);
} else if(difference < 0){
sceneNamesList.RemoveRange(sceneNamesList.Count + difference, -difference);
}
sceneNames = sceneNamesList.ToArray();
}
for(int i = 0; showScenes && i < nScenes; i++){
sceneNames[i] = EditorGUILayout.TextField("Scene " + i + ":", sceneNames[i]);
}
buildWin = EditorGUILayout.Toggle("Build Windows", buildWin);
buildMac = EditorGUILayout.Toggle("Build OSX", buildMac);
buildLin = EditorGUILayout.Toggle("Build Linux", buildLin);
if (GUILayout.Button("Build")) {
if(buildWin){
BuildReport winReport = BuildPipeline.BuildPlayer(getBuildOptions(BuildVersion.Windows));
BuildSummary winSummary = winReport.summary;
}
if(buildMac){
BuildReport macReport = BuildPipeline.BuildPlayer(getBuildOptions(BuildVersion.OSX));
BuildSummary macSummary = macReport.summary;
}
if(buildLin){
BuildReport linReport = BuildPipeline.BuildPlayer(getBuildOptions(BuildVersion.Linux));
BuildSummary linSummary = linReport.summary;
}
}
SavePrefs();
}
void LoadPrefs(){
nScenes = EditorPrefs.GetInt("BuildPlayer_nScenes");
sceneNames = new string[nScenes];
for(int i = 0; i < nScenes; i++){
sceneNames[i] = EditorPrefs.GetString("BuildPlayer_Scene" + (i + 1));
}
showScenes = EditorPrefs.GetBool("BuildPlayer_showScenes");
buildWin = EditorPrefs.GetBool("BuildPlayer_buildWin");
buildMac = EditorPrefs.GetBool("BuildPlayer_buildMac");
buildLin = EditorPrefs.GetBool("BuildPlayer_buildLin");
}
void SavePrefs(){
EditorPrefs.SetInt("BuildPlayer_nScenes", nScenes);
for(int i = 0; i < nScenes; i++){
EditorPrefs.SetString("BuildPlayer_Scene" + (i + 1), sceneNames[i]);
}
EditorPrefs.SetBool("BuildPlayer_showScenes", showScenes);
EditorPrefs.SetBool("BuildPlayer_buildWin", buildWin);
EditorPrefs.SetBool("BuildPlayer_buildMac", buildMac);
EditorPrefs.SetBool("BuildPlayer_buildLin", buildLin);
}
}
#endif
I've used this script in most of my Unity games. It provides a utility for letting music loop with an intro bit. E.g. if a song has an "introduction" section, it will play the introduction section, and when it loops it'll skip that intro.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(AudioSource))]
public class IntroLoop : MonoBehaviour
{
public float loop_start_seconds;
public float loop_end_seconds;
AudioSource source;
// Start is called before the first frame update
void Start()
{
source = GetComponent<AudioSource>();
}
// Update is called once per frame
void Update()
{
if(source.time >= loop_end_seconds){
source.time = loop_start_seconds;
}
}
}