Home Designer Suite
A comprehensive Unity toolkit for building home and interior design applications. Draw 2D floor plans, generate 3D buildings with procedural geometry, furnish interiors, and export your designs.
Installation
Before opening the demo scenes, install the following packages via the Unity Package Manager (Window > Package Manager).
Input System
Required for all keyboard and touch input handling. Install from the Unity Registry:
- In the Package Manager, select Unity Registry from the dropdown.
- Search for Input System.
- Click Install. Unity will prompt you to enable the new input system and restart the editor — accept.
UniGLTF (required for GLB export)
Install from a Git URL:
- In the Package Manager, click + > Add package from git URL.
- Enter the following URL and click Add:
Git URL
https://github.com/vrm-c/UniVRM.git?path=/Packages/UniGLTF#v0.131.0
UNIGLTF scripting define symbol in Edit > Project Settings > Player > Other Settings > Scripting Define Symbols to enable the GLB export feature.Quick Start
Try the Demo Scene
The fastest way to get started is to open and run the Demo_Editor scene:
Assets/Exoa/HomeDesigner/Scenes/Demo_Editor.unity
This scene contains a fully working interior design editor — you can draw floor plans, paint materials, place furniture, and switch to 3D preview mode right away. It is designed to be used as-is or as a starting point: feel free to extend it, rearrange the UI, or strip it down to only the features your application needs.
Demo_API.unity, demonstrates how to create buildings entirely from code using HeadlessBuildingAPI — useful if you want to generate buildings programmatically without the editor UI.License Activation
A valid license is required to use Home Designer in the Unity Editor. To activate:
- In the Unity menu bar, go to Exoa > Home Designer > Licensing.
- Enter your email address and your invoice number (from your Asset Store purchase).
- Click Activate.
Home Designer Settings
The settings panel is the central place to configure the plugin. Open it from the menu bar:
Tools > Exoa > Home Designer > Settings
The panel is a direct reflection of the HomeDesignerSettings ScriptableObject. Any change made in the panel is immediately written to that asset. It exposes all the settings needed to set up the plugin, including the database of furniture modules and materials available to the designer at runtime.
HomeDesignerSettings asset directly in the Project window (HomeDesignerSettings) and edit it in the Inspector — both views show the same data.GLB Export Setup (Optional)
When a project is saved, Home Designer can export the building as a GLB 3D model alongside the JSON file. This requires the UniGLTF package and a scripting define symbol.
HomeDesignerSettings ScriptableObject (HomeDesignerSettings). Enable it there to activate automatic GLB export on save.Step 1 — Install UniGLTF
Open Window > Package Manager, click + > Add package from git URL, and enter the UniGLTF package URL. Alternatively install it via the .unitypackage file if you have a local copy.
Step 2 — Add the Scripting Define Symbol
Open Edit > Project Settings > Player > Other Settings > Scripting Define Symbols and add:
Symbol
UNIGLTF
Apply the change. Unity will recompile; GLB export code will be compiled in after that.
Step 3 — Enable in HomeDesignerSettings
Select the HomeDesignerSettings asset in the Project window. The Export GLB On Save option is now available — enable it to export a .glb file every time a project is saved.
Render Pipeline Setup
Home Designer supports Built-in RP, URP, and HDRP. After importing the package or switching your project's render pipeline, you must convert the built-in materials to match your pipeline using the Render Pipeline Switcher.
Using the Render Pipeline Switcher
Open it from the menu bar: Tools > Exoa > Render Pipeline Switcher.
The tool batch-converts all Home Designer materials (walls, floors, grid, doors, windows, ghost, exterior) to the shaders expected by your current render pipeline. Select your target pipeline in the popup and click Convert.
Samples/Modules/ use their own materials — you must convert these yourself, either through Edit > Render Pipeline > Upgrade Project Materials (URP/HDRP built-in utility) or by manually reassigning shaders on each material.
Per-Pipeline Notes
| Pipeline | Notes |
|---|---|
| Built-in RP | Default. No extra setup required after conversion. |
| URP | Ensure a URP Asset is assigned in Project Settings > Graphics before converting. |
| HDRP | If the grid is not visible, adjust the emissive intensity on the grid material to match your directional light settings. |
Project Structure
Key Scenes
| Scene | Purpose | Path |
|---|---|---|
Demo_Editor | Full interactive editor — floor plan drawing, material painting, furniture placement, preview mode | Exoa/HomeDesigner/Scenes/ |
Demo_API | Headless/programmatic API demo — creates buildings via HeadlessBuildingAPI from code without editor UI (BuildingAPIExample component with 4 example buttons) | Exoa/HomeDesigner/Scenes/ |
Requirements
Unity Version
Unity 6 (6000.3.9f1) or later
Required Packages
Unity GLB Exporter (via Package Manager)
Target Platforms
Windows, Mac, Android, iOS
License System
The license check runs editor-only — it is stripped from all builds. Activating the license via Exoa > Home Designer > Licensing stores a token locally; no check occurs at runtime in a shipped application. If the license DLLs are deleted, a compile error is raised by design to prevent accidental removal.
Scripting Define Symbols
| Symbol | Purpose | When to Use |
|---|---|---|
UNIGLTF | Enables GLB Export functionality | When UniGLTF package is installed |
Integration Guide
If you want to embed Home Designer into an existing scene or project rather than building on top of Demo_Editor, follow the steps below.
Minimum Required Components
Your scene needs the following GameObjects at minimum to run the plugin:
| Component | Purpose |
|---|---|
AppController | State machine — drives all mode transitions. Singleton. |
HDInputManager | Reads keyboard/touch input and routes it to the event system. Attach alongside AppController. |
HeadlessBuildingAPI | Factory for creating buildings, floors, rooms, and openings. Singleton. |
BuildingFactory | Instantiates building component prefabs and converts coordinates. Singleton. |
ProjectSerializer | Handles JSON save/load and undo/redo snapshots. Singleton. |
The easiest approach is to copy the root GameObject hierarchy from Demo_Editor and strip out only the UI you don't need.
Adding the UI (Optional)
If you want the built-in editor panels, add the main UI Canvas prefab to your scene and ensure BuildingUIBridge is present. After creating a building call:
C# BuildingUIBridge.Instance.ConnectBuildingToUI(building);
This wires every floor, room, and opening in the building to the corresponding UI list items. If you create buildings programmatically after scene load, call this once after building.Build().
Running Without UI
Omit the Canvas and BuildingUIBridge entirely. Use HeadlessBuildingAPI to create geometry and HomeDesignerEvents to react to state changes. This is the pattern used by Demo_API.
Multiple Buildings
Each BuildingController is an independent GameObject. Instantiate as many as you need. The system tracks one active building — switch with:
C# BuildingController.SetCurrentBuilding(mySecondBuilding);
Editor operations (draw, paint, furnish) always apply to the active building.
HomeDesignerSettings ScriptableObject to the AppController inspector field. Without it the plugin will throw a NullReferenceException on startup.Architecture Overview
Home Designer follows an event-driven architecture with a centralized state machine. The core pattern: user input → events → controllers → procedural generation → 3D output.
⚙ State Management
AppController— singleton state machine- States drive UI visibility & behavior
- State changes broadcast via delegate
⚡ Event System
HomeDesignerEvents— static Action delegates- No Unity messaging — all
System.Action - OnRequest* for UI → logic, OnFile* for persistence
✎ Drawing & Control
ControlPointsController— point managementIObjectDrawer— common draw interface- Snap-to-grid and snap-to-pathlines
⚙ Procedural Generation
- ProceduralToolkit + ClipperLib + LibTessDotNet
- MeshDraft abstraction for mesh building
- Rooms, openings, exterior shells, roofs
📁 Data Persistence Layer
ProjectSerializer— JSON save/load via Newtonsoft.Json (IL2CPP compatible)UndoRedoManager— up to 50 JSON snapshots for undo/redoHeadlessBuildingAPI— programmatic building creation from data
State Machine
AppController is a SingletonMonoBehaviour<AppController> managing the application lifecycle. Setting AppController.State fires the OnAppStateChange delegate.
| State | Description | Active UI Panels |
|---|---|---|
Idle | No project loaded; welcome screen | Project wizard |
Floors | Floor management — add/remove/duplicate floors | UIFloorsMenu |
FloorPlan | Draw rooms and openings on the 2D grid | UIFloorPlanMenu |
Paint | Apply materials to walls, floors, ceilings | MaterialPopup |
Furnish | Place and arrange furniture modules | UIFurnishMenu |
Preview | 3D preview with stacked floors at Y intervals | Preview controls |
C# // Subscribe to state changes AppController.OnAppStateChange += (AppController.States newState) => { if (newState == AppController.States.FloorPlan) ShowFloorPlanUI(); }; // Change state programmatically AppController.Instance.State = AppController.States.Preview;
Data Flow Pipeline
From user drawing to 3D geometry — the complete build pipeline:
Class Hierarchy
Controllers
Hierarchy MonoBehaviour ├── SingletonMonoBehaviour<T> │ ├── AppController // State machine │ ├── ProjectSerializer // JSON persistence │ ├── HeadlessBuildingAPI // Programmatic building API │ ├── BuildingFactory // Prefab instantiation & coord conversion │ └── BuildingUIBridge // Scene↔UI connection ├── BuildingComponentBase │ ├── SpaceController : IObjectDrawer // Base for rooms/outside │ │ ├── RoomController // Room management │ │ └── OutsideController // Outdoor areas │ └── OpeningController : IObjectDrawer // Doors/windows/archways ├── BuildingController // Building root ├── FloorController // Per-floor management ├── ControlPointsController // Point drawing system └── ServiceLocator (static) // Lightweight DI container
Procedural Generation
Hierarchy BuildingComponentBase ├── ProceduralSpace (abstract) │ ├── ProceduralRoom // Walls, floor, ceiling, colliders, reflection probe │ └── ProceduralOutside // Outdoor area geometry ├── ProceduralArea // Area visualization / overlay geometry ├── ProceduralOpening // Door/window/archway meshes ├── ProceduralExterior // Building shell from contours └── ProceduralRoof (abstract, IConstructible<MeshDraft>) ├── ProceduralRoofFlat ├── ProceduralRoofHipped └── ProceduralRoofGabled
HeadlessBuildingAPI
SingletonMonoBehaviour<HeadlessBuildingAPI> — High-level API for creating buildings programmatically without UI dependencies. Used by deserialization, undo/redo, and headless creation scenarios.
HeadlessBuildingAPI.Instance. All methods can be called without any UI being present in the scene.Building Creation
| Method | Returns | Description |
|---|---|---|
CreateBuilding(string name, BuildingConstructionSettings settings) |
BuildingController |
Creates an empty building with construction settings |
CreateBuilding(string name, UnifiedBuildingData data) |
BuildingController |
Creates a complete building from data (floors, rooms, openings) |
CreateBuildingFromJson(string name, string json) |
BuildingController |
Deserializes JSON and creates building |
GetBuildingJson(BuildingController building) |
string |
Serializes building to JSON string |
GetBuildingJson(UnifiedBuildingData data) |
string |
Serializes data to JSON string |
Floor Creation
| Method | Returns | Description |
|---|---|---|
CreateFloor(BuildingController building) |
FloorController |
Adds an empty floor to a building |
CreateFloor(FloorData data, BuildingController building) |
FloorController |
Creates a floor from data with all rooms/openings |
Room & Opening Creation
| Method | Returns | Description |
|---|---|---|
CreateRectangularRoom(float w, float l, Vector3 pos, FloorController, string name) |
RoomController |
Creates a simple rectangular room |
CreateRoom(List<Vector3> points, FloorController, string name) |
RoomController |
Creates a room from arbitrary polygon points |
CreateRoom(FloorSpaceItemData data, FloorController) |
RoomController |
Creates a room from serialized data |
CreateDoor(FloorController, Vector3 pos, float w, float h) |
OpeningController |
Creates a door opening |
CreateWindow(FloorController, Vector3 pos, float w, float h, float ypos) |
OpeningController |
Creates a window opening |
CreateOutside(FloorSpaceItemData data, FloorController) |
OutsideController |
Creates an outdoor area |
DeleteSpace(SpaceController, FloorController) |
void |
Removes a room/outside and disconnects from UI |
DeleteOpening(OpeningController, FloorController) |
void |
Removes an opening and disconnects from UI |
Complete Example
C# // Example 1: Create a simple house programmatically var settings = new BuildingConstructionSettings(); settings.ApplyDefaults(); BuildingController building = HeadlessBuildingAPI.Instance.CreateBuilding("My House", settings); FloorController floor = HeadlessBuildingAPI.Instance.CreateFloor(building); // Create a 6x8 meter room RoomController room = HeadlessBuildingAPI.Instance.CreateRectangularRoom( width: 6f, length: 8f, floorCtrl: floor, position: Vector3.zero, roomName: "Living Room" ); // Add a front door OpeningController door = HeadlessBuildingAPI.Instance.CreateDoor( floor, worldPos: new Vector3(8f, 0, 0), w: 1.0f, h: 2.0f ); // Add windows HeadlessBuildingAPI.Instance.CreateWindow( floor, worldPos: new Vector3(-3f, 0, 0), windowWidth: 1.2f, windowHeight: 1.0f ); // Build the 3D geometry building.Build();
C# // Example 2: Create building from data objects var buildingData = new UnifiedBuildingData("v3"); buildingData.constructionSettings.ApplyDefaults(); var floor = new FloorData(null); // Add room with normalized positions var office = new FloorSpaceItemData(SpaceType.Room, "Office"); office.normalizedPositions = BuildingFactory.Instance.ConvertWorldPointsToNormalized( new List<Vector3> { new Vector3(-2, 0, -2), new Vector3(2, 0, -2), new Vector3(2, 0, 2), new Vector3(-2, 0, 2) }); floor.spaces.Add(office); buildingData.floors.Add(floor); BuildingController building = HeadlessBuildingAPI.Instance.CreateBuilding("Office", buildingData); building.Build();
C# // Example 3: Create from JSON string string json = File.ReadAllText("building.json"); BuildingController building = HeadlessBuildingAPI.Instance.CreateBuildingFromJson("Loaded", json); building.Build(); // Optionally connect to UI BuildingUIBridge.Instance?.ConnectBuildingToUI(building);
BuildingController
Root controller for a building. Manages floor collection, construction settings, and the build pipeline. Static currentBuilding tracks the active building.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
wallsHeight | float | 3.0 | Height of walls in meters |
doorsHeight | float | 2.5 | Height of door openings |
interiorWallThickness | float | 0.05 | Thickness of interior walls |
exteriorWallThickness | float | 0.1 | Thickness of exterior walls |
windowsThickness | float | 0.06 | Thickness of window frames |
doorsThickness | float | 0.06 | Thickness of door frames |
roof | RoofConfig | — | Roof type, thickness, and overhang |
Key Methods
| Method | Description |
|---|---|
Init() | Initializes the floor list |
Build() | Builds all floors (or current floor if set) |
GetData() → UnifiedBuildingData | Extracts complete building data for serialization |
GetConstructionSettings() | Returns construction settings as serializable data |
SetConstructionSettings(settings) | Applies settings from data object |
GetAllFloors() → List<FloorController> | Returns all floor controllers |
GetCurrentFloor() | Returns the currently active floor |
SetCurrentFloor(FloorController) | Sets which floor is active |
AddFloorIfNew(FloorController) | Adds a floor if its unique ID is new |
static GetCurrentBuilding() | Returns the global active building |
static SetCurrentBuilding(building) | Sets the global active building |
IObjectDrawer Interface
Common interface implemented by both SpaceController and OpeningController. Defines the contract for drawable objects.
C# public interface IObjectDrawer { ControlPointsController Cpc { get; set; } Color DrawingColor { get; set; } void Init(); // Initialize for drawing void Build(); // Generate 3D geometry void SetDisplayed(bool displayed); // Show/hide void SetData(FloorPlanItemData data); // Apply data FloorPlanItemData GetData(); // Read data void OnSettingsChanged(FloorPlanItemData data); }
SpaceController
Base class for room and outside controllers. Inherits BuildingComponentBase, implements IObjectDrawer. Manages control points, procedural space generation, and opening lists.
OpeningController
Controls doors, windows, and archways. Manages drawing, snapping to walls, and procedural mesh generation. Throttles rebuilds with configurable delayBetweenRebuilds.
FloorController
Manages all rooms and openings for a single floor. Runs the multi-pass build pipeline: collecting openings, building spaces twice (so wall geometry exists before opening holes are cut), building opening meshes, and finally building the exterior shell.
Key Methods
| Method | Returns | Description |
|---|---|---|
Build() | void | Full floor pipeline: collect → build spaces × 2 → build openings → exterior |
GetAllSpaces() | List<SpaceController> | All room and outside controllers on this floor |
GetAllOpenings() | List<OpeningController> | All door/window/archway controllers on this floor |
GetFloorData() | FloorData | Extracts serializable floor data for save/undo |
SetFloorData(FloorData) | void | Applies serialized data to reconstruct the floor |
GetUniqueId() | string | Returns the floor's stable GUID |
Build Pipeline
Event System
All inter-system communication uses System.Action delegates in Exoa.Events.HomeDesignerEvents. No Unity messaging — clean, type-safe event dispatch.
OnRequest* events are fired by UI to request actions. OnFile* events notify about persistence operations. Always unsubscribe in OnDisable() or OnDestroy().File & Persistence Events
| Event | Signature | Description |
|---|---|---|
OnFileLoaded | Action<FileType> | Fired after a building data file or screenshot is loaded |
OnFileCreated | Action<FileType> | Fired after a new file is created |
OnFileSaved | Action<string, FileType> | Fired after a file is saved (name, type) |
OnFileChanged | Action<string, FileType> | Fired when active file changes |
OnScreenShotSaved | Action<string, MenuType> | Fired after screenshot saved |
Request Events (UI → Logic)
| Event | Signature | Description |
|---|---|---|
OnRequestClearAll | Action<bool, bool, bool> | Clear scene / floor UI / floor-plan UI |
OnRequestRebuild | Action | Request full rebuild of building geometry |
OnRequestRepositionOpenings | Action | Reposition all openings (wall snapping) |
OnRequestButtonAction | Action<ButtonAction, bool> | Toolbar button pressed (action, active state) |
OnRequestFloorAction | Action<FloorPlanAction, string> | Floor-level operation (action, floorId) |
OnRequestFloorPlanItemAction | Action<FloorPlanItemAction, GameObject> | Operation on a floor-plan item |
State & UI Events
| Event | Signature | Description |
|---|---|---|
OnFloorChanged | Action<FloorData, int> | Displayed floor changed (data, index) |
OnUndoRedoStateChanged | Action | Undo/redo stack changed; refresh UI buttons |
OnDragEvent | Action<bool> | Camera drag start (true) / end (false) |
OnRenderForScreenshot | Action<bool> | Before (true) / after (false) screenshot render |
Enums
ButtonAction
ToggleExteriorWalls, ToggleGizmos, Exit, NewProject, ToggleRoof, NewFloor, ShowExteriorWalls, ShowGizmos, ShowRoof, NewFromTemplate, SaveProject, ProjectWizard, CenterBuilding, MoveBuildingToggle, Undo, Redo
FloorPlanAction
Select, Add, Remove, Duplicate, Preview
FloorPlanItemAction
EditPoints, Split, Move, Remove, Paint
FileType / MenuType
FileType: BuildingDataFile, ScreenshotFile
MenuType: FloorPlanMenu, InteriorMenu, FloorsMenu
C# // Subscribing to events void OnEnable() { HomeDesignerEvents.OnFileLoaded += OnFileLoaded; HomeDesignerEvents.OnRequestRebuild += OnRebuild; HomeDesignerEvents.OnFloorChanged += OnFloorChanged; } void OnDisable() { HomeDesignerEvents.OnFileLoaded -= OnFileLoaded; HomeDesignerEvents.OnRequestRebuild -= OnRebuild; HomeDesignerEvents.OnFloorChanged -= OnFloorChanged; } // Firing events HomeDesignerEvents.OnRequestButtonAction?.Invoke(ButtonAction.SaveProject, true); HomeDesignerEvents.OnRequestFloorAction?.Invoke(FloorPlanAction.Add, floorId);
Procedural Generation
The procedural system generates all 3D geometry from 2D floor plan data. It uses ProceduralToolkit for mesh building, ClipperLib for polygon offsetting (wall thickness), and LibTessDotNet for tessellation.
ProceduralRoom
Generates complete room geometry from a polygon of control points.
Walls
Tessellated wall meshes with configurable thickness. Interior walls use interiorWallThickness, built via polygon offsetting with ClipperLib.
Floor & Ceiling
Tessellated flat surfaces from the room polygon. Floor at Y=0, ceiling at Y=wallsHeight. Material-assignable per room.
Collision Box
Box collider for furniture placement and raycasting. Auto-sized to room bounds.
Reflection Probe
Per-room reflection probe for realistic material reflections in the 3D view.
ProceduralOpening
Generates door, window, and archway meshes. Supports configurable dimensions, glass panels, handles, and window subdivisions.
| Opening Type | Features |
|---|---|
Door | Frame mesh, door panel, configurable width/height |
Window | Frame, glass panel, subdivisions (H & V), Y-position offset |
Archway | Open archway without door/glass |
ProceduralExterior
Builds the exterior shell from room contours using polygon union operations. Creates the outer walls visible in Preview mode.
Roof Types
Flat Roof
RoofType = 0 — Simple flat slab with configurable thickness and overhang.
Hipped Roof
RoofType = 1 — All sides slope down from a central ridge using straight skeleton algorithm.
Gabled Roof
RoofType = 2 — Traditional two-slope roof with vertical gable ends.
Build Pipeline Detail
Build()
openings
(pass 1)
to walls
(pass 2)
meshes
shell
Serialization
ProjectSerializer (singleton) handles all JSON persistence via the included Newtonsoft.Json (custom IL2CPP-compatible build).
Data Model Hierarchy
Structure UnifiedBuildingData ├── version: "v3" ├── constructionSettings: BuildingConstructionSettings │ ├── wallsHeight, doorsHeight │ ├── interiorWallThickness, exteriorWallThickness │ ├── windowsThickness, doorsThickness │ ├── roofType, roofThickness, roofOverhang │ └── RoofConfig { type, thickness, overhang, color } ├── estheticSettings: BuildingEstheticSettings │ └── exteriorWallMat, roofMat, texture settings └── floors: List<FloorData> ├── uniqueId: string ├── spaces: List<FloorSpaceItemData> │ ├── type: SpaceType (Room | Outside | SingleWall) │ ├── name, uniqueId │ ├── normalizedPositions: List<Vector2> │ ├── roomSettings (wall/floor/ceiling textures) │ └── sceneObjects (furniture placements) └── openings: List<FloorOpeningItemData> ├── type: OpeningType (Door | Window | Archway) ├── name, uniqueId ├── width, height, ypos ├── normalizedPositions: List<Vector2> ├── directions: List<Vector3> ├── hasWindow, windowFrameSize └── windowSubDivH, windowSubDivV
Enums
| Enum | Values |
|---|---|
SpaceType | Room = 0, Outside = 1, SingleWall = 2 |
OpeningType | Door = 0, Window = 1, Archway = 2 |
RoofType | Flat = 0, Hipped = 1, Gabled = 2 |
Save / Load Flow
Undo/Redo
UndoRedoManager stores up to 50 JSON snapshots. Before each user action, TakeSnapshot() serializes the current building state. Undo/Redo destroys the current building and recreates from a snapshot.
File Locations
| Location | Purpose |
|---|---|
Application.persistentDataPath | Local project saves |
JSON Example
JSON { "version": "v3", "floors": [ { "uniqueId": "e9eb0652-19b3-475a-9b47-7bce13274c22", "spaces": [ { "type": 0, "name": "Office", "normalizedPositions": [ { "x": 0.3, "y": 0.3 }, { "x": 0.7, "y": 0.3 }, { "x": 0.7, "y": 0.7 }, { "x": 0.3, "y": 0.7 } ] } ], "openings": [ { "type": 0, "width": 1.0, "height": 2.0, "name": "Office Door", "normalizedPositions": [{ "x": 0.5, "y": 0.3 }] } ] } ], "constructionSettings": { "wallsHeight": 2.5, "doorsHeight": 2.0, "interiorWallThickness": 0.05, "exteriorWallThickness": 0.1, "roofType": 2, "roofThickness": 0.1, "roofOverhang": 0.8 } }
Save / Load Walkthrough
The simplest way to trigger save and load is through the event system, which lets existing UI and any other subscribers react automatically:
C# // ── Trigger save (fires OnFileSaved when complete) ────────────────── HomeDesignerEvents.OnRequestButtonAction?.Invoke( HomeDesignerEvents.ButtonAction.SaveProject, true); // ── Listen for save / load completion ─────────────────────────────── void OnEnable() { HomeDesignerEvents.OnFileSaved += OnSaved; HomeDesignerEvents.OnFileLoaded += OnLoaded; } void OnDisable() { HomeDesignerEvents.OnFileSaved -= OnSaved; HomeDesignerEvents.OnFileLoaded -= OnLoaded; } void OnSaved(string fileName, HomeDesignerEvents.FileType type) => Debug.Log($"Saved: {fileName}"); void OnLoaded(HomeDesignerEvents.FileType type) => Debug.Log("Project loaded"); // ── Direct API (bypasses event chain) ─────────────────────────────── ProjectSerializer.Instance.SaveProject("MyHouse"); ProjectSerializer.Instance.LoadProject("MyHouse"); // ── Enumerate saved files ──────────────────────────────────────────── string[] files = System.IO.Directory.GetFiles( Application.persistentDataPath, "*.json"); foreach (var f in files) Debug.Log(System.IO.Path.GetFileNameWithoutExtension(f));
Module System (Furniture)
Furniture modules and their categories are configured through the HomeDesignerSettings database. Open it via Tools > Exoa > Home Designer > Settings.
Module Data Model
C# public class SceneObjectItemData { public string uniqueId; // Instance GUID public string objectId; // Prefab identifier public Vector3 position; // World-space position public Vector3 rotation; // Euler rotation angles public Vector3 scale; // Local scale public int variantIndex; // Active variant index }
Asset Locations
Wall materials, floor materials, and furniture module prefabs are no longer loaded from a Resources/ folder. All asset references are managed directly in the HomeDesignerSettings database (Tools > Exoa > Home Designer > Settings).
Sample Module Categories
Ready-to-use furniture prefabs in Samples/Modules/, each with a thumbnail:
| Category | Contents |
|---|---|
Appliances/ | Kitchen & utility appliances |
BedRoom/ | Beds, wardrobes, bedroom furniture |
DoorsWindows/ | Door, window, and archway modules |
Kitchen/ | Cabinets, worktops, kitchen-specific items |
Lights/ | Ceiling lights, floor lamps, wall fixtures |
LivingRoom/ | Sofas, tables, living area furniture |
Patio/ | Outdoor & patio elements |
Creating a Custom Module
Any GameObject from the Project panel can be converted into a ready-to-use furniture module in a few steps.
ModuleControllerThe converter creates a new prefab containing your object, a Box Collider (used for placement validation and snapping), and a ModuleController component. Once the prefab is configured, register it in the HomeDesignerSettings database to make it available at runtime.
Interior Design System
The interior design system handles furniture and decoration placement in 3D space. It activates in the Furnish app state and supports both mouse and touch-driven module manipulation with ghost preview, wall detection, and variant switching.
Key Classes
InteriorDesigner
- Orchestrates module placement & selection
- Ghost (preview) object shown before confirming placement
- Wall & joint detection for smart positioning
- Long-press drag interaction
- Raises
OnRequestButtonActionevents for toolbar state
ModuleController
- Controls an individual placed furniture piece
- Handles selection, drag, rotate, and scale
- Arrow key rotation (15° steps) and scale adjustment
- Placement validation (collision, floor bounds)
- Serializes state as
SceneObjectItemData
ModuleVariants
- Stores alternative visual representations per module
- Variant index persisted in
SceneObjectItemData.variantIndex - Switched at runtime without re-instantiation
ModuleControllerEditor
- Custom Unity Editor inspector for
ModuleController - Warns in the Inspector when a settings entry has a missing prefab reference
- Located in
Scripts/Editor/
Placement Layers
Modules raycast against the following Unity layers to determine valid placement surfaces:
| Layer | Surface |
|---|---|
InteriorFloor | Interior room floor meshes |
ExteriorFloor | Exterior / outdoor floor surfaces (terraces, patios, gardens) |
ExteriorFloor layer to any exterior ground geometry to allow module placement on outdoor surfaces. Both layers are checked simultaneously during placement raycasts.Serialized Module Data
Placed modules are stored as SceneObjectItemData entries inside FloorSpaceItemData.sceneObjects:
C# public class SceneObjectItemData { public string uniqueId; // Instance GUID public string objectId; // Prefab identifier public Vector3 position; // World-space position public Vector3 rotation; // Euler rotation angles public Vector3 scale; // Local scale public int variantIndex; // Active variant index }
Material System
Materials can be applied independently to interior room surfaces (per-wall, floor, ceiling), exterior building walls, and the roof. All material controllers are render-pipeline aware and work with Built-in RP, URP, and HDRP.
Material Controllers
SpaceMaterialController
- Applies materials to an individual room's surfaces
- Per-room wall, floor, and ceiling material slots
- Reads from
RoomSettinginsideFloorSpaceItemData - Triggered in the
Paintapp state
ExteriorMaterialController
- Controls exterior wall and roof surface materials
- Reads from
BuildingEstheticSettings - Applied to
ProceduralExteriorand roof geometry
Material Asset References
Wall and floor material assets are registered in the HomeDesignerSettings database. Open it via Tools > Exoa > Home Designer > Settings to add, remove, or reorder available materials.
RoomSetting — Per-Room Textures
C# // RoomSetting is stored inside FloorSpaceItemData public class RoomSetting { public string wallMat; // Wall material resource name public string floorMat; // Floor material resource name public string ceilingMat; // Ceiling material resource name }
Render Pipeline Switching
Use Tools > Exoa > Render Pipeline Switcher to batch-convert all project materials between Built-in RP, URP, and HDRP. At runtime, MaterialExtensions handles pipeline-aware material property lookups (e.g., _BaseColor vs _Color).
Preview Mode
PreviewModeManager controls the multi-floor 3D preview where all floors of a building are displayed simultaneously, stacked vertically so the entire building is visible at once.
How It Works
When entering the Preview state, each FloorController is repositioned at Y = floorIndex × wallsHeight. The exterior shell and roof are rebuilt to span the full stacked height. Returning to another state restores each floor to Y = 0 and rebuilds normally.
Transition Events
Entering/leaving preview mode fires HomeDesignerEvents.OnRequestRebuild and triggers camera refocus via CameraEvents. UI panels are hidden (only preview controls remain visible) via CanvasGroupsFader.
State Table
| Preview On | Preview Off |
|---|---|
| All floors stacked at Y intervals | All floors at Y = 0 |
| Exterior shell spans full height | Per-floor exterior built independently |
| Furniture visible on all floors | Only current floor furniture visible in edit views |
UI System
Event-driven UI that subscribes to HomeDesignerEvents and fires OnRequest* events. All panel transitions managed by CanvasGroupsFader.
Menu Panels
| Class | Purpose | Active In State |
|---|---|---|
UIFloorsMenu | Floor list — add, remove, duplicate, select | Floors |
UIFloorPlanMenu | Room and opening list for current floor | FloorPlan |
UIFurnishMenu | Furniture category browser & placement | Furnish |
UIBuildingSettings | Wall height, thickness, roof controls | FloorPlan, Paint |
Popup Types
BaseFloatingPopup
Draggable popup that follows a target object. Used for contextual menus attached to rooms/openings.
BaseStaticPopup
Modal overlay popup. Used for alerts, settings, and forms.
AlertPopup
Simple alert dialog with title, message, and confirmation button.
MaterialPopup
Material picker for walls, floors, and ceilings. Loads from Resources.
AddFloorPlanItemPopup
Popup for adding new rooms or openings with type selection.
ControlPointInfoPopup
Context menu for control points — edit coordinates, delete points.
UI Item Hierarchy
Hierarchy UIBaseItem ├── UISpaceBaseItem │ └── UIRoomItem // Room list entry ├── UIOpeningBaseItem │ └── UIOpeningItem // Door/window list entry └── UIFloorItem // Floor list entry
BuildingUIBridge
Singleton that connects scene objects to UI list items. Key methods:
| Method | Description |
|---|---|
ConnectBuildingToUI(BuildingController) | Connect entire building hierarchy to UI |
ConnectFloorToUI(FloorController) | Connect a floor to the floor list |
ConnectToUI(IObjectDrawer) | Connect a room/opening to the item list |
DisconnectFromUI(IObjectDrawer) | Remove a room/opening from the UI |
Custom UI Integration
The built-in UI is entirely optional. You can replace it, extend it, or drive the plugin from a completely different interface — the core logic communicates only through HomeDesignerEvents static delegates.
Wiring a Custom Button
To trigger any action from your own UI, fire the appropriate OnRequest* event:
C# // Save project from a custom Save button public void OnSaveClicked() { HomeDesignerEvents.OnRequestButtonAction?.Invoke( HomeDesignerEvents.ButtonAction.SaveProject, true); } // Switch to Furnish mode from a custom tab public void OnFurnishTabClicked() { AppController.Instance.State = AppController.States.Furnish; } // Add a new floor from a custom button public void OnAddFloorClicked() { HomeDesignerEvents.OnRequestFloorAction?.Invoke( HomeDesignerEvents.FloorPlanAction.Add, null); }
Reacting to State Changes
Subscribe to AppController.OnAppStateChange to show/hide your own panels when the app state changes:
C# void OnEnable() { AppController.OnAppStateChange += OnStateChanged; } void OnDisable() { AppController.OnAppStateChange -= OnStateChanged; } void OnStateChanged(AppController.States state) { myFloorPanel.SetActive(state == AppController.States.Floors); myFurnishPanel.SetActive(state == AppController.States.Furnish); }
Replacing the UI Entirely
To run with no built-in UI at all:
- Remove the UI Canvas prefab from your scene.
- Use
HeadlessBuildingAPIto create and modify buildings from code. - Subscribe directly to
HomeDesignerEventsdelegates to react to persistence and state changes. - Call
AppController.Instance.State = ...to drive the state machine from your own logic.
BuildingUIBridge.ConnectBuildingToUI() when running without the built-in UI — that method only wires scene objects to the built-in list panels.Camera System
TouchCameraLite provides orbit, pan, zoom, and focus controls. Supports both mouse and touch input for desktop and mobile platforms.
Key Features
Orbit & Pan
Smooth orbit around a focal point with configurable damping. Pan via middle-mouse or two-finger drag.
Zoom
Mouse scroll or pinch-to-zoom with min/max distance limits and smooth interpolation.
Focus
Auto-center camera on selected objects with smooth transition. Triggered by F key or CameraEvents.
Perspective Switch
Toggle between perspective and orthographic views. Top-down view for floor plan editing, 3D for interior design.
CameraEvents
Secondary event source for camera-specific operations: focus on objects, perspective switching, and camera state changes.
Extension Methods
A rich set of C# extension methods in Exoa.Extensions namespace.
| File | Extends | Key Methods |
|---|---|---|
TransformExtensions | Transform | DestroyUniversal, find helpers |
RendererExtensions | Renderer | Bounds calculations, material helpers |
MaterialExtensions | Material | Pipeline-aware material operations |
ComponentExtensions | Component | GetOrAddComponent, hierarchy queries |
ColliderExtensions | Collider | Collider sizing and bounds |
ListExtensions | List<T> | Shuffle, safe access |
StringExtensions | string | Sanitization, formatting |
RectTransformExtensions | RectTransform | UI layout helpers |
AnimatorExtensions | Animator | Animation state queries |
Troubleshooting
Icons Not Rendering
Ensure only one TMP Settings file exists in the project with the Font Awesome fallback font configured.
Loading / Saving Errors
You must use the included Newtonsoft.Json version. The NuGet version is not IL2CPP compatible and will cause runtime crashes.
HDRP Grid Not Visible
Adjust emissive intensity on grid material based on your directional light's emission settings.
Render Pipeline Setup
Use Tools > Exoa > Render Pipeline Switcher to convert all materials between Built-in, URP, and HDRP.
Input System Not Active
If keyboard shortcuts or touch input do nothing, verify the Input System package is installed and that Active Input Handling is set to Input System Package (New) or Both in Project Settings > Player. Restart the editor after changing this setting.
HomeDesignerSettings Asset Missing
If the plugin throws a NullReferenceException on startup, the HomeDesignerSettings ScriptableObject may be missing from the Resources/ folder. Re-import the package or create a new one via Tools > Exoa > Home Designer > Settings.
Module Not Appearing in the Panel
After creating a custom module, make sure it is registered in the HomeDesignerSettings database. The furniture panel only lists modules that appear in that database — adding the prefab to the project folder alone is not enough.
Domain Reload / Hot Reload Issues
Static events in HomeDesignerEvents are not cleared on domain reload. If you see duplicate event handlers after entering Play Mode a second time, ensure all subscribers unsubscribe in OnDisable() or OnDestroy().
GLB Export Button Missing
The GLB export option only appears in HomeDesignerSettings when the UNIGLTF scripting define symbol is present. See the GLB Export Setup section for install steps.
FAQ
Can I use Home Designer without the built-in UI?
Yes. HeadlessBuildingAPI creates and manipulates buildings entirely from code with no UI dependencies. Load the Demo_API scene for a working example, or instantiate HeadlessBuildingAPI in your own scene. The UI is optional — connect it later via BuildingUIBridge.Instance.ConnectBuildingToUI(building) if needed.
Can I have multiple buildings in one scene?
The system tracks one active building via BuildingController.currentBuilding. You can instantiate multiple BuildingController prefabs, but only one is "active" at a time for editor operations. Switching the active building is done with BuildingController.SetCurrentBuilding(building).
Does it work on WebGL?
The core procedural and serialization systems are WebGL-compatible. GLB export is not available on WebGL (filesystem access is restricted). Test thoroughly — some Application.persistentDataPath operations behave differently in a browser sandbox.
Can I customise keyboard shortcuts?
Yes — open the HomeDesignerInputActions asset in the Input Action editor and remap any binding. No code changes required. Camera shortcuts (F, Space, scroll) live in the same asset under the Camera action map.
How do I add a new app state?
Add a value to the AppController.States enum, then handle the new state in any subscriber of AppController.OnAppStateChange. The state machine itself has no hard-coded logic per state — all behaviour is driven by event subscribers.
Is the license check included in builds?
No. The license system is editor-only (#if UNITY_EDITOR). Builds contain no license enforcement — the DLLs that perform the check are editor assemblies and are stripped at build time.
Can I save and load multiple projects?
Yes. Projects are saved as individual JSON files (and optionally GLB files) under Application.persistentDataPath. Each file is identified by its project name. Call ProjectSerializer.Instance.SaveProject(name) and ProjectSerializer.Instance.LoadProject(name) to manage them, and enumerate the directory to show a file list in your UI.
Upgrading
When updating to a new version of Home Designer, follow these steps to avoid conflicts:
- Back up your project (or commit to version control) before importing the new package.
- Delete the old package folder (
Assets/Exoa/HomeDesigner/) before importing the new.unitypackage— stale scripts can cause compile errors if files are renamed or removed. - Re-run the Render Pipeline Switcher after import if new materials were added (Tools > Exoa > Render Pipeline Switcher).
- Regenerate the Input Actions asset if new shortcuts were added — run Tools > Exoa > Create Input Actions Asset to overwrite the old one.
- Check HomeDesignerSettings for any new fields that need to be configured after the upgrade.
"version": "v3"). Existing saves load correctly in newer versions. Always verify with a test load after upgrading.Breaking Changes to Watch For
| Area | What to check |
|---|---|
| Renamed classes | Check the release notes for any class renames (e.g. FloorPlanSerializer → ProjectSerializer) and update your own code accordingly. |
| Removed scripting symbols | Old symbols (FLOORMAP_MODULE, INTERIOR_MODULE, FBX_EXPORTER) are no longer used — remove them from Player Settings to avoid confusion. |
| Input System | If the HomeDesignerInputActions asset was customised, merge your bindings manually after regenerating. |
Third-Party Dependencies
| Library | Purpose | Notes |
|---|---|---|
| Newtonsoft.Json | JSON serialization/deserialization | Custom IL2CPP-compatible build — do NOT replace |
| ProceduralToolkit | Mesh construction (MeshDraft abstraction) | Included in Packages/Toolkit/ |
| ClipperLib | Polygon offsetting for wall thickness | Bundled with ProceduralToolkit |
| LibTessDotNet | Polygon tessellation | Bundled with ProceduralToolkit |
| Outline | Object selection outline / highlight effect | Included in Packages/Outline/ — assembly: Exoa.Effects.Scripts |
| Unity GLB Exporter | Export buildings as GLB files for cloud upload | Install via Package Manager — enable with UNIGLTF symbol |
| TextMeshPro | UI text rendering | With Font Awesome fallback for icons |
Common Recipes
Copy-pasteable snippets for the most frequent tasks.
Create a rectangular room from code
C# var settings = new BuildingConstructionSettings(); settings.ApplyDefaults(); BuildingController building = HeadlessBuildingAPI.Instance.CreateBuilding("House", settings); FloorController floor = HeadlessBuildingAPI.Instance.CreateFloor(building); RoomController room = HeadlessBuildingAPI.Instance.CreateRectangularRoom( width: 5f, length: 7f, position: Vector3.zero, floorCtrl: floor, roomName: "Living Room"); building.Build();
Trigger a full rebuild
C# HomeDesignerEvents.OnRequestRebuild?.Invoke();
Switch app state
C# AppController.Instance.State = AppController.States.FloorPlan; // enter draw mode AppController.Instance.State = AppController.States.Preview; // enter 3D preview
Clear the current scene
C# // args: clearScene, clearFloorUI, clearFloorPlanUI HomeDesignerEvents.OnRequestClearAll?.Invoke(true, true, true);
React to floor selection changes
C# void OnEnable() => HomeDesignerEvents.OnFloorChanged += OnFloorChanged; void OnDisable() => HomeDesignerEvents.OnFloorChanged -= OnFloorChanged; void OnFloorChanged(FloorData data, int index) => Debug.Log($"Now editing floor {index}");
Load a building from a JSON string
C# string json = File.ReadAllText(path); BuildingController building = HeadlessBuildingAPI.Instance.CreateBuildingFromJson("Loaded", json); building.Build(); BuildingUIBridge.Instance?.ConnectBuildingToUI(building);
Focus camera on the current building
C# CameraEvents.OnRequestObjectFocus?.Invoke( BuildingController.GetCurrentBuilding().gameObject, true);
Assembly Definitions
The project uses assembly definitions to isolate libraries and reduce compile times:
| Assembly | Location | References |
|---|---|---|
Exoa.HomeDesigner.Scripts | Scripts/ | Common assemblies, ProceduralToolkit, TouchCameraPro, Effects |
Exoa.Designer.Editor | Editor/ | Exoa.HomeDesigner.Scripts |
Exoa.HomeDesigner.Tests.Editor | Tests/Editor/ | Exoa.HomeDesigner.Scripts |
Exoa.Common.Scripts.Extensions | Scripts/Extensions/ | Maths, Touch |
Exoa.Common.Scripts.Maths | Scripts/Maths/ | — |
Exoa.Common.Scripts.Touch | Scripts/Inputs/Touch/ | — |
ProceduralToolkit | Packages/Toolkit/Runtime/ | ClipperLib, LibTessDotNet |
ProceduralToolkit.ClipperLib | Packages/Toolkit/Runtime/ClipperLib/ | — |
ProceduralToolkit.LibTessDotNet | Packages/Toolkit/Runtime/LibTessDotNet/ | — |
ProceduralToolkit.Editor | Packages/Toolkit/Editor/ | ProceduralToolkit |
Exoa.TouchCameraPro.Scripts | Packages/TouchCameraLite/Scripts/ | — |
Exoa.TouchCameraPro.Editor | Packages/TouchCameraLite/Editor/ | Exoa.TouchCameraPro.Scripts |
Exoa.Effects.Scripts | Packages/Outline/ | — |
Exoa.Effects.Scripts.Editor | Packages/Outline/Scripts/Editor/ | Exoa.Effects.Scripts |
Service Locator
ServiceLocator is a lightweight dependency injection container that decouples systems from direct singleton references. Services are registered once (typically in Awake) and resolved wherever needed.
C# // Register a service (e.g. in Awake) ServiceLocator.Register<BuildingController>(building); // Resolve anywhere — no direct static reference needed var building = ServiceLocator.Get<BuildingController>(); building.Build(); // Check registration before use if (ServiceLocator.Has<BuildingController>()) ServiceLocator.Get<BuildingController>().Build(); // Unregister on scene unload ServiceLocator.Clear();
ServiceLocator over direct singleton access when writing new systems — it keeps code testable and loosely coupled without requiring scene-level MonoBehaviour wiring.BuildingFactory
BuildingFactory (singleton) provides factory methods for instantiating building component prefabs and converting between world-space coordinates and the normalized [0,1] range used in data models.
Key Methods
| Method | Description |
|---|---|
ConvertWorldPointsToNormalized(List<Vector3>) |
Converts world-space polygon points to normalized Vector2 list for storage in FloorSpaceItemData.normalizedPositions |
ConvertNormalizedPointsToWorld(List<Vector2>) |
Converts stored normalized positions back to world space for drawing and mesh generation |
InstantiateRoom(prefab, parent) |
Creates a room GameObject as child of a FloorController |
InstantiateOpening(prefab, parent) |
Creates an opening (door/window/archway) GameObject as child of a FloorController |
BuildingFactory.ConvertWorldPointsToNormalized() before storing user-drawn points.