Community Made Tools

Have you made any useful utilities with Odin?

Login and submit your creations here

LayerMask Drawer

Authored by Leo
Shared 06-05-2020

A custom drawer for Layer Masks, in the style of the Enum Dropdown

  • select multiple layers without opening the dropdown every single time you select or deselect a layer
  • have nice and easy to read graphics that show way better what layers are selected than vanilla unity design (horizontal gradient separations, cool icons)
  • See the first few selected layers without opening the dropdown instead of "Mixed"
  • Free and open source, modify the code if you want to
  • automatically applies to all layermask fields

How to use:

  1. put the script in any folder named "Editor"
  2. done

Showcase:

https://youtu.be/_eHBsGb2XMA

Code:

// =============================== // AUTHOR : John Leonard // CREATE DATE : 25.04.2020 // NOTE : Always include this // Header when copying the file // =============================== // Licence: MIT (https://opensource.org/licenses/MIT) // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // =============================== using Sirenix.OdinInspector.Editor; using Sirenix.Utilities.Editor; using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; public class LayerMaskDrawer : OdinValueDrawer<LayerMask> { Rect buttonRect; List<string> layerNames = new List<string>(); List<int> layerIndexes = new List<int>(); List<bool> selectedLayers = new List<bool>(); string buttonText = null; protected override void DrawPropertyLayout(GUIContent label) { GetAllLayerNames(layerNames, layerIndexes); EditorGUILayout.BeginHorizontal(); EditorGUILayout.PrefixLabel(label); // get a list of selected layers with bitwise operations selectedLayers.Clear(); int layerBitVal = ValueEntry.SmartValue; for (int i = 0; i < layerNames.Count; i++) { int bitVal = (int)Mathf.Pow(2, layerIndexes[i]); bool isSelected = (layerBitVal & bitVal) == bitVal; selectedLayers.Add(isSelected); } if (string.IsNullOrEmpty(buttonText)) { bool all = true; bool none = true; buttonText = ""; for (int i = 0; i < layerNames.Count; i++) { if (selectedLayers[i]) { none = false; buttonText += layerNames[i] + ", "; } else { all = false; } } if (none) buttonText = "Nothing"; else if (all) buttonText = "Everything"; else if (buttonText.Length > 1) buttonText = buttonText.Remove(buttonText.Length - 2); } if (GUILayout.Button(buttonText, SirenixGUIStyles.DropDownMiniButton, GUILayout.MinWidth(100))) { buttonText = null; PopupWindow.Show(buttonRect, new LayerMaskPopupSelector() { layerNames = layerNames, layerIndexes = layerIndexes, selectedLayers = selectedLayers, bitmask = ValueEntry.SmartValue, OnSet = (bitmask) => { ValueEntry.SmartValue = bitmask; buttonText = null; } }); } // get the rectangle for the popup window if (Event.current.type == EventType.Repaint) buttonRect = GUILayoutUtility.GetLastRect(); EditorGUILayout.EndHorizontal(); } public class LayerMaskPopupSelector : PopupWindowContent { public List<string> layerNames = new List<string>(); public List<int> layerIndexes = new List<int>(); public List<bool> selectedLayers = new List<bool>(); public Action<int> OnSet; public int bitmask; public override Vector2 GetWindowSize() { // every button is 30 but the label is only 22 so we substract 8 float heightOfOne = 30; float height = heightOfOne * (2 + layerNames.Count) - 8; return new Vector2(200, height); } public override void OnGUI(Rect rect) { GUILayout.Label("Layers", EditorStyles.boldLabel); // check if all or none are selected bool all = true; bool none = true; foreach (bool b in selectedLayers) { if (b) none = false; else all = false; if (!all && !none) break; } // none button Texture2D noneIcon = none ? EditorIcons.TestPassed : EditorIcons.TestNormal; if (SirenixEditorGUI.MenuButton(0, " None", false, noneIcon)) { SetBitmask(-1, true); OnSet(bitmask); for (int i = 0; i < selectedLayers.Count; i++) { selectedLayers[i] = false; } } // layer buttons for (int i = 0; i < layerNames.Count; i++) { Texture2D currentIcon = selectedLayers[i] ? EditorIcons.TestPassed : EditorIcons.TestNormal; if (SirenixEditorGUI.MenuButton(0, " " + layerNames[i], false, currentIcon)) { selectedLayers[i] = !selectedLayers[i]; SetBitmask(layerIndexes[i], selectedLayers[i]); OnSet(bitmask); } } // all button Texture2D allIcon = all ? EditorIcons.TestPassed : EditorIcons.TestNormal; if (SirenixEditorGUI.MenuButton(0, " All", false, allIcon)) { SetBitmask(-2, true); OnSet(bitmask); for (int i = 0; i < selectedLayers.Count; i++) { selectedLayers[i] = true; } } OnSet(bitmask); // faster update of this window, otherwise the highlight of buttons lags editorWindow.Repaint(); } void SetBitmask(int index, bool set) { // -1 = none if (index == -1) { bitmask = 0; return; } // -2 = all if (index == -2) { bitmask = ~0; return; } int bitVal = (int)Mathf.Pow(2, index); if (set) // or "|" will add the value, the 1 at the right position bitmask |= bitVal; else // and "&" will multiply the value, but we take the inverse // so the bit position is 0 while all others are 1 // everything stays as it is, except for the one value // which will be set to 0 bitmask &= ~bitVal; } public override void OnOpen() { } public override void OnClose() { OnSet(bitmask); } } // simple function to return the names of all layers and their corresponding index List<string> GetAllLayerNames(List<string> layerNames, List<int> layerIndexes) { layerNames.Clear(); layerIndexes.Clear(); //user defined layers start with layer 8 and unity //supports 31 layers for (int i = 0; i <= 31; i++) { //get the name of the layer var layerN = LayerMask.LayerToName(i); //only add the layer if it has been named if (layerN.Length > 0) { layerIndexes.Add(i); layerNames.Add(layerN); } } return layerNames; } }