├── menu.png ├── screen.png ├── addons └── quickenv │ ├── plugin.cfg │ ├── PresetEnv.tscn │ └── plugin.gd ├── LICENSE └── README.md /menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainProton42/godot-quickenv/HEAD/menu.png -------------------------------------------------------------------------------- /screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainProton42/godot-quickenv/HEAD/screen.png -------------------------------------------------------------------------------- /addons/quickenv/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="QuickEnv" 4 | description="A tiny plugin that allows you to quickly add a custom environment to the 3D scene editor." 5 | author="John Wigg" 6 | version="1.0" 7 | script="plugin.gd" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 John Wigg 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/quickenv/PresetEnv.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | sky_top_color = Color( 0.211765, 0.313726, 0.552941, 1 ) 5 | sky_horizon_color = Color( 0.545098, 0.686275, 0.811765, 1 ) 6 | ground_bottom_color = Color( 0.545098, 0.686275, 0.811765, 1 ) 7 | ground_horizon_color = Color( 0.545098, 0.686275, 0.811765, 1 ) 8 | sun_latitude = 50.0 9 | sun_angle_max = 30.0 10 | sun_curve = 0.13 11 | 12 | [sub_resource type="Environment" id=2] 13 | background_mode = 2 14 | background_sky = SubResource( 1 ) 15 | ambient_light_color = Color( 0.501961, 0.501961, 0.501961, 1 ) 16 | ambient_light_energy = 3.0 17 | ambient_light_sky_contribution = 0.3 18 | tonemap_mode = 2 19 | tonemap_white = 6.0 20 | 21 | [node name="PresetEnv" type="Spatial"] 22 | 23 | [node name="DirectionalLight" type="DirectionalLight" parent="."] 24 | transform = Transform( -1, -2.49609e-07, 2.09447e-07, 0, 0.642788, 0.766044, -3.25841e-07, 0.766044, -0.642788, 0, 0, 0 ) 25 | light_energy = 2.0 26 | shadow_enabled = true 27 | shadow_color = Color( 0.6, 0.6, 0.6, 1 ) 28 | 29 | [node name="WorldEnvironment" type="WorldEnvironment" parent="."] 30 | environment = SubResource( 2 ) 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuickEnv 2 | 3 | This tiny plugin for the [Godot Engine](https://godotengine.org/) allows you to load a custom scene as an environment in the 3D editor. 4 | 5 | ![](screen.png) 6 | 7 | An environment scene can be any ordinary 3D scene which means you can include directional lights and even objects. This is especially useful for materials that rely on lights other than ambient being present in the scene (like the [cel shader material](https://github.com/EXPWorlds/Godot-Cel-Shader) by DaveTheDev in the image above) and allows you to work with these materials without adding nodes to the scene tree. It's also just a quick way to get a nice looking environment in the editor. 8 | 9 | The plugin comes with a preset environment that can be quickly accessed via the `Load Preset` option. It attempts to replicate the environment from this Godot proposal: https://github.com/godotengine/godot-proposals/issues/348. 10 | 11 | ## How To Use 12 | 13 | 1. Open a 3D Scene in the editor. 14 | 2. Click `Editor Environment` in the 3D editor menu bar. 15 | 3. Select `Load Environment Scene...` to select a custom scene or `Load Preset` to include the preset environment included with this plugin. 16 | 17 | * `Edit Environment Scene` will open the currently loaded environment scene in the editor. 18 | * `Enabled` enables and disables the environment scene. (Note that this setting is global and *not* per-scene.) 19 | 20 | ![](menu.png) 21 | 22 | To create your own custom environment scene, just create a 3D scene with the lights and environment you want and save it. You can then load it from the menu. 23 | 24 | ## Issues 25 | 26 | * You can interact with objects inside the environment scene in the editor viewport (*not* the scene tree). However, changes to the environment will not be saved and reverted when you switch tabs or the scene is reloaded. 27 | * Loading an environment that contains a `WorldEnvironment` node when editing a scene that already contains a `WorldEnvironment` scene will result in unexpected behaviour. A workaround is to disable the environment and refresh the scene (by switching tabs for reopening it). -------------------------------------------------------------------------------- /addons/quickenv/plugin.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | var _scene = null 5 | var _instance = null 6 | 7 | var _restore_enabled = false # Used to restore enabled setting after env has been edited 8 | 9 | var _button = MenuButton.new() 10 | var _file_dialog = EditorFileDialog.new() 11 | var _msg_dialog = AcceptDialog.new() 12 | 13 | enum MenuOption { 14 | MENU_LOAD_SCENE, 15 | MENU_LOAD_PRESET 16 | MENU_EDIT_SCENE, 17 | MENU_ENABLE, 18 | } 19 | 20 | func _enter_tree(): 21 | connect("scene_changed", self, "_on_scene_changed") 22 | get_tree().connect("node_added", self, "_on_node_added") 23 | 24 | _file_dialog.add_filter("*.tscn, *.scn; Scenes") 25 | _file_dialog.mode = _file_dialog.MODE_OPEN_FILE 26 | _file_dialog.connect("file_selected", self, "_on_file_selected") 27 | 28 | var editor_interface = get_editor_interface() 29 | var base_control = editor_interface.get_base_control() 30 | base_control.add_child(_file_dialog) 31 | base_control.add_child(_msg_dialog) 32 | 33 | _button.get_popup().connect("id_pressed", self, "_on_popup_id_pressed") 34 | 35 | _button.text = "Editor Environment" 36 | _button.get_popup().add_item("Load Environment Scene...", MenuOption.MENU_LOAD_SCENE) 37 | _button.get_popup().add_item("Load Preset", MenuOption.MENU_LOAD_PRESET) 38 | _button.get_popup().add_item("Edit Environment Scene", MenuOption.MENU_EDIT_SCENE) 39 | _button.get_popup().add_check_item("Enabled", MenuOption.MENU_ENABLE) 40 | 41 | _button.get_popup().set_item_disabled(_button.get_popup().get_item_index(MenuOption.MENU_EDIT_SCENE), true) 42 | _button.get_popup().set_item_disabled(_button.get_popup().get_item_index(MenuOption.MENU_ENABLE), true) 43 | 44 | add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, _button) 45 | 46 | if _scene_is_empty(): 47 | _disable_menu() 48 | 49 | func _exit_tree(): 50 | remove_control_from_container(CONTAINER_SPATIAL_EDITOR_MENU, _button) 51 | 52 | func _on_popup_id_pressed(id): 53 | match id: 54 | MenuOption.MENU_LOAD_SCENE: 55 | _on_load_scene_pressed() 56 | MenuOption.MENU_LOAD_PRESET: 57 | _on_load_preset_pressed() 58 | MenuOption.MENU_EDIT_SCENE: 59 | _on_edit_scene_pressed() 60 | MenuOption.MENU_ENABLE: 61 | _on_enabled_pressed() 62 | 63 | func _on_load_scene_pressed(): 64 | _file_dialog.popup_centered_ratio(0.8) 65 | 66 | func _on_load_preset_pressed(): 67 | _load_environment("res://addons/quickenv/PresetEnv.tscn") 68 | 69 | func _on_edit_scene_pressed(): 70 | if _scene.resource_path == "res://addons/quickenv/PresetEnv.tscn": 71 | _scene = _scene.duplicate(true) 72 | ResourceSaver.save("res://PresetEnv.tscn", _scene) 73 | _scene.resource_path = "res://PresetEnv.tscn" 74 | _msg_dialog.dialog_text = "Editable scene created at " + _scene.resource_path + "." 75 | _msg_dialog.window_title = "File created." 76 | _msg_dialog.popup_centered() 77 | 78 | get_editor_interface().open_scene_from_path(_scene.resource_path) 79 | 80 | func _on_enabled_pressed(): 81 | _toggle_environment() 82 | 83 | func _on_file_selected(path): 84 | _load_environment(path) 85 | 86 | func _toggle_environment(): 87 | if _has_environment(): 88 | _remove_environment() 89 | else: 90 | _add_environment() 91 | _button.get_popup().set_item_checked(_button.get_popup().get_item_index(MenuOption.MENU_ENABLE), _has_environment()) 92 | 93 | func _load_environment(path): 94 | if _has_environment(): 95 | _remove_environment() 96 | 97 | var resource = load(path) 98 | if not resource is PackedScene: 99 | _msg_dialog.dialog_text = "You must select a scene file!" 100 | _msg_dialog.window_title = "Cannot load scene." 101 | _msg_dialog.popup_centered() 102 | return 103 | 104 | _scene = resource 105 | _instance = _scene.instance() 106 | 107 | if _scene_is_environment() or _scene_is_empty(): 108 | _disable_menu() 109 | else: 110 | _enable_menu() 111 | _button.get_popup().set_item_disabled(_button.get_popup().get_item_index(MenuOption.MENU_EDIT_SCENE), false) 112 | _button.get_popup().set_item_disabled(_button.get_popup().get_item_index(MenuOption.MENU_ENABLE), false) 113 | _add_environment() 114 | 115 | func _on_scene_changed(_scene_root): 116 | if _scene_is_environment() or _scene_is_empty(): 117 | _disable_menu() 118 | if _has_environment(): 119 | _restore_enabled = true 120 | _remove_environment() 121 | else: 122 | _enable_menu() 123 | if _has_scene_loaded(): 124 | if _has_environment(): 125 | _remove_environment() 126 | _reload_resource() 127 | _add_environment() 128 | else: 129 | _reload_resource() 130 | if _restore_enabled: 131 | _restore_enabled = false 132 | _add_environment() 133 | 134 | 135 | func _remove_environment(): 136 | _instance.get_parent().remove_child(_instance) 137 | _button.get_popup().set_item_checked(_button.get_popup().get_item_index(MenuOption.MENU_ENABLE), false) 138 | 139 | func _add_environment(): 140 | get_editor_interface().get_edited_scene_root().add_child(_instance) 141 | _button.get_popup().set_item_checked(_button.get_popup().get_item_index(MenuOption.MENU_ENABLE), true) 142 | 143 | func _reload_resource(): 144 | _instance = _scene.instance() 145 | 146 | func _has_environment(): 147 | if _instance and _instance.get_parent(): 148 | return true 149 | else: 150 | return false 151 | 152 | func _has_scene_loaded(): 153 | if _scene: 154 | return true 155 | else: 156 | return false 157 | 158 | func _scene_is_environment(): 159 | var scene_root = get_editor_interface().get_edited_scene_root() 160 | return _has_scene_loaded() and scene_root != null and scene_root.filename == _scene.resource_path 161 | 162 | func _scene_is_empty(): 163 | return get_editor_interface().get_edited_scene_root() == null 164 | 165 | func _disable_menu(): 166 | _button.disabled = true 167 | 168 | func _enable_menu(): 169 | _button.disabled = false 170 | 171 | func _on_node_added(node): 172 | if node == get_editor_interface().get_edited_scene_root(): 173 | _enable_menu() 174 | if _restore_enabled: 175 | _restore_enabled = false 176 | _add_environment() --------------------------------------------------------------------------------