Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save aprius/9e77e8a75957102f73fec3f12a189d33 to your computer and use it in GitHub Desktop.
Save aprius/9e77e8a75957102f73fec3f12a189d33 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.
// 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
}
// 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
}
// 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;
}
}
// 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