Last active
February 2, 2024 15:58
-
-
Save LotteMakesStuff/7fa131b0402ffb50a9e617c0d45ffa59 to your computer and use it in GitHub Desktop.
TrafficLight control/layout/property drawer: Adds a new editor control that draws lil Traffic Lights in the inspector. its really useful for visualizing state. For example, checkboxes can be hard to read at a glace, but a Red or Green status light is easy! Recommend you use the attached package, as it has all the icon image files.
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
// Non Editor code | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
public abstract class TrafficLightAttribute : PropertyAttribute | |
{ | |
public bool DrawLabel = true; | |
public string CustomLabel; | |
public bool AlsoDrawDefault; | |
public bool FillLights; | |
public int LightCount; | |
public LightColor[] Colors; | |
} | |
public class SingleTrafficLight : TrafficLightAttribute | |
{ | |
public SingleTrafficLight() | |
{ | |
LightCount = 1; | |
Colors = new [] { LightColor.Green }; | |
} | |
public SingleTrafficLight(LightColor a) | |
{ | |
LightCount = 1; | |
Colors = new[] { a }; | |
} | |
} | |
public class DoubleTrafficLight : TrafficLightAttribute | |
{ | |
public DoubleTrafficLight() | |
{ | |
LightCount = 2; | |
Colors = new[] { LightColor.Red, LightColor.Green }; | |
} | |
public DoubleTrafficLight(LightColor a, LightColor b) | |
{ | |
LightCount = 2; | |
Colors = new[] { a, b }; | |
} | |
} | |
public class TripleTrafficLight : TrafficLightAttribute | |
{ | |
public TripleTrafficLight() | |
{ | |
LightCount = 3; | |
Colors = new[] { LightColor.Red, LightColor.Amber, LightColor.Green }; | |
} | |
public TripleTrafficLight(LightColor a, LightColor b, LightColor c) | |
{ | |
LightCount = 3; | |
Colors = new[] { a, b, c }; | |
} | |
} | |
public class QuadTrafficLight : TrafficLightAttribute | |
{ | |
public QuadTrafficLight() | |
{ | |
LightCount = 4; | |
Colors = new[] { LightColor.Blue, LightColor.Green, LightColor.Yellow, LightColor.Pink }; | |
} | |
public QuadTrafficLight(LightColor a, LightColor b, LightColor c, LightColor d) | |
{ | |
LightCount = 4; | |
Colors = new[] { a, b, c, d }; | |
} | |
} | |
public enum LightColor | |
{ | |
Off, | |
Red, | |
Amber, | |
Green, | |
Yellow, | |
Blue, | |
Pink | |
} |
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
// Editor Code | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEditor; | |
using UnityEngine; | |
public class TrafficLightControl | |
{ | |
private static Texture[] lightArray; | |
private static Texture[] frameArray; | |
public static Vector2 SingleLightSize { get { return new Vector2(20, 20); } } | |
public static Vector2 DoubleLightSize { get { return new Vector2(39, 20); } } | |
public static Vector2 TripleLightSize { get { return new Vector2(56, 20); } } | |
public static Vector2 QuadLightSize { get { return new Vector2(79, 20); } } | |
private static string AssetPath = "Assets/Editor/Controls/"; | |
private static Texture GetLightTexture(LightColor tex) | |
{ | |
// get the light texture for a specified colour | |
// if the texture has not yet been loaded find it | |
if (lightArray == null) | |
{ | |
lightArray = new Texture[7]; | |
} | |
if (lightArray[(int)tex] == null) | |
{ | |
// NOTE if you put the texturea in a different folder please update this | |
lightArray[(int)tex] = AssetDatabase.LoadAssetAtPath<Texture>(AssetPath + "light" + System.Enum.GetName(typeof(LightColor), tex) + ".png"); | |
} | |
return lightArray[(int)tex]; | |
} | |
private static Texture GetTrafficLightFrameTexture(TrafficLightSize tex) | |
{ | |
// get the light frame texture for a specified colour | |
// if the texture has not yet been loaded find it | |
if (frameArray == null) | |
{ | |
frameArray = new Texture[4]; | |
} | |
if (frameArray[(int)tex] == null) | |
{ | |
// NOTE if you put the texturea in a different folder please update this | |
frameArray[(int)tex] = AssetDatabase.LoadAssetAtPath<Texture>(AssetPath + "tlFrame" + System.Enum.GetName(typeof(TrafficLightSize), tex) + ".png"); | |
} | |
return frameArray[(int)tex]; | |
} | |
// Draws a single light texture | |
private static void DrawLight(Rect position, LightColor type) | |
{ | |
GUI.Label(position, GetLightTexture(type), GUIStyle.none); | |
} | |
// These methods draw the Traffic Light in a given rectangle. | |
// Reccomend you use the layout versions, as then you dont | |
// have to worry about getting the sizes right!! | |
public static void DrawTrafficLightSingle(Rect position, LightColor type) | |
{ | |
// Draw the frame | |
GUI.Label(position, GetTrafficLightFrameTexture(TrafficLightSize.Single), GUIStyle.none); | |
// offset position and draw the light | |
position.position += new Vector2(2, 2); | |
position.size = new Vector2(16, 16); | |
DrawLight(position, type); | |
} | |
public static void DrawTrafficLightDouble(Rect position, LightColor typeA, LightColor typeB) | |
{ | |
GUI.Label(position, GetTrafficLightFrameTexture(TrafficLightSize.Double), GUIStyle.none); | |
position.position += new Vector2(3, 2); | |
position.size = new Vector2(16, 16); | |
DrawLight(position, typeA); | |
position.position += new Vector2(16 + 1, 0); | |
DrawLight(position, typeB); | |
} | |
public static void DrawTrafficLightTriple(Rect position, LightColor typeA, LightColor typeB, LightColor typeC) | |
{ | |
GUI.Label(position, GetTrafficLightFrameTexture(TrafficLightSize.Triple), GUIStyle.none); | |
position.position += new Vector2(3, 2); | |
position.size = new Vector2(16, 16); | |
DrawLight(position, typeA); | |
position.position += new Vector2(16 + 1, 0); | |
DrawLight(position, typeB); | |
position.position += new Vector2(16 + 1, 0); | |
DrawLight(position, typeC); | |
} | |
public static void DrawTrafficLightQuad(Rect position, LightColor typeA, LightColor typeB, LightColor typeC, LightColor typeD) | |
{ | |
GUI.Label(position, GetTrafficLightFrameTexture(TrafficLightSize.Quad), GUIStyle.none); | |
position.position += new Vector2(3, 2); | |
position.size = new Vector2(16, 16); | |
DrawLight(position, typeA); | |
position.position += new Vector2(16 + 1, 0); | |
DrawLight(position, typeB); | |
position.position += new Vector2(16 + 1, 0); | |
DrawLight(position, typeC); | |
position.position += new Vector2(16 + 1, 0); | |
DrawLight(position, typeD); | |
} | |
public static Vector2 GetTrafficLightSize(TrafficLightSize size) | |
{ | |
// Helper method for getting the light frame texture sizes. | |
var frame = GetTrafficLightFrameTexture(size); | |
return new Vector2(frame.width, frame.height); | |
} | |
} | |
public static class TrafficLightLayout | |
{ | |
// auto lays out and draws a traffic light | |
public static void DrawTrafficLightSingle(LightColor colorA) | |
{ | |
var size = TrafficLightControl.GetTrafficLightSize(TrafficLightSize.Single); | |
size += new Vector2(0, 2); // add a lil bit of padding under | |
var rect = GUILayoutUtility.GetRect(size.x, size.y); | |
TrafficLightControl.DrawTrafficLightSingle(rect, colorA); | |
} | |
public static void DrawTrafficLightDouble(LightColor colorA, LightColor ccolorB) | |
{ | |
var size = TrafficLightControl.GetTrafficLightSize(TrafficLightSize.Double); | |
size += new Vector2(0, 2); | |
var rect = GUILayoutUtility.GetRect(size.x, size.y); | |
TrafficLightControl.DrawTrafficLightDouble(rect, colorA, ccolorB); | |
} | |
public static void DrawTrafficLightTriple(LightColor colorA, LightColor colorB, LightColor colorC) | |
{ | |
var size = TrafficLightControl.GetTrafficLightSize(TrafficLightSize.Triple); | |
size += new Vector2(0, 2); | |
var rect = GUILayoutUtility.GetRect(size.x, size.y); | |
TrafficLightControl.DrawTrafficLightTriple(rect, colorA, colorB, colorC); | |
} | |
public static void DrawTrafficLightQuad(LightColor colorA, LightColor colorB, LightColor colorC, LightColor colorD) | |
{ | |
var size = TrafficLightControl.GetTrafficLightSize(TrafficLightSize.Quad); | |
size += new Vector2(0, 2); | |
var rect = GUILayoutUtility.GetRect(size.x, size.y); | |
TrafficLightControl.DrawTrafficLightQuad(rect, colorA, colorB, colorC, colorD); | |
} | |
public static void BoolLight(bool val, LightColor color = LightColor.Green) | |
{ | |
DrawTrafficLightSingle(val ? color : LightColor.Off); | |
} | |
public static void BoolLightLarge(bool val, LightColor colorA = LightColor.Red, LightColor colorB = LightColor.Green) | |
{ | |
DrawTrafficLightDouble(!val ? colorA : LightColor.Off, val ? colorB : LightColor.Off); | |
} | |
} | |
public enum TrafficLightSize | |
{ | |
Single, | |
Double, | |
Triple, | |
Quad | |
} |
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
// editor code | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEditor; | |
using System; | |
[CustomPropertyDrawer(typeof(TrafficLightAttribute), true)] | |
public class TrafficLightDrawer : PropertyDrawer | |
{ | |
TrafficLightAttribute trafficLight; | |
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) | |
{ | |
if (!string.IsNullOrEmpty(trafficLight.CustomLabel)) | |
{ | |
label = new GUIContent(trafficLight.CustomLabel); | |
} | |
// store the attribute, it will be helpful! | |
trafficLight = attribute as TrafficLightAttribute; | |
// If were being directed to draw a label, Make sure it goes down first | |
if (trafficLight.DrawLabel) | |
{ | |
label = EditorGUI.BeginProperty(position, label, property); | |
if (trafficLight.AlsoDrawDefault) | |
{ | |
// dont supply a control id (for label highlighting and selecting). | |
// this will auto use the next one thats drawn (via EditorGUI.PropertyField) | |
position = EditorGUI.PrefixLabel(position, label); | |
} | |
else | |
{ | |
// becuase no editable control will be displayed, just the control image | |
// we need to mark ourselves as passive | |
position = EditorGUI.PrefixLabel(position, EditorGUIUtility.GetControlID(FocusType.Passive), label); | |
} | |
} | |
// figure out what kinda type were drawing.... | |
switch (property.propertyType) | |
{ | |
// Bools are supported with either one or two lights | |
case SerializedPropertyType.Boolean: | |
bool val = property.boolValue; | |
if (trafficLight.LightCount == 1) | |
{ | |
TrafficLightControl.DrawTrafficLightSingle(position, GetLightColor(val, 0)); | |
} | |
else if (trafficLight.LightCount == 2) | |
{ | |
TrafficLightControl.DrawTrafficLightDouble(position, GetLightColor(!val, 0), GetLightColor(val, 1)); | |
} | |
break; | |
// Ints/Floats/Enums are all treated as ints. The light matching the current | |
// int value of the property will be lit up by default. If the FillLights flag | |
// is set, instead all values up to and including the current int value will be | |
// lit. | |
case SerializedPropertyType.Integer: | |
int i = Mathf.Clamp(property.intValue, -1, trafficLight.LightCount - 1); | |
bool[] active = FillActiveLightArray(i); | |
DrawActiveLights(position, active); | |
break; | |
case SerializedPropertyType.Float: | |
i = Mathf.Clamp((int)property.floatValue, 0, trafficLight.LightCount - 1); | |
active = FillActiveLightArray(i); | |
DrawActiveLights(position, active); | |
break; | |
case SerializedPropertyType.Enum: | |
i = Mathf.Clamp(property.enumValueIndex, 0, trafficLight.LightCount - 1); | |
active = FillActiveLightArray(i); | |
DrawActiveLights(position, active); | |
break; | |
// TODO vectors could be treated as arrays that map to 2/3/4 light | |
case SerializedPropertyType.Vector2: | |
active = FillActiveLightArray(property.vector2Value); | |
DrawActiveLights(position, active); | |
break; | |
// TrafficLights, each field controling a single light? | |
default: | |
// NOT SUPPORTED OOPS | |
// and probably just dosnt make sense! | |
break; | |
} | |
var sizeType = GetLightSize(); | |
float size = TrafficLightControl.GetTrafficLightSize(sizeType).y; | |
if (trafficLight.AlsoDrawDefault) | |
{ | |
// Offset the default property so it looks better, as the stock art | |
// for the traffic light control is a bit larger than a line | |
position.position += new Vector2(0, 3); | |
position.width -= TrafficLightControl.GetTrafficLightSize(sizeType).x + 2; | |
position.x += TrafficLightControl.GetTrafficLightSize(sizeType).x + 2; | |
// Draw the default property control without a label, cos we already did it if we wanted it | |
EditorGUI.PropertyField(position, property, GUIContent.none, true); | |
} | |
if (trafficLight.DrawLabel) | |
{ | |
EditorGUI.EndProperty(); | |
} | |
} | |
#region Helpers | |
// these methods help with drawing trafficlights, especally the larger int lights | |
private LightColor GetLightColor(bool val, int i) | |
{ | |
return val ? trafficLight.Colors[i] : LightColor.Off; | |
} | |
private bool[] FillActiveLightArray(int val) | |
{ | |
bool[] active = new bool[trafficLight.LightCount]; | |
for (int x = 0; x < trafficLight.LightCount; x++) | |
{ | |
if (trafficLight.FillLights) | |
{ | |
active[x] = (val >= 0 && x <= val); | |
} | |
else | |
{ | |
active[x] = x == val; | |
} | |
} | |
return active; | |
} | |
private bool[] FillActiveLightArray(Vector2 vector2Value) | |
{ | |
return new[] { vector2Value[0] >= 1, vector2Value[1] >= 1 }; | |
} | |
private void DrawActiveLights(Rect position, bool[] active) | |
{ | |
int length = active.Length; | |
if (length == 1) | |
{ | |
TrafficLightControl.DrawTrafficLightSingle(position, GetLightColor(active[0], 0)); | |
} | |
else if (length == 2) | |
{ | |
TrafficLightControl.DrawTrafficLightDouble(position, GetLightColor(active[0], 0), GetLightColor(active[1], 1)); | |
} | |
else if (length == 3) | |
{ | |
TrafficLightControl.DrawTrafficLightTriple(position, GetLightColor(active[0], 0), GetLightColor(active[1], 1), GetLightColor(active[2], 2)); | |
} | |
else if (length == 4) | |
{ | |
TrafficLightControl.DrawTrafficLightQuad(position, GetLightColor(active[0], 0), GetLightColor(active[1], 1), GetLightColor(active[2], 2), GetLightColor(active[3], 3)); | |
} | |
} | |
private TrafficLightSize GetLightSize() | |
{ | |
return (TrafficLightSize)(trafficLight.LightCount - 1); | |
} | |
#endregion | |
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) | |
{ | |
trafficLight = attribute as TrafficLightAttribute; | |
var sizeType = GetLightSize(); // all lights are 20px with the default art | |
float size = TrafficLightControl.GetTrafficLightSize(sizeType).y; | |
if (trafficLight.AlsoDrawDefault) | |
{ | |
size = Mathf.Max(size, EditorGUI.GetPropertyHeight(property)); | |
} | |
return size; | |
} | |
} |
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
// API demo | |
// none editor code | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
public class GameManager : MonoBehaviour | |
{ | |
[Header("Game State")] | |
[SingleTrafficLight(LightColor.Pink, DrawLabel = true)] | |
public bool HasMatchStarted; | |
[QuadTrafficLight(LightColor.Red, LightColor.Red, LightColor.Red, LightColor.Green, DrawLabel = true, FillLights = true)] | |
public int GameStartCountdown = -1; | |
[TripleTrafficLight(AlsoDrawDefault = true, DrawLabel = true)] | |
public TurnPhase MyTurnPhase; | |
[SingleTrafficLight] | |
public bool SwitchingSides; | |
[TripleTrafficLight(AlsoDrawDefault = true, DrawLabel = true)] | |
public TurnPhase EnemyTurnPhase; | |
[Header("Network")] | |
[DoubleTrafficLight(LightColor.Blue, LightColor.Blue, CustomLabel = "Net Data [in][out]")] | |
public Vector2 data; | |
// Use this for initialization | |
IEnumerator Start () { | |
yield return new WaitUntil(() => { return Input.anyKeyDown; }); | |
yield return new WaitForSeconds(0.2f); | |
HasMatchStarted = true; | |
// animate the start countdown | |
yield return new WaitForSeconds(0.2f); | |
GameStartCountdown = 0; | |
yield return new WaitForSeconds(0.2f); | |
GameStartCountdown = 1; | |
yield return new WaitForSeconds(0.2f); | |
GameStartCountdown = 2; | |
yield return new WaitForSeconds(0.2f); | |
GameStartCountdown = 3; | |
StartCoroutine(Data()); | |
while (true) | |
{ | |
// play game | |
MyTurnPhase = TurnPhase.WaitingForMyTurn; | |
EnemyTurnPhase = TurnPhase.WaitingForMyTurn; | |
yield return new WaitForSeconds(0.7f); | |
MyTurnPhase = TurnPhase.DrawPhase; | |
yield return new WaitForSeconds(0.7f); | |
MyTurnPhase = TurnPhase.ActionPhase; | |
yield return new WaitForSeconds(0.7f); | |
SwitchingSides = true; | |
yield return new WaitForSeconds(0.2f); | |
SwitchingSides = false; | |
yield return new WaitForSeconds(0.2f); | |
SwitchingSides = true; | |
yield return new WaitForSeconds(0.2f); | |
SwitchingSides = false; | |
yield return new WaitForSeconds(0.2f); | |
MyTurnPhase = TurnPhase.WaitingForMyTurn; | |
yield return new WaitForSeconds(0.7f); | |
EnemyTurnPhase = TurnPhase.DrawPhase; | |
yield return new WaitForSeconds(0.7f); | |
EnemyTurnPhase = TurnPhase.ActionPhase; | |
yield return new WaitForSeconds(0.7f); | |
SwitchingSides = true; | |
yield return new WaitForSeconds(0.2f); | |
SwitchingSides = false; | |
yield return new WaitForSeconds(0.2f); | |
SwitchingSides = true; | |
yield return new WaitForSeconds(0.2f); | |
SwitchingSides = false; | |
yield return new WaitForSeconds(0.2f); | |
} | |
} | |
IEnumerator Data() | |
{ | |
while (true) | |
{ | |
data.x = Random.Range(0, 2); | |
data.y = Random.Range(0, 2); | |
yield return new WaitForSeconds(0.1f); | |
} | |
} | |
// Update is called once per frame | |
void Update () { | |
} | |
} | |
public enum TurnPhase | |
{ | |
WaitingForMyTurn, | |
DrawPhase, | |
ActionPhase, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment