├── demo ├── base_data.gd.uid ├── base_data.gd └── test_scene.tscn ├── addons └── layerNames │ ├── layerNames_plugin.gd.uid │ ├── plugin.cfg │ ├── LICENSE │ └── layerNames_plugin.gd ├── icon.png ├── .gitattributes ├── .gitignore ├── project.godot ├── icon.png.import ├── LICENSE └── README.md /demo/base_data.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c3lr4poy5xjsw 2 | -------------------------------------------------------------------------------- /addons/layerNames/layerNames_plugin.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cri6m6agrc3i2 2 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MauriceButler/godot-layer-name-enums/HEAD/icon.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /demo/base_data.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | func _ready() -> void: 4 | var render_layer_number = LayerNames.RENDER_2D.PLAYER_NUM 5 | var render_layer_bit = LayerNames.RENDER_2D.PLAYER_BIT 6 | 7 | print(render_layer_number) 8 | print(render_layer_bit) 9 | -------------------------------------------------------------------------------- /demo/test_scene.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://babefhxnseam6"] 2 | 3 | [ext_resource type="Script" uid="uid://c3lr4poy5xjsw" path="res://demo/base_data.gd" id="1_3s576"] 4 | 5 | [node name="TestScene" type="Node2D"] 6 | script = ExtResource("1_3s576") 7 | -------------------------------------------------------------------------------- /addons/layerNames/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Project Layer Names" 4 | description="Provides user friendly lookups of project layer names, numbers, and bit masks for both GDScript and C#." 5 | author="MauriceButler" 6 | version="2.2.0" 7 | script="layerNames_plugin.gd" 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | 4 | 5 | # Godot-specific ignores 6 | .import/ 7 | export.cfg 8 | export_presets.cfg 9 | 10 | # Imported translations (automatically generated from CSV files) 11 | *.translation 12 | 13 | # Mono-specific ignores 14 | .mono/ 15 | data_*/ 16 | mono_crash.*.json 17 | 18 | # Android 19 | *.aar 20 | 21 | # Mac crap 22 | .DS_Store 23 | 24 | # plugin specific 25 | addons/layerNames/generated/* -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="Project Layer Names" 14 | config/features=PackedStringArray("4.5", "Mobile") 15 | config/icon="res://icon.svg" 16 | 17 | [autoload] 18 | 19 | LayerNames="*res://addons/layerNames/generated/layerNames.gd" 20 | 21 | [editor_plugins] 22 | 23 | enabled=PackedStringArray("res://addons/layerNames/plugin.cfg") 24 | 25 | [layer_names] 26 | 27 | 2d_render/layer_1="Player" 28 | 2d_render/layer_2="Enemies" 29 | 2d_render/layer_3="Walls" 30 | 2d_render/layer_4="camelsAreCool" 31 | 2d_render/layer_5="PascalCouldPaint" 32 | 2d_render/layer_6="snakes_are_cool" 33 | 2d_render/layer_7="kebabs-are-yum" 34 | 35 | [rendering] 36 | 37 | renderer/rendering_method="mobile" 38 | -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://l24gll6ffchm" 6 | path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.png" 14 | dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/uastc_level=0 22 | compress/rdo_quality_loss=0.0 23 | compress/hdr_compression=1 24 | compress/normal_map=0 25 | compress/channel_pack=0 26 | mipmaps/generate=false 27 | mipmaps/limit=-1 28 | roughness/mode=0 29 | roughness/src_normal="" 30 | process/channel_remap/red=0 31 | process/channel_remap/green=1 32 | process/channel_remap/blue=2 33 | process/channel_remap/alpha=3 34 | process/fix_alpha_border=true 35 | process/premult_alpha=false 36 | process/normal_map_invert_y=false 37 | process/hdr_as_srgb=false 38 | process/hdr_clamp_exposure=false 39 | process/size_limit=0 40 | detect_3d/compress_to=1 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Maurice Butler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /addons/layerNames/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Maurice Butler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Godot Layer Name Enums 2 | 3 | Automatically generate user-friendly lookups of project layer names. 4 | Each layer will have its layer number and its bit available from the Enum 5 | 6 | ## Usage 7 | 8 | ### GDScript 9 | ```gdscript 10 | func _ready() -> void: 11 | var render_layer_number = LayerNames.RENDER_2D.LAYER_8_NUM # 8 12 | var render_layer_bit = LayerNames.RENDER_2D.LAYER_8_BIT # 128 13 | ``` 14 | 15 | ### C# 16 | ```csharp 17 | public override void _Ready() 18 | { 19 | var render_layer_number = (int)LayerNames.RENDER_2D.LAYER_8_NUM; // 8 20 | var render_layer_bit = (int)LayerNames.RENDER_2D.LAYER_8_BIT; // 128 21 | } 22 | ``` 23 | 24 | ## Configuration 25 | 26 | The plugin supports generating enums in multiple languages. You can configure the output format in: 27 | 28 | `Project` -> `Project Settings` -> `addons/project_layer_names/output` 29 | 30 | **Options:** 31 | - **GDScript** (default): Generate only GDScript enums 32 | - **C#**: Generate only C# enums 33 | - **Both**: Generate both GDScript and C# enums 34 | 35 | image 36 | 37 | 38 | ## Installation 39 | 40 | Copy the `addons/layerNames` directory into your `res://addons/` directory, or install via the [Asset Library](https://godotengine.org/asset-library/asset/3372) 41 | 42 | Go to `Project` -> `Project Settings` -> `Plugins` and enable `Layer Names`. 43 | 44 | ![install](https://github.com/user-attachments/assets/382c36c1-4bdc-4599-92ef-ef6246ab9c8b) 45 | 46 | 47 | ## License 48 | 49 | Licensed under the [MIT license](LICENSE) 50 | -------------------------------------------------------------------------------- /addons/layerNames/layerNames_plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | const OUTPUT_PATH := "res://addons/layerNames/generated/" 5 | const OUTPUT_FILE_GDSCRIPT := OUTPUT_PATH + "layerNames.gd" 6 | const OUTPUT_FILE_CSHARP := OUTPUT_PATH + "LayerNames.cs" 7 | const SINGLETON_NAME := "LayerNames" 8 | const CSHARP_NAMESPACE_DEFAULT := "ProjectLayerNames" 9 | const SETTING_KEY_FORMAT := "layer_names/%s/layer_%s" 10 | const OUTPUT_SETTING_KEY := "addons/project_layer_names/output" 11 | const NAMESPACE_SETTING_KEY := "addons/project_layer_names/c#_namespace" 12 | const INPUT_WAIT_SECONDS := 1.5 13 | const VALID_IDENTIFIER_PATTERN := "[^a-z,A-Z,0-9,_,\\s]" 14 | const BIT_SHIFT_OFFSET := 1 15 | 16 | # Layer type definitions: layer_type -> max_count 17 | const LAYER_TYPES := { 18 | "2d_render": 20, 19 | "2d_physics": 32, 20 | "2d_navigation": 32, 21 | "3d_render": 20, 22 | "3d_physics": 32, 23 | "3d_navigation": 32, 24 | "avoidance": 32 25 | } 26 | 27 | enum OutputLanguage { 28 | GDScript = 0, 29 | CSharp = 1, 30 | Both = 2 31 | } 32 | 33 | var previous_gdscript_hash := "" 34 | var previous_csharp_hash := "" 35 | var wait_tickets := 0 36 | var layer_settings_cache := {} 37 | var regex_cache := RegEx.new() 38 | 39 | func _enter_tree() -> void: 40 | print("LayerNames plugin activated.") 41 | 42 | _register_project_settings() 43 | ProjectSettings.settings_changed.connect(_update_layer_names) 44 | 45 | DirAccess.make_dir_recursive_absolute(OUTPUT_PATH) 46 | 47 | regex_cache.compile(VALID_IDENTIFIER_PATTERN) 48 | 49 | _update_layer_names() 50 | 51 | func _exit_tree() -> void: 52 | ProjectSettings.settings_changed.disconnect(_update_layer_names) 53 | remove_autoload_singleton(SINGLETON_NAME) 54 | layer_settings_cache.clear() 55 | 56 | func _register_project_settings() -> void: 57 | if not ProjectSettings.has_setting(OUTPUT_SETTING_KEY): 58 | ProjectSettings.set_setting(OUTPUT_SETTING_KEY, OutputLanguage.GDScript) 59 | ProjectSettings.add_property_info({ 60 | "name": OUTPUT_SETTING_KEY, 61 | "type": TYPE_INT, 62 | "hint": PROPERTY_HINT_ENUM, 63 | "hint_string": "GDScript,C#,Both", 64 | "default": OutputLanguage.GDScript 65 | }) 66 | ProjectSettings.save() 67 | 68 | ProjectSettings.set_initial_value(OUTPUT_SETTING_KEY, OutputLanguage.GDScript) 69 | 70 | if not ProjectSettings.has_setting(NAMESPACE_SETTING_KEY): 71 | ProjectSettings.set_setting(NAMESPACE_SETTING_KEY, CSHARP_NAMESPACE_DEFAULT) 72 | ProjectSettings.add_property_info({ 73 | "name": NAMESPACE_SETTING_KEY, 74 | "type": TYPE_STRING, 75 | "default": CSHARP_NAMESPACE_DEFAULT 76 | }) 77 | ProjectSettings.save() 78 | 79 | ProjectSettings.set_initial_value(NAMESPACE_SETTING_KEY, CSHARP_NAMESPACE_DEFAULT) 80 | 81 | func _update_layer_names() -> void: 82 | wait_tickets += 1 83 | var wait_number := wait_tickets 84 | await get_tree().create_timer(INPUT_WAIT_SECONDS).timeout 85 | if wait_number != wait_tickets: return 86 | 87 | layer_settings_cache.clear() 88 | 89 | var output_setting := ProjectSettings.get_setting(OUTPUT_SETTING_KEY, OutputLanguage.GDScript) 90 | 91 | match output_setting: 92 | OutputLanguage.GDScript: 93 | _generate_gdscript_file() 94 | OutputLanguage.CSharp: 95 | _generate_csharp_file() 96 | OutputLanguage.Both: 97 | _generate_csharp_file() 98 | _generate_gdscript_file() 99 | 100 | func _write_to_file(file_path: String, content: String) -> void: 101 | var file := FileAccess.open(file_path, FileAccess.WRITE) 102 | file.store_string(content) 103 | file.close() 104 | 105 | func _generate_hash(content: String) -> String: 106 | return content.sha256_text() 107 | 108 | func _generate_gdscript_file() -> void: 109 | var text_parts := PackedStringArray() 110 | text_parts.append("extends Node\n\n") 111 | 112 | for layer_type in LAYER_TYPES: 113 | text_parts.append(_create_enum_string(OutputLanguage.GDScript, layer_type, LAYER_TYPES[layer_type])) 114 | 115 | var current_text := "".join(text_parts) 116 | var current_hash := _generate_hash(current_text) 117 | if current_hash == previous_gdscript_hash: 118 | return 119 | 120 | print("Regenerating LayerNames GDScript enums") 121 | _write_to_file(OUTPUT_FILE_GDSCRIPT, current_text) 122 | add_autoload_singleton(SINGLETON_NAME, OUTPUT_FILE_GDSCRIPT) 123 | previous_gdscript_hash = current_hash 124 | 125 | func _generate_csharp_file() -> void: 126 | var text_parts := PackedStringArray() 127 | var namespace_setting := ProjectSettings.get_setting(NAMESPACE_SETTING_KEY); 128 | 129 | text_parts.append("using Godot;\n\nnamespace ") 130 | text_parts.append(namespace_setting) 131 | text_parts.append(" {\n\tpublic partial class LayerNames : Node {\n\t\t\n") 132 | 133 | # Add singleton boilerplate 134 | text_parts.append(_generate_singleton_boilerplate()) 135 | 136 | for layer_type in LAYER_TYPES: 137 | text_parts.append(_create_enum_string(OutputLanguage.CSharp, layer_type, LAYER_TYPES[layer_type])) 138 | 139 | text_parts.append("\t}\n}\n") 140 | 141 | var current_text := "".join(text_parts) 142 | var current_hash := _generate_hash(current_text) 143 | if current_hash == previous_csharp_hash: 144 | return 145 | 146 | print("Regenerating LayerNames C# enums") 147 | _write_to_file(OUTPUT_FILE_CSHARP, current_text) 148 | add_autoload_singleton(SINGLETON_NAME, OUTPUT_FILE_CSHARP) 149 | previous_csharp_hash = current_hash 150 | 151 | func _generate_singleton_boilerplate() -> String: 152 | var boilerplate_parts := PackedStringArray() 153 | 154 | boilerplate_parts.append("\t\tprivate static LayerNames _instance;\n") 155 | boilerplate_parts.append("\t\tpublic static LayerNames Instance\n") 156 | boilerplate_parts.append("\t\t{\n") 157 | boilerplate_parts.append("\t\t\tget\n") 158 | boilerplate_parts.append("\t\t\t{\n") 159 | boilerplate_parts.append("\t\t\t\tif (_instance == null)\n") 160 | boilerplate_parts.append("\t\t\t\t\tGD.PrintErr(\"LayerNames singleton not initialized.\");\n") 161 | boilerplate_parts.append("\t\t\t\treturn _instance;\n") 162 | boilerplate_parts.append("\t\t\t}\n") 163 | boilerplate_parts.append("\t\t}\n") 164 | boilerplate_parts.append("\t\tpublic override void _Ready()\n") 165 | boilerplate_parts.append("\t\t{\n") 166 | boilerplate_parts.append("\t\t\tif (_instance == null)\n") 167 | boilerplate_parts.append("\t\t\t\t_instance = this;\n") 168 | boilerplate_parts.append("\t\t\telse if (_instance != this)\n") 169 | boilerplate_parts.append("\t\t\t\tQueueFree();\n") 170 | boilerplate_parts.append("\t\t}\n") 171 | boilerplate_parts.append("\t\tpublic override void _ExitTree()\n") 172 | boilerplate_parts.append("\t\t{\n") 173 | boilerplate_parts.append("\t\t\tif (_instance == this)\n") 174 | boilerplate_parts.append("\t\t\t\t_instance = null;\n") 175 | boilerplate_parts.append("\t\t}\n\n") 176 | 177 | return "".join(boilerplate_parts) 178 | 179 | func _create_enum_string(language: OutputLanguage, layer_type: String, max_layer_count: int) -> String: 180 | var enum_name := _get_enum_name(layer_type) 181 | var enum_type := " : uint" if language == OutputLanguage.CSharp else "" 182 | var enum_indent := "\t\t" if language == OutputLanguage.CSharp else "" 183 | var entry_indent := "\t\t\t" if language == OutputLanguage.CSharp else "\t" 184 | var public_keyword := "public " if language == OutputLanguage.CSharp else "" 185 | 186 | var enum_parts := PackedStringArray() 187 | enum_parts.append("%s%senum " % [enum_indent, public_keyword]) 188 | enum_parts.append(enum_name) 189 | enum_parts.append(enum_type) 190 | enum_parts.append(" {\n") 191 | enum_parts.append("%sNONE_NUM = 0,\n" % entry_indent) 192 | enum_parts.append("%sNONE_BIT = 0,\n" % entry_indent) 193 | 194 | for index in max_layer_count: 195 | var layer_number := index + 1 196 | var layer_name := _get_layer_name(layer_type, layer_number) 197 | enum_parts.append(_generate_enum_entry(language, layer_number, layer_name)) 198 | 199 | enum_parts.append("%s}\n\n" % enum_indent) 200 | return "".join(enum_parts) 201 | 202 | func _get_enum_name(layer_type: String) -> String: 203 | # Convert layer type to enum name (e.g., '2d_render' -> 'RENDER_2D') 204 | var parts := layer_type.split("_") 205 | parts.reverse() 206 | return _sanitise(" ".join(parts)) 207 | 208 | func _get_layer_name(layer_type: String, layer_number: int) -> String: 209 | var cache_key := "%s_%s" % [layer_type, layer_number] 210 | if not layer_settings_cache.has(cache_key): 211 | layer_settings_cache[cache_key] = ProjectSettings.get_setting( 212 | SETTING_KEY_FORMAT % [layer_type, layer_number] 213 | ) 214 | return layer_settings_cache[cache_key] 215 | 216 | func _generate_enum_entry(language: OutputLanguage, layer_number: int, layer_name: String) -> String: 217 | var key := _sanitise(layer_name) 218 | if not key: 219 | key = "LAYER_%s" % layer_number 220 | 221 | var entry_indent := "\t\t\t" if language == OutputLanguage.CSharp else "\t" 222 | var bit_value := 1 << (layer_number - BIT_SHIFT_OFFSET) 223 | 224 | var entry_parts := PackedStringArray() 225 | entry_parts.append("%s%s_NUM = %s,\n" % [entry_indent, key, layer_number]) 226 | entry_parts.append("%s%s_BIT = %s,\n" % [entry_indent, key, bit_value]) 227 | 228 | return "".join(entry_parts) 229 | 230 | func _sanitise(input: String) -> String: 231 | if input.is_empty(): 232 | return "" 233 | 234 | var output := regex_cache.sub(input.replace("-", "_"), "", true) 235 | output = output.to_snake_case().to_upper() 236 | 237 | return output if output.is_valid_identifier() else "" 238 | --------------------------------------------------------------------------------