├── Docs └── Images │ ├── window.jpg │ ├── build_all.png │ ├── inspector.png │ ├── main_menu.jpg │ ├── overview.png │ ├── build_all_2.jpg │ ├── context_menu.jpg │ ├── hierarchy_root.jpg │ ├── my_component.jpg │ ├── override_theme.jpg │ ├── override_tools.jpg │ ├── trilib_loader.jpg │ ├── universal_rp_1.jpg │ ├── universal_rp_2.jpg │ ├── universal_rp_3.jpg │ ├── builtin_windows.jpg │ ├── custom_window_1.jpg │ ├── custom_window_2.jpg │ ├── custom_window_3.jpg │ ├── example_scenes_1.jpg │ ├── example_scenes_2.jpg │ ├── expose_to_editor.jpg │ ├── file_importers_1.jpg │ ├── file_importers_2.jpg │ ├── import_sources_1.jpg │ ├── import_sources_2.jpg │ ├── import_sources_3.jpg │ ├── import_sources_4.jpg │ ├── import_sources_5.jpg │ ├── jint_scripting_1.jpg │ ├── jint_scripting_2.jpg │ ├── jint_scripting_3.jpg │ ├── jint_scripting_4.jpg │ ├── jint_scripting_5.jpg │ ├── jint_scripting_6.jpg │ ├── jint_scripting_7.jpg │ ├── jint_scripting_8.jpg │ ├── jint_scripting_9.jpg │ ├── property_editors.jpg │ ├── compatibility_mode.jpg │ ├── enumerators_folder.png │ ├── extending_window_1.jpg │ ├── generate_surrogate.png │ ├── getting_started_1.png │ ├── getting_started_2.png │ ├── getting_started_3.jpg │ ├── getting_started_4.jpg │ ├── getting_started_5.jpg │ ├── my_import_source_1.jpg │ ├── my_import_source_2.jpg │ ├── override_layout_1.jpg │ ├── override_layout_2.jpg │ ├── override_layout_3.jpg │ ├── surrogates_folder.png │ ├── modify_importsource_1.jpg │ ├── modify_importsource_2.jpg │ ├── modify_importsource_3.jpg │ ├── override_scene_params.jpg │ ├── persistent_data_path.jpg │ ├── manage_projects_example.jpg │ ├── ui-scale-after-override.png │ ├── ui-scale-before-override.png │ ├── inspector_configuration_1.jpg │ ├── instantiate_asset_example.jpg │ ├── manage_projects_example_2.jpg │ ├── assetdatabase_project_folder.jpg │ ├── custom_external_asset_loader.jpg │ ├── import_source_addressable_1.jpg │ ├── import_source_addressable_2.jpg │ ├── import_source_addressable_3.jpg │ ├── import_source_addressable_3.png │ ├── inspector_configuration_window.jpg │ ├── use_zero_time_scale_in_edit_mode.jpg │ └── component_gameobject_material_editors.jpg ├── Misc └── MarkDownToHTML.md └── README.md /Docs/Images/window.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/window.jpg -------------------------------------------------------------------------------- /Docs/Images/build_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/build_all.png -------------------------------------------------------------------------------- /Docs/Images/inspector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/inspector.png -------------------------------------------------------------------------------- /Docs/Images/main_menu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/main_menu.jpg -------------------------------------------------------------------------------- /Docs/Images/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/overview.png -------------------------------------------------------------------------------- /Docs/Images/build_all_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/build_all_2.jpg -------------------------------------------------------------------------------- /Docs/Images/context_menu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/context_menu.jpg -------------------------------------------------------------------------------- /Docs/Images/hierarchy_root.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/hierarchy_root.jpg -------------------------------------------------------------------------------- /Docs/Images/my_component.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/my_component.jpg -------------------------------------------------------------------------------- /Docs/Images/override_theme.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/override_theme.jpg -------------------------------------------------------------------------------- /Docs/Images/override_tools.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/override_tools.jpg -------------------------------------------------------------------------------- /Docs/Images/trilib_loader.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/trilib_loader.jpg -------------------------------------------------------------------------------- /Docs/Images/universal_rp_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/universal_rp_1.jpg -------------------------------------------------------------------------------- /Docs/Images/universal_rp_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/universal_rp_2.jpg -------------------------------------------------------------------------------- /Docs/Images/universal_rp_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/universal_rp_3.jpg -------------------------------------------------------------------------------- /Docs/Images/builtin_windows.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/builtin_windows.jpg -------------------------------------------------------------------------------- /Docs/Images/custom_window_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/custom_window_1.jpg -------------------------------------------------------------------------------- /Docs/Images/custom_window_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/custom_window_2.jpg -------------------------------------------------------------------------------- /Docs/Images/custom_window_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/custom_window_3.jpg -------------------------------------------------------------------------------- /Docs/Images/example_scenes_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/example_scenes_1.jpg -------------------------------------------------------------------------------- /Docs/Images/example_scenes_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/example_scenes_2.jpg -------------------------------------------------------------------------------- /Docs/Images/expose_to_editor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/expose_to_editor.jpg -------------------------------------------------------------------------------- /Docs/Images/file_importers_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/file_importers_1.jpg -------------------------------------------------------------------------------- /Docs/Images/file_importers_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/file_importers_2.jpg -------------------------------------------------------------------------------- /Docs/Images/import_sources_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/import_sources_1.jpg -------------------------------------------------------------------------------- /Docs/Images/import_sources_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/import_sources_2.jpg -------------------------------------------------------------------------------- /Docs/Images/import_sources_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/import_sources_3.jpg -------------------------------------------------------------------------------- /Docs/Images/import_sources_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/import_sources_4.jpg -------------------------------------------------------------------------------- /Docs/Images/import_sources_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/import_sources_5.jpg -------------------------------------------------------------------------------- /Docs/Images/jint_scripting_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/jint_scripting_1.jpg -------------------------------------------------------------------------------- /Docs/Images/jint_scripting_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/jint_scripting_2.jpg -------------------------------------------------------------------------------- /Docs/Images/jint_scripting_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/jint_scripting_3.jpg -------------------------------------------------------------------------------- /Docs/Images/jint_scripting_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/jint_scripting_4.jpg -------------------------------------------------------------------------------- /Docs/Images/jint_scripting_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/jint_scripting_5.jpg -------------------------------------------------------------------------------- /Docs/Images/jint_scripting_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/jint_scripting_6.jpg -------------------------------------------------------------------------------- /Docs/Images/jint_scripting_7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/jint_scripting_7.jpg -------------------------------------------------------------------------------- /Docs/Images/jint_scripting_8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/jint_scripting_8.jpg -------------------------------------------------------------------------------- /Docs/Images/jint_scripting_9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/jint_scripting_9.jpg -------------------------------------------------------------------------------- /Docs/Images/property_editors.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/property_editors.jpg -------------------------------------------------------------------------------- /Docs/Images/compatibility_mode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/compatibility_mode.jpg -------------------------------------------------------------------------------- /Docs/Images/enumerators_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/enumerators_folder.png -------------------------------------------------------------------------------- /Docs/Images/extending_window_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/extending_window_1.jpg -------------------------------------------------------------------------------- /Docs/Images/generate_surrogate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/generate_surrogate.png -------------------------------------------------------------------------------- /Docs/Images/getting_started_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/getting_started_1.png -------------------------------------------------------------------------------- /Docs/Images/getting_started_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/getting_started_2.png -------------------------------------------------------------------------------- /Docs/Images/getting_started_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/getting_started_3.jpg -------------------------------------------------------------------------------- /Docs/Images/getting_started_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/getting_started_4.jpg -------------------------------------------------------------------------------- /Docs/Images/getting_started_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/getting_started_5.jpg -------------------------------------------------------------------------------- /Docs/Images/my_import_source_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/my_import_source_1.jpg -------------------------------------------------------------------------------- /Docs/Images/my_import_source_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/my_import_source_2.jpg -------------------------------------------------------------------------------- /Docs/Images/override_layout_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/override_layout_1.jpg -------------------------------------------------------------------------------- /Docs/Images/override_layout_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/override_layout_2.jpg -------------------------------------------------------------------------------- /Docs/Images/override_layout_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/override_layout_3.jpg -------------------------------------------------------------------------------- /Docs/Images/surrogates_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/surrogates_folder.png -------------------------------------------------------------------------------- /Docs/Images/modify_importsource_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/modify_importsource_1.jpg -------------------------------------------------------------------------------- /Docs/Images/modify_importsource_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/modify_importsource_2.jpg -------------------------------------------------------------------------------- /Docs/Images/modify_importsource_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/modify_importsource_3.jpg -------------------------------------------------------------------------------- /Docs/Images/override_scene_params.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/override_scene_params.jpg -------------------------------------------------------------------------------- /Docs/Images/persistent_data_path.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/persistent_data_path.jpg -------------------------------------------------------------------------------- /Docs/Images/manage_projects_example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/manage_projects_example.jpg -------------------------------------------------------------------------------- /Docs/Images/ui-scale-after-override.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/ui-scale-after-override.png -------------------------------------------------------------------------------- /Docs/Images/ui-scale-before-override.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/ui-scale-before-override.png -------------------------------------------------------------------------------- /Docs/Images/inspector_configuration_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/inspector_configuration_1.jpg -------------------------------------------------------------------------------- /Docs/Images/instantiate_asset_example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/instantiate_asset_example.jpg -------------------------------------------------------------------------------- /Docs/Images/manage_projects_example_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/manage_projects_example_2.jpg -------------------------------------------------------------------------------- /Docs/Images/assetdatabase_project_folder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/assetdatabase_project_folder.jpg -------------------------------------------------------------------------------- /Docs/Images/custom_external_asset_loader.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/custom_external_asset_loader.jpg -------------------------------------------------------------------------------- /Docs/Images/import_source_addressable_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/import_source_addressable_1.jpg -------------------------------------------------------------------------------- /Docs/Images/import_source_addressable_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/import_source_addressable_2.jpg -------------------------------------------------------------------------------- /Docs/Images/import_source_addressable_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/import_source_addressable_3.jpg -------------------------------------------------------------------------------- /Docs/Images/import_source_addressable_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/import_source_addressable_3.png -------------------------------------------------------------------------------- /Docs/Images/inspector_configuration_window.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/inspector_configuration_window.jpg -------------------------------------------------------------------------------- /Docs/Images/use_zero_time_scale_in_edit_mode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/use_zero_time_scale_in_edit_mode.jpg -------------------------------------------------------------------------------- /Docs/Images/component_gameobject_material_editors.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattlehubCode/RTE_Docs/HEAD/Docs/Images/component_gameobject_material_editors.jpg -------------------------------------------------------------------------------- /Misc/MarkDownToHTML.md: -------------------------------------------------------------------------------- 1 | 2 | # Repo 3 | 4 | https://github.com/Battlehub0x/markdown-to-html-github-style 5 | 6 | # Dependencies 7 | 8 | ``` 9 | npm install 10 | ``` 11 | 12 | # Usage 13 | ``` 14 | node convert.js MyPageTitle 15 | ``` 16 | 17 | > Note **Add ** move <style> to header 18 | 19 | 20 | [https://rteditor.battlehub.net/](#https://rteditor.battlehub.net/) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Runtime Editor for Unity 2 | 3 | Welcome to the [**Runtime Editor v.4.5.4**](https://assetstore.unity.com/packages/tools/modeling/runtime-editor-64806) documentation. This asset includes scripts and prefabs designed to help you create scene editors, game level editors, or your own modeling applications. 4 | 5 | [![Asset Store][youtube_icon]](https://assetstore.unity.com/packages/tools/modeling/runtime-editor-64806) 6 | 7 | > **Note** 8 | **Documentation for the previous versions can be found** [**here**](https://rteditor.battlehub.net/v350/). 9 | 10 | > **GPT Assistant can be found** [**here**](https://chatgpt.com/g/g-9Dws8Nfb2-runtime-editor-assistant) 11 | 12 | # Contents 13 | 14 | <!-- TOC start (generated with https://github.com/derlin/bitdowntoc) --> 15 | 16 | - [Introduction](#introduction) 17 | - [Getting Started](#getting-started) 18 | - [Example Scenes](#example-scenes) 19 | - [Compatibility Modes](#compatibility-modes) 20 | + [None](#none) 21 | + [AssetDatabaseOverRTSL](#assetdatabaseoverrtsl) 22 | + [LegacyRTSL](#legacyrtsl) 23 | - [RTSL to AssetDatabase Project Converter](#rtsl-to-assetdatabase-project-converter) 24 | - [Universal Render Pipeline Support](#universal-render-pipeline-support) 25 | - [HDRP Support](#hdrp-support) 26 | - [Common Infrastructure](#common-infrastructure) 27 | * [Overview](#overview) 28 | * [Expose To Editor](#expose-to-editor) 29 | * [IOC](#ioc) 30 | * [Runtime Selection](#runtime-selection) 31 | + [Properties](#properties) 32 | + [Events](#events) 33 | + [Methods](#methods) 34 | * [Runtime Objects](#runtime-objects) 35 | + [Events](#events-1) 36 | + [Methods](#methods-1) 37 | + [Example Usage](#example-usage) 38 | * [Runtime Tools](#runtime-tools) 39 | + [Example Usage](#example-usage-1) 40 | * [Runtime Undo](#runtime-undo) 41 | + [Example Usage](#example-usage-2) 42 | * [Drag And Drop](#drag-and-drop) 43 | + [Example Usage](#example-usage-3) 44 | * [Time Scale](#time-scale) 45 | - [Runtime Editor UI and Window System](#runtime-editor-ui-and-window-system) 46 | * [Overview](#overview-1) 47 | * [RuntimeWindow](#runtimewindow) 48 | + [Key Properties of `RuntimeWindow`](#key-properties-of-runtimewindow) 49 | + [Example Usage](#example-usage-4) 50 | * [Built In Windows](#built-in-windows) 51 | * [Window Manager](#window-manager) 52 | + [Properties and Methods](#properties-and-methods) 53 | - [Properties](#properties-1) 54 | - [Methods](#methods-2) 55 | + [Getting the Window Manager](#getting-the-window-manager) 56 | + [Showing a Message Box](#showing-a-message-box) 57 | + [Showing a Confirmation Dialog](#showing-a-confirmation-dialog) 58 | + [Activating a Window](#activating-a-window) 59 | + [Creating a Window](#creating-a-window) 60 | + [Creating a Dialog Window](#creating-a-dialog-window) 61 | * [Main and Context Menu](#main-and-context-menu) 62 | + [Extending the Main Menu](#extending-the-main-menu) 63 | + [Instance Methods as Menu Commands](#instance-methods-as-menu-commands) 64 | + [MenuDefinition with the sceneName parameter](#menudefinition-with-the-scenename-parameter) 65 | + [Opening a Context Menu with Custom Commands](#opening-a-context-menu-with-custom-commands) 66 | - [Events](#events-2) 67 | - [Methods](#methods-3) 68 | * [Editor Extension](#editor-extension) 69 | + [Methods](#methods-4) 70 | + [Example](#example) 71 | + [Additional Convenience Extension Base Classes](#additional-convenience-extension-base-classes) 72 | - [LayoutExtension](#layoutextension) 73 | * [Methods](#methods-5) 74 | * [Example](#example-1) 75 | - [RuntimeWindowExtension](#runtimewindowextension) 76 | * [Methods](#methods-6) 77 | * [Example](#example-2) 78 | - [SceneComponentExtension](#scenecomponentextension) 79 | * [Methods](#methods-7) 80 | * [Example](#example-3) 81 | * [Window](#window) 82 | - [View-ViewModel-ViewBinding Architecture](#view-viewmodel-viewbinding-architecture) 83 | + [UnityWeld](#unityweld) 84 | + [View](#view) 85 | - [Key Properties](#key-properties) 86 | - [Key Events](#key-events) 87 | - [Key Methods](#key-methods) 88 | - [Utility Methods](#utility-methods) 89 | - [Example Usage](#example-usage-5) 90 | + [ViewModel](#viewmodel) 91 | - [Key Properties](#key-properties-1) 92 | - [Key Methods](#key-methods-1) 93 | - [Example Usage](#example-usage-6) 94 | + [HierarchicalDataViewModel ](#hierarchicaldataviewmodel) 95 | - [Key Properties](#key-properties-2) 96 | - [Key Events](#key-events-1) 97 | - [Key Methods](#key-methods-2) 98 | - [Context Menu Handling](#context-menu-handling) 99 | - [IHierarchicalData Implementation](#ihierarchicaldata-implementation) 100 | - [Bound UnityEvent Handlers](#bound-unityevent-handlers) 101 | - [Example Usage](#example-usage-7) 102 | * [Custom windows ](#custom-windows) 103 | * [Extending Existing Windows](#extending-existing-windows) 104 | + [1. Override the Window Prefab](#1-override-the-window-prefab) 105 | + [2. Override Programmatically Using Layout Extension](#2-override-programmatically-using-layout-extension) 106 | + [3. Override ViewModel or View Using RuntimeWindowExtension and ReplaceWith Method](#3-override-viewmodel-or-view-using-runtimewindowextension-and-replacewith-method) 107 | * [Overriding the Default Layout](#overriding-the-default-layout) 108 | + [Example](#example-4) 109 | + [LayoutInfo Methods](#layoutinfo-methods) 110 | - [Vertical Split](#vertical-split) 111 | - [Horizontal Split](#horizontal-split) 112 | - [Tab Group](#tab-group) 113 | - [Vertical Split Example](#vertical-split-example) 114 | - [Horizontal Split Example](#horizontal-split-example) 115 | - [Tab Group Example](#tab-group-example) 116 | * [Overriding Scene Parameters](#overriding-scene-parameters) 117 | + [Example](#example-5) 118 | + [Explanation](#explanation) 119 | * [Overriding Tools Panel](#overriding-tools-panel) 120 | * [Setting ui scale](#setting-ui-scale) 121 | * [Overriding UI Scale](#overriding-ui-scale) 122 | + [Example Script](#example-script) 123 | - [Before UI Scale Override](#before-ui-scale-override) 124 | - [After UI Scale Override](#after-ui-scale-override) 125 | * [Overriding the Theme](#overriding-the-theme) 126 | + [Example Script](#example-script-1) 127 | * [Inspector View](#inspector-view) 128 | + [Inspector Editors](#inspector-editors) 129 | + [Property Editors](#property-editors) 130 | * [Inspector Configuration](#inspector-configuration) 131 | + [Register Editors Programatically](#register-editors-programatically) 132 | + [Steps to Register Property Editors Programmatically](#steps-to-register-property-editors-programmatically) 133 | + [Component Properties Visibility](#component-properties-visibility) 134 | + [Custom Type Properties Visibility](#custom-type-properties-visibility) 135 | + [Customizing Component Editor Header](#customizing-component-editor-header) 136 | + [HeaderDescriptor Structure](#headerdescriptor-structure) 137 | * [Localization](#localization) 138 | + [Built-in String Resources](#built-in-string-resources) 139 | - [UI Controls](#ui-controls) 140 | * [Dock Panel Control](#dock-panel-control) 141 | * [Tree View Control](#tree-view-control) 142 | * [Menu Control](#menu-control) 143 | - [Runtime Transform Handles](#runtime-transform-handles) 144 | * [Runtime Gizmos](#runtime-gizmos) 145 | - [Animation Editor ](#animation-editor) 146 | - [Runtime Editor Extensions](#runtime-editor-extensions) 147 | - [Runtime Scripting](#runtime-scripting) 148 | * [Getting Started with Runtime Scripting](#getting-started-with-runtime-scripting) 149 | * [Jint Script](#jint-script) 150 | * [Declaring Variables](#declaring-variables) 151 | * [Using UnityEngine API](#using-unityengine-api) 152 | * [JintComponent.Add, JintComponent.Get](#jintcomponentadd-jintcomponentget) 153 | * [Create Jint Script Programmatically](#create-jint-script-programmatically) 154 | * [More Runtime Scripting Samples](#more-runtime-scripting-samples) 155 | - [Asset Database](#asset-database) 156 | * [IRuntimeEditor and IAssetDatabaseModel Interfaces](#iruntimeeditor-and-iassetdatabasemodel-interfaces) 157 | + [IRuntimeEditor Interface](#iruntimeeditor-interface) 158 | + [IAssetDatabaseModel Interface](#iassetdatabasemodel-interface) 159 | + [Key Methods and Properties](#key-methods-and-properties) 160 | - [IRuntimeEditor](#iruntimeeditor) 161 | - [IAssetDatabaseModel](#iassetdatabasemodel) 162 | * [IAssetDatabaseModel vs IRuntimeEditor Interface](#iassetdatabasemodel-vs-iruntimeeditor-interface) 163 | * [Projects Root Folder Path](#projects-root-folder-path) 164 | * [Manage Projects](#manage-projects) 165 | * [Import Export Project](#import-export-project) 166 | * [Create Folder](#create-folder) 167 | * [Set Current Folder](#set-current-folder) 168 | * [Runtime Scene](#runtime-scene) 169 | * [Create New, Save, Load scene](#create-new-save-load-scene) 170 | * [Create, Save, Load, Delete Assets and Folders](#create-save-load-delete-assets-and-folders) 171 | * [Instantiate asset](#instantiate-asset) 172 | * [Duplicate Asset](#duplicate-asset) 173 | * [Move Asset](#move-asset) 174 | * [Import Export Assets](#import-export-assets) 175 | * [External Asset Loaders](#external-asset-loaders) 176 | + [TriLibLoader Example](#trilibloader-example) 177 | - [Implement IExternalAssetLoaderModel](#implement-iexternalassetloadermodel) 178 | - [Register TriLibLoader](#register-trilibloader) 179 | - [Import FBX as External Asset](#import-fbx-as-external-asset) 180 | * [Import Sources](#import-sources) 181 | + [Resources Import Source](#resources-import-source) 182 | + [Addressables Import Source](#addressables-import-source) 183 | + [Custom Import Source](#custom-import-source) 184 | * [File Importers](#file-importers) 185 | + [Custom File Importer](#custom-file-importer) 186 | * [Web Storage Sample](#web-storage-sample) 187 | * [Serializer Extensions](#serializer-extensions) 188 | * [Enumerators](#enumerators) 189 | * [Dynamic Surrogates (Preview)](#dynamic-surrogates-preview) 190 | * [Build All](#build-all) 191 | * [Support](#support) 192 | 193 | <!-- TOC end --> 194 | 195 | # Introduction 196 | The Runtime Editor consists of several major parts, effectively decoupled using MVVM and dependency injection mechanisms: 197 | - **Common Infrastructure APIs**: Includes Selection, Tools, IOC, and Drag & Drop. 198 | - **UI Controls**: DockPanel, TreeView, Main Menu, and Context Menu. 199 | - **WindowManager**: Provides built-in windows and dialogs like Inspector, Scene, Hierarchy, Project, and Console. 200 | - **Transform Handles & Gizmos**: Facilitates scene controls and navigation. 201 | - **Runtime Asset Database**: Manages assets, scenes, and prefabs. 202 | - **Extensions**: Includes Terrain Editor, Animation Editor, ProBuilder Editor, and Runtime Scripting. 203 | 204 | # Getting Started 205 | 1. Create a new scene. 206 | 2. Click **Tools -> Runtime Editor -> Create RTEditor**. 207 | ![Create RuntimeEditor][getting_started_1] 208 | 3. Optionally, click **Tools -> Runtime Editor -> Create RTExtensions**. 209 | 4. Click "Yes" when prompted to add the Game View Camera script. 210 | ![Create Game View Camera Script][getting_started_2] 211 | 5. Create several Game Objects and add the [Expose To Editor](#expose-to-editor) component. 212 | ![Add Expose To Editor][getting_started_3] 213 | 6. Move these game objects to the Scene game object children. 214 | ![Scene Children][getting_started_4] 215 | 7. Press "Play". 216 | ![Getting Started Result][getting_started_5] 217 | 218 | > **Note** 219 | > In Unity 6.2 and higher, make sure to switch **Active Input Handling** to **Both**. 220 | > You can do this from the Project Settings window: 221 | > **Edit > Project Settings > Player > Other Settings > Configuration > Active Input Handling** 222 | 223 | # Example Scenes 224 | There are various example scenes available. To access them: 225 | 226 | 1. Unpack "Assets/Battlehub/**2 RTEditor Demo.unitypackage**". 227 | ![Unpack 2 RTEditor Demo][example_scenes_1] 228 | 2. Click **Tools -> Runtime Editor -> Show me examples**. 229 | ![Show Me Examples][example_scenes_2] 230 | 231 | > **Note** 232 | If you are using Universal or HDRP, when you open the demo scene, also follow the steps in [Universal Render Pipeline Support](#universal-render-pipeline-support) or [HDRP Support](#hdrp-support). 233 | 234 | # Compatibility Modes 235 | In Runtime Editor 4.x.x, the [Runtime Asset Database](https://github.com/Battlehub0x/RuntimeAssetDatabase) has replaced the [Runtime Save Load](https://rteditor.battlehub.net/v350/manual/save-load.html) subsystem, which is now considered legacy. By default, Runtime Editor cannot open projects created using RTSL. However, there are two compatibility modes, **AssetDatabaseOverRTSL** and **LegacyRTSL**, 236 | which allow you to open and work with projects created before RTE 4.x.x. 237 | 238 | > **Note** 239 | Before selecting compatibility mode, extract **Assets/Battlehub/LegacyRTSL.unitypackage** 240 | 241 | To select a compatibility mode, use the **Compatibility Mode** dropdown in the Runtime Editor component editor. 242 | ![Compatibility Mode][compatibility_mode] 243 | 244 | ### None 245 | In this mode, users will not be able to open legacy RTSL projects. The Project window is replaced with the AssetDatabase window, the Save Scene Dialog and Save Asset Dialog are replaced with the AssetDatabaseSave dialog, and the Select Object Dialog is replaced with the AssetDatabaseSelect dialog. Additional compatibility prefabs like RTSLDeps and AssetDatabaseOverRTSL, along with corresponding compatibility scripts, are not created. 246 | 247 | ### AssetDatabaseOverRTSL 248 | In this mode, users can open both new Asset Database projects and legacy RTSL projects. The Project window is replaced with the AssetDatabase window, the Save Scene Dialog and Save Asset Dialog are replaced with the AssetDatabaseSave dialog, and the Select Object Dialog is replaced with the AssetDatabaseSelect dialog. Old IProjectAsync APIs are available, but corresponding functions must be called using the IRuntimeEditor or IAssetDatabaseModel interface, which is implemented by the AssetDatabaseOverRTSL class. 249 | 250 | ### LegacyRTSL 251 | In this mode, users can open only legacy RTSL projects. All legacy windows and APIs, such as IProjectAsync, are available. While it is possible to use some new methods of the IRuntimeEditor interface and IAssetDatabase interface, some of them might throw exceptions. This mode is not recommended. 252 | 253 | # RTSL to AssetDatabase Project Converter 254 | 255 | An example of a project converter from the old RTSL format to the new AssetDatabase format can be found in [Assets\Battlehub\RTEditorDemo\Examples\RTSL to AssetDatabase Project Converter.unity](#example-scenes). 256 | 257 | > **Note** 258 | To access this example, first extract **Assets/Battlehub/LegacyRTSL.unitypackage** 259 | 260 | The converter is implemented in the `RTSLProjectConverter` class. It takes the RTSL project path and the new AssetDatabase project path as inputs. The converter performs the following steps: 261 | 262 | 1. Copies the folder structure. 263 | 2. Converts assets like materials, meshes, etc. 264 | 3. Converts prefabs. 265 | 4. Converts scenes. 266 | 267 | # Universal Render Pipeline Support 268 | 1. Unpack "Assets/Battlehub/**1 UniversalRP Support.unitypackage**". 269 | ![Unpack 1 Universal RP][universal_rp_1] 270 | 2. Click "Install/Upgrade" dependencies to install the required Universal Render Pipeline package. 271 | 3. Click **Tools -> Runtime Editor -> Use URP RenderPipelineAsset**. 272 | 4. Click **Tools -> Runtime Editor -> Add URP support for RTEditor**. 273 | 5. If you are using Runtime Editor Extensions (RTExtensions prefab), click **Tools -> Runtime Editor -> Add URP support for RTExtensions**. 274 | ![Add Required Prefabs][universal_rp_2] 275 | 6. In Project Settings / Player, switch the color space to **Linear**. 276 | ![Linear Color Space][universal_rp_3] 277 | 278 | > **Note** 279 | > If the scene window will always be docked, set `RenderPipelineInfo.UserForegroundLayerForUI = false`. See the [MinimalLayoutExample script in Scene1 and Scene2](#example-scenes) for details. 280 | 281 | # HDRP Support 282 | The procedure is the same as for [URP Support](#universal-render-pipeline-support), except you need to unpack Assets/Battlehub/**6 HDRP Support.unitypackage** and use the corresponding **HDRP** menu items. 283 | 284 | # Common Infrastructure 285 | ## Overview 286 | Common infrastructure classes and interfaces form the core API of the Runtime Editor. This includes selection, object lifecycle, tools, undo-redo, and drag-and-drop APIs. 287 | 288 | ## Expose To Editor 289 | Add the Assets/Battlehub/RTEditor/Runtime/RTCommon/**ExposeToEditor** component to any Game Object you want to make available for selection and editing. 290 | 291 | ![Expose To Editor][expose_to_editor] 292 | 293 | 1. **Selection Events**: 294 | - **Selected (ExposeToEditor)** 295 | - **Unselected (ExposeToEditor)** 296 | 297 | 2. **Bounds Configuration**: 298 | - **Bounds Object**: Specifies the GameObject to which the collider will be added (if AddColliders == false) 299 | - **Bounds Type**: Determines the type of bounds to be used. 300 | - **Custom Bounds**: If Bounds Type is set to Custom, it allows for specifying custom bounds with center and extents. 301 | 302 | 3. **Capabilities**: 303 | - **Can Transform**, **Can Inspect**, **Can Duplicate**, **Can Delete**, **Can Rename**, **Can Create Prefab**, **Show Selection Gizmo**: These options are self-explanatory. 304 | 305 | 4. **Collider Management**: 306 | - **Add Colliders**: You can unselect the Add Colliders option if you want to specify the collider to be used for selection yourself. If this option is selected, a MeshCollider will be added automatically.. 307 | 308 | ## IOC 309 | Assets/Battlehub/RTEditor/Runtime/RTCommon/Utils/**IOC.cs** is a simple IoC container implementation. 310 | 311 | **Methods**: 312 | 313 | - `static T Resolve<T>()`: Resolve a dependency of type 'T'. 314 | - `static void Register<T>(Func<T> func)`: Register a construction function. 315 | - `static void Register<T>(T instance)`: Register an instance. 316 | - `static void Unregister<T>(Func<T> func)`: Unregister a construction function. 317 | - `static void Unregister<T>(T instance)`: Unregister an instance. 318 | 319 | **Example 1**: 320 | 321 | ```csharp 322 | using UnityEngine; 323 | using Battlehub.RTCommon; 324 | 325 | public interface IDependency { } 326 | 327 | public class Dependency : MonoBehaviour, IDependency 328 | { 329 | void Awake() 330 | { 331 | IOC.Register<IDependency>(this); 332 | } 333 | 334 | void OnDestroy() 335 | { 336 | IOC.Unregister<IDependency>(this); 337 | } 338 | } 339 | 340 | public class User : MonoBehaviour 341 | { 342 | void Start() 343 | { 344 | IDependency dependency = IOC.Resolve<IDependency>(); 345 | } 346 | } 347 | ``` 348 | 349 | **Example 2**: 350 | 351 | ```csharp 352 | using UnityEngine; 353 | using Battlehub.RTCommon; 354 | 355 | [DefaultExecutionOrder(-1)] 356 | public class Registrar : MonoBehaviour 357 | { 358 | void Awake() 359 | { 360 | IOC.Register<IDependency>(() => 361 | { 362 | GameObject go = new GameObject(); 363 | return go.AddComponent<Dependency>(); 364 | }); 365 | } 366 | 367 | private void OnDestroy() 368 | { 369 | IOC.Unregister<IDependency>(); 370 | } 371 | } 372 | 373 | public interface IDependency { } 374 | 375 | public class Dependency : MonoBehaviour, IDependency 376 | { 377 | } 378 | 379 | public class User : MonoBehaviour 380 | { 381 | void Awake() 382 | { 383 | IDependency dependency = IOC.Resolve<IDependency>(); 384 | } 385 | } 386 | ``` 387 | 388 | ## Runtime Selection 389 | 390 | The `IRuntimeSelection` interface is a key part of the Runtime Editor's API, providing functionality to manage and interact with the selection of objects within the editor. Here is a detailed description of its properties and methods: 391 | 392 | ### Properties 393 | - **Enabled**: Gets or sets a value indicating whether the selection functionality is enabled. 394 | - **EnableUndo**: Gets or sets a value indicating whether undo functionality is enabled for selection changes. 395 | - **activeGameObject**: Gets or sets the currently active selected GameObject. 396 | - **activeObject**: Gets or sets the currently active selected object. 397 | - **objects**: Gets or sets the array of currently selected objects. 398 | - **gameObjects**: Gets an array of the currently selected GameObjects. 399 | - **activeTransform**: Gets the Transform of the currently active selected GameObject. 400 | - **Length**: Gets the number of currently selected objects. 401 | 402 | ### Events 403 | - **SelectionChanged**: An event that is triggered when the selection changes. Subscribers to this event can perform actions in response to selection changes. 404 | 405 | ### Methods 406 | - **IsSelected(Object obj)**: Determines whether a given object is currently selected. 407 | - **Select(Object activeObject, Object[] selection)**: Selects the specified objects, with the specified object set as the active object. 408 | 409 | This interface allows for robust selection management within the Runtime Editor, supporting multiple selection states, undo functionality, and event-driven responses to selection changes. 410 | 411 | ```csharp 412 | using UnityEngine; 413 | using Battlehub.RTCommon; 414 | 415 | public class RuntimeSelectionExample : MonoBehaviour 416 | { 417 | IRuntimeSelection m_selection; 418 | 419 | void Start() 420 | { 421 | var editor = IOC.Resolve<IRTE>(); 422 | m_selection = editor.Selection; 423 | 424 | // Subscribe to the selection changed event 425 | m_selection.SelectionChanged += OnSelectionChanged; 426 | 427 | // Create a sample game object 428 | var go = GameObject.CreatePrimitive(PrimitiveType.Capsule); 429 | go.AddComponent<ExposeToEditor>(); 430 | 431 | // Add object to the scene 432 | editor.AddGameObjectToHierarchy(go); 433 | 434 | // Select the game object 435 | m_selection.activeObject = go; 436 | } 437 | 438 | void OnDestroy() 439 | { 440 | if (m_selection != null) 441 | { 442 | m_selection.SelectionChanged -= OnSelectionChanged; 443 | } 444 | } 445 | 446 | void OnSelectionChanged(Object[] unselectedObjects) 447 | { 448 | if (unselectedObjects != null) 449 | { 450 | for (int i = 0; i < unselectedObjects.Length; ++i) 451 | { 452 | Object unselected = unselectedObjects[i]; 453 | Debug.Log("Unselected: " + unselected.name); 454 | } 455 | } 456 | 457 | if (m_selection.objects != null) 458 | { 459 | for (int i = 0; i < m_selection.objects.Length; ++i) 460 | { 461 | Object selected = m_selection.objects[i]; 462 | Debug.Log("Selected: " + selected.name); 463 | } 464 | } 465 | } 466 | } 467 | ``` 468 | 469 | > **Note** <br/> 470 | For more examples, see **Scene20 - Selection** in the [Example Scenes section](#example-scenes). 471 | 472 | 473 | ## Runtime Objects 474 | The `IRuntimeObjects` interface is designed to provide events and methods for managing and responding to changes in runtime objects within the Runtime Editor environment. 475 | This interface enables the monitoring of various object lifecycle events and component-related changes. 476 | 477 | ### Events 478 | - **Awaked**: Triggered when an object awakens (i.e., when its Awake method is called). 479 | - **Started**: Triggered when an object starts (i.e., when its Start method is called). 480 | - **Enabled**: Triggered when an object is enabled. 481 | - **Disabled**: Triggered when an object is disabled. 482 | - **Destroying**: Triggered when an object is about to be destroyed. 483 | - **Destroyed**: Triggered when an object has been destroyed. 484 | - **MarkAsDestroyedChanging**: Triggered when an object's "mark as destroyed" status is about to change. 485 | - **MarkAsDestroyedChanged**: Triggered when an object's "mark as destroyed" status has changed. 486 | - **TransformChanged**: Triggered when an object's transform has changed. 487 | - **NameChanged**: Triggered when an object's name has changed. 488 | - **ParentChanged**: Triggered when an object's parent has changed. 489 | - **ComponentAdded**: Triggered when a component is added to an object. 490 | - **ComponentDestroyed**: Triggered when a component is removed from an object. 491 | - **ReloadComponentEditor**: Triggered when a component editor needs to be reloaded, with a boolean parameter indicating whether the component was just added or is being reloaded. 492 | 493 | ### Methods 494 | - **IEnumerable<ExposeToEditor> Get(bool rootsOnly, bool useCache = true)**: Retrieves a collection of objects exposed to the editor. The `rootsOnly` parameter specifies whether to include only root objects, and `useCache` indicates whether to use cache instead of Unity Engine API calls to find objects. 495 | 496 | ### Example Usage 497 | 498 | ```csharp 499 | using UnityEngine; 500 | using Battlehub.RTCommon; 501 | 502 | public class ListenAwakeEventExample : MonoBehaviour 503 | { 504 | IRuntimeObjects m_object; 505 | 506 | void Start() 507 | { 508 | m_object = IOC.Resolve<IRTE>().Object; 509 | m_object.Awaked += OnObjectAwaked; 510 | 511 | GameObject go = new GameObject(); 512 | go.AddComponent<ExposeToEditor>(); 513 | } 514 | 515 | void OnDestroy() 516 | { 517 | if (m_object != null) 518 | { 519 | m_object.Awaked -= OnObjectAwaked; 520 | } 521 | } 522 | 523 | void OnObjectAwaked(ExposeToEditor obj) 524 | { 525 | Debug.Log("Awake: " + obj); 526 | } 527 | } 528 | ``` 529 | 530 | > **Note** <br/> 531 | For more examples, see **Scene24 - Object Lifecycle events** in the [Example Scenes section](#example-scenes). 532 | 533 | ## Runtime Tools 534 | 535 | The RuntimeTools class allows you to track and manipulate the current tool, locking state, pivot modes, and other aspects of the editor's toolset. 536 | 537 | **Properties**: 538 | 539 | - **LockObject LockAxes**: Holds the aggregated locking state of selected objects. 540 | - **UnityObject ActiveTool**: Reference to the active transform handle or gizmo. Active means that the user is currently interacting with it. 541 | - **RuntimeTool Current**: The tool that is currently enabled for the Scene View: 542 | - None 543 | - Move 544 | - Rotate 545 | - Scale 546 | - View 547 | - Rect 548 | - Custom 549 | - **RuntimePivotMode PivotMode**: Indicates whether we are in Center or Pivot mode. 550 | - **RuntimePivotRotation PivotRotation**: Specifies the rotation of the tool handle: 551 | - Global 552 | - Local 553 | 554 | **Events**: 555 | 556 | - **event RuntimeToolsEvent ToolChanged**: Raised when the current tool changes. 557 | - **event RuntimeToolsEvent PivotRotationChanged**: Raised when the pivot rotation changes. 558 | - **event RuntimeToolsEvent PivotModeChanged**: Raised when the pivot mode changes. 559 | - **event RuntimeToolsEvent LockAxesChanged**: Raised when the lock axes change. 560 | 561 | **Additional Properties and Events**: 562 | 563 | - **IsViewing**: Indicates whether the editor is in viewing mode. Changing this property raises the `IsViewingChanged` event. 564 | - **AutoFocus**: Indicates whether auto-focus is enabled. Changing this property raises the `AutoFocusChanged` event. 565 | - **UnitSnapping**: Indicates whether unit snapping is enabled. Changing this property raises the `UnitSnappingChanged` event. 566 | - **IsSnapping**: Indicates whether snapping is enabled. Changing this property raises the `IsSnappingChanged` event. 567 | - **SnappingMode**: Specifies the snapping mode. Changing this property raises the `SnappingModeChanged` event. 568 | - **CustomPivotPosition**: Specifies a custom pivot position. 569 | - **SelectionMode**: Specifies the selection mode. Changing this property raises the `SelectionModeChanged` event. 570 | - **IsBoxSelectionEnabled**: Indicates whether box selection is enabled. Changing this property raises the `IsBoxSelectionEnabledChanged` event. 571 | 572 | ### Example Usage 573 | 574 | **Changing the Current Tool**: 575 | 576 | ```csharp 577 | using UnityEngine; 578 | using Battlehub.RTCommon; 579 | 580 | public class SwitchToolBehaviour : MonoBehaviour 581 | { 582 | void Start() 583 | { 584 | IRTE editor = IOC.Resolve<IRTE>(); 585 | editor.Tools.Current = RuntimeTool.Rotate; 586 | } 587 | } 588 | ``` 589 | 590 | **Locking Axes for All Selected Objects**: 591 | 592 | ```csharp 593 | using UnityEngine; 594 | using Battlehub.RTCommon; 595 | 596 | public class LockAxesForAllObjects : MonoBehaviour 597 | { 598 | IRTE m_editor; 599 | void Start() 600 | { 601 | m_editor = IOC.Resolve<IRTE>(); 602 | m_editor.Selection.SelectionChanged += OnSelectionChanged; 603 | m_editor.Tools.ToolChanged += OnToolChanged; 604 | } 605 | 606 | void OnDestroy() 607 | { 608 | if(m_editor != null) 609 | { 610 | m_editor.Selection.SelectionChanged -= OnSelectionChanged; 611 | m_editor.Tools.ToolChanged -= OnToolChanged; 612 | } 613 | } 614 | 615 | void OnToolChanged() 616 | { 617 | Lock(); 618 | } 619 | 620 | void OnSelectionChanged(Object[] unselectedObjects) 621 | { 622 | Lock(); 623 | } 624 | 625 | static void Lock() 626 | { 627 | IRTE editor = IOC.Resolve<IRTE>(); 628 | editor.Tools.LockAxes = new LockObject 629 | { 630 | PositionY = true, 631 | RotationX = true, 632 | RotationZ = true 633 | }; 634 | } 635 | } 636 | ``` 637 | 638 | > **Note** <br/> 639 | For more examples, see **Scene21 - Tools** in the [Example Scenes section](#example-scenes). 640 | 641 | 642 | ## Runtime Undo 643 | 644 | The `IRuntimeUndo` interface is used to record changes, maintain the undo/redo stack, and perform undo and redo operations. 645 | 646 | **Properties**: 647 | 648 | - **bool Enabled { get; set; }**: Indicates whether undo and redo operations are enabled. 649 | - **bool CanUndo { get; }**: Indicates whether an undo operation can be performed. 650 | - **bool CanRedo { get; }**: Indicates whether a redo operation can be performed. 651 | - **bool IsRecording { get; }**: Indicates whether multiple changes are being recorded. 652 | 653 | **Methods**: 654 | 655 | - **void BeginRecord()**: Begins recording multiple changes. 656 | - **void EndRecord()**: Ends recording multiple changes. 657 | - **void GroupRecords(int count = -1)**: Groups the specified number of records into a single undo/redo operation. 658 | - **Record CreateRecord(UndoRedoCallback redoCallback, UndoRedoCallback undoCallback, PurgeCallback purgeCallback = null, EraseReferenceCallback eraseCallback = null)**: Creates a generic record. 659 | - **Record CreateRecord(object target, object newState, object oldState, UndoRedoCallback redoCallback, UndoRedoCallback undoCallback, PurgeCallback purgeCallback = null, EraseReferenceCallback eraseCallback = null)**: Creates a record with a specific target, new state, and old state. 660 | - **void RegisterCreatedObjects(ExposeToEditor[] createdObjects, Action afterRedo = null, Action afterUndo = null)**: Registers created objects. 661 | - **void DestroyObjects(ExposeToEditor[] destroyedObjects, Action afterRedo = null, Action afterUndo = null)**: Registers destroy objects operation. 662 | - **void BeginRecordValue(object target, MemberInfo memberInfo)**: Begins recording a value change. 663 | - **void BeginRecordValue(object target, object accessor, MemberInfo memberInfo)**: Begins recording a value change with accessor. 664 | - **void EndRecordValue(object target, MemberInfo memberInfo, Action afterRedo = null, Action afterUndo = null)**: Ends recording a value change. 665 | - **void EndRecordValue(object target, object accessor, MemberInfo memberInfo, Action<object, object> targetErased = null, Action afterRedo = null, Action afterUndo = null)**: Ends recording a value change with accessor. 666 | - **void BeginRecordTransform(Transform target, Action<Transform> afterUndo = null)**: Begins recording a transform change. 667 | - **void EndRecordTransform(Transform target, Action<Transform> afterRedo = null)**: Ends recording a transform change. 668 | - **void BeginRecordTransform(Transform target, Transform parent, int siblingIndex = -1, Action<Transform> afterUndo = null)**: Begins recording a transform change with parent and sibling index. 669 | - **void EndRecordTransform(Transform target, Transform parent, int siblingIndex = -1, Action<Transform> afterRedo = null)**: Ends recording a transform change with parent and sibling index. 670 | - **void AddComponent(ExposeToEditor obj, Type type)**: Adds a component and pushes the corresponding record to the stack. 671 | - **void AddComponentWithRequirements(ExposeToEditor obj, Type type)**: Adds a component along with its required components. 672 | - **void DestroyComponent(Component destroy, MemberInfo[] memberInfo)**: Destroys a component and pushes the corresponding record to the stack. 673 | - **void Redo()**: Performs a redo operation. 674 | - **void Undo()**: Performs an undo operation. 675 | - **void Purge()**: Purges all records. All “marked as destroyed” objects will be destroyed. 676 | - **void Erase(object oldRef, object newRef = null, bool ignoreLock = false)**: Replaces oldRef with newRef for all records in the stack. 677 | - **void Store()**: Creates a new stack and stores the current undo and redo stack. 678 | - **void Restore()**: Restores a previously stored stack. 679 | 680 | **Events**: 681 | 682 | - **event RuntimeUndoEventHandler BeforeUndo**: Raised before an undo operation. 683 | - **event RuntimeUndoEventHandler UndoCompleted**: Raised after an undo operation. 684 | - **event RuntimeUndoEventHandler BeforeRedo**: Raised before a redo operation. 685 | - **event RuntimeUndoEventHandler RedoCompleted**: Raised after a redo operation. 686 | - **event RuntimeUndoEventHandler StateChanged**: Raised whenever one of the following operations is performed: 687 | - Store 688 | - Restore 689 | - Purge 690 | 691 | ### Example Usage 692 | 693 | **Recording a Value and then Undoing Changes**: 694 | 695 | ```csharp 696 | using UnityEngine; 697 | using System.Reflection; 698 | using Battlehub.RTCommon; 699 | using Battlehub.Utils; 700 | 701 | public class RecordValueThenUndoChanges : MonoBehaviour 702 | { 703 | IRuntimeUndo m_undo; 704 | 705 | [SerializeField] 706 | int m_value = 1; 707 | 708 | void Start() 709 | { 710 | m_undo = IOC.Resolve<IRTE>().Undo; 711 | m_undo.UndoCompleted += OnUndoCompleted; 712 | 713 | var valueInfo = Strong.MemberInfo((RecordValueThenUndoChanges x) => x.m_value); 714 | 715 | m_undo.BeginRecordValue(this, valueInfo); 716 | m_value = 2; 717 | m_undo.EndRecordValue(this, valueInfo); 718 | 719 | m_undo.Undo(); 720 | } 721 | 722 | void OnDestroy() 723 | { 724 | if (m_undo != null) 725 | { 726 | m_undo.UndoCompleted -= OnUndoCompleted; 727 | } 728 | } 729 | 730 | void OnUndoCompleted() 731 | { 732 | Debug.Log(m_value); // 1 733 | } 734 | } 735 | ``` 736 | > **Note** <br/> 737 | For more examples, see **Scene25 - Undo & Redo** in the [Example Scenes section](#example-scenes). 738 | 739 | ## Drag And Drop 740 | The `IDragDrop` interface is used as a common interface for all drag-and-drop operations. 741 | 742 | **Properties**: 743 | 744 | - **object[] DragObjects { get; }**: Objects being dragged. 745 | - **object Source { get; }**: Drag-and-drop operation source object. 746 | - **bool InProgress { get; }**: Indicates if a drag-and-drop operation is in progress. 747 | 748 | **Methods**: 749 | 750 | - **void Reset()**: Cancels the current drag-and-drop operation. 751 | - **void SetCursor(KnownCursor cursorType)**: Sets the cursor type. 752 | - KnownCursor.DropNotAllowed 753 | - KnownCursor.DropAllowed 754 | - **void RaiseBeginDrag(object source, object[] dragItems, PointerEventData pointerEventData)**: Begins a drag-and-drop operation. 755 | - **void RaiseDrag(PointerEventData eventData)**: Handles the drag operation. 756 | - **void RaiseDrop(PointerEventData pointerEventData)**: Ends the drag-and-drop operation. 757 | 758 | **Events**: 759 | 760 | - **event DragDropEventHander BeginDrag**: Raised by the `RaiseBeginDrag` method. 761 | - **event DragDropEventHander Drag**: Raised by the `RaiseDrag` method. 762 | - **event DragDropEventHander Drop**: Raised by the `RaiseDrop` method. 763 | 764 | ### Example Usage 765 | 766 | In this example, we will handle a drag-and-drop operation into the console window (Add this script to the scene and try to drag and drop something from the Hierarchy or Project to the Console) 767 | 768 | ```csharp 769 | using Battlehub.RTCommon; 770 | using UnityEngine; 771 | using UnityEngine.EventSystems; 772 | 773 | public class ConsoleDragDropHandler : MonoBehaviour 774 | { 775 | IDragDrop m_dragDrop; 776 | RuntimeWindow m_target; 777 | 778 | void Start() 779 | { 780 | m_target = IOC.Resolve<IRTE>().GetWindow(RuntimeWindowType.Console); 781 | m_dragDrop = IOC.Resolve<IRTE>().DragDrop; 782 | m_dragDrop.Drop += OnDrop; 783 | } 784 | 785 | void OnDestroy() 786 | { 787 | if(m_dragDrop != null) 788 | { 789 | m_dragDrop.Drop -= OnDrop; 790 | } 791 | } 792 | 793 | void OnDrop(PointerEventData pointerEventData) 794 | { 795 | if(m_target != null && m_target.IsPointerOver) 796 | { 797 | Debug.Log(m_dragDrop.DragObjects[0]); 798 | } 799 | } 800 | } 801 | ``` 802 | 803 | > **Note** <br/> 804 | For more examples, see **Scene11 - Handling Drag and Drop** in the [Example Scenes section](#example-scenes). 805 | 806 | 807 | ## Time Scale 808 | 809 | Sometimes, it's useful to "disable" physics and prevent scripts from updating, effectively pausing your scene. 810 | 811 | To achieve this, `Time.timeScale` is used (see: [Unity documentation](https://docs.unity3d.com/ScriptReference/Time-timeScale.html)). 812 | 813 | To enable a mode where `Time.timeScale` is set to 0 in edit mode and 1 in play mode, set `UseZeroTimeScaleInEditMode` to true (default value is false). 814 | 815 | ![Use Zero TimeScale in Edit Mode][use_zero_time_scale_in_edit_mode] 816 | 817 | # Runtime Editor UI and Window System 818 | ## Overview 819 | 820 | The runtime editor UI is built using Unity's UGUI and includes three complex controls: [Dock Panel](#dock-panel-control), [Tree View](#tree-view-control), and [Menu](#menu-control). 821 | 822 | 823 | The Runtime Editor Windows system is primarily managed using the `IWindowManager` interface. It allows for the creation of complex windows, such as inspectors or scenes, and simple dialogs, like messages or confirmation boxes. A key distinction is that dialogs cannot be docked and do not deactivate other windows, whereas windows can. 824 | All windows and dialogs require a `RuntimeWindow` component to function correctly. 825 | 826 | ## RuntimeWindow 827 | The RuntimeWindow class provides essential properties and methods to manage window behavior, activation, and interaction within the Runtime Editor 828 | 829 | ### Key Properties of `RuntimeWindow` 830 | 831 | 1. **CanActivate**: 832 | - **Type**: `bool` 833 | - **Description**: Indicates whether the window can be activated. 834 | - **Default**: `true` 835 | 836 | 2. **ActivateOnAnyKey**: 837 | - **Type**: `bool` 838 | - **Description**: Specifies if the window should activate on any key press. 839 | - **Default**: `false` 840 | 841 | 3. **Camera**: 842 | - **Type**: `Camera` 843 | - **Description**: The camera associated with the window. 844 | - **Default**: `null` 845 | - **Behavior**: Throws `NotSupportedException` if set. 846 | 847 | 4. **Pointer**: 848 | - **Type**: `Pointer` 849 | - **Description**: Manages pointer events for the window. 850 | 851 | 5. **IOCContainer**: 852 | - **Type**: `IOCContainer` 853 | - **Description**: Inversion of Control container for dependency management within the window. 854 | 855 | 6. **WindowType**: 856 | - **Type**: `RuntimeWindowType` 857 | - **Description**: Specifies the type of the window (e.g., Scene, Game). 858 | - **Default**: `RuntimeWindowType.Scene` 859 | 860 | 7. **Index**: 861 | - **Type**: `int` 862 | - **Description**: Index used to identify and manage the window within the editor. 863 | 864 | 8. **CanvasGroup**: 865 | - **Type**: `CanvasGroup` 866 | - **Description**: Manages canvas properties such as visibility and interaction. 867 | 868 | 9. **Canvas**: 869 | - **Type**: `Canvas` 870 | - **Description**: The parent canvas of the window. 871 | 872 | 10. **Background**: 873 | - **Type**: `Image` 874 | - **Description**: The background image of the window. 875 | 876 | 11. **IsPointerOver**: 877 | - **Type**: `bool` 878 | - **Description**: Indicates whether the pointer is currently over the window. 879 | 880 | 12. **ViewRoot**: 881 | - **Type**: `RectTransform` 882 | - **Description**: The root transform for the window's view. 883 | 884 | ### Example Usage 885 | 886 | ```csharp 887 | using Battlehub.RTCommon; 888 | using UnityEngine; 889 | 890 | public class CustomRuntimeWindow : RuntimeWindow 891 | { 892 | protected override void OnActivated() 893 | { 894 | base.OnActivated(); 895 | Debug.Log("Window Activated"); 896 | } 897 | 898 | protected override void OnDeactivated() 899 | { 900 | base.OnDeactivated(); 901 | Debug.Log("Window Deactivated"); 902 | } 903 | } 904 | ``` 905 | 906 | ## Built In Windows 907 | 908 | The built-in windows component allows you to set up built-in windows, specify whether a window is a dialog, update their content prefab, tab header, and header text, set startup arguments, and specify the maximum number of windows that can be opened simultaneously. 909 | 910 | ![Built In Windows][builtin_windows] 911 | 912 | The BuiltInWindowNames class provides a list of names for built-in windows in the Runtime Editor. Here are some of the main built-in window names: 913 | 914 | ``` 915 | public static class BuiltInWindowNames 916 | { 917 | public readonly static string Game = RuntimeWindowType.Game.ToString().ToLower(); 918 | public readonly static string Scene = RuntimeWindowType.Scene.ToString().ToLower(); 919 | public readonly static string Hierarchy = RuntimeWindowType.Hierarchy.ToString().ToLower(); 920 | public readonly static string ProjectTree = RuntimeWindowType.ProjectTree.ToString().ToLower(); 921 | public readonly static string ProjectFolder = RuntimeWindowType.ProjectFolder.ToString().ToLower(); 922 | public readonly static string Inspector = RuntimeWindowType.Inspector.ToString().ToLower(); 923 | public readonly static string Console = RuntimeWindowType.Console.ToString().ToLower(); 924 | public readonly static string Animation = RuntimeWindowType.Animation.ToString().ToLower(); 925 | public readonly static string ToolsPanel = RuntimeWindowType.ToolsPanel.ToString().ToLower(); 926 | public readonly static string ImportFile = RuntimeWindowType.ImportFile.ToString().ToLower(); 927 | public readonly static string OpenProject = RuntimeWindowType.OpenProject.ToString().ToLower(); 928 | public readonly static string About = RuntimeWindowType.About.ToString().ToLower(); 929 | public readonly static string SaveFile = RuntimeWindowType.SaveFile.ToString().ToLower(); 930 | public readonly static string OpenFile = RuntimeWindowType.OpenFile.ToString().ToLower(); 931 | public readonly static string SelectColor = RuntimeWindowType.SelectColor.ToString().ToLower(); 932 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 933 | public readonly static string AssetDatabase = "assetdatabase"; 934 | public readonly static string AssetDatabaseSaveScene = "assetdatabasesavescene"; 935 | public readonly static string AssetDatabaseSave = "assetdatabasesave"; 936 | public readonly static string AssetDatabaseSelect = "assetdatabaseselect"; 937 | public readonly static string AssetDatabaseImportSource = "assetdatabaseimportsource"; 938 | public readonly static string AssetDatabaseImport = "assetdatabaseimport"; 939 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 940 | public static string Project => ... 941 | public static string SaveScene => ... 942 | public static string SaveAsset => ... 943 | public static string SelectObject => ... 944 | public static string SelectAssetLibrary => ... 945 | public static string ImportAssets => ... 946 | } 947 | ``` 948 | 949 | 950 | ## Window Manager 951 | 952 | The `IWindowManager` interface provides a comprehensive set of methods for managing windows in the Runtime Editor. It allows you to create, activate, and manage both standard windows and dialog boxes. Below are some examples of how to use the `IWindowManager` to perform various tasks. 953 | 954 | ### Properties and Methods 955 | 956 | The `IWindowManager` interface includes various properties and methods to manage window layouts, components, and dialog boxes: 957 | 958 | #### Properties 959 | 960 | - **bool IsDialogOpened**: Returns true if a dialog is currently opened. 961 | - **RectTransform PopupRoot**: Root panel for popups and floating windows. 962 | - **Transform ComponentsRoot**: Root transform for additional window components. 963 | 964 | #### Methods 965 | 966 | - **void SetDefaultLayout()**: Rebuilds the layout using the default layout builder function. 967 | - **bool RegisterWindow(CustomWindowDescriptor desc)**: Registers a window with a custom descriptor. 968 | - **Transform CreateWindow(string windowTypeName, bool isFree, RegionSplitType splitType, float flexibleSize, Transform parentWindow)**: Creates a window of the specified type. 969 | - **bool IsWindowRegistered(string windowTypeName)**: Determines whether a window of the type is registered. 970 | - **WindowDescriptor GetWindowDescriptor(string windowTypeName, out bool isDialog)**: Gets the window descriptor. 971 | - **string GetWindowTypeName(Transform content)**: Gets the window type name. 972 | - **Transform GetWindow(string windowTypeName)**: Gets the window transform by type. 973 | - **Transform[] GetWindows()**: Gets transforms of all windows. 974 | - **Transform[] GetWindows(string windowTypeName)**: Gets transforms of windows of the specified type. 975 | - **Transform[] GetComponents(Transform content)**: Gets extra components associated with the window. 976 | - **bool Exists(string windowTypeName)**: Checks if a window of the specified type exists. 977 | - **bool IsActive(string windowTypeName)**: Checks if the window of the specified type is active. 978 | - **bool IsActive(Transform content)**: Checks if the window is active. 979 | - **Transform FindPointerOverWindow(RuntimeWindow exceptWindow)**: Finds the uppermost window to which the pointer is pointing. 980 | - **bool ActivateWindow(Transform content)**: Activates the window of the specified content. 981 | - **void SetWindowArgs(Transform content, string args)**: Sets window arguments. 982 | - **void DestroyWindow(Transform content)**: Destroys the specified window. 983 | - **void DestroyWindowsOfType(string windowTypeName)**: Destroys windows of the specified type. 984 | - **void DestroyDialogWindow()**: Destroys the topmost dialog window. 985 | - **void MessageBox(string header, string text, DialogAction<DialogCancelArgs> ok = null)**: Creates a message box. 986 | 987 | 988 | > **Note** <br/> 989 | For a complete list of properties and methods, refer to the IWindowManager interface definition. 990 | 991 | ### Getting the Window Manager 992 | 993 | To access the `IWindowManager`, use the `IOC.Resolve<IWindowManager>()` method: 994 | 995 | ```csharp 996 | using Battlehub.RTCommon; 997 | using Battlehub.RTEditor; 998 | using UnityEngine; 999 | 1000 | public class GetWindowManager : MonoBehaviour 1001 | { 1002 | void Start() 1003 | { 1004 | IWindowManager wm = IOC.Resolve<IWindowManager>(); 1005 | } 1006 | } 1007 | ``` 1008 | 1009 | ### Showing a Message Box 1010 | 1011 | You can display a message box with a header and text, and handle the OK button click event: 1012 | 1013 | ```csharp 1014 | wm.MessageBox("Header", "Text", (sender, args) => 1015 | { 1016 | Debug.Log("OK Click"); 1017 | }); 1018 | ``` 1019 | 1020 | ### Showing a Confirmation Dialog 1021 | 1022 | You can display a confirmation dialog with custom buttons and handle the Yes and No button click events: 1023 | 1024 | ```csharp 1025 | wm.Confirmation("Header", "Text", 1026 | (sender, args) => 1027 | { 1028 | Debug.Log("Yes click"); 1029 | }, 1030 | (sender, args) => 1031 | { 1032 | Debug.Log("No click"); 1033 | }, 1034 | "Yes", "No"); 1035 | ``` 1036 | 1037 | ### Activating a Window 1038 | 1039 | To activate an existing window, use the `ActivateWindow` method with the name of the window: 1040 | 1041 | ```csharp 1042 | wm.ActivateWindow(BuiltInWindowNames.Scene); 1043 | ``` 1044 | 1045 | ### Creating a Window 1046 | 1047 | To create a new window, use the `CreateWindow` method with the name of the window: 1048 | 1049 | ```csharp 1050 | wm.CreateWindow(BuiltInWindowNames.Scene); 1051 | ``` 1052 | 1053 | ### Creating a Dialog Window 1054 | 1055 | To create a dialog window, use the `CreateDialogWindow` method with the name of the window and handle the OK and Cancel button click events: 1056 | 1057 | ```csharp 1058 | IWindowManager wm = IOC.Resolve<IWindowManager>(); 1059 | wm.CreateDialogWindow(BuiltInWindowNames.About, "Header", 1060 | (sender, args) => { Debug.Log("OK"); }, 1061 | (sender, args) => { Debug.Log("Cancel"); }); 1062 | ``` 1063 | 1064 | > **Note** <br/> 1065 | For examples, see **Scene8 - Window Management** in the [Example Scenes section](#example-scenes). 1066 | 1067 | ## Main and Context Menu 1068 | 1069 | The Runtime Editor uses the Menu control to implement both the main menu and context menus. To extend the main menu, you can create a static class with the **[MenuDefinition]** attribute and add static methods with the **[MenuCommand]** attribute. 1070 | 1071 | ### Extending the Main Menu 1072 | 1073 | To add new commands or modify existing ones, follow the steps below: 1074 | 1075 | 1. Create a static class with the **[MenuDefinition]** attribute. 1076 | 2. Add static methods with the **[MenuCommand]** attribute to define new menu items or modify existing ones. 1077 | 1078 | ```csharp 1079 | using Battlehub.RTCommon; 1080 | using Battlehub.RTEditor; 1081 | using Battlehub.UIControls.MenuControl; 1082 | using UnityEngine; 1083 | 1084 | [MenuDefinition] 1085 | public static class MyMenu 1086 | { 1087 | // Add new command to existing menu 1088 | [MenuCommand("MenuWindow/Create My Window")] 1089 | public static void CreateMyWindow() 1090 | { 1091 | Debug.Log("Create My Window"); 1092 | } 1093 | 1094 | // Add new command to new menu 1095 | [MenuCommand("My Menu/My Submenu/My Command")] 1096 | public static void CreateMyMenu() 1097 | { 1098 | Debug.Log("Create My Menu"); 1099 | } 1100 | 1101 | // Disable menu item 1102 | [MenuCommand("My Menu/My Submenu/My Command", validate: true)] 1103 | public static bool ValidateMyCommand() 1104 | { 1105 | Debug.Log("Disable My Command"); 1106 | return false; 1107 | } 1108 | 1109 | // Replace existing menu item 1110 | [MenuCommand("MenuFile/Close")] 1111 | public static void Close() 1112 | { 1113 | Debug.Log("Intercepted"); 1114 | 1115 | IRuntimeEditor rte = IOC.Resolve<IRuntimeEditor>(); 1116 | rte.Close(); 1117 | } 1118 | 1119 | // Hide existing menu item 1120 | [MenuCommand("MenuHelp/About RTE", hide: true)] 1121 | public static void HideAbout() { } 1122 | } 1123 | ``` 1124 | 1125 | ![Main Menu][main_menu] 1126 | 1127 | ### Instance Methods as Menu Commands 1128 | 1129 | It is possible to use instance methods as menu commands. For this to work, the corresponding MonoBehaviour must exist in the scene. 1130 | 1131 | ```csharp 1132 | using Battlehub.UIControls.MenuControl; 1133 | using UnityEngine; 1134 | 1135 | [MenuDefinition] 1136 | public class MyMenu : MonoBehaviour 1137 | { 1138 | // Add new command to new menu 1139 | [MenuCommand("My Menu/My Submenu/My Command")] 1140 | public void CreateMyMenu() 1141 | { 1142 | Debug.Log("Create My Menu"); 1143 | } 1144 | 1145 | // Disable menu item 1146 | [MenuCommand("My Menu/My Submenu/My Command", validate: true)] 1147 | public bool ValidateMyCommand() 1148 | { 1149 | Debug.Log("Disable My Command"); 1150 | return false; 1151 | } 1152 | } 1153 | ``` 1154 | 1155 | ### MenuDefinition with the sceneName parameter 1156 | Use the [MenuDefinition] attribute with the sceneName parameter to specify the scene. This way, the menu will only exist in the Unity scene with the corresponding name. 1157 | ```csharp 1158 | using Battlehub.UIControls.MenuControl; 1159 | using UnityEngine; 1160 | 1161 | [MenuDefinition(sceneName:"Scene")] 1162 | public class MyMenu : MonoBehaviour 1163 | { 1164 | 1165 | // Add new command to new menu 1166 | [MenuCommand("My Menu/My Submenu/My Command")] 1167 | public void CreateMyMenu() 1168 | { 1169 | Debug.Log("Create My Menu"); 1170 | } 1171 | } 1172 | ``` 1173 | 1174 | > **Note** <br/> 1175 | For examples, see **Scene9 - Extending Main Menu** in the [Example Scenes section](#example-scenes). 1176 | 1177 | ### Opening a Context Menu with Custom Commands 1178 | 1179 | The `IContextMenuModel` interface defines the structure for managing context menus within the runtime editor. It provides methods and events to create, open, and manage context menus dynamically. 1180 | 1181 | #### Events 1182 | 1183 | - **event EventHandler<ContextMenuArgs> Open**: Event triggered when the context menu is opened. The `ContextMenuArgs` provides details about the menu items and their context. 1184 | - **event EventHandler Close**: Event triggered when the context menu is closed. 1185 | 1186 | #### Methods 1187 | 1188 | - **void RaiseOpen(ContextMenuArgs args)**: Raises the `Open` event with the specified context menu arguments. 1189 | - **void Show()**: Shows an empty context menu. It is possible to add menu items in Open event handler. 1190 | - **void Show(params ContextMenuItem[] items)**: Shows a context menu with the specified items. 1191 | - **void Show(ContextMenuArgs args)**: Shows a context menu using the specified `ContextMenuArgs`. The `ContextMenuArgs` includes details about the menu items and their context. 1192 | 1193 | To open a context menu with custom commands, follow these steps: 1194 | 1195 | 1. Define a class with methods to open the context menu. 1196 | 2. Use the **IContextMenuModel** interface to create and show the context menu with the defined commands. 1197 | 1198 | ```csharp 1199 | using Battlehub.RTCommon; 1200 | using Battlehub.RTEditor; 1201 | using Battlehub.RTEditor.Models; 1202 | using UnityEngine; 1203 | 1204 | public class MyContextMenu : MonoBehaviour 1205 | { 1206 | private void Update() 1207 | { 1208 | if (Input.GetKeyDown(KeyCode.Space)) 1209 | { 1210 | OpenContextMenu(); 1211 | } 1212 | } 1213 | 1214 | public void OpenContextMenu() 1215 | { 1216 | IContextMenuModel contextMenu = IOC.Resolve<IContextMenuModel>(); 1217 | 1218 | contextMenu.Show( 1219 | new ContextMenuItem 1220 | { 1221 | Path = "My Command 1", 1222 | Action = arg => 1223 | { 1224 | Debug.Log("Run My Command1"); 1225 | 1226 | var editor = IOC.Resolve<IRuntimeEditor>(); 1227 | Debug.Log(editor.Selection.activeGameObject); 1228 | } 1229 | }, 1230 | new ContextMenuItem 1231 | { 1232 | Path = "My Command 2", 1233 | Action = arg => Debug.Log("Run My Command2"), 1234 | Validate = arg => arg.IsValid = false 1235 | }); 1236 | } 1237 | } 1238 | ``` 1239 | 1240 | Basically, the same can be done using the following syntax: 1241 | 1242 | ``` 1243 | using Battlehub.RTCommon; 1244 | using Battlehub.RTEditor; 1245 | using Battlehub.RTEditor.Models; 1246 | using UnityEngine; 1247 | 1248 | public class MyContextMenu : MonoBehaviour 1249 | { 1250 | private void Update() 1251 | { 1252 | if (Input.GetKeyDown(KeyCode.Space)) 1253 | { 1254 | OpenContextMenu(); 1255 | } 1256 | } 1257 | 1258 | public void OpenContextMenu() 1259 | { 1260 | var contextMenu = IOC.Resolve<IContextMenuModel>(); 1261 | contextMenu.Open += OnContextMenuOpen; 1262 | contextMenu.Show(); 1263 | contextMenu.Open -= OnContextMenuOpen; 1264 | } 1265 | 1266 | private void OnContextMenuOpen(object sender, ContextMenuArgs e) 1267 | { 1268 | if (e.WindowName == BuiltInWindowNames.Scene) 1269 | { 1270 | e.AddMenuItem("My Command 1", arg => 1271 | { 1272 | Debug.Log("Run My Command1"); 1273 | var editor = IOC.Resolve<IRuntimeEditor>(); 1274 | Debug.Log(editor.Selection.activeGameObject); 1275 | }); 1276 | 1277 | e.AddMenuItem("My Command 2", 1278 | arg => Debug.Log("Run My Command2"), 1279 | arg => arg.IsValid = false); 1280 | } 1281 | } 1282 | } 1283 | ``` 1284 | 1285 | ![Context Menu][context_menu] 1286 | 1287 | > **Note** <br/> 1288 | For examples, see **Scene10 - Extending Context Menu** in the [Example Scenes section](#example-scenes). 1289 | 1290 | 1291 | ## Editor Extension 1292 | The `EditorExtension` class serves as a recommended base class for writing Runtime Editor Extensions. It provides two useful methods that can be overridden: `OnInit()` and `OnCleanup()`. These methods are intended for initializing and cleaning up the extension, respectively. 1293 | 1294 | ### Methods 1295 | 1296 | - **void OnInit()**: This method is called when the extension is initialized. You should override this method to write the extension initialization code. 1297 | - **void OnCleanup()**: This method is called when the extension is being cleaned up. You should override this method to write the extension cleanup code. 1298 | 1299 | ### Example 1300 | 1301 | ```csharp 1302 | using Battlehub.RTCommon; 1303 | using Battlehub.RTEditor; 1304 | 1305 | public class EditorExtensionExample : EditorExtension 1306 | { 1307 | protected override void OnInit() 1308 | { 1309 | var editor = IOC.Resolve<IRuntimeEditor>(); 1310 | 1311 | // Do extension initialization here 1312 | Debug.Log("Editor extension initialized."); 1313 | } 1314 | 1315 | protected override void OnCleanup() 1316 | { 1317 | // Do extension cleanup here 1318 | Debug.Log("Editor extension cleaned up."); 1319 | } 1320 | } 1321 | ``` 1322 | 1323 | ### Additional Convenience Extension Base Classes 1324 | 1325 | There are three additional convenience extension base classes: 1326 | 1327 | 1. **LayoutExtension** 1328 | 2. **RuntimeWindowExtension** 1329 | 3. **SceneComponentExtension** 1330 | 1331 | #### LayoutExtension 1332 | 1333 | The `LayoutExtension` class provides four additional methods for handling window registration, layout building, and events for additional handling before and after the layout. 1334 | 1335 | ##### Methods 1336 | 1337 | - **void OnRegisterWindows(IWindowManager wm)**: Registers custom windows. 1338 | - **void OnBeforeBuildLayout(IWindowManager wm)**: Invoked before the layout is built. 1339 | - **void OnAfterBuildLayout(IWindowManager wm)**: Invoked after the layout is built. 1340 | - **LayoutInfo GetLayoutInfo(IWindowManager wm)**: Provides layout information. 1341 | 1342 | ##### Example 1343 | 1344 | ```csharp 1345 | using Battlehub.RTCommon; 1346 | using Battlehub.RTEditor; 1347 | using UnityEngine; 1348 | 1349 | public class LayoutExtensionExample : LayoutExtension 1350 | { 1351 | [SerializeField] 1352 | private GameObject m_sceneWindow = null; 1353 | 1354 | protected override void OnInit() 1355 | { 1356 | } 1357 | 1358 | protected override void OnCleanup() 1359 | { 1360 | } 1361 | 1362 | protected override void OnRegisterWindows(IWindowManager wm) 1363 | { 1364 | if (m_sceneWindow != null) 1365 | { 1366 | wm.OverrideWindow(BuiltInWindowNames.Scene, m_sceneWindow); 1367 | } 1368 | } 1369 | 1370 | protected override void OnBeforeBuildLayout(IWindowManager wm) 1371 | { 1372 | // Hide header toolbar 1373 | wm.OverrideTools(null); 1374 | } 1375 | 1376 | protected override void OnAfterBuildLayout(IWindowManager wm) 1377 | { 1378 | base.OnAfterBuildLayout(wm); 1379 | } 1380 | 1381 | protected override LayoutInfo GetLayoutInfo(IWindowManager wm) 1382 | { 1383 | // Initializing a layout with one window - Scene 1384 | LayoutInfo layoutInfo = wm.CreateLayoutInfo(BuiltInWindowNames.Scene); 1385 | layoutInfo.IsHeaderVisible = false; 1386 | 1387 | return layoutInfo; 1388 | } 1389 | } 1390 | ``` 1391 | 1392 | #### RuntimeWindowExtension 1393 | 1394 | The `RuntimeWindowExtension` class calls `Extend` and `Cleanup` methods for a specific window type. 1395 | 1396 | ##### Methods 1397 | 1398 | - **void Extend(RuntimeWindow window)**: Extends the specified window. 1399 | - **void Cleanup(RuntimeWindow window)**: Cleans up the specified window. 1400 | 1401 | ##### Example 1402 | 1403 | ```csharp 1404 | using Battlehub.RTCommon; 1405 | using Battlehub.RTEditor; 1406 | using UnityEngine; 1407 | 1408 | public class RuntimeWindowExtensionExample : RuntimeWindowExtension 1409 | { 1410 | [SerializeField] 1411 | private RectTransform m_layer = null; 1412 | 1413 | public override string WindowTypeName => BuiltInWindowNames.Scene; 1414 | 1415 | protected override void Extend(RuntimeWindow window) 1416 | { 1417 | Instantiate(m_layer, window.ViewRoot).Stretch(); 1418 | } 1419 | 1420 | protected override void Cleanup(RuntimeWindow window) 1421 | { 1422 | base.Cleanup(window); 1423 | } 1424 | } 1425 | ``` 1426 | 1427 | #### SceneComponentExtension 1428 | 1429 | The `SceneComponentExtension` class allows you to extend the scene view when it is activated and perform cleanup when it is deactivated. 1430 | 1431 | ##### Methods 1432 | 1433 | - **void OnSceneActivated(IRuntimeSceneComponent sceneComponent)**: Called when the scene is activated. 1434 | - **void OnSceneDeactivated(IRuntimeSceneComponent sceneComponent)**: Called when the scene is deactivated. 1435 | 1436 | ##### Example 1437 | 1438 | ```csharp 1439 | using Battlehub.RTCommon; 1440 | using Battlehub.RTEditor; 1441 | using Battlehub.RTHandles; 1442 | using UnityEngine; 1443 | 1444 | public class SceneComponentExtensionExample : SceneComponentExtension 1445 | { 1446 | protected override void OnSceneActivated(IRuntimeSceneComponent sceneComponent) 1447 | { 1448 | base.OnSceneActivated(sceneComponent); 1449 | 1450 | sceneComponent.PositionHandle.Drag.AddListener(OnDrag); 1451 | } 1452 | 1453 | protected override void OnSceneDeactivated(IRuntimeSceneComponent sceneComponent) 1454 | { 1455 | base.OnSceneDeactivated(sceneComponent); 1456 | 1457 | sceneComponent.PositionHandle.Drag.RemoveListener(OnDrag); 1458 | } 1459 | 1460 | private void OnDrag(BaseHandle handle) 1461 | { 1462 | foreach (Transform target in handle.ActiveTargets) 1463 | { 1464 | Vector3 p = target.position; 1465 | target.position = new Vector3(Mathf.Round(p.x), Mathf.Round(p.y), Mathf.Round(p.z)); 1466 | } 1467 | } 1468 | } 1469 | ``` 1470 | 1471 | ## Window 1472 | 1473 | ![Window][window] 1474 | 1475 | A typical Runtime Editor Window consists of several parts, including a window prefab with a `RuntimeWindow` component, a frame, background, and view. 1476 | It also incorporates a View, ViewModel, and ViewBinding, built on top of `UnityWeld.dll`. 1477 | This structure ensures a modular and flexible design, allowing for a seamless integration of UI components and binding logic. 1478 | 1479 | #### View-ViewModel-ViewBinding Architecture 1480 | The window uses the Model-View-ViewModel (MVVM) pattern, facilitated by `UnityWeld.dll`, to ensure a clear separation of concerns and enhance maintainability. 1481 | 1. **View**: Contains the UI elements and visual components. 1482 | 2. **ViewModel**: Holds the logic and data for the view, acting as an intermediary between the view and the model. 1483 | 3. **ViewBinding**: Binds the view to the ViewModel, ensuring that changes in the ViewModel are reflected in the view and vice versa. 1484 | 1485 | ### UnityWeld 1486 | https://github.com/Battlehub0x/Unity-Weld?tab=readme-ov-file 1487 | 1488 | ### View 1489 | 1490 | The `View` class in the Runtime Editor framework is responsible for managing the visual representation and interaction logic of editor windows. It handles various events related to dragging, dropping, activating, and deactivating, and it provides mechanisms for binding the view to a `ViewModel`. 1491 | 1492 | #### Key Properties 1493 | 1494 | 1. **DragObjects**: An enumerable property that holds the objects being dragged. 1495 | 2. **CanDropExternalObjects**: A boolean property indicating whether external drag objects can be dropped. 1496 | 3. **IsDraggingOver**: A boolean property indicating whether a drag operation is currently over the view. 1497 | 4. **ViewInput**: A protected property providing access to the `ViewInput` component. 1498 | 5. **Window**: A protected property providing access to the associated `RuntimeWindow`. 1499 | 6. **Editor**: A protected property providing access to the `IRTE` instance. 1500 | 1501 | #### Key Events 1502 | 1503 | 1. **SelectAll**: An event triggered to select all items. 1504 | 2. **Duplicate**: An event triggered to duplicate items. 1505 | 3. **Delete**: An event triggered to delete items. 1506 | 4. **DragEnter**: An event triggered when a drag operation enters the view. 1507 | 5. **DragLeave**: An event triggered when a drag operation leaves the view. 1508 | 6. **Drag**: An event triggered during a drag operation. 1509 | 7. **Drop**: An event triggered when a drop operation occurs. 1510 | 8. **DragObjectsChanged**: An event triggered when the `DragObjects` property changes. 1511 | 9. **Activated**: An event triggered when the view is activated. 1512 | 10. **Deactivated**: An event triggered when the view is deactivated. 1513 | 1514 | #### Key Methods 1515 | 1516 | 1. **Awake()**: Initializes the view, setting up event handlers for the associated `RuntimeWindow`. 1517 | 2. **OnEnable()**: Called when the view is enabled. 1518 | 3. **Start()**: Ensures the `ViewInput` component is present. 1519 | 4. **OnDisable()**: Called when the view is disabled. 1520 | 5. **OnDestroy()**: Cleans up event handlers and references when the view is destroyed. 1521 | 6. **OnDragEnter(PointerEventData)**: Handles the drag enter event, updating the `DragObjects` property and setting the cursor. 1522 | 7. **OnDragLeave(PointerEventData)**: Handles the drag leave event, resetting the `DragObjects` property and setting the cursor. 1523 | 8. **OnDrag(PointerEventData)**: Handles the drag event. 1524 | 9. **OnDrop(PointerEventData)**: Handles the drop event, resetting the `DragObjects` property. 1525 | 10. **OnActivated(object, EventArgs)**: Handles the activation event. 1526 | 11. **OnDeactivated(object, EventArgs)**: Handles the deactivation event. 1527 | 1528 | #### Utility Methods 1529 | 1530 | 1. **ReplaceWith<T>(Component, bool)**: Replaces the current `View` component with a new one of type `T`. 1531 | 2. **ReplaceWith<T>(GameObject, bool)**: Replaces the current `View` component on the specified `GameObject` with a new one of type `T`. 1532 | 3. **ReplaceWith(Type, Component, bool)**: Replaces the current `View` component with a new one of the specified type. 1533 | 4. **ReplaceWith(Type, GameObject, bool)**: Replaces the current `View` component on the specified `GameObject` with a new one of the specified type. 1534 | 1535 | #### Example Usage 1536 | 1537 | ```csharp 1538 | using Battlehub.RTCommon; 1539 | using Battlehub.RTEditor.Views; 1540 | using UnityEngine; 1541 | using UnityEngine.EventSystems; 1542 | 1543 | public class CustomView : View 1544 | { 1545 | protected override void Awake() 1546 | { 1547 | base.Awake(); 1548 | Debug.Log("CustomView Awake"); 1549 | } 1550 | 1551 | protected override void OnEnable() 1552 | { 1553 | base.OnEnable(); 1554 | Debug.Log("CustomView OnEnable"); 1555 | } 1556 | 1557 | protected override void OnDisable() 1558 | { 1559 | base.OnDisable(); 1560 | Debug.Log("CustomView OnDisable"); 1561 | } 1562 | 1563 | protected override void OnDragEnter(PointerEventData pointerEventData) 1564 | { 1565 | base.OnDragEnter(pointerEventData); 1566 | Debug.Log("Drag entered CustomView"); 1567 | } 1568 | 1569 | protected override void OnDrop(PointerEventData pointerEventData) 1570 | { 1571 | base.OnDrop(pointerEventData); 1572 | Debug.Log("Dropped on CustomView"); 1573 | } 1574 | } 1575 | ``` 1576 | 1577 | 1578 | 1579 | ### ViewModel 1580 | 1581 | The `ViewModel` base class is a foundational component in the MVVM architecture used within the Runtime Editor. It provides essential properties and methods to facilitate the interaction between the view and the underlying data and logic. This class inherits from `ViewModelBase` and uses the `UnityWeld.Binding` attribute for data binding. 1582 | 1583 | #### Key Properties 1584 | 1585 | 1. **CanDropExternalObjects**: A boolean property indicating whether external drag objects can be dropped. It uses `RaisePropertyChanged` to notify the view of any changes. 1586 | 2. **ExternalDragObjects**: An enumerable property that holds the external drag objects. 1587 | 3. **Editor**: Provides access to the `IRuntimeEditor` instance. 1588 | 4. **Localization**: Provides access to the `ILocalization` instance. 1589 | 5. **WindowManager**: Provides access to the `IWindowManager` instance. 1590 | 6. **Selection**: Represents the current runtime selection. 1591 | 7. **Undo**: Represents the undo functionality. 1592 | 8. **SelectionOverride**: Allows overriding the default selection behavior. 1593 | 9. **UndoOverride**: Allows overriding the default undo behavior. 1594 | 10. **WindowName**: The name of the window associated with this ViewModel. 1595 | 1596 | #### Key Methods 1597 | 1598 | 1. **Awake()**: Initializes the `Editor`, `Localization`, and `WindowManager` properties and sets the `WindowName` if the ViewModel is attached to a `RuntimeWindow`. 1599 | 2. **OnEnable()**: Sets up the `Selection` and `Undo` properties. 1600 | 3. **OnDisable()**: Called when the ViewModel is disabled. 1601 | 4. **OnDestroy()**: Cleans up references to the `Editor`, `Localization`, `WindowManager`, `Selection`, and `Undo`. 1602 | 5. **SetBusy()**: Sets the editor to a busy state. 1603 | 6. **OnActivated()**: Called when the ViewModel is activated. 1604 | 7. **OnDeactivated()**: Called when the ViewModel is deactivated. 1605 | 8. **OnSelectAll()**: Handles the select all action. 1606 | 9. **OnDelete()**: Handles the delete action. 1607 | 10. **OnDuplicate()**: Handles the duplicate action. 1608 | 11. **OnExternalObjectEnter()**: Called when an external object enters the drop area. 1609 | 12. **OnExternalObjectLeave()**: Called when an external object leaves the drop area. 1610 | 13. **OnExternalObjectDrag()**: Called during the dragging of an external object. 1611 | 14. **OnExternalObjectDrop()**: Called when an external object is dropped. 1612 | 1613 | 15. **RaisePropertyChanged(string propertyName)**: 1614 | - Notifies listeners that a property value has changed. 1615 | - **Parameter**: `propertyName` (string) - The name of the property that changed. 1616 | 1617 | 16. **RaisePropertyChanged(PropertyChangedEventArgs args)**: 1618 | - Notifies listeners that a property value has changed using `PropertyChangedEventArgs`. 1619 | - **Parameter**: `args` (PropertyChangedEventArgs) - The event arguments containing the property name. 1620 | 1621 | 17. **ReplaceWith<T>(UnityEngine.Component component) where T : ViewModelBase**: 1622 | - Replaces the current `ViewModelBase` component on the specified component's game object with a new one of type `T`. 1623 | - **Parameter**: `component` (UnityEngine.Component) - The component whose game object's view model will be replaced. 1624 | 1625 | 18. **ReplaceWith<T>(GameObject go) where T : ViewModelBase**: 1626 | - Replaces the current `ViewModelBase` component on the specified game object with a new one of type `T`. 1627 | - **Parameter**: `go` (GameObject) - The game object whose view model will be replaced. 1628 | 1629 | 19. **ReplaceWith(Type type, UnityEngine.Component component)**: 1630 | - Replaces the current `ViewModelBase` component on the specified component's game object with a new one of the specified type. 1631 | - **Parameter**: `type` (Type) - The type of the new view model. 1632 | - **Parameter**: `component` (UnityEngine.Component) - The component whose game object's view model will be replaced. 1633 | 1634 | 20. **ReplaceWith(Type type, GameObject go)**: 1635 | - Replaces the current `ViewModelBase` component on the specified game object with a new one of the specified type. 1636 | - **Parameter**: `type` (Type) - The type of the new view model. 1637 | - **Parameter**: `go` (GameObject) - The game object whose view model will be replaced. 1638 | 1639 | #### Example Usage 1640 | 1641 | ```csharp 1642 | [Binding] 1643 | public class MyViewModel : ViewModel 1644 | { 1645 | private bool m_isEnabled; 1646 | 1647 | [Binding] 1648 | public bool IsEnabled 1649 | { 1650 | get { return m_isEnabled; } 1651 | set 1652 | { 1653 | if (m_isEnabled != value) 1654 | { 1655 | m_isEnabled = value; 1656 | RaisePropertyChanged(nameof(IsEnabled)); 1657 | } 1658 | } 1659 | } 1660 | 1661 | protected override void OnActivated() 1662 | { 1663 | base.OnActivated(); 1664 | Debug.Log("ViewModel activated."); 1665 | } 1666 | 1667 | protected override void OnDeactivated() 1668 | { 1669 | base.OnDeactivated(); 1670 | Debug.Log("ViewModel deactivated."); 1671 | } 1672 | 1673 | [Binding] 1674 | public override void OnSelectAll() 1675 | { 1676 | base.OnSelectAll(); 1677 | Debug.Log("Select All action triggered."); 1678 | } 1679 | 1680 | [Binding] 1681 | public override void OnDelete() 1682 | { 1683 | base.OnDelete(); 1684 | Debug.Log("Delete action triggered."); 1685 | } 1686 | } 1687 | ``` 1688 | 1689 | ### HierarchicalDataViewModel 1690 | 1691 | The `HierarchicalDataViewModel<T>` class is a base view model class used for managing hierarchical data structures in the Runtime Editor, such as those found in Hierarchy and Project windows. 1692 | This class provides mechanisms for managing tree-like data structures and handling common operations like selection, drag-and-drop, and context menus. 1693 | 1694 | #### Key Properties 1695 | 1696 | - **DataSource**: Gets the current instance as the data source for hierarchical data binding. 1697 | - **SelectedItem**: Gets or sets the currently selected item. 1698 | - **SelectedItems**: Gets or sets the collection of currently selected items. 1699 | - **CanDropItems**: Indicates whether items can be dropped into the current target. 1700 | - **SourceItems**: Gets or sets the items that are being dragged. 1701 | - **TargetItem**: Gets or sets the item that is the current drop target. 1702 | - **ContextMenu**: Gets or sets the collection of context menu items. 1703 | 1704 | #### Key Events 1705 | 1706 | - **HierarchicalDataChanged**: Raised when the hierarchical data changes. 1707 | - **ContextMenuOpened**: Raised when the context menu is opened. 1708 | 1709 | #### Key Methods 1710 | 1711 | 1. **RaiseHierarchicalDataChanged**: Raises the `HierarchicalDataChanged` event with the specified arguments. 1712 | 2. **RaiseItemAdded**: Raises the `HierarchicalDataChanged` event to indicate an item was added. 1713 | 3. **RaiseItemInserted**: Raises the `HierarchicalDataChanged` event to indicate an item was inserted. 1714 | 4. **RaiseItemRemoved**: Raises the `HierarchicalDataChanged` event to indicate an item was removed. 1715 | 5. **RaiseRemoveSelected**: Raises the `HierarchicalDataChanged` event to indicate selected items should be removed. 1716 | 6. **RaiseNextSiblingChanged**: Raises the `HierarchicalDataChanged` event to indicate the next sibling of an item has changed. 1717 | 7. **RaisePrevSiblingChanged**: Raises the `HierarchicalDataChanged` event to indicate the previous sibling of an item has changed. 1718 | 8. **RaiseParentChanged**: Raises the `HierarchicalDataChanged` event to indicate the parent of an item has changed. 1719 | 9. **RaiseExpand**: Raises the `HierarchicalDataChanged` event to indicate an item should be expanded. 1720 | 10. **RaiseCollapse**: Raises the `HierarchicalDataChanged` event to indicate an item should be collapsed. 1721 | 11. **RaiseSelect**: Raises the `HierarchicalDataChanged` event to select items. 1722 | 12. **RaiseReset**: Raises the `HierarchicalDataChanged` event to reset the hierarchical data. 1723 | 13. **RaiseDataBindVisible**: Raises the `HierarchicalDataChanged` event to rebind the visible data. 1724 | 1725 | #### Context Menu Handling 1726 | 1727 | 1. **GetContextMenuAnchor**: Returns the context menu anchor, which includes the target item and selected items. 1728 | 2. **OpenContextMenu**: Opens the context menu and populates it with items. 1729 | 3. **OnContextMenu**: Method for adding items to the context menu. Override this method to customize the context menu. 1730 | 1731 | #### IHierarchicalData Implementation 1732 | 1733 | 1. **GetChildren**: Returns the children of a given parent item. 1734 | 2. **GetFlags**: Returns the hierarchical data flags. 1735 | 3. **GetItemFlags**: Returns the flags for a specific item. 1736 | 4. **GetParent**: Returns the parent of a given item. 1737 | 5. **HasChildren**: Indicates whether a given item has children. 1738 | 6. **IndexOf**: Returns the index of a child item within its parent. 1739 | 7. **Expand**: Expands a given item. 1740 | 8. **Collapse**: Collapses a given item. 1741 | 9. **Select**: Selects the specified items. 1742 | 1743 | #### Bound UnityEvent Handlers 1744 | 1745 | 1. **OnItemsBeginDrag**: Called when items begin to be dragged. 1746 | 2. **OnItemDragEnter**: Called when an item drag enters the view. 1747 | 3. **OnItemDragLeave**: Called when an item drag leaves the view. 1748 | 4. **OnItemsDrag**: Called when items are being dragged. 1749 | 5. **OnItemsSetLastChild**: Called to set the last child during drag-and-drop. 1750 | 6. **OnItemsSetNextSibling**: Called to set the next sibling during drag-and-drop. 1751 | 7. **OnItemsSetPrevSibling**: Called to set the previous sibling during drag-and-drop. 1752 | 8. **OnItemsCancelDrop**: Called to cancel the drop operation. 1753 | 9. **OnItemsBeginDrop**: Called when items begin to be dropped. 1754 | 10. **OnItemsDrop**: Called when items are dropped. 1755 | 11. **OnItemsEndDrag**: Called when items finish being dragged. 1756 | 12. **OnItemsRemoved**: Called when items are removed. 1757 | 13. **OnItemBeginEdit**: Called when an item begins editing. 1758 | 14. **OnItemEndEdit**: Called when an item ends editing. 1759 | 15. **OnItemHold**: Called when an item is held. 1760 | 16. **OnItemClick**: Called when an item is clicked. 1761 | 17. **OnItemDoubleClick**: Called when an item is double-clicked. 1762 | 18. **OnHold**: Called when the view is held. 1763 | 19. **OnClick**: Called when the view is clicked. 1764 | 1765 | #### Example Usage 1766 | 1767 | ```csharp 1768 | using Battlehub.RTEditor.ViewModels; 1769 | using System.Collections.Generic; 1770 | using UnityWeld.Binding; 1771 | 1772 | [Binding] 1773 | public class MyHierarchicalDataViewModel : HierarchicalDataViewModel<MyItemType> 1774 | { 1775 | public override IEnumerable<MyItemType> GetChildren(MyItemType parent) 1776 | { 1777 | // Return children of the parent item 1778 | } 1779 | 1780 | public override void OnContextMenu(List<MenuItemViewModel> menuItems) 1781 | { 1782 | // Add custom menu items 1783 | menuItems.Add(new MenuItemViewModel 1784 | { 1785 | Path = "Custom Command", 1786 | Action = cmd => Debug.Log("Custom command executed") 1787 | }); 1788 | } 1789 | 1790 | protected override void OnSelectedItemsChanged(IEnumerable<MyItemType> unselectedObjects, IEnumerable<MyItemType> selectedObjects) 1791 | { 1792 | // Handle selection change 1793 | } 1794 | } 1795 | ``` 1796 | 1797 | ## Custom windows 1798 | To create a custom window, follow these steps: 1799 | 1800 | 1. **Click Tools -> Runtime Editor -> Create Custom Window** 1801 | 1802 | 2. **Enter the Name** 1803 | - Enter `MyCustomWindow.prefab` and click **Save**. 1804 | 1805 | 3. **Enter the Namespace** 1806 | - Enter `MyNamespace` namespace and click **OK**. 1807 | 1808 | 4. **Add RegisterMyCustomWindow Component** 1809 | - Add the `RegisterMyCustomWindow` component created in step 1 to a GameObject in the scene. 1810 | 1811 | 5. **Drag & Drop Prefab** 1812 | - Drag & drop `MyCustomWindow.prefab` to the Prefab field of the `RegisterMyCustomWindow` script. 1813 | 1814 | 6. **Click Play** 1815 | - Click **Play** in the Unity Editor. 1816 | 1817 | 7. **Open Custom Window** 1818 | - You should be able to open the custom window using the main menu. 1819 | 1820 | ![Click Create Custom Window][custom_window_1] 1821 | ![Register My Custom Window][custom_window_2] 1822 | ![Register My Custom Window][custom_window_3] 1823 | 1824 | 1825 | ## Extending Existing Windows 1826 | 1827 | There are several possible approaches for extending existing windows in the Runtime Editor. 1828 | 1829 | ### 1. Override the Window Prefab 1830 | 1831 | First, locate the window prefab you want to override or extend. Create a prefab variant and set the corresponding field in the `BuiltInWindows` component to your custom window. 1832 | ![Extending Window By Overriding Prefab][extending_window_1] 1833 | 1834 | ### 2. Override Programmatically Using Layout Extension 1835 | 1836 | You can also achieve this programmatically using a `LayoutExtension`. 1837 | 1838 | ```csharp 1839 | using Battlehub.RTEditor; 1840 | using UnityEngine; 1841 | 1842 | public class MyLayoutExtension : LayoutExtension 1843 | { 1844 | [SerializeField] 1845 | private GameObject m_myHierarchyWindow; 1846 | 1847 | protected override void OnRegisterWindows(IWindowManager wm) 1848 | { 1849 | wm.OverrideWindow(BuiltInWindowNames.Hierarchy, 1850 | new WindowDescriptor { ContentPrefab = m_myHierarchyWindow }); 1851 | } 1852 | } 1853 | ``` 1854 | 1855 | ### 3. Override ViewModel or View Using RuntimeWindowExtension and ReplaceWith Method 1856 | 1857 | You can override the `ViewModel` or `View` of a window using a `RuntimeWindowExtension` and the `ReplaceWith` method. 1858 | 1859 | ```csharp 1860 | using Battlehub.RTCommon; 1861 | using Battlehub.RTEditor.ViewModels; 1862 | 1863 | public class ExtendHierarchyWindow : RuntimeWindowExtension 1864 | { 1865 | public override string WindowTypeName => BuiltInWindowNames.Hierarchy; 1866 | 1867 | protected override void Extend(RuntimeWindow window) 1868 | { 1869 | ViewModelBase.ReplaceWith<HierarchyViewModelWithContextMenu>(window); 1870 | } 1871 | } 1872 | ``` 1873 | 1874 | ```csharp 1875 | using Battlehub.RTEditor.ViewModels; 1876 | using System.Collections.Generic; 1877 | using UnityEngine; 1878 | using UnityWeld.Binding; 1879 | 1880 | namespace Battlehub.RTEditor.Examples.Scene10 1881 | { 1882 | 1883 | [Binding] 1884 | public class HierarchyViewModelWithContextMenuExample : HierarchyViewModel 1885 | { 1886 | protected override void OnContextMenu(List<MenuItemViewModel> menuItems) 1887 | { 1888 | base.OnContextMenu(menuItems); 1889 | menuItems.Clear(); 1890 | 1891 | MenuItemViewModel myCommand = new MenuItemViewModel 1892 | { 1893 | Path = "My Context Menu Cmd", Command = "My Cmd Args" 1894 | }; 1895 | myCommand.Action = MenuCmd; 1896 | myCommand.Validate = ValidateMenuCmd; 1897 | menuItems.Add(myCommand); 1898 | } 1899 | 1900 | private void ValidateMenuCmd(MenuItemViewModel.ValidationArgs args) 1901 | { 1902 | if (!HasSelectedItems) 1903 | { 1904 | args.IsValid = false; 1905 | } 1906 | } 1907 | 1908 | private void MenuCmd(string arg) 1909 | { 1910 | Debug.Log($"My Context Menu Command with {arg}"); 1911 | } 1912 | } 1913 | } 1914 | ``` 1915 | 1916 | > **Note** 1917 | > You can combine the above approaches. 1918 | 1919 | > **Note** 1920 | > Most of the runtime editor windows follow the architecture described in the [window](#window) section. 1921 | 1922 | > **Note** 1923 | > For more examples, see **Scene10 - Extending Context Menu** in the [Example Scenes section](#example-scenes). 1924 | 1925 | ## Overriding the Default Layout 1926 | 1927 | To override the default layout, follow these steps: 1928 | 1929 | 1. **Create a Script Derived from LayoutExtension**: 1930 | Define a new script that inherits from `LayoutExtension`. 1931 | 1932 | 2. **Override GetLayoutInfo Method**: 1933 | Implement the `GetLayoutInfo` method to specify the custom layout. 1934 | 1935 | 3. **Add Script to GameObject**: 1936 | Create a new empty GameObject in your scene and attach the `ThreeColumnsLayoutExample` script to it. 1937 | 1938 | ### Example 1939 | 1940 | Here is an example script that creates a three-column layout (inspector, scene, hierarchy): 1941 | 1942 | ```csharp 1943 | using Battlehub.UIControls.DockPanels; 1944 | 1945 | namespace Battlehub.RTEditor.Examples.Layout 1946 | { 1947 | /// <summary> 1948 | /// Creates three columns layout (inspector, (scene, hierarchy)) 1949 | /// </summary> 1950 | public class ThreeColumnsLayoutExample : LayoutExtension 1951 | { 1952 | protected override LayoutInfo GetLayoutInfo(IWindowManager wm) 1953 | { 1954 | LayoutInfo scene = wm.CreateLayoutInfo(BuiltInWindowNames.Scene); 1955 | scene.IsHeaderVisible = true; 1956 | 1957 | LayoutInfo hierarchy = wm.CreateLayoutInfo(BuiltInWindowNames.Hierarchy); 1958 | LayoutInfo inspector = wm.CreateLayoutInfo(BuiltInWindowNames.Inspector); 1959 | 1960 | // Defines a region divided into two equal parts (ratio 1 / 2) 1961 | LayoutInfo sceneAndHierarchy = 1962 | LayoutInfo.Horizontal(scene, hierarchy, ratio: 1/2.0f); 1963 | 1964 | // Defines a region divided into two parts 1965 | // 1/3 for the inspector and 2/3 for the scene and hierarchy) 1966 | LayoutInfo layoutRoot = 1967 | LayoutInfo.Horizontal(inspector, sceneAndHierarchy, ratio: 1/3.0f); 1968 | 1969 | return layoutRoot; 1970 | } 1971 | } 1972 | } 1973 | ``` 1974 | 1975 | ![Add Three Columns Layout Example][override_layout_1] 1976 | 1977 | ![Result][override_layout_2] 1978 | 1979 | > **Note !!!** 1980 | > Make sure to remove conflicting layout extensions (like AutoSaveLayout). 1981 | 1982 | ![Remove Coflicting Layout Extensions][override_layout_3] 1983 | 1984 | > **Note** <br/> 1985 | For [examples](#example-scenes), see <br/> **Scene1 - Minimal**, <br/> **Scene2 - Minimal Mobile**, <br/> **Scene4 - Multiple Scenes**, <br/> **Scene5 - Layout SaveLoad** 1986 | 1987 | ### LayoutInfo Methods 1988 | 1989 | The `LayoutInfo` class provides three main methods to construct various types of layouts. These methods allow you to create complex layouts by splitting parent containers or grouping windows into tabs. Here's a description of each method: 1990 | 1991 | #### Vertical Split 1992 | 1993 | The `Vertical` method splits the parent container into two parts: one on top of the other. The specified ratio determines the size of the parts. A smaller ratio results in a smaller upper part. 1994 | 1995 | ```csharp 1996 | public static LayoutInfo Vertical(LayoutInfo top, LayoutInfo bottom, float ratio = 0.5f) 1997 | { 1998 | return new LayoutInfo(true, top, bottom, ratio); 1999 | } 2000 | ``` 2001 | 2002 | #### Horizontal Split 2003 | 2004 | The `Horizontal` method splits the parent container into two parts: one next to the other. The specified ratio determines the size of the parts. A smaller ratio results in a smaller left part. 2005 | 2006 | ```csharp 2007 | public static LayoutInfo Horizontal(LayoutInfo left, LayoutInfo right, float ratio = 0.5f) 2008 | { 2009 | return new LayoutInfo(false, left, right, ratio); 2010 | } 2011 | ``` 2012 | 2013 | #### Tab Group 2014 | 2015 | The `Group` method does not split the parent container. Instead, it creates a tab group, grouping multiple `LayoutInfo` instances into tabs within the same container. 2016 | 2017 | ```csharp 2018 | public static LayoutInfo Group(params LayoutInfo[] tabGroup) 2019 | { 2020 | return new LayoutInfo(tabGroup); 2021 | } 2022 | ``` 2023 | 2024 | #### Vertical Split Example 2025 | 2026 | Splitting a container into a scene view on top and a hierarchy view on the bottom with a 70% and 30% ratio respectively. 2027 | 2028 | ```csharp 2029 | LayoutInfo layout = LayoutInfo.Vertical(sceneViewLayout, hierarchyViewLayout, 0.7f); 2030 | ``` 2031 | 2032 | #### Horizontal Split Example 2033 | 2034 | Splitting a container into an inspector view on the left and a scene view on the right with a 30% and 70% ratio respectively. 2035 | 2036 | ```csharp 2037 | LayoutInfo layout = LayoutInfo.Horizontal(inspectorViewLayout, sceneViewLayout, 0.3f); 2038 | ``` 2039 | 2040 | #### Tab Group Example 2041 | 2042 | Grouping an inspector view and a console view into a single tab group. 2043 | 2044 | ```csharp 2045 | LayoutInfo layout = LayoutInfo.Group(inspectorViewLayout, consoleViewLayout); 2046 | ``` 2047 | 2048 | ## Overriding Scene Parameters 2049 | 2050 | 2051 | 1. **Create the Script**: Save the below code in a new C# script named `ScenesSetupExample.cs`. 2052 | 2. **Attach the Script**: Add the `ScenesSetupExample` script to a new GameObject in your scene. 2053 | 2054 | ### Example 2055 | 2056 | ```csharp 2057 | using Battlehub.RTCommon; 2058 | using Battlehub.RTHandles; 2059 | using UnityEngine; 2060 | 2061 | namespace Battlehub.RTEditor.Examples.SceneSetup 2062 | { 2063 | /// <summary> 2064 | /// This extension initializes 2D scene window 2065 | /// </summary> 2066 | public class SceneSetupExample : RuntimeWindowExtension 2067 | { 2068 | /// <summary> 2069 | /// Type of window to be extended 2070 | /// </summary> 2071 | public override string WindowTypeName => BuiltInWindowNames.Scene; 2072 | 2073 | protected override void Extend(RuntimeWindow window) 2074 | { 2075 | // Get a reference to the IRuntimeSceneComponent of the window 2076 | IRuntimeSceneComponent sceneComponent = 2077 | window.IOCContainer.Resolve<IRuntimeSceneComponent>(); 2078 | 2079 | // This is the point the camera looks at and orbits around 2080 | sceneComponent.Pivot = Vector3.zero; 2081 | 2082 | // Switch the scene component and scene camera to orthographic mode 2083 | sceneComponent.IsOrthographic = true; 2084 | 2085 | // Disable scene gizmo 2086 | sceneComponent.IsSceneGizmoEnabled = false; 2087 | 2088 | // Disable rotation 2089 | sceneComponent.CanRotate = false; 2090 | 2091 | // Disable free move 2092 | sceneComponent.CanFreeMove = false; 2093 | 2094 | // Prevent camera position changes when zooming in and out 2095 | sceneComponent.ChangeOrthographicSizeOnly = true; 2096 | 2097 | // Set initial orthographic size 2098 | sceneComponent.OrthographicSize = 5.0f; 2099 | 2100 | // Set camera position according to window.Args 2101 | const float distance = 100; 2102 | sceneComponent.CameraPosition = -Vector3.forward * distance; 2103 | } 2104 | } 2105 | } 2106 | ``` 2107 | 2108 | ### Explanation 2109 | 2110 | - **WindowTypeName**: Specifies the type of window to be extended. In this case, it is set to `BuiltInWindowNames.Scene`. 2111 | - **Extend Method**: Contains the logic to configure the scene window. It sets various properties of the `IRuntimeSceneComponent` 2112 | to customize the scene view, such as setting the camera to orthographic mode, disabling certain features, and setting the initial camera position and size. 2113 | 2114 | ![Override Scene Params][override_scene_params] 2115 | 2116 | ## Overriding Tools Panel 2117 | 2118 | To override the tools panel in the runtime editor, follow these steps: 2119 | 2120 | 1. Create a script named `ToolsPanelOverride`. 2121 | 2. Create a new GameObject and add the `ToolsPanelOverride` component to it. 2122 | 3. Set the `Tools Prefab` field in the Inspector. 2123 | 2124 | Here's an example script demonstrating how to override the tools panel: 2125 | 2126 | ```csharp 2127 | using Battlehub.RTEditor; 2128 | using UnityEngine; 2129 | 2130 | public class ToolsPanelOverride : LayoutExtension 2131 | { 2132 | [SerializeField] 2133 | private Transform m_toolsPrefab = null; 2134 | 2135 | protected override void OnBeforeBuildLayout(IWindowManager wm) 2136 | { 2137 | wm.OverrideTools(m_toolsPrefab); 2138 | } 2139 | } 2140 | ``` 2141 | 2142 | ![Override Tools][override_tools] 2143 | 2144 | > **Note** </br> 2145 | The original tools view can be found in: Assets/Battlehub/RTEditor/Content/Runtime/RTEditor/Prefabs/Views/**ToolsView.prefab** 2146 | 2147 | ## Setting ui scale 2148 | ## Overriding UI Scale 2149 | 2150 | To override the UI scale in the runtime editor, follow these steps: 2151 | 2152 | 1. Create a script named `UIScaleOverride`. 2153 | 2. Create a new GameObject and add the `UIScaleOverride` component to it. 2154 | 3. Set the desired scale in the Inspector. 2155 | 2156 | ### Example Script 2157 | 2158 | Here's an example script demonstrating how to override the UI scale: 2159 | 2160 | ```csharp 2161 | using Battlehub.RTCommon; 2162 | using Battlehub.RTEditor; 2163 | using UnityEngine; 2164 | 2165 | public class UIScaleOverride : LayoutExtension 2166 | { 2167 | [SerializeField] 2168 | private float Scale = 2; 2169 | 2170 | protected override void OnInit() 2171 | { 2172 | ISettingsComponent settings = IOC.Resolve<ISettingsComponent>(); 2173 | settings.UIScale = Scale; 2174 | } 2175 | } 2176 | ``` 2177 | 2178 | #### Before UI Scale Override 2179 | ![Before UI Scale Override][ui_scale_1] 2180 | 2181 | #### After UI Scale Override 2182 | ![After UI Scale Override][ui_scale_2] 2183 | 2184 | ## Overriding the Theme 2185 | 2186 | To override the theme in the runtime editor, follow these steps: 2187 | 2188 | 1. Create a script named `OverrideTheme`. 2189 | 2. Create a new GameObject and add the `OverrideTheme` component to it. 2190 | 3. Assign a ThemeAsset to the `m_theme` field in the Inspector. 2191 | 2192 | ### Example Script 2193 | 2194 | Here's an example script demonstrating how to override the theme: 2195 | 2196 | ```csharp 2197 | using Battlehub.RTCommon; 2198 | using UnityEngine; 2199 | 2200 | namespace Battlehub.RTEditor.Demo 2201 | { 2202 | public class OverrideTheme : EditorExtension 2203 | { 2204 | [SerializeField] 2205 | private ThemeAsset m_theme; 2206 | 2207 | protected override void OnInit() 2208 | { 2209 | ISettingsComponent settings = IOC.Resolve<ISettingsComponent>(); 2210 | settings.SelectedTheme = m_theme; 2211 | } 2212 | } 2213 | } 2214 | ``` 2215 | ![Override Theme][override_theme] 2216 | 2217 | ## Inspector View 2218 | The main purpose of the inspector is to create different editors depending on the type of selected object and its components. Here is a general idea of what is happening: 2219 | 2220 | 1. **GameObject Selection**: When the user selects a GameObject, the inspector creates a GameObject editor. 2221 | 2. **Component Editors Creation**: The GameObject editor creates component editors for each component attached to the selected GameObject. 2222 | 3. **Property Editors Creation**: Each component editor creates property editors for the properties of the component. 2223 | 2224 | ![Insepector][inspector] 2225 | 2226 | ### Inspector Editors 2227 | 2228 | In the `Assets/Battlehub/RTEditor/Content/Runtime/RTEditor/Prefabs/Editors` folder, you can find various editor prefabs like `GameObjectEditor`, `AssetEditor`, `LayerEditor`, and `MaterialEditor`. 2229 | These editors are used by the inspector to create specific editing ui based on the selected object's type and its components. 2230 | ![Insepector][inspector_editors] 2231 | 2232 | ### Property Editors 2233 | 2234 | Property editors, created by component editors, are located in the `Assets/Battlehub/RTEditor/Content/Runtime/RTEditor/Prefabs/Editors/PropertyEditors` folder. 2235 | These editors provide specific ui for editing individual properties of components within the inspector. 2236 | ![Inspector][inspector_property_editors] 2237 | 2238 | ## Inspector Configuration 2239 | 2240 | To configure the editors used by the inspector, follow these steps: 2241 | 2242 | 1. Click `Tools -> Runtime Editor -> Inspector Configuration`. 2243 | 2244 | ![Inspector Configuration][inspector_configuration_1] 2245 | 2246 | 2. The configuration window has five sections: 2247 | - **Object Editors**: Select which editor to use for Game Objects and Asset Editors. 2248 | - **Property Editors**: Select which editors to use for component properties. 2249 | - **Material Editors**: Select which editors to use for materials. 2250 | - **Standard Component Editors**: Select which editors to use for standard components. 2251 | - **Script Editors**: Select which editors to use for scripts. 2252 | 2253 | 3. After selecting and enabling the desired component editors, click the `Save Editors Map` button. 2254 | 2255 | ![Save Editors Map][inspector_configuration_window] 2256 | 2257 | ### Register Editors Programatically 2258 | 2259 | To register property editors programmatically, you need to create a script that defines and registers your custom property editors. Below is an example demonstrating how to achieve this: 2260 | 2261 | ```csharp 2262 | using Battlehub.RTCommon; 2263 | using Battlehub.RTEditor; 2264 | using UnityEngine; 2265 | 2266 | public class RegisterPropertyEditorsExample : EditorExtension 2267 | { 2268 | [SerializeField] 2269 | private GameObject m_vector3Editor = null; 2270 | 2271 | [SerializeField] 2272 | private GameObject m_vector2Editor = null; 2273 | 2274 | protected override void OnInit() 2275 | { 2276 | base.OnInit(); 2277 | 2278 | IEditorsMap editorsMap = IOC.Resolve<IEditorsMap>(); 2279 | if (m_vector3Editor != null) 2280 | { 2281 | editorsMap.RemoveMapping(typeof(Vector3)); 2282 | editorsMap.AddMapping(typeof(Vector3), m_vector3Editor, true, true); 2283 | EnableStyling(m_vector3Editor); 2284 | } 2285 | 2286 | if (m_vector2Editor != null) 2287 | { 2288 | editorsMap.RemoveMapping(typeof(Vector2)); 2289 | editorsMap.AddMapping(typeof(Vector2), m_vector2Editor, true, true); 2290 | EnableStyling(m_vector2Editor); 2291 | } 2292 | } 2293 | } 2294 | ``` 2295 | 2296 | ### Steps to Register Component Editors Programmatically 2297 | 2298 | 1. **Create a New Script**: Create a new C# script, for example, `RegisterPropertyEditorsExample.cs`. 2299 | 2300 | 2. **Implement the Script**: Implement the script as shown above, registering your custom property editors. 2301 | 2302 | 3. **Attach the Script to a GameObject**: Create a new GameObject in your scene and attach the `RegisterPropertyEditorsExample` script to it 2303 | 2304 | To register a custom component editor programmatically, you can use a similar approach to the one used for registering property editors. Below is an example demonstrating how to achieve this: 2305 | 2306 | ```csharp 2307 | using Battlehub.RTCommon; 2308 | using Battlehub.RTEditor; 2309 | using UnityEngine; 2310 | 2311 | public class CustomComponentEditorInit : EditorExtension 2312 | { 2313 | [SerializeField] 2314 | private GameObject m_terrainComponentEditor = null; 2315 | 2316 | protected override void OnInit() 2317 | { 2318 | base.OnInit(); 2319 | 2320 | IEditorsMap editorsMap = IOC.Resolve<IEditorsMap>(); 2321 | if (m_terrainComponentEditor != null) 2322 | { 2323 | if (!editorsMap.HasMapping(typeof(Terrain))) 2324 | { 2325 | editorsMap.AddMapping(typeof(Terrain), 2326 | m_terrainComponentEditor, enabled: true, isPropertyEditor: false); 2327 | } 2328 | } 2329 | } 2330 | } 2331 | ``` 2332 | 2333 | ### Component Properties Visibility 2334 | 2335 | To select the properties displayed by the component editor, you need to create a class that inherits from `ComponentDescriptorBase<>`. Implement the `GetProperties` method to return `PropertyDescriptors` for all properties that will be present in the component editor UI. 2336 | 2337 | For example, suppose you have the following component, and you want to display `Field1`, `Property1`, and a button that will call `Method()`. `Field2` and `Property2` must stay hidden. 2338 | 2339 | ```csharp 2340 | public class MyComponent : MonoBehaviour 2341 | { 2342 | public string Field1; 2343 | 2344 | public string Field2; 2345 | 2346 | public string Property1 2347 | { 2348 | get; 2349 | set; 2350 | } 2351 | 2352 | public string Property2 2353 | { 2354 | get; 2355 | set; 2356 | } 2357 | 2358 | public void Method() 2359 | { 2360 | Debug.Log("Method"); 2361 | } 2362 | } 2363 | ``` 2364 | 2365 | To achieve this, create the following component descriptor: 2366 | 2367 | ```csharp 2368 | using Battlehub.RTEditor; 2369 | using Battlehub.Utils; 2370 | using UnityEngine; 2371 | 2372 | public class MyComponentDescriptor : ComponentDescriptorBase<MyComponent> 2373 | { 2374 | public override PropertyDescriptor[] GetProperties(ComponentEditor editor, object converter) 2375 | { 2376 | var field1 = Strong.MemberInfo((MyComponent x) => x.Field1); 2377 | var property1 = Strong.MemberInfo((MyComponent x) => x.Property1); 2378 | var method = Strong.MethodInfo((MyComponent x) => x.Method()); 2379 | 2380 | return new[] 2381 | { 2382 | new PropertyDescriptor("My Field", editor.Components, field1), 2383 | new PropertyDescriptor("My Property", editor.Components, property1), 2384 | new PropertyDescriptor("Click Me!", editor.Components, method), 2385 | }; 2386 | } 2387 | } 2388 | ``` 2389 | 2390 | And enable the ComponentEditor using the [Inspector Configuration Window](#inspector-configuration). 2391 | 2392 | ![My Component][my_component] 2393 | 2394 | Here is more complex example of a `TransformComponentDescriptor`: 2395 | 2396 | ```csharp 2397 | using UnityEngine; 2398 | using System.Reflection; 2399 | using Battlehub.Utils; 2400 | using Battlehub.RTCommon; 2401 | 2402 | namespace Battlehub.RTEditor 2403 | { 2404 | public class TransformComponentDescriptor : ComponentDescriptorBase<Transform> 2405 | { 2406 | public override object CreateConverter(ComponentEditor editor) 2407 | { 2408 | object[] converters = new object[editor.Components.Length]; 2409 | Component[] components = editor.Components; 2410 | for (int i = 0; i < components.Length; ++i) 2411 | { 2412 | Transform transform = (Transform)components[i]; 2413 | if (transform != null) 2414 | { 2415 | converters[i] = new TransformPropertyConverter 2416 | { 2417 | ExposeToEditor = transform.GetComponent<ExposeToEditor>() 2418 | }; 2419 | } 2420 | } 2421 | return converters; 2422 | } 2423 | 2424 | public override PropertyDescriptor[] GetProperties( 2425 | ComponentEditor editor, object converter) 2426 | { 2427 | object[] converters = (object[])converter; 2428 | 2429 | MemberInfo position = Strong.PropertyInfo( 2430 | (Transform x) => x.localPosition, "localPosition"); 2431 | MemberInfo positionConverted = Strong.PropertyInfo( 2432 | (TransformPropertyConverter x) => x.LocalPosition, "LocalPosition"); 2433 | MemberInfo rotation = Strong.PropertyInfo( 2434 | (Transform x) => x.localRotation, "localRotation"); 2435 | MemberInfo rotationConverted = Strong.PropertyInfo( 2436 | (TransformPropertyConverter x) => x.LocalEuler, "LocalEulerAngles"); 2437 | MemberInfo scale = Strong.PropertyInfo( 2438 | (Transform x) => x.localScale, "localScale"); 2439 | MemberInfo scaleConverted = Strong.PropertyInfo( 2440 | (TransformPropertyConverter x) => x.LocalScale, "LocalScale"); 2441 | 2442 | return new[] 2443 | { 2444 | new PropertyDescriptor("Position", converters, positionConverted, position), 2445 | new PropertyDescriptor("Rotation", converters, rotationConverted, rotation), 2446 | new PropertyDescriptor("Scale", converters, scaleConverted, scale) 2447 | }; 2448 | } 2449 | } 2450 | } 2451 | 2452 | ``` 2453 | 2454 | > **Note** </br> 2455 | TransformPropertyConverter is used to convert a quaternion to Euler angles, enabling the use of Vector3Editor instead of QuaternionEditor. 2456 | 2457 | > **Note** <br/> 2458 | The remaining built-in component descriptors can be found in the `Assets/Battlehub/RTEditor/Runtime/RTEditor/Editors/ComponentDescriptors` folder. 2459 | 2460 | > **Note** </br> 2461 | To save a new component, you should create a **Surrogate** class for it. See the [Serializer Extensions](#serializer-extensions) section for details. 2462 | 2463 | ### Custom Type Properties Visibility 2464 | 2465 | Here is the example on how to create custom type property descriptor: 2466 | 2467 | ```csharp 2468 | using System.Collections.Generic; 2469 | using Battlehub.RTEditor; 2470 | using UnityEngine; 2471 | 2472 | 2473 | public class CustomTypeDescriptor : CustomTypeDescriptorBase<CustomType> 2474 | { 2475 | public override PropertyDescriptor[] GetProperties() 2476 | { 2477 | return new [] 2478 | { 2479 | Property("Name", (CustomType x) => x.Name), 2480 | Property("Val", (CustomType x) => x.Value, new Range(1, 100)), 2481 | Property("Col", (CustomType x) => x.Color), 2482 | Method("Reset", (CustomType x) => x.Reset()) 2483 | }; 2484 | } 2485 | } 2486 | 2487 | [System.Serializable] 2488 | public class CustomType 2489 | { 2490 | [SerializeField] 2491 | private string m_name; 2492 | 2493 | public string Name 2494 | { 2495 | get { return m_name; } 2496 | set { m_name = value; } 2497 | } 2498 | 2499 | [SerializeField] 2500 | private float m_value; 2501 | 2502 | public float Value 2503 | { 2504 | get { return m_value; } 2505 | set { m_value = value; } 2506 | } 2507 | 2508 | [SerializeField] 2509 | private Color m_color; 2510 | 2511 | public Color Color 2512 | { 2513 | get { return m_color; } 2514 | set { m_color = value; } 2515 | } 2516 | 2517 | public void Reset() 2518 | { 2519 | Color = Color.white; 2520 | Value = 0; 2521 | } 2522 | } 2523 | 2524 | public class NewBehaviourScript : MonoBehaviour 2525 | { 2526 | [SerializeField] 2527 | private CustomType m_field = new CustomType(); 2528 | 2529 | [SerializeField] 2530 | private List<CustomType> m_list; 2531 | } 2532 | ``` 2533 | 2534 | ### Customizing Component Editor Header 2535 | 2536 | To customize the header of a component editor, you may want to override the `GetHeaderDescriptor` method. This allows you to specify various aspects of the header, such as its display name, the visibility of certain buttons, and whether to show an icon. 2537 | 2538 | Here is an example of how to override the `GetHeaderDescriptor` method in a component descriptor: 2539 | 2540 | ```csharp 2541 | public class MyComponentDescriptor : ComponentDescriptorBase<MyComponent> 2542 | { 2543 | public override HeaderDescriptor GetHeaderDescriptor(IRTE editor) 2544 | { 2545 | // Customize the header descriptor as needed 2546 | return new HeaderDescriptor( 2547 | displayName: "My Custom Component", 2548 | showExpander: true, 2549 | showResetButton: true, 2550 | showEnableButton: true, 2551 | showRemoveButton: true, 2552 | showIcon: true, 2553 | icon: null 2554 | ); 2555 | } 2556 | } 2557 | ``` 2558 | 2559 | ### HeaderDescriptor Structure 2560 | 2561 | The `HeaderDescriptor` struct allows you to configure the appearance and functionality of the component editor's header. It has the following properties: 2562 | 2563 | - **DisplayName**: The display name of the component. 2564 | - **ShowExpander**: Determines whether to show the expander toggle (usually used to collapse or expand the component's properties). 2565 | - **ShowResetButton**: Determines whether to show the reset button. 2566 | - **ShowEnableButton**: Determines whether to show the enable/disable button. 2567 | - **ShowRemoveButton**: Determines whether to show the remove button. 2568 | - **Icon**: The icon to be displayed in the header. 2569 | - **ShowIcon**: Determines whether to show the icon. 2570 | 2571 | ## Localization 2572 | 2573 | To localize your application, follow these steps to create and load string resources. 2574 | 2575 | 1. Create a file named `My.StringResources.en-US.xml` with the following format and place it in the `Resources` folder: 2576 | 2577 | ```xml 2578 | <?xml version="1.0" encoding="utf-8"?> 2579 | <StringResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 2580 | <Resources> 2581 | <!-- Example String Resource --> 2582 | <StringResource id="ID_String" value="Localized String"/> 2583 | </Resources> 2584 | </StringResources> 2585 | ``` 2586 | 2587 | 2. Load the string resources using the following code: 2588 | 2589 | ```csharp 2590 | ILocalization lc = IOC.Resolve<ILocalization>(); 2591 | lc.LoadStringResources("My.StringResources"); 2592 | ``` 2593 | 2594 | 3. Retrieve localized strings using the `GetString` method: 2595 | 2596 | ```csharp 2597 | var localizedString = lc.GetString("ID_String"); 2598 | ``` 2599 | 2600 | ### Built-in String Resources 2601 | 2602 | Runtime Editor includes the following built-in string resources: 2603 | - `RTBuilder.StringResources.en-US` 2604 | - `RTDeformer.StringResources.en-US` 2605 | - `RTEditor.StringResources.en-US` 2606 | - `RTTerrain.StringResources.en-US` 2607 | 2608 | > **Note** </br> 2609 | Locale can be changed using following code `lc.Locale = "en-US";` This will work provided that string resources files with the corresponding prefix exist. 2610 | 2611 | # UI Controls 2612 | ## Dock Panel Control 2613 | https://rteditor.battlehub.net/v350/manual/dock-panels.html 2614 | ## Tree View Control 2615 | https://rteditor.battlehub.net/v350/manual/vtv.html 2616 | ## Menu Control 2617 | https://rteditor.battlehub.net/v350/manual/menu-control.html 2618 | 2619 | 2620 | # Runtime Transform Handles 2621 | 2622 | Runtime Transform Handles are the runtime 3D controls used to manipulate GameObjects in the scene. 2623 | There are three built-in transform tools for positioning, rotating, and scaling objects via the transform component. 2624 | Supplementary controls, such as the scene gizmo and grid, allow for changing the viewing angle and projection mode, and for showing the grid in the scene. 2625 | Other important components include 2626 | 2627 | - **Selection Component**: Allows for selecting objects. 2628 | - **Scene Component**: Facilitates navigation within the scene. 2629 | - **Handles Component**: Enables changing the appearance of transform handles. 2630 | 2631 | https://github.com/BattlehubCode/RTH_Docs 2632 | 2633 | ## Runtime Gizmos 2634 | 2635 | Runtime Gizmos are the runtime 3D controls used to manipulate items in the scene. Unlike transform handles, gizmos do not modify the transformation of objects. 2636 | Instead, they are used to modify colliders, bounding boxes, and properties of light and audio sources. 2637 | 2638 | https://rteditor.battlehub.net/v350/manual/gizmos.html 2639 | 2640 | # Animation Editor 2641 | 2642 | https://rteditor.battlehub.net/v350/manual/animation-editor.html 2643 | 2644 | # Runtime Editor Extensions 2645 | 2646 | https://rteditor.battlehub.net/v350/manual/editor-extensions.html 2647 | 2648 | # Runtime Scripting 2649 | 2650 | In Runtime Editor 4.4.0, support for Jint as a runtime scripting backend was added. 2651 | Jint is a JavaScript interpreter for .NET that can run on any modern .NET platform, as it supports .NET Standard 2.0 and .NET 4.6.2 targets (and later). [Learn more about Jint here](https://github.com/sebastienros/jint). 2652 | 2653 | ## Getting Started with Runtime Scripting 2654 | 2655 | 1. Unpack `Assets/Battlehub/3 RTEditor Scripting (Jint).unitypackage`. 2656 | 2. If you're starting with a new scene, click **Tools > Runtime Editor > Create RTEditor**. 2657 | 3. Click **Tools > Runtime Editor > Add Runtime Scripting (Jint)**. 2658 | ![Add Jint Scripting][jint_scripting_1] 2659 | 4. Start the Runtime Editor. 2660 | 5. In the Project View, right-click and select **Create > JintScript** from the context menu. 2661 | ![Create Jint Script][jint_scripting_2] 2662 | 6. Create an empty GameObject. 2663 | ![Create Empty GameObject][jint_scripting_3] 2664 | 7. In the Inspector, click **Add Component** and type `JintScript`. 2665 | ![Add Component][jint_scripting_4] 2666 | 8. Click **Play** in the Runtime Editor. 2667 | 9. Observe `Debug.Log` messages printed by `JintScript` in the console. 2668 | ![Click Play in Runtime Editor][jint_scripting_5] 2669 | 10. You can open the script editor by double-clicking the script in the Project View. 2670 | ![Script Editor][jint_scripting_6] 2671 | 2672 | ## Jint Script 2673 | A typical Jint script looks as follows: 2674 | 2675 | ```javascript 2676 | var UnityEngine = importNamespace('UnityEngine'); 2677 | 2678 | var Awake = function (component) { 2679 | UnityEngine.Debug.Log("Awake"); 2680 | }; 2681 | 2682 | var Start = function (component) { 2683 | UnityEngine.Debug.Log("Start"); 2684 | }; 2685 | 2686 | var OnDestroy = function (component) { 2687 | UnityEngine.Debug.Log("OnDestroy"); 2688 | }; 2689 | 2690 | return { 2691 | Awake: Awake, 2692 | Start: Start, 2693 | OnDestroy: OnDestroy 2694 | }; 2695 | ``` 2696 | 2697 | Method names correspond to the names of Unity MonoBehaviour methods. You can declare Awake, OnEnable, Start, OnDisable, Update, FixedUpdate, and OnDestroy. 2698 | There are two additional methods called by the Runtime Editor: OnRemove and OnRestore. The Runtime Editor does not immediately delete GameObjects; they remain in the undo stack until eventually destroyed, at which point the OnDestroy method is called. If you restore a GameObject from the undo/redo stack, the OnRestore method will be called. 2699 | Other methods like LateUpdate and OnApplicationQuit are currently not supported. 2700 | 2701 | ## Declaring variables 2702 | There are four types of properties that can be exposed to the Runtime Editor and displayed in the Inspector: String, Number, Boolean, and Object. 2703 | 2704 | ```javascript 2705 | var UnityEngine = importNamespace('UnityEngine'); 2706 | var Log = UnityEngine.Debug.Log; 2707 | 2708 | var Start = function(component) { 2709 | Log(this.String + "", "" + this.Number + "", "" + this.Boolean + "", "" + this.Object); 2710 | } 2711 | 2712 | return { 2713 | String: ""Runtime Editor"", 2714 | Number: 2024, 2715 | Boolean: true, 2716 | Object: {}, 2717 | 2718 | Start : Start 2719 | }; 2720 | ``` 2721 | ![Properties][jint_scripting_7] 2722 | 2723 | ## Using UnityEngine API 2724 | 2725 | Using the Unity API from a Jint script is intuitive and not much different from how it's used in C#. 2726 | However, to access methods of certain objects, you need to unwrap the object using the u(obj) method, which is a shortcut for jint.clrHelper.unwrap(obj). 2727 | See the u(rb) call in the example below and refer to the following issue for details: [https://github.com/sebastienros/jint/pull/1613](https://github.com/sebastienros/jint/pull/1613) 2728 | 2729 | ``` javascript 2730 | var UnityEngine = importNamespace('UnityEngine'); 2731 | var RTCommon = importNamespace('Battlehub.RTCommon'); 2732 | 2733 | var GameObject = UnityEngine.GameObject; 2734 | var PrimitiveType = UnityEngine.PrimitiveType; 2735 | var Rigidbody = UnityEngine.Rigidbody; 2736 | var Vector3 = UnityEngine.Vector3; 2737 | 2738 | var ExposeToEditor = RTCommon.ExposeToEditor; 2739 | 2740 | var Start = function (component) { 2741 | var plane = GameObject.CreatePrimitive(PrimitiveType.Plane); 2742 | plane.transform.parent = component.transform.parent; 2743 | plane.position = Vector3.zero; 2744 | plane.AddComponent(ExposeToEditor); 2745 | 2746 | var go = GameObject.CreatePrimitive(PrimitiveType.Cube); 2747 | go.transform.parent = component.transform.parent; 2748 | go.transform.position = new Vector3(0, 10, 0); 2749 | go.AddComponent(ExposeToEditor); 2750 | 2751 | var rb = go.AddComponent(Rigidbody); 2752 | u(rb).AddForce( 2753 | Vector3.right, 2754 | UnityEngine.ForceMode.Impulse); 2755 | } 2756 | 2757 | return { 2758 | Start: Start 2759 | }; 2760 | " 2761 | ``` 2762 | ![Properties][jint_scripting_8] 2763 | 2764 | ## JintComponent.Add, JintComponent.Get 2765 | 2766 | JintComponent.Add can be used to add a Jint script by name, equivalent to AddComponent. 2767 | 2768 | ``` javascript 2769 | var UnityEngine = importNamespace('UnityEngine'); 2770 | var RTScripting = importNamespace('Battlehub.RTScripting'); 2771 | 2772 | var Hello = function (arg) { 2773 | UnityEngine.Debug.Log(""Hi "" + arg + "", nice to meet you!"") 2774 | } 2775 | 2776 | var Start = function (component) { 2777 | RTScripting.JintComponent.Add(component.gameObject, ""TestJintComponent"") 2778 | } 2779 | 2780 | return { 2781 | Start: Start, 2782 | Hello: Hello 2783 | }; 2784 | ``` 2785 | 2786 | JintComponent.Get can be used to retrieve a Jint script by name, equivalent to GetComponent. 2787 | 2788 | ```javascript 2789 | var UnityEngine = importNamespace('UnityEngine'); 2790 | var RTScripting = importNamespace('Battlehub.RTScripting'); 2791 | 2792 | var Start = function (component) { 2793 | UnityEngine.Debug.Log(""I am Test Component!""); 2794 | 2795 | var otherComponent = RTScripting.JintComponent.Get(component.gameObject, ""AddJintComponent""); 2796 | otherComponent.Call(""Hello"", ""Test Component""); 2797 | } 2798 | 2799 | return { 2800 | Start: Start 2801 | } 2802 | ``` 2803 | 2804 | ## Create Jint Script programmatically 2805 | 2806 | The following approach can be used to create Jint scripts programmatically: 2807 | 2808 | ```csharp 2809 | private async Task CreateExampleScriptAsync(string gameObjectName, string scriptName, string scriptCode) 2810 | { 2811 | var editor = IOC.Resolve<IRuntimeEditor>(); 2812 | 2813 | // Get script path inside root folder 2814 | var scriptPath = editor.GetRootFolderPath($"{scriptName}.js"); 2815 | 2816 | // Create script asset 2817 | await editor.CreateAssetAsync(scriptCode, scriptPath, forceOverwrite: true); 2818 | 2819 | // Create game object 2820 | var go = CreateGameObject(gameObjectName); 2821 | 2822 | // And add it to the Scene 2823 | editor.AddGameObjectToScene(go); 2824 | 2825 | // Add JintComponent with newly created script 2826 | go.AddJintComponent(scriptName); 2827 | } 2828 | ``` 2829 | 2830 | ```csharp 2831 | string scriptCode = @" 2832 | var UnityEngine = importNamespace('UnityEngine'); 2833 | 2834 | UnityEngine.Debug.Log(""Hello World!""); 2835 | "; 2836 | 2837 | CreateExampleScript( 2838 | gameObjectName: "Hello World", 2839 | scriptName: "Hello World", 2840 | scriptCode: scriptCode); 2841 | ``` 2842 | 2843 | ## More Runtime Scripting samples 2844 | 2845 | 1. Unpack **Assets/Battlehub/3 RTEditor Scripting Demo (Jint).unitypackage.** 2846 | 2. Click Tools > Runtime Editor > Show me examples. 2847 | 3. Open **Scene61 - Scripting (Jint)** in the [Example Scenes section](#example-scenes). 2848 | ![Properties][jint_scripting_9] 2849 | 2850 | # Asset Database 2851 | In **Runtime Editor 4.0.0**, the RTSL subsystem was replaced by the [Runtime Asset Database](https://github.com/Battlehub0x/RuntimeAssetDatabase). 2852 | For information on making the runtime editor compatible with projects created using previous versions, see the [Compatibility Modes](#compatibility-modes) section. 2853 | The documentation for Runtime Save Load can be found [here](https://rteditor.battlehub.net/v350/manual/save-load.html). 2854 | This section focuses on the new asset database and new API methods. 2855 | 2856 | ## Core Methods and Events 2857 | 2858 | Most of the new core project, scene, prefab, and asset management methods are defined in the `IRuntimeEditor` and `IAssetDatabaseModel` interfaces. 2859 | 2860 | ## IRuntimeEditor and IAssetDatabaseModel Interfaces 2861 | 2862 | ### IRuntimeEditor Interface 2863 | 2864 | The `IRuntimeEditor` interface extends `IRTE` and `IAssetDatabaseModel` to provide a comprehensive set of methods and properties for managing projects, scenes, and assets in the Runtime Editor. Key functionalities include: 2865 | 2866 | - **Project Management**: 2867 | - Create, load, delete, and list projects. 2868 | - Check if a project type is supported. 2869 | - Handle project-related events. 2870 | 2871 | - **Scene Management**: 2872 | - Create new scenes and save scenes with specified parameters. 2873 | - Handle scene-related events. 2874 | 2875 | - **Asset Management**: 2876 | - Create assets from binary data, text, or Unity objects. 2877 | - Handle asset-related events. 2878 | 2879 | - **Window Management**: 2880 | - Create or activate editor windows. 2881 | - Reset to default layout. 2882 | 2883 | ### IAssetDatabaseModel Interface 2884 | 2885 | The `IAssetDatabaseModel` interface provides methods and events for managing assets within the runtime editor. It includes: 2886 | 2887 | - **Event Handling**: 2888 | - Events for project loading, unloading, asset creation, deletion, and modification. 2889 | - Events for scene initialization, asset instantiation, and changes in asset selection. 2890 | 2891 | - **Asset Operations**: 2892 | - Methods to load, save, and delete assets. 2893 | - Methods to move, duplicate, and instantiate assets. 2894 | - Methods to open, close, and edit assets and prefabs. 2895 | - Methods to manage asset folders. 2896 | 2897 | - **Utility Methods**: 2898 | - Methods to add and remove runtime serializable types. 2899 | - Methods to add and remove extensions and external asset loaders. 2900 | - Methods to check various conditions related to assets, instances, and prefabs. 2901 | 2902 | ### Key Methods and Properties 2903 | 2904 | #### IRuntimeEditor 2905 | ```csharp 2906 | Task<ProjectListEntry[]> GetProjectsAsync(); 2907 | Task<ProjectListEntry> CreateProjectAsync(string projectPath); 2908 | Task<ProjectListEntry> DeleteProjectAsync(string projectPath); 2909 | void NewScene(bool confirm = true); 2910 | void SaveScene(); 2911 | void SaveSceneAs(); 2912 | Task SaveCurrentSceneAsync(ID folderID, string name); 2913 | Task SaveCurrentSceneAsync(string path = null); 2914 | Task CreateAssetAsync(byte[] binaryData, string path, bool forceOverwrite = false, bool select = true); 2915 | Task CreateAssetAsync(string text, string path, bool forceOverwrite = false, bool select = true); 2916 | Task CreateAssetAsync(UnityObject obj, string path = null, bool forceOverwrite = false, bool? includeDependencies = null, bool? variant = null, bool select = true); 2917 | ``` 2918 | 2919 | #### IAssetDatabaseModel 2920 | ```csharp 2921 | Task LoadProjectAsync(string projectID, string version = null); 2922 | Task UnloadProjectAsync(); 2923 | Task<ID> CreateFolderAsync(string path); 2924 | Task<ID> CreateAssetAsync(object obj, string path, bool variant = false, bool extractSubassets = false); 2925 | Task<ID> ImportExternalAssetAsync(ID folderID, object key, string loaderID, string desiredName); 2926 | Task<ID> ImportExternalAssetAsync(ID folderID, ID assetID, object key, string loaderID, string desiredName); 2927 | Task InitializeNewSceneAsync(); 2928 | Task UnloadAllAndClearSceneAsync(); 2929 | Task SaveAssetAsync(ID assetID); 2930 | Task UpdateThumbnailAsync(ID assetID); 2931 | Task MoveAssetsAsync(IReadOnlyList<ID> assetIDs, IReadOnlyList<string> toPaths); 2932 | Task DuplicateAssetsAsync(IReadOnlyList<ID> assetIDs, IReadOnlyList<string> toPaths); 2933 | Task DeleteAssetsAsync(IReadOnlyList<ID> assetIDs); 2934 | Task SelectPrefabAsync(GameObject instance); 2935 | Task OpenPrefabAsync(GameObject instance); 2936 | Task ClosePrefabAsync(); 2937 | Task OpenAssetAsync(ID assetID); 2938 | Task<InstantiateAssetsResult> InstantiateAssetsAsync(ID[] assetIDs, Transform parent = null); 2939 | Task DetachAsync(GameObject[] instances, bool completely); 2940 | Task SetDirtyAsync(Component component); 2941 | Task DuplicateAsync(GameObject[] instances); 2942 | Task ReleaseAsync(GameObject[] instances); 2943 | Task ApplyChangesAsync(GameObject instance); 2944 | Task ApplyToBaseAsync(GameObject instance); 2945 | Task RevertToBaseAsync(GameObject instance); 2946 | Task<byte[]> SerializeAsync(object asset); 2947 | Task<object> DeserializeAsync(byte[] data, object target = null); 2948 | Task<T> GetValueAsync<T>(string key); 2949 | Task SetValueAsync<T>(string key, T obj); 2950 | Task DeleteValueAsync<T>(string key); 2951 | ``` 2952 | 2953 | ## IAssetDatabaseModel vs IRuntimeEditor Interface 2954 | 2955 | ```csharp 2956 | var assetDatabase = IOC.Resolve<IAssetDatabaseModel>(); 2957 | var runtimeEditor = IOC.Resolve<IRuntimeEditor>(); 2958 | ``` 2959 | 2960 | These two interfaces can be used almost interchangeably. 2961 | 2962 | `IAssetDatabaseModel` has fewer methods, but it might be a good idea to use it if you want some parts of your code to run without opening the runtime editor. 2963 | 2964 | Apart from the runtime editor itself, there are two implementations of the `IAssetDatabaseModel` interface: `AssetDatabaseModel` and `AssetDatabaseModelOverRTSL` (which exists for [compatibility](#compatibility-modes) with projects created using the Runtime Save Load subsystem in previous runtime editor versions). 2965 | 2966 | - The `AssetDatabaseModel` is a wrapper for the [Runtime Asset Database](https://github.com/Battlehub0x/RuntimeAssetDatabase). 2967 | - The `AssetDatabaseModelOverRTSL` is a wrapper for [RTSL](https://rteditor.battlehub.net/v350/manual/save-load.html). 2968 | 2969 | ## Projects Root Folder Path 2970 | 2971 | You can set the projects root folder in two ways: 2972 | 2973 | 1. By updating the `Project Root Folder Path` field of the Runtime Editor (script) (see RuntimeEditor.prefab/Runtime Editor (Script)/Extra Settings/Projects Root Folder Path). 2974 | 2975 | 2. Programmatically: 2976 | 2977 | ```csharp 2978 | using Battlehub.RTEditor; 2979 | using Battlehub.RTCommon; 2980 | 2981 | public class SetRootFolderExt : EditorExtension 2982 | { 2983 | protected override void OnInit() 2984 | { 2985 | var editor = IOC.Resolve<IRuntimeEditor>(); 2986 | editor.ProjectsRootFolderPath = @"C:\Runtime Editor Projects"; 2987 | } 2988 | } 2989 | ``` 2990 | 2991 | ## Manage Projects 2992 | 2993 | The following example demonstrates how to create, open, close, and delete projects using the Runtime Editor. 2994 | 2995 | The projects are stored in [Application.persistentDataPath](https://docs.unity3d.com/ScriptReference/Application-persistentDataPath.html) folder 2996 | ![Persistent Data Path][persistent_data_path] 2997 | 2998 | A typical project folder looks like this: 2999 | ![Asset Database Project Folder][asset_database_project_folder] 3000 | 3001 | ```csharp 3002 | using Battlehub.RTCommon; 3003 | using Battlehub.UIControls.MenuControl; 3004 | using System; 3005 | using UnityEngine; 3006 | 3007 | namespace Battlehub.RTEditor.Examples.Scene31 3008 | { 3009 | /// <summary> 3010 | /// Example on how to create, open, close, and delete projects. 3011 | /// </summary> 3012 | [MenuDefinition] 3013 | public class ProjectManagementExampleMenu : EditorExtension 3014 | { 3015 | private IWindowManager m_wm; 3016 | private IRuntimeEditor m_editor; 3017 | 3018 | protected override void OnInit() 3019 | { 3020 | base.OnInit(); 3021 | 3022 | m_wm = IOC.Resolve<IWindowManager>(); 3023 | m_editor = IOC.Resolve<IRuntimeEditor>(); 3024 | m_editor.BeforeLoadProject += OnBeforeLoadProject; 3025 | m_editor.LoadProject += OnLoadProject; 3026 | m_editor.BeforeUnloadProject += OnBeforeUnloadProject; 3027 | m_editor.UnloadProject += OnUnloadProject; 3028 | } 3029 | 3030 | protected override void OnCleanup() 3031 | { 3032 | base.OnCleanup(); 3033 | m_editor.BeforeLoadProject -= OnBeforeLoadProject; 3034 | m_editor.LoadProject -= OnLoadProject; 3035 | m_editor.BeforeUnloadProject -= OnBeforeUnloadProject; 3036 | m_editor.UnloadProject -= OnUnloadProject; 3037 | m_editor = null; 3038 | m_wm = null; 3039 | } 3040 | 3041 | [MenuCommand("Example/Manage Projects")] 3042 | public void ManageProjects() 3043 | { 3044 | m_wm.CreateWindow(BuiltInWindowNames.OpenProject); 3045 | } 3046 | 3047 | [MenuCommand("Example/Create Project")] 3048 | public void CreateProject() 3049 | { 3050 | m_wm.Prompt("Enter Project Name", "My Project", async (sender, args) => 3051 | { 3052 | using var b = m_editor.SetBusy(); 3053 | await m_editor.CreateProjectAsync(args.Text); 3054 | }); 3055 | } 3056 | 3057 | [MenuCommand("Example/Load Project")] 3058 | public void OpenProject() 3059 | { 3060 | m_wm.Prompt("Enter Project Name", "My Project", async (sender, args) => 3061 | { 3062 | using var b = m_editor.SetBusy(); 3063 | if (m_editor.IsProjectLoaded) 3064 | { 3065 | await m_editor.UnloadProjectAsync(); 3066 | } 3067 | await m_editor.LoadProjectAsync(args.Text); 3068 | }); 3069 | } 3070 | 3071 | [MenuCommand("Example/Delete Project")] 3072 | public void DeleteProject() 3073 | { 3074 | m_wm.Prompt("Enter Project Name", "My Project", async (sender, args) => 3075 | { 3076 | using var b = m_editor.SetBusy(); 3077 | await m_editor.DeleteProjectAsync(args.Text); 3078 | }); 3079 | } 3080 | 3081 | [MenuCommand("Example/Close Project")] 3082 | public async void CloseProject() 3083 | { 3084 | using var b = m_editor.SetBusy(); 3085 | await m_editor.UnloadProjectAsync(); 3086 | } 3087 | 3088 | private void OnBeforeLoadProject(object sender, EventArgs e) 3089 | { 3090 | Debug.Log($"On Before Load {m_editor.ProjectID}"); 3091 | } 3092 | 3093 | private void OnLoadProject(object sender, EventArgs e) 3094 | { 3095 | Debug.Log($"On Load {m_editor.ProjectID}"); 3096 | } 3097 | 3098 | private void OnBeforeUnloadProject(object sender, EventArgs e) 3099 | { 3100 | Debug.Log($"On Before Unload {m_editor.ProjectID}"); 3101 | } 3102 | 3103 | private void OnUnloadProject(object sender, EventArgs e) 3104 | { 3105 | Debug.Log($"On Unload {m_editor.ProjectID}"); 3106 | } 3107 | } 3108 | } 3109 | ``` 3110 | 3111 | ![Manage Projects][manage_projects_example] 3112 | ![Manage Projects][manage_projects_example_2] 3113 | 3114 | ## Import Export Project 3115 | 3116 | Install the [SharpZipLib package](https://docs.unity3d.com/Packages/com.unity.sharp-zip-lib@1.3/manual/index.html). In Package Manager, click on **"+ Add package by name"** and enter **"com.unity.sharp-zip-lib"** 3117 | 3118 | To export or import a project you can use following methods: 3119 | 3120 | ```csharp 3121 | private async Task ExportProjectAsync(string sourceProjectName, string archivePath) 3122 | { 3123 | if (File.Exists(archivePath)) 3124 | { 3125 | File.Delete(archivePath); 3126 | } 3127 | 3128 | using (var fs = File.OpenWrite(archivePath)) 3129 | { 3130 | var editor = IOC.Resolve<IRuntimeEditor>(); 3131 | using var b = editor.SetBusy(); 3132 | await editor.ExportProjectAsync(fs, sourceProjectName); 3133 | } 3134 | } 3135 | ``` 3136 | 3137 | ```csharp 3138 | private async Task ImportProjectAsync(string archivePath, string targetProjectName) 3139 | { 3140 | using (var fs = File.OpenRead(archivePath)) 3141 | { 3142 | var editor = IOC.Resolve<IRuntimeEditor>(); 3143 | using var b = editor.SetBusy(); 3144 | await editor.ImportProjectAsync(fs, targetProjectName); 3145 | } 3146 | } 3147 | ``` 3148 | 3149 | ## Create Folder 3150 | 3151 | The following example demonstrates how to create folders using the `IRuntimeEditor` interface in the Runtime Editor. 3152 | 3153 | ```csharp 3154 | using Battlehub.RTCommon; 3155 | using Battlehub.RTEditor; 3156 | using Battlehub.RTEditor.Models; 3157 | using Battlehub.UIControls.MenuControl; 3158 | using UnityEngine; 3159 | 3160 | [MenuDefinition] 3161 | public class CreateFolderExample : EditorExtension 3162 | { 3163 | private IRuntimeEditor m_editor; 3164 | 3165 | protected override void OnInit() 3166 | { 3167 | base.OnInit(); 3168 | 3169 | m_editor = IOC.Resolve<IRuntimeEditor>(); 3170 | m_editor.CreateFolder += OnCreateFolder; 3171 | } 3172 | 3173 | protected override void OnCleanup() 3174 | { 3175 | base.OnCleanup(); 3176 | m_editor.CreateFolder -= OnCreateFolder; 3177 | } 3178 | 3179 | [MenuCommand("Example/Create Folder")] 3180 | public void CreateFolder() 3181 | { 3182 | var wm = IOC.Resolve<IWindowManager>(); 3183 | wm.Prompt("Enter Folder Name", "New Folder", async (sender, args) => 3184 | { 3185 | string newFolderName = args.Text; 3186 | if (!m_editor.IsValidName(newFolderName)) 3187 | { 3188 | wm.MessageBox("Error", "Folder Name is invalid"); 3189 | return; 3190 | } 3191 | 3192 | // Use the current folder as a parent 3193 | var parentFolderID = m_editor.CurrentFolderID; 3194 | 3195 | // Make sure that the folder path is unique 3196 | string path = m_editor.GetUniquePath(parentFolderID, newFolderName); 3197 | 3198 | // Show busy indicator 3199 | using var b = m_editor.SetBusy(); 3200 | 3201 | await m_editor.CreateFolderAsync(path); 3202 | }); 3203 | } 3204 | 3205 | private void OnCreateFolder(object sender, CreateFolderEventArgs e) 3206 | { 3207 | // A folder is a special kind of asset and has an AssetID just like real assets 3208 | // The folder asset ID is temporary and it is different between sessions 3209 | ID assetID = e.AssetID; 3210 | 3211 | // You can get the folder path by its ID 3212 | var path = m_editor.GetPath(assetID); 3213 | 3214 | Debug.Log($"On Create Folder: {path}"); 3215 | } 3216 | } 3217 | 3218 | ``` 3219 | ## Set Current Folder 3220 | 3221 | The current folder is the folder selected in the project tree, whose contents are displayed in the right section of the asset database window. 3222 | 3223 | ```csharp 3224 | using Battlehub.RTCommon; 3225 | using Battlehub.RTEditor; 3226 | using Battlehub.RTEditor.Models; 3227 | using Battlehub.UIControls.MenuControl; 3228 | 3229 | [MenuDefinition] 3230 | public class SetCurrentFolderExample : EditorExtension 3231 | { 3232 | private IRuntimeEditor m_editor; 3233 | 3234 | protected override void OnInit() 3235 | { 3236 | base.OnInit(); 3237 | 3238 | m_editor = IOC.Resolve<IRuntimeEditor>(); 3239 | m_editor.LoadProject += OnLoadProject; 3240 | } 3241 | 3242 | protected override void OnCleanup() 3243 | { 3244 | base.OnCleanup(); 3245 | m_editor.LoadProject -= OnLoadProject; 3246 | } 3247 | 3248 | private async void OnLoadProject(object sender, System.EventArgs e) 3249 | { 3250 | using var b = m_editor.SetBusy(); 3251 | 3252 | var path = $"{m_editor.GetRootFolderPath()}/New Folder"; 3253 | 3254 | if (!m_editor.Exists(path)) 3255 | { 3256 | await m_editor.CreateFolderAsync(path); 3257 | } 3258 | 3259 | m_editor.CurrentFolderID = m_editor.GetAssetID(path); 3260 | } 3261 | } 3262 | ``` 3263 | 3264 | ## Runtime Scene 3265 | 3266 | ![HierarchyRoot][hierarchy_root] 3267 | 3268 | In the current version, the runtime editor requires that all runtime scene objects must be added to the `Scene` game object (the hierarchy root), which has the "Respawn" tag. 3269 | This ensures that the objects are visible in the hierarchy and can be saved as part of the scene. 3270 | 3271 | The exact value of the tag is specified by the `ExposeToEditor.HierarchyRootTag` field: 3272 | ```csharp 3273 | public class ExposeToEditor : MonoBehaviour 3274 | { 3275 | public static string HierarchyRootTag = "Respawn"; 3276 | } 3277 | ``` 3278 | To add a game object to the runtime editor scene programmatically, use the `AddGameObjectToScene` method: 3279 | ```csharp 3280 | var editor = IOC.Resolve<IRuntimeEditor>(); 3281 | 3282 | // Create a game object 3283 | var capsule = GameObject.CreatePrimitive(PrimitiveType.Capsule); 3284 | 3285 | // Add the game object to the scene 3286 | editor.AddGameObjectToScene(capsule); 3287 | 3288 | ``` 3289 | 3290 | ## Create New, Save, Load scene 3291 | 3292 | Here is an example of how to create, save, and open a scene in the Runtime Editor 3293 | 3294 | ```csharp 3295 | using Battlehub.RTCommon; 3296 | using Battlehub.RTEditor.Models; 3297 | using Battlehub.UIControls.MenuControl; 3298 | using UnityEngine; 3299 | 3300 | namespace Battlehub.RTEditor.Examples.Scene31 3301 | { 3302 | [MenuDefinition] 3303 | public class SceneExampleMenu : EditorExtension 3304 | { 3305 | private IRuntimeEditor m_editor; 3306 | private IWindowManager m_wm; 3307 | 3308 | protected override void OnInit() 3309 | { 3310 | base.OnInit(); 3311 | 3312 | m_editor = IOC.Resolve<IRuntimeEditor>(); 3313 | m_editor.InitializeNewScene += OnInitializeNewScene; 3314 | m_editor.BeforeSaveCurrentScene += OnBeforeSaveCurrentScene; 3315 | m_editor.SaveCurrentScene += OnSaveCurrentScene; 3316 | m_editor.BeforeOpenScene += OnBeforeOpenScene; 3317 | m_editor.OpenScene += OnOpenScene; 3318 | m_wm = IOC.Resolve<IWindowManager>(); 3319 | } 3320 | 3321 | protected override void OnCleanup() 3322 | { 3323 | base.OnCleanup(); 3324 | 3325 | m_editor.InitializeNewScene -= OnInitializeNewScene; 3326 | m_editor.BeforeSaveCurrentScene -= OnBeforeSaveCurrentScene; 3327 | m_editor.SaveCurrentScene -= OnSaveCurrentScene; 3328 | m_editor.BeforeOpenScene -= OnBeforeOpenScene; 3329 | m_editor.OpenScene -= OnOpenScene; 3330 | m_editor = null; 3331 | m_wm = null; 3332 | } 3333 | 3334 | 3335 | [MenuCommand("Example/New Scene")] 3336 | public async void InitializeNewScene() 3337 | { 3338 | using var b = m_editor.SetBusy(); 3339 | 3340 | if (m_editor.CanInitializeNewScene) 3341 | { 3342 | await m_editor.InitializeNewSceneAsync(); 3343 | } 3344 | else 3345 | { 3346 | m_wm.MessageBox("Error", "Can't initialize new scene"); 3347 | } 3348 | } 3349 | 3350 | private void OnInitializeNewScene(object sender, System.EventArgs e) 3351 | { 3352 | Debug.Log($"On Create New Scene {m_editor.CurrentScene.name} (ID: {m_editor.CurrentSceneID})"); 3353 | } 3354 | 3355 | private string GetScenePath(PromptDialogArgs args) 3356 | { 3357 | string currentFolderPath = m_editor.GetCurrentFolderPath(); 3358 | string sceneExt = m_editor.GetSceneExt(); 3359 | string scenePath = $"{currentFolderPath}/{args.Text}{sceneExt}"; 3360 | return scenePath; 3361 | } 3362 | 3363 | [MenuCommand("Example/Save Scene")] 3364 | public void SaveCurrentScene() 3365 | { 3366 | m_wm.Prompt("Enter Scene Name", "My Scene", async (sender, args) => 3367 | { 3368 | using var b = m_editor.SetBusy(); 3369 | 3370 | if (m_editor.CanSaveScene) 3371 | { 3372 | string scenePath = GetScenePath(args); 3373 | 3374 | await m_editor.SaveCurrentSceneAsync(scenePath); 3375 | } 3376 | else 3377 | { 3378 | m_wm.MessageBox("Error", "Can't save current scene"); 3379 | } 3380 | }); 3381 | } 3382 | 3383 | private void OnBeforeSaveCurrentScene(object sender, BeforeSaveSceneEventArgs e) 3384 | { 3385 | Debug.Log($"On Before Save Current Scene"); 3386 | } 3387 | 3388 | private void OnSaveCurrentScene(object sender, SaveSceneEventArgs e) 3389 | { 3390 | Debug.Log($"On Save Current Scene: {e.Scene.name} (ID: {e.SceneID} Path: {e.ScenePath})"); 3391 | } 3392 | 3393 | [MenuCommand("Example/Open Scene")] 3394 | public void OpenScene() 3395 | { 3396 | m_wm.Prompt("Enter Scene Name", "My Scene", async (sender, args) => 3397 | { 3398 | using var b = m_editor.SetBusy(); 3399 | 3400 | string scenePath = GetScenePath(args); 3401 | 3402 | if (m_editor.Exists(scenePath)) 3403 | { 3404 | await m_editor.OpenSceneAsync(scenePath); 3405 | } 3406 | else 3407 | { 3408 | m_wm.MessageBox("Error", $"Scene {scenePath} does not exist"); 3409 | } 3410 | }); 3411 | } 3412 | 3413 | private void OnBeforeOpenScene(object sender, AssetEventArgs e) 3414 | { 3415 | Debug.Log($"On Before Open Scene (ID: {e.AssetID})"); 3416 | } 3417 | 3418 | private void OnOpenScene(object sender, AssetEventArgs e) 3419 | { 3420 | Debug.Log($"On Open Scene (ID: {e.AssetID})"); 3421 | } 3422 | } 3423 | } 3424 | ``` 3425 | 3426 | ## Create, Save, Load, Delete Assets and Folders 3427 | 3428 | This is example on how to create, save, load, delete assets and folders 3429 | 3430 | ```csharp 3431 | 3432 | using Battlehub.RTCommon; 3433 | using Battlehub.RTEditor.Models; 3434 | using Battlehub.UIControls.MenuControl; 3435 | using System.Linq; 3436 | using UnityEngine; 3437 | 3438 | namespace Battlehub.RTEditor.Examples.Scene32 3439 | { 3440 | 3441 | [MenuDefinition] 3442 | public class FolderAndAssetExampleMenu : EditorExtension 3443 | { 3444 | private IRuntimeEditor m_editor; 3445 | private IWindowManager m_wm; 3446 | 3447 | protected override void OnInit() 3448 | { 3449 | m_editor = IOC.Resolve<IRuntimeEditor>(); 3450 | m_editor.CreateFolder += OnCreateFolder; 3451 | m_editor.CreateAsset += OnCreateAsset; 3452 | m_editor.SaveAsset += OnSaveAsset; 3453 | m_editor.DeleteAssets += OnDeleteAssets; 3454 | m_wm = IOC.Resolve<IWindowManager>(); 3455 | } 3456 | 3457 | protected override void OnCleanup() 3458 | { 3459 | m_editor.CreateFolder -= OnCreateFolder; 3460 | m_editor.CreateAsset -= OnCreateAsset; 3461 | m_editor.SaveAsset -= OnSaveAsset; 3462 | m_editor.DeleteAssets -= OnDeleteAssets; 3463 | m_editor = null; 3464 | m_wm = null; 3465 | } 3466 | 3467 | private string GetPath(string name, string ext = null) 3468 | { 3469 | string currentFolderPath = m_editor.GetCurrentFolderPath(); 3470 | return $"{currentFolderPath}/{name}{ext}"; 3471 | } 3472 | 3473 | [MenuCommand("Example/Create Folder")] 3474 | public void CreateFolder() 3475 | { 3476 | m_wm.Prompt("Enter Folder Name", "My Folder", async (sender, args) => 3477 | { 3478 | string path = GetPath(args.Text); 3479 | 3480 | if (!m_editor.Exists(path)) 3481 | { 3482 | using var b = m_editor.SetBusy(); 3483 | 3484 | await m_editor.CreateFolderAsync(path); 3485 | } 3486 | }); 3487 | } 3488 | 3489 | [MenuCommand("Example/Delete Folder")] 3490 | public void DeleteFolder() 3491 | { 3492 | m_wm.Prompt("Enter Folder Name", "My Folder", async (sender, args) => 3493 | { 3494 | string path = GetPath(args.Text); 3495 | 3496 | if (m_editor.Exists(path)) 3497 | { 3498 | using var b = m_editor.SetBusy(); 3499 | 3500 | await m_editor.DeleteAssetAsync(path); 3501 | } 3502 | else 3503 | { 3504 | m_wm.MessageBox("Error", 3505 | $"A folder named '{args.Text}' is not in the current folder"); 3506 | } 3507 | }); 3508 | } 3509 | 3510 | [MenuCommand("Example/Delete Current Folder", validate: true)] 3511 | public bool CanDeleteCurrentFolder() 3512 | { 3513 | return m_editor.CurrentFolderID != m_editor.RootFolderID; 3514 | } 3515 | 3516 | [MenuCommand("Example/Delete Current Folder")] 3517 | public async void DeleteCurrentFolder() 3518 | { 3519 | using var b = m_editor.SetBusy(); 3520 | 3521 | var currentFolderID = m_editor.CurrentFolderID; 3522 | await m_editor.DeleteAssetAsync(currentFolderID); 3523 | } 3524 | 3525 | [MenuCommand("Example/Create Prefab", validate: true)] 3526 | public bool CanCreatePrefab() 3527 | { 3528 | var go = m_editor.Selection.activeGameObject; 3529 | return m_editor.CanCreatePrefab(go); 3530 | } 3531 | 3532 | [MenuCommand("Example/Create Prefab")] 3533 | public async void CreatePrefab() 3534 | { 3535 | using var b = m_editor.SetBusy(); 3536 | 3537 | var go = m_editor.Selection.activeGameObject; 3538 | await m_editor.CreateAssetAsync(go); 3539 | } 3540 | 3541 | [MenuCommand("Example/Create Material")] 3542 | public async void CreateMaterial() 3543 | { 3544 | var material = Instantiate(RenderPipelineInfo.DefaultMaterial); 3545 | material.name = "Material"; 3546 | material.Color(Color.green); 3547 | material.MainTexture(Resources.Load<Texture2D>("Duck")); 3548 | 3549 | using var b = m_editor.SetBusy(); 3550 | await m_editor.CreateAssetAsync(material); 3551 | } 3552 | 3553 | 3554 | [MenuCommand("Example/Update Material", validate: true)] 3555 | public bool CanUpdateMaterial() 3556 | { 3557 | return m_editor.Selection.activeObject is Material; 3558 | } 3559 | 3560 | [MenuCommand("Example/Update Material")] 3561 | public async void UpdateMaterial() 3562 | { 3563 | Material material = (Material)m_editor.Selection.activeObject; 3564 | material.Color(Random.ColorHSV()); 3565 | 3566 | ID assetID = m_editor.GetAssetID(material); 3567 | if (assetID != ID.Empty) 3568 | { 3569 | using var b = m_editor.SetBusy(); 3570 | await m_editor.SaveAssetAsync(assetID); 3571 | } 3572 | } 3573 | 3574 | [MenuCommand("Example/Delete Selected", validate: true)] 3575 | public bool CanDeleteSelected() 3576 | { 3577 | return m_editor.SelectedAssets.Length > 0 && 3578 | !m_editor.SelectedAssets.Contains(m_editor.RootFolderID); 3579 | } 3580 | 3581 | [MenuCommand("Example/Delete Selected")] 3582 | public async void DeleteSelected() 3583 | { 3584 | using var b = m_editor.SetBusy(); 3585 | var selection = m_editor.SelectedAssets; 3586 | await m_editor.DeleteAssetsAsync(selection); 3587 | } 3588 | 3589 | private void OnCreateFolder(object sender, CreateFolderEventArgs e) 3590 | { 3591 | Debug.Log( 3592 | $"On Create Folder (ID: {e.AssetID} Path: {m_editor.GetPath(e.AssetID)})"); 3593 | } 3594 | 3595 | private void OnCreateAsset(object sender, CreateAssetEventArgs e) 3596 | { 3597 | Debug.Log( 3598 | $"On Create Asset (ID: {e.AssetID} Path: {m_editor.GetPath(e.AssetID)})"); 3599 | } 3600 | 3601 | private void OnSaveAsset(object sender, SaveAssetEventArgs e) 3602 | { 3603 | Debug.Log( 3604 | $"On Save Asset (ID: {e.AssetID} Path: {m_editor.GetPath(e.AssetID)})"); 3605 | } 3606 | 3607 | private void OnDeleteAssets(object sender, DeleteAssetsEventArgs e) 3608 | { 3609 | for (int i = 0; i < e.AssetID.Count; ++i) 3610 | { 3611 | ID assetID = e.AssetID[i]; 3612 | Debug.Log( 3613 | $"On Delete Asset (ID: {assetID}, Children: {e.ChildrenID[i].Count})"); 3614 | } 3615 | } 3616 | } 3617 | } 3618 | 3619 | ``` 3620 | 3621 | ## Instantiate asset 3622 | 3623 | This is an example of creating and instantiating an asset. 3624 | 3625 | ```csharp 3626 | using Battlehub.RTCommon; 3627 | using Battlehub.RTEditor; 3628 | using Battlehub.UIControls.MenuControl; 3629 | using System.Linq; 3630 | using UnityEngine; 3631 | 3632 | [MenuDefinition] 3633 | public class InstantiateAssetExample : MonoBehaviour 3634 | { 3635 | [MenuCommand("Example/Create Asset")] 3636 | public async void Create() 3637 | { 3638 | var editor = IOC.Resolve<IRuntimeEditor>(); 3639 | 3640 | // Create game object 3641 | var capsule = GameObject.CreatePrimitive(PrimitiveType.Capsule); 3642 | editor.AddGameObjectToScene(capsule, select:false); 3643 | 3644 | // Expose to editor 3645 | capsule.AddComponent<ExposeToEditor>(); 3646 | 3647 | // Get .prefab extension 3648 | var ext = editor.GetExt(capsule); 3649 | 3650 | // Get unique asset path 3651 | var path = editor.GetUniquePath($"Capsule{ext}"); 3652 | 3653 | // Create asset 3654 | await editor.CreateAssetAsync(capsule, path); 3655 | 3656 | // Select create asset 3657 | editor.SelectedAssets = new[] { editor.GetAssetID(path) }; 3658 | } 3659 | 3660 | [MenuCommand("Example/Instantiate Asset", validate:true)] 3661 | public bool CanInstanate() 3662 | { 3663 | var editor = IOC.Resolve<IRuntimeEditor>(); 3664 | return editor.SelectedAssets.Any(asset => editor.CanInstantiateAsset(asset)); 3665 | } 3666 | 3667 | 3668 | [MenuCommand("Example/Instantiate Asset")] 3669 | public async void Instanate() 3670 | { 3671 | var editor = IOC.Resolve<IRuntimeEditor>(); 3672 | using var b = editor.SetBusy(); 3673 | 3674 | // Instantiate selected assets 3675 | var result = await editor.InstantiateAssetsAsync(editor.SelectedAssets); 3676 | 3677 | // Select instances 3678 | editor.Selection.objects = result.Instances; 3679 | } 3680 | } 3681 | ``` 3682 | 3683 | ![Instanate Asset][instantiate_asset_example] 3684 | 3685 | ## Duplicate Asset 3686 | 3687 | You can duplicate any asset or folder you select in the project view. 3688 | 3689 | ```csharp 3690 | using Battlehub.RTCommon; 3691 | using Battlehub.RTEditor; 3692 | using Battlehub.RTEditor.Models; 3693 | using Battlehub.UIControls.MenuControl; 3694 | using UnityEngine; 3695 | 3696 | [MenuDefinition] 3697 | public class DuplicateAssetExample : MonoBehaviour 3698 | { 3699 | [MenuCommand("Example/Duplicate Asset", validate:true)] 3700 | public bool CanDuplicate() 3701 | { 3702 | var editor = IOC.Resolve<IRuntimeEditor>(); 3703 | return editor.SelectedAssets.Length > 0 && 3704 | editor.CanDuplicateAsset(editor.SelectedAssets[0]); 3705 | } 3706 | 3707 | 3708 | [MenuCommand("Example/Duplicate Asset")] 3709 | public async void Dupicate() 3710 | { 3711 | var editor = IOC.Resolve<IRuntimeEditor>(); 3712 | 3713 | var assetID = editor.SelectedAssets[0]; 3714 | string path = editor.GetPath(assetID); 3715 | string targetPath = editor.GetUniquePath(path); 3716 | 3717 | using var b = editor.SetBusy(); 3718 | await editor.DuplicateAssetAsync(assetID, targetPath); 3719 | } 3720 | } 3721 | ``` 3722 | 3723 | ## Move Asset 3724 | 3725 | This example shows how to move an asset. You can rename assets using the same approach. 3726 | 3727 | ```csharp 3728 | using Battlehub.RTCommon; 3729 | using Battlehub.RTEditor; 3730 | using Battlehub.RTEditor.Models; 3731 | using Battlehub.UIControls.MenuControl; 3732 | using UnityEngine; 3733 | 3734 | [MenuDefinition] 3735 | public class MoveAssetExample : MonoBehaviour 3736 | { 3737 | [MenuCommand("Example/Move Asset", validate: true)] 3738 | public bool CanMoveAsset() 3739 | { 3740 | var editor = IOC.Resolve<IRuntimeEditor>(); 3741 | return editor.SelectedAssets.Length > 0; 3742 | } 3743 | 3744 | 3745 | [MenuCommand("Example/Move Asset")] 3746 | public async void MoveAsset() 3747 | { 3748 | var editor = IOC.Resolve<IRuntimeEditor>(); 3749 | 3750 | var assetID = editor.SelectedAssets[0]; 3751 | string path = editor.GetPath(assetID); 3752 | string targetPath = editor.GetUniquePath(path); 3753 | 3754 | using var b = editor.SetBusy(); 3755 | await editor.MoveAssetAsync(editor.SelectedAssets[0], targetPath); 3756 | } 3757 | } 3758 | ``` 3759 | 3760 | ## Import Export Assets 3761 | 3762 | Install the [SharpZipLib package](https://docs.unity3d.com/Packages/com.unity.sharp-zip-lib@1.3/manual/index.html). In Package Manager, click on **"+ Add package by name"** and enter **"com.unity.sharp-zip-lib"** 3763 | 3764 | ```csharp 3765 | private async Task ExportAssetsAsync(string path) 3766 | { 3767 | if (File.Exists(path)) 3768 | { 3769 | File.Delete(path); 3770 | } 3771 | 3772 | using (var fs = File.OpenWrite(path)) 3773 | { 3774 | var editor = IOC.Resolve<IRuntimeEditor>(); 3775 | 3776 | using var b = editor.SetBusy(); 3777 | await editor.ExportAssetsAsync(editor.SelectedAssets, fs, includeDependencies:true); 3778 | } 3779 | } 3780 | 3781 | 3782 | private async Task ImportAssetsAsync(string path) 3783 | { 3784 | using (var fs = File.OpenRead(path)) 3785 | { 3786 | var editor = IOC.Resolve<IRuntimeEditor>(); 3787 | 3788 | using var b = editor.SetBusy(); 3789 | await editor.ImportAssetsAsync(fs); 3790 | } 3791 | } 3792 | ``` 3793 | 3794 | > **Note** 3795 | > For complete example, see **Scene33 - Export And Import Asset** in the [Example Scenes section](#example-scenes). 3796 | 3797 | 3798 | 3799 | ## External Asset Loaders 3800 | 3801 | External asset loaders are used to load external assets imported into a project. 3802 | An external asset is an asset loaded into the project using a specific loader, such as the Addressable Loader, a loader that loads assets from the Resources folder, a GLB loader, or any other third-party loader. 3803 | External assets are read-only and contain only identifiers for parts within the data file. If you need to make edits to an external asset, you can create an asset variant of it. 3804 | 3805 | There are four built-in external asset loaders: 3806 | 3807 | - **Resources Loader** 3808 | Located at `Assets\Battlehub\RTEditor\Runtime\RTEditor\Models\AssetDatabaseImportSources\ResourcesLoaderModel.cs` 3809 | 3810 | - **Addressables Loader** 3811 | Located at `Assets\Battlehub\RTEditor\Runtime\RTEditor\Models\AssetDatabaseImportSources\AddressablesLoaderModel.cs` 3812 | > **Important:** You must install **com.unity.addressables** to use this loader. For more information, refer to the [Unity Addressables documentation](https://docs.unity3d.com/Packages/com.unity.addressables@1.21/manual/LoadingAddressableAssets.html). 3813 | 3814 | - **GLTFast Loader** 3815 | Located at `Assets\Battlehub\RTImporter\Runtime\Importers\GltfImporter.cs` 3816 | > **Important:** You must install **com.unity.cloud.gltfast** to use this loader. For more information, refer to the [GLTFast documentation](https://docs.unity3d.com/Packages/com.unity.cloud.gltfast@6.6/manual/index.html). 3817 | 3818 | - **DemoGameAssets Loader** 3819 | Located at `Assets\Battlehub\RTEditorDemo\Examples\RTEditor DemoGame\DemoGameAssetsLoader.cs` 3820 | 3821 | To create your own loader, implement the **IExternalAssetLoaderModel** interface: 3822 | 3823 | ```csharp 3824 | using Battlehub.RTEditor.Models; 3825 | using System; 3826 | using System.Collections.Generic; 3827 | using System.Threading.Tasks; 3828 | using UnityEngine; 3829 | 3830 | using UnityObject = UnityEngine.Object; 3831 | public class MyLoader : IExternalAssetLoaderModel 3832 | { 3833 | public string LoaderID => nameof(MyLoader); 3834 | 3835 | public Task<object> LoadAsync(string key, object root, IProgress<float> progress = null) 3836 | { 3837 | var parent = root as Transform; 3838 | 3839 | GameObject asset; 3840 | switch(key) 3841 | { 3842 | case "Cube": 3843 | asset = GameObject.CreatePrimitive(PrimitiveType.Cube); 3844 | break; 3845 | case "Capsule": 3846 | asset = GameObject.CreatePrimitive(PrimitiveType.Capsule); 3847 | break; 3848 | default: 3849 | throw new KeyNotFoundException(key); 3850 | } 3851 | 3852 | asset.transform.SetParent(parent, false); 3853 | 3854 | return Task.FromResult<object>(asset); 3855 | } 3856 | 3857 | public void Release(object obj) 3858 | { 3859 | var asset = obj as UnityObject; 3860 | if (asset != null) 3861 | { 3862 | UnityObject.Destroy(asset); 3863 | } 3864 | } 3865 | } 3866 | 3867 | ``` 3868 | 3869 | To register your custom loader, use the AddExternalAssetLoader method: 3870 | 3871 | ```csharp 3872 | using Battlehub.RTCommon; 3873 | using Battlehub.RTEditor; 3874 | using Battlehub.RTEditor.Models; 3875 | 3876 | public class RegisterMyLoader : EditorExtension 3877 | { 3878 | private IRuntimeEditor m_editor; 3879 | private IExternalAssetLoaderModel m_loader; 3880 | 3881 | protected override void OnInit() 3882 | { 3883 | m_editor = IOC.Resolve<IRuntimeEditor>(); 3884 | 3885 | m_loader = new MyLoader(); 3886 | 3887 | m_editor.AddExternalAssetLoader(m_loader); 3888 | } 3889 | 3890 | protected override void OnCleanup() 3891 | { 3892 | m_editor.RemoveExternalAssetLoader(m_loader); 3893 | m_editor = null; 3894 | } 3895 | } 3896 | ``` 3897 | 3898 | After registering the custom loader, you can use the ImportExternalAssetAsync method to import an external asset by providing the loader ID and key to the method: 3899 | 3900 | ```csharp 3901 | using var b = m_editor.SetBusy(); 3902 | await m_editor.ImportExternalAssetAsync(m_editor.RootFolderID, key: "Cube", loaderID: nameof(MyLoader), desiredName: "My External Asset"); 3903 | ``` 3904 | 3905 | ![Custom External Asset Loader][custom_external_asset_loader] 3906 | 3907 | > **Note** <br/> 3908 | For the **Resources Loader**, you should use the relative path in the Resources folder as the key (this is the same as the path parameter in the [Resources.Load method](https://docs.unity3d.com/ScriptReference/Resources.Load.html)).<br/> 3909 | For the **Addressables Loader**, the key will be used as a parameter for the [Addressables.LoadAsset method](https://docs.unity3d.com/Packages/com.unity.addressables@1.20/api/UnityEngine.AddressableAssets.Addressables.LoadAsset.html#UnityEngine_AddressableAssets_Addressables_LoadAsset__1_System_Object_).<br/> 3910 | For the **GLTFastLoader Loader**, the key can be an absolute URI, an absolute path, or a path relative to the **.Library** folder inside the runtime project folder.<br/> 3911 | 3912 | ### TriLibLoader Example 3913 | 3914 | The Runtime Editor is only able to load .glb and .gltf models. For .fbx and other formats, you need to use an asset such as [TriLib](https://assetstore.unity.com/packages/tools/modeling/trilib-2-model-loading-package-157548). Here is an example of how to create an external asset loader using the TriLib library and integrate it with the Runtime Editor. The procedure is almost the same as for any other loader. 3915 | 3916 | #### Implement IExternalAssetLoaderModel 3917 | 3918 | ```csharp 3919 | using Battlehub.RTCommon; 3920 | using Battlehub.RTEditor.Models; 3921 | using System; 3922 | using System.IO; 3923 | using System.Threading.Tasks; 3924 | using TriLibCore; 3925 | using UnityEngine; 3926 | 3927 | public class TriLibLoaderModel : IExternalAssetLoaderModel 3928 | { 3929 | public string LoaderID => nameof(TriLibLoaderModel); 3930 | 3931 | public string LibraryFolder { get; set; } 3932 | 3933 | private Task<GameObject> LoadUsingTriLibAsync(string filePath, Transform root) 3934 | { 3935 | TaskCompletionSource<GameObject> tcs = new TaskCompletionSource<GameObject>(); 3936 | GameObject go = null; 3937 | var options = AssetLoader.CreateDefaultLoaderOptions(); 3938 | options.AddAssetUnloader = false; 3939 | 3940 | AssetLoader.LoadModelFromFile(filePath, ctx => 3941 | { 3942 | go = ctx.RootGameObject; 3943 | go.transform.SetParent(root); 3944 | }, 3945 | ctx => 3946 | { 3947 | tcs.SetResult(go); 3948 | }, 3949 | (ctx, progress) => { }, 3950 | err => 3951 | { 3952 | tcs.SetException(err.GetInnerException()); 3953 | }, 3954 | null, options); 3955 | 3956 | return tcs.Task; 3957 | } 3958 | 3959 | public async Task<object> LoadAsync(string key, object root, IProgress<float> progress = null) 3960 | { 3961 | string path; 3962 | if (Uri.TryCreate(key, UriKind.Absolute, out _) || Path.IsPathRooted(key)) 3963 | { 3964 | path = key; 3965 | } 3966 | else 3967 | { 3968 | path = !string.IsNullOrEmpty(LibraryFolder) ? $"{LibraryFolder}/{key}" : key; 3969 | } 3970 | 3971 | var go = await LoadUsingTriLibAsync(path, root as Transform); 3972 | var parts = go.GetComponentsInChildren<Transform>(true); 3973 | foreach (var part in parts) 3974 | { 3975 | part.gameObject.AddComponent<ExposeToEditor>(); 3976 | } 3977 | 3978 | return go; 3979 | } 3980 | 3981 | public void Release(object obj) 3982 | { 3983 | GameObject go = obj as GameObject; 3984 | if (go != null) 3985 | { 3986 | UnityEngine.Object.Destroy(go); 3987 | } 3988 | } 3989 | } 3990 | ``` 3991 | 3992 | #### Register TriLibLoader 3993 | 3994 | ```csharp 3995 | using Battlehub.RTCommon; 3996 | using Battlehub.RTEditor; 3997 | using Battlehub.RTEditor.Models; 3998 | 3999 | public class RegisterTriLibLoader : EditorExtension 4000 | { 4001 | private IRuntimeEditor m_editor; 4002 | private IExternalAssetLoaderModel m_loader; 4003 | 4004 | protected override void OnInit() 4005 | { 4006 | m_editor = IOC.Resolve<IRuntimeEditor>(); 4007 | m_loader = new TriLibLoaderModel(); 4008 | m_editor.AddExternalAssetLoader(m_loader); 4009 | } 4010 | 4011 | protected override void OnCleanup() 4012 | { 4013 | m_editor.RemoveExternalAssetLoader(m_loader); 4014 | m_editor = null; 4015 | } 4016 | } 4017 | ``` 4018 | 4019 | #### Import FBX as External Asset 4020 | 4021 | ```csharp 4022 | using var b = m_editor.SetBusy(); 4023 | 4024 | string key = "F:/ModelsCollection/ikea desk.fbx"; 4025 | string loaderID = nameof(TriLibLoaderModel); 4026 | string name = "My Model"; 4027 | 4028 | await m_editor.ImportExternalAssetAsync(m_editor.RootFolderID, key, loaderID, name); 4029 | ``` 4030 | 4031 | ![TriLib Asset Loader][trilib_loader] 4032 | 4033 | 4034 | ## Import Sources 4035 | 4036 | If you want to allow the user to select which assets to import and import them themselves, you should create import sources. 4037 | 4038 | There are three built-in import sources: 4039 | - **Addressables Import Source:** To use Addressables Import Source, you should install the [**com.unity.addressables**](https://docs.unity3d.com/Packages/com.unity.addressables@1.20/manual/index.html) package. 4040 | - **Resources Import Source** 4041 | - **DemoGame Import Source** 4042 | 4043 | In the Runtime Editor, if you click `File` > `Import Assets`, the asset database import source window will open, and you will see a dropdown with all available import sources. 4044 | 4045 | 1. **Select an import source from the dropdown.** 4046 | 2. The list of asset groups will be loaded (by default, there is only one Default group in all built-in sources). 4047 | 3. Select a group and click OK. 4048 | 4. You will be navigated to the asset database import view. 4049 | 5. Select assets and click Import. 4050 | 4051 | The `IRuntimeEditor.ImportExternalAssetAsync` method will be called for each of the selected assets with the corresponding key and loader ID. 4052 | 4053 | ![Open Import Sources window][import_sources_1] 4054 | ![Select Import Source][import_sources_2] 4055 | ![Select Import Group][import_sources_3] 4056 | ![Select Import Assets][import_sources_4] 4057 | ![Import Assets][import_sources_5] 4058 | 4059 | ### Resources Import Source 4060 | 4061 | To access built-in import sources and modify groups and assets, follow these steps: 4062 | 4063 | 1. **Create Asset Database** 4064 | - Click `Tools` -> `Runtime Editor` -> `Create Asset Database` 4065 | ![Create Asset Database][modify_import_source_1] 4066 | 4067 | 2. **Collect Scene Assets (Optional)** 4068 | - If you want assets referenced from the scene to be collected into the SceneAssetsList and [registered as external assets](https://github.com/Battlehub0x/RuntimeAssetDatabase?tab=readme-ov-file#register-external-asset), click Save. Otherwise, click Cancel. 4069 | ![Scene Assets List][modify_import_source_2] 4070 | 4071 | 3. **Add Assets or Groups** 4072 | - Add more assets or groups. The Group key in the groups list must match the Group key in the assets list. The Asset Key must be equal to the path relative to the Resources folder. 4073 | ![Add Assets or Groups][modify_import_source_3] 4074 | 4075 | > **Note** 4076 | > 4077 | > It is possible to do the same programmatically. The `ResourcesImportSourceModel` has two public methods: 4078 | > ```csharp 4079 | > void ResourcesImportSourceModel.SetGroups(ImportGroup[] groups) 4080 | > void ResourcesImportSourceModel.SetAssets(ImportAsset[] assets) 4081 | > ``` 4082 | The `ImportGroup` and `ImportAsset` classes are used to define the groups and assets to be imported. 4083 | 4084 | ### Addressables Import Source 4085 | 4086 | 1. **Install the Addressables Package** 4087 | - Install the [**com.unity.addressables**](https://docs.unity3d.com/Packages/com.unity.addressables@1.20/manual/index.html) package. 4088 | 4089 | 2. **Create Addressables Settings** 4090 | - Navigate to `Window` > `Asset Management` > `Addressables` > `Groups` (click `Create Addressables Settings` if needed). 4091 | 4092 | 3. **Make Prefabs Addressable** 4093 | - Make some prefabs addressable by selecting Addressable checkbox. Assign the "default" label. 4094 | ![Make some prefab addressable][import_source_addressable_1] 4095 | 4096 | 4. **Create a New Build (Optional)** 4097 | - Optionally, switch the PlayMode script to "Use Existing Build" and create a new build. 4098 | 4099 | 5. **Configure AssetDatabaseModel** 4100 | - Select the `AssetDatabaseModel` game object (If you don't have it in the scene, add it as described [here](#resources-import-source)). 4101 | - Ensure the group key matches the label of the addressables you want to show in that group during import. 4102 | ![Configure AssetDatabaseModel][import_source_addressable_2] 4103 | ![Import Addressable][import_source_addressable_3] 4104 | 4105 | ### Custom Import Source 4106 | 4107 | To create your own import source, follow these steps: 4108 | 4109 | 1. **Implement the `IImportSourceModel` Interface** 4110 | 4111 | ```csharp 4112 | using Battlehub.RTEditor.Models; 4113 | using System.Threading.Tasks; 4114 | 4115 | public class MyImportSource : IImportSourceModel 4116 | { 4117 | public bool IsEnabled => true; 4118 | 4119 | public int SortIndex => 0; 4120 | 4121 | public string DisplayName => "My Import Source"; 4122 | 4123 | public string LoaderID => nameof(MyLoader); 4124 | 4125 | public Task<IImportGroup[]> GetGroupsAsync() 4126 | { 4127 | IImportGroup[] groups = new[] 4128 | { 4129 | new ImportGroup( 4130 | key: "DefaultGroupKey", 4131 | name: "My Group") 4132 | }; 4133 | return Task.FromResult(groups); 4134 | } 4135 | 4136 | public Task<IImportAsset[]> GetAssetsAsync(string groupKey) 4137 | { 4138 | var assets = new[] 4139 | { 4140 | new ImportAsset("DefaultGroupKey", "Cube", "My Cube"), 4141 | new ImportAsset("DefaultGroupKey", "Capsule", "My Capsule"), 4142 | }; 4143 | 4144 | return Task.FromResult<IImportAsset[]>(assets); 4145 | } 4146 | } 4147 | ``` 4148 | 4149 | 2. **Register the Custom Import Source** 4150 | 4151 | ```csharp 4152 | using Battlehub.RTCommon; 4153 | using Battlehub.RTEditor; 4154 | 4155 | public class RegisterMyImportSource : EditorExtension 4156 | { 4157 | private IRuntimeEditor m_editor; 4158 | private MyImportSource m_importSource; 4159 | 4160 | protected override void OnInit() 4161 | { 4162 | m_importSource = new MyImportSource(); 4163 | m_editor = IOC.Resolve<IRuntimeEditor>(); 4164 | m_editor.AddImportSource(m_importSource); 4165 | } 4166 | 4167 | protected override void OnCleanup() 4168 | { 4169 | m_editor.RemoveImportSource(m_importSource); 4170 | m_importSource = null; 4171 | m_editor = null; 4172 | } 4173 | } 4174 | ``` 4175 | 4176 | 3. **Use the Custom Import Source** 4177 | 4178 | Once registered, your custom import source will be available in the Runtime Editor's asset import dropdown. Users can select it and import assets as configured. 4179 | 4180 | ![Custom Import Source][my_import_source_1] 4181 | ![Custom Import Source][my_import_source_2] 4182 | 4183 | > **Note** 4184 | > 4185 | > In this example, `MyLoader` from [External Asset Loaders](#external-asset-loaders) is being used. Ensure you have implemented and registered `MyLoader` as described in the previous sections. 4186 | 4187 | ## File Importers 4188 | 4189 | File importers come into play when you open the file picker using `File` -> `Import From File` in the Runtime Editor menu. 4190 | 4191 | ![Open File Picker][file_importers_1] 4192 | ![File Picker][file_importers_2] 4193 | 4194 | The file picker determines which types of files to show by inspecting the supported extensions of available file importers. 4195 | 4196 | There are four built-in file importers: 4197 | 4198 | - **GlbImporter** 4199 | - **GltfImporter** 4200 | - **PngImporter** 4201 | - **JpgImporter** 4202 | 4203 | > **Note** 4204 | > To enable `GlbImporter` and `GltfImporter`, you should install **com.unity.cloud.gltfast**. For more information, refer to the [GLTFast documentation](https://docs.unity3d.com/Packages/com.unity.cloud.gltfast@6.6/manual/index.html). 4205 | 4206 | 4207 | ### Custom File Importer 4208 | 4209 | To create a custom file importer, you need to create a class derived from `AssetDatabaseFileImporter`. 4210 | Override the `FileExt` field to return the supported file extension and implement the `ImportAsync` method. 4211 | 4212 | In the example below, the `TriLibLoader` from the [TriLib Loader Example](#trilibloader-example) is used to load an external asset. 4213 | Imported models are cached in the `.Library` folder. 4214 | 4215 | > **Note** 4216 | > You don't have to use loaders in the import method; you can simply use the IRuntimeEditor.CreateAsset method to create a normal asset, not an external one. 4217 | 4218 | ```csharp 4219 | using Battlehub.RTEditor; 4220 | using Battlehub.RTEditor.Models; 4221 | using System; 4222 | using System.Threading; 4223 | using System.Threading.Tasks; 4224 | 4225 | public class FbxFileImporter : AssetDatabaseFileImporter 4226 | { 4227 | public override int Priority 4228 | { 4229 | get { return int.MinValue; } 4230 | } 4231 | 4232 | public override string FileExt 4233 | { 4234 | get { return ".fbx"; } 4235 | } 4236 | 4237 | public override string IconPath 4238 | { 4239 | get { return "Importers/Fbx"; } 4240 | } 4241 | 4242 | public override async Task ImportAsync(string filePath, string targetPath, CancellationToken cancelToken) 4243 | { 4244 | try 4245 | { 4246 | Guid assetID = Guid.NewGuid(); 4247 | string externalAssetKey = AddFileToLibrary(filePath, assetID); 4248 | await ImportExternalAsset(targetPath, assetID, externalAssetKey, m_assetLoader); 4249 | } 4250 | catch (Exception e) 4251 | { 4252 | throw new FileImporterException(e.Message, e); 4253 | } 4254 | } 4255 | 4256 | private IExternalAssetLoaderModel m_assetLoader; 4257 | public override void Load() 4258 | { 4259 | base.Load(); 4260 | 4261 | var triLibLoader = new TriLibLoaderModel 4262 | { 4263 | LibraryFolder = Editor.LibraryRootFolder 4264 | }; 4265 | m_assetLoader = triLibLoader; 4266 | 4267 | Editor.AddExternalAssetLoader(m_assetLoader.LoaderID, m_assetLoader); 4268 | } 4269 | 4270 | public override void Unload() 4271 | { 4272 | Editor.RemoveExternalAssetLoader(m_assetLoader.LoaderID); 4273 | m_assetLoader = null; 4274 | 4275 | base.Unload(); 4276 | } 4277 | } 4278 | ``` 4279 | 4280 | ## Web Storage Sample 4281 | 4282 | You can use the runtime asset database in conjunction with an HTTP web server, in WebGL and Standalone builds. 4283 | 4284 | To start the web sample, follow these steps: 4285 | 4286 | 1. Install the [Newtonsoft Json package]((https://docs.unity3d.com/Packages/com.unity.nuget.newtonsoft-json@3.2/manual/index.html)). In Package Manager, click on "+ Add package by name" and enter "com.unity.nuget.newtonsoft-json" 4287 | 2. Unpack the `Asset/Battlehub/Storage.Web` Unity package. 4288 | 3. Open the `Asset/Battlehub.Extensions/Storage.Web/SampleScene` Unity scene. 4289 | 4. Extract `Asset/Battlehub.Extensions/Storage.Web/SampleHttpServer.zip` to a folder (e.g., `C:\SampleHttpWebServer`). 4290 | 5. Install Node.js from [Node.js](https://nodejs.org/en/learn/getting-started/how-to-install-nodejs). 4291 | 6. Open a terminal and navigate to `C:\SampleHttpWebServer`. 4292 | 7. Run the command `npm install`. 4293 | 8. Run the command `node app.js`. 4294 | 9. Enter play mode in Unity. 4295 | 10. The projects will be created in the `Project Root Folder Path` (see SampleScene/RuntimeEditor/Runtime Editor (Script)/Extra Settings/Projects Root Folder Path). 4296 | 4297 | 4298 | ## Serializer Extensions 4299 | Surrogates are intermediary classes used by the Serializer to facilitate the reading and writing of data to Unity objects during serialization. **To enable the serialization of a specific class, you must create a surrogate for it**. These surrogates can be generated automatically or created from scratch. To generate a Surrogate class, you can use the "Create Surrogates" window. 4300 | 4301 | ![Generate Surrogate][generate_surrogate] 4302 | 4303 | Sample component: 4304 | 4305 | ```C# 4306 | using UnityEngine; 4307 | 4308 | public class MyComponent : MonoBehaviour 4309 | { 4310 | public Material Material; 4311 | 4312 | public GameObject Target; 4313 | 4314 | public int IntValue; 4315 | 4316 | public string StringValue; 4317 | } 4318 | ``` 4319 | 4320 | ![Surrogates Folder][surrogates_folder] 4321 | 4322 | ```C# 4323 | using ProtoBuf; 4324 | using System; 4325 | using System.Threading.Tasks; 4326 | 4327 | namespace Battlehub.Storage.Surrogates 4328 | { 4329 | [ProtoContract] 4330 | [Surrogate(typeof(global::MyComponent), _PROPERTY_INDEX, _TYPE_INDEX)] 4331 | public class MyComponentSurrogate<TID> : ISurrogate<TID> where TID : IEquatable<TID> 4332 | { 4333 | const int _PROPERTY_INDEX = 7; 4334 | const int _TYPE_INDEX = 153; 4335 | //_PLACEHOLDER_FOR_EXTENSIONS_DO_NOT_DELETE_OR_CHANGE_THIS_LINE_PLEASE 4336 | 4337 | [ProtoMember(2)] 4338 | public TID id { get; set; } 4339 | 4340 | [ProtoMember(3)] 4341 | public TID gameObjectId { get; set; } 4342 | 4343 | [ProtoMember(4))] 4344 | public global::System.Boolean enabled { get; set; } 4345 | 4346 | [ProtoMember(5)] 4347 | public TID Material { get; set; } 4348 | 4349 | [ProtoMember(6)] 4350 | public TID Target { get; set; } 4351 | 4352 | [ProtoMember(7))] 4353 | public global::System.Int32 IntValue { get; set; } 4354 | //_PLACEHOLDER_FOR_NEW_PROPERTIES_DO_NOT_DELETE_OR_CHANGE_THIS_LINE_PLEASE 4355 | 4356 | public ValueTask Serialize(object obj, ISerializationContext<TID> ctx) 4357 | { 4358 | var idmap = ctx.IDMap; 4359 | 4360 | var o = (global::MyComponent)obj; 4361 | id = idmap.GetOrCreateID(o); 4362 | gameObjectId = idmap.GetOrCreateID(o.gameObject); 4363 | enabled = o.enabled; 4364 | Material = idmap.GetOrCreateID(o.Material); 4365 | Target = idmap.GetOrCreateID(o.Target); 4366 | IntValue = o.IntValue; 4367 | //_PLACEHOLDER_FOR_SERIALIZE_METHOD_BODY_DO_NOT_DELETE_OR_CHANGE_THIS_LINE_PLEASE 4368 | 4369 | return default; 4370 | } 4371 | 4372 | public ValueTask<object> Deserialize(ISerializationContext<TID> ctx) 4373 | { 4374 | var idmap = ctx.IDMap; 4375 | 4376 | var o = idmap.GetComponent<global::MyComponent, TID>(id, gameObjectId); 4377 | o.enabled = enabled; 4378 | o.Material = idmap.GetObject<global::UnityEngine.Material>(Material); 4379 | o.Target = idmap.GetObject<global::UnityEngine.GameObject>(Target); 4380 | o.IntValue = IntValue; 4381 | //_PLACEHOLDER_FOR_DESERIALIZE_METHOD_BODY_DO_NOT_DELETE_OR_CHANGE_THIS_LINE_PLEASE 4382 | 4383 | return new ValueTask<object>(o); 4384 | } 4385 | } 4386 | } 4387 | 4388 | ``` 4389 | After successfully generating surrogates, you have the flexibility to make various customizations within your surrogate class. You can remove properties that you don't want to be serialized, add new properties, or perform other operations as needed. 4390 | 4391 | To prevent changes you make to surrogates from being tracked and displayed in the "Update Surrogates" window, you can set the enableUpdates attribute to false using the following syntax: 4392 | 4393 | ```C# 4394 | [Surrogate(typeof(global::MyComponent), _PROPERTY_INDEX, _TYPE_INDEX, enableUpdates: false)] 4395 | ``` 4396 | 4397 | For value types, make sure to set enabled to false like this: 4398 | 4399 | ```C# 4400 | [Surrogate(typeof(global::MyComponent), _PROPERTY_INDEX, _TYPE_INDEX, enabled: false)] 4401 | ``` 4402 | 4403 | Two constants, **int _PROPERTY_INDEX** and **int _TYPE_INDEX**, have following purpose: 4404 | 4405 | - int _PROPERTY_INDEX: This constant assists the surrogate updater in determining the index of the next property to be generated. 4406 | - int _TYPE_INDEX: This constant acts as a unique type index. 4407 | 4408 | **Please note that references to other types with surrogates are replaced with their identifier (TID). 4409 | You can use an "idmap" to generate unique IDs for objects and retrieve objects using their corresponding IDs.** 4410 | 4411 | <div style="page-break-after: always;"></div> 4412 | 4413 | ## Enumerators 4414 | The enumerator is created along with the surrogate. Enumerators are specialized classes used to retrieve Unity object dependencies in a structured manner. These enumerators enable the serialization of an entire object tree, ensuring that dependencies are deserialized before dependent objects during the deserialization process. 4415 | 4416 | ![Enumerators Folder][enumerators_folder] 4417 | 4418 | ```C# 4419 | namespace Battlehub.Storage.Enumerators 4420 | { 4421 | [ObjectEnumerator(typeof(global::MyComponent))] 4422 | public class MyComponentEnumerator : ObjectEnumerator<global::MyComponent> 4423 | { 4424 | public override bool MoveNext() 4425 | { 4426 | do 4427 | { 4428 | switch (Index) 4429 | { 4430 | 4431 | case 0: 4432 | if (MoveNext(TypedObject.Material, 5)) 4433 | return true; 4434 | break; 4435 | case 1: 4436 | if (MoveNext(TypedObject.Target, 6)) 4437 | return true; 4438 | break; 4439 | case 2: 4440 | if (MoveNext(Object, -1)) 4441 | return true; 4442 | break; 4443 | default: 4444 | return false; 4445 | } 4446 | } 4447 | while (true); 4448 | } 4449 | } 4450 | } 4451 | ``` 4452 | 4453 | **Note that the second parameter of the MoveNext method is the property index, which should be equal to the argument of the ProtoMember attribute assigned to that property in the surrogate class.** 4454 | 4455 | You can also use the following simplified syntax when editing an enumerator: 4456 | ```C# 4457 | namespace Battlehub.Storage.Enumerators 4458 | { 4459 | [ObjectEnumerator(typeof(global::MyComponent))] 4460 | public class MyComponentEnumerator : ObjectEnumerator<global::MyComponent> 4461 | { 4462 | protected override IEnumerator<(object Object, int Key)> GetNext() 4463 | { 4464 | yield return (TypedObject.Material, 5); 4465 | yield return (TypedObject.Target, 6); 4466 | yield return (TypedObject, -1); 4467 | } 4468 | } 4469 | } 4470 | ``` 4471 | 4472 | ## Dynamic Surrogates (Preview) 4473 | 4474 | It is possible to avoid creating surrogates by hand, instead letting the runtime asset database serialize types using reflection at runtime. This process is roughly equivalent to Unity serialization rules. It works with fields and not with properties. 4475 | 4476 | To enable Dynamic Surrogate for a type, use the following code: 4477 | 4478 | ```csharp 4479 | var assetDatabase = IOC.Resolve<IAssetDatabaseModel>(); 4480 | assetDatabase.AddRuntimeSerializableTypes(typeof(MyMonoBehaviour)); 4481 | ``` 4482 | 4483 | To use field serialization, ensure that the field: 4484 | 4485 | - Is public, or has a `SerializeField` attribute. 4486 | - Isn’t static. 4487 | - Isn’t const. 4488 | - Isn’t readonly. 4489 | - Has a field type that can be serialized: 4490 | - Primitive data types (int, float, double, bool, string, etc.) 4491 | - Enum types (32 bits or smaller) 4492 | - Fixed-size buffers 4493 | - Unity built-in types, for example, Vector2, Vector3, Rect, Matrix4x4, Color. *Some types like AnimationCurve will not be serialized.* 4494 | - Custom structs with the `Serializable` attribute 4495 | - References to objects that derive from `UnityEngine.Object` 4496 | - Custom classes with the `Serializable` attribute (see Serialization of custom classes). 4497 | - An array of a field type mentioned above. 4498 | - A `List<T>` of a field type mentioned above. 4499 | 4500 | ### Serialization of Custom Classes 4501 | 4502 | For Unity to serialize a custom class, ensure the class: 4503 | 4504 | - Has the `Serializable` attribute. 4505 | - Isn’t static. 4506 | 4507 | The `[SerializeReference]` attribute is not supported. 4508 | 4509 | ### Custom Serialization 4510 | 4511 | Sometimes you might want to serialize something that the serializer doesn’t support (for example, a C# Dictionary). The best approach is to implement the `ISerializationCallbackReceiver` interface in your class. This allows you to implement callbacks that are invoked at key points during serialization and deserialization: 4512 | 4513 | - When an object is about to be serialized, Runtime Asset Database invokes the `OnBeforeSerialize()` callback. Inside this callback is where you can transform your data into something Runtime Asset Database understands. For example, to serialize a C# Dictionary, copy the data from the Dictionary into an array of keys and an array of values. 4514 | - After the `OnBeforeSerialize()` callback is complete, Runtime Asset Database serializes the arrays. 4515 | - Later, when the object is deserialized, Runtime Asset Database invokes the `OnAfterDeserialize()` callback. Inside this callback is where you can transform the data back into a form that’s convenient for the object in memory. For example, use the key and value arrays to repopulate the C# Dictionary. 4516 | 4517 | 4518 | ## Build All 4519 | After finishing creating or updating surrogates, make sure to click **"Tools" > "Runtime Asset Database" > "Build All"** from the main menu. This command will build the type model and serializer. 4520 | 4521 | ![Build All Menu][build_all_2] 4522 | ![Type Model][build_all] 4523 | 4524 | ## Support 4525 | If you cannot find something in the documentation or have any questions, please feel free to send an email to [Battlehub@outlook.com](mailto:Battlehub@outlook.com) or ask directly in [this support group](https://t.me/battlehub). You can also create an issue [here](https://github.com/Battlehub0x/RTE_Docs/issues?q=is%3Aissue+is%3Aclosed). 4526 | Keep up the great work in your development journey! 😊 4527 | 4528 | <div style="page-break-after: always;"></div> 4529 | 4530 | [youtube_icon]: Docs/Images/overview.png 4531 | [getting_started_1]: Docs/Images/getting_started_1.png 4532 | [getting_started_2]: Docs/Images/getting_started_2.png 4533 | [getting_started_3]: Docs/Images/getting_started_3.jpg 4534 | [getting_started_4]: Docs/Images/getting_started_4.jpg 4535 | [getting_started_5]: Docs/Images/getting_started_5.jpg 4536 | [example_scenes_1]: Docs/Images/example_scenes_1.jpg 4537 | [example_scenes_2]: Docs/Images/example_scenes_2.jpg 4538 | [compatibility_mode]: Docs/Images/compatibility_mode.jpg 4539 | [universal_rp_1]: Docs/Images/universal_rp_1.jpg 4540 | [universal_rp_2]: Docs/Images/universal_rp_2.jpg 4541 | [universal_rp_3]: Docs/Images/universal_rp_3.jpg 4542 | [generate_surrogate]: Docs/Images/generate_surrogate.png 4543 | [surrogates_folder]: Docs/Images/surrogates_folder.png 4544 | [enumerators_folder]: Docs/Images/enumerators_folder.png 4545 | [build_all]: Docs/Images/build_all.png 4546 | [expose_to_editor]: Docs/Images/expose_to_editor.jpg 4547 | [use_zero_time_scale_in_edit_mode]: Docs/Images/use_zero_time_scale_in_edit_mode.jpg 4548 | [builtin_windows]: Docs/Images/builtin_windows.jpg 4549 | [main_menu]: Docs/Images/main_menu.jpg 4550 | [context_menu]: Docs/Images/context_menu.jpg 4551 | [window]: Docs/Images/window.jpg 4552 | [custom_window_1]: Docs/Images/custom_window_1.jpg 4553 | [custom_window_2]: Docs/Images/custom_window_2.jpg 4554 | [custom_window_3]: Docs/Images/custom_window_3.jpg 4555 | [extending_window_1]: Docs/Images/extending_window_1.jpg 4556 | [override_layout_1]: Docs/Images/override_layout_1.jpg 4557 | [override_layout_2]: Docs/Images/override_layout_2.jpg 4558 | [override_layout_3]: Docs/Images/override_layout_3.jpg 4559 | [override_scene_params]: Docs/Images/override_scene_params.jpg 4560 | [override_tools]: Docs/Images/override_tools.jpg 4561 | [ui_scale_1]: Docs/Images/ui-scale-before-override.png 4562 | [ui_scale_2]: Docs/Images/ui-scale-after-override.png 4563 | [override_theme]: Docs/Images/override_theme.jpg 4564 | [inspector]: Docs/Images/inspector.png 4565 | [inspector_editors]: Docs/Images/component_gameobject_material_editors.jpg 4566 | [inspector_property_editors]: Docs/Images/property_editors.jpg 4567 | [inspector_configuration_1]: Docs/Images/inspector_configuration_1.jpg 4568 | [inspector_configuration_window]: Docs/Images/inspector_configuration_window.jpg 4569 | [my_component]: Docs/Images/my_component.jpg 4570 | [build_all_2]: Docs/Images/build_all_2.jpg 4571 | [manage_projects_example]: Docs/Images/manage_projects_example.jpg 4572 | [manage_projects_example_2]: Docs/Images/manage_projects_example_2.jpg 4573 | [instantiate_asset_example]: Docs/Images/instantiate_asset_example.jpg 4574 | [hierarchy_root]:Docs/Images/hierarchy_root.jpg 4575 | [custom_external_asset_loader]:Docs/Images/custom_external_asset_loader.jpg 4576 | [asset_database_project_folder]:Docs/Images/assetdatabase_project_folder.jpg 4577 | [persistent_data_path]:Docs/Images/persistent_data_path.jpg 4578 | [trilib_loader]:Docs/Images/trilib_loader.jpg 4579 | [import_sources_1]:Docs/Images/import_sources_1.jpg 4580 | [import_sources_2]:Docs/Images/import_sources_2.jpg 4581 | [import_sources_3]:Docs/Images/import_sources_3.jpg 4582 | [import_sources_4]:Docs/Images/import_sources_4.jpg 4583 | [import_sources_5]:Docs/Images/import_sources_5.jpg 4584 | [modify_import_source_1]:Docs/Images/modify_importsource_1.jpg 4585 | [modify_import_source_2]:Docs/Images/modify_importsource_2.jpg 4586 | [modify_import_source_3]:Docs/Images/modify_importsource_3.jpg 4587 | [import_source_addressable_1]:Docs/Images/import_source_addressable_1.jpg 4588 | [import_source_addressable_2]:Docs/Images/import_source_addressable_2.jpg 4589 | [import_source_addressable_3]:Docs/Images/import_source_addressable_3.jpg 4590 | [my_import_source_1]:Docs/Images/my_import_source_1.jpg 4591 | [my_import_source_2]:Docs/Images/my_import_source_2.jpg 4592 | [file_importers_1]:Docs/Images/file_importers_1.jpg 4593 | [file_importers_2]:Docs/Images/file_importers_2.jpg 4594 | [jint_scripting_1]:Docs/Images/jint_scripting_1.jpg 4595 | [jint_scripting_2]:Docs/Images/jint_scripting_2.jpg 4596 | [jint_scripting_3]:Docs/Images/jint_scripting_3.jpg 4597 | [jint_scripting_4]:Docs/Images/jint_scripting_4.jpg 4598 | [jint_scripting_5]:Docs/Images/jint_scripting_5.jpg 4599 | [jint_scripting_6]:Docs/Images/jint_scripting_6.jpg 4600 | [jint_scripting_7]:Docs/Images/jint_scripting_7.jpg 4601 | [jint_scripting_8]:Docs/Images/jint_scripting_8.jpg 4602 | [jint_scripting_9]:Docs/Images/jint_scripting_9.jpg 4603 | 4604 | 4605 | 4606 | 4607 | --------------------------------------------------------------------------------