so as i said to you iam currently building my own logic drawing based on your plugin `using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Exoa.Designer;
using static Exoa.Designer.DataModel;
using Exoa.Events;
using Exoa.Json;
public class FloorMapLogic : MonoBehaviour
{
public enum Mode { Idle, Draw, EditPoints }
[Serializable]
public class MenuItem
{
public Button button;
public GameObject uiItemPrefab;
public FloorMapItemType type;
}
[Header("Menu Items (each has a Button)")]
public List<MenuItem> menuItems = new List<MenuItem>();
[SerializeField] private CustomFloorMapUI customFloorMapUI;
[SerializeField] private FloorMapSerializer serializer;
[Header("UI Placement")]
public RectTransform uiItemsContainer;
[Header("Scene Containers (use your Controller children)")]
public Transform globalContainer;
public Transform roomsContainer;
public Transform openingsContainer;
public Transform controlPointsContainer;
[Header("Runtime")]
public Button exitDrawButton;
public bool createItemOnStartDrawing = true;
public bool useSerializerPrefabs = true;
public static FloorMapLogic Instance { get; private set; }
public Mode CurrentMode { get; private set; } = Mode.Idle;
private class ItemRuntime
{
public UIBaseItem ui;
public ControlPointsController cpc;
public IObjectDrawer drawer;
public FloorMapItemType Type;
}
private readonly Dictionary<UIBaseItem, ItemRuntime> registry = new();
private readonly Dictionary<FloorMapItemType, UIBaseItem> itemByType = new();
private MenuItem currentMenuItem;
private UIBaseItem activeUI;
private bool initialized;
private void Awake()
{
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
Instance = this;
if (globalContainer == null)
{
var g = new GameObject("GlobalContainer");
globalContainer = g.transform;
}
if (roomsContainer == null)
{
var r = new GameObject("Rooms"); r.transform.SetParent(globalContainer, false);
roomsContainer = r.transform;
}
if (openingsContainer == null)
{
var o = new GameObject("Openings"); o.transform.SetParent(globalContainer, false);
openingsContainer = o.transform;
}
if (controlPointsContainer == null)
{
var c = new GameObject("ControlPointsControllers"); c.transform.SetParent(globalContainer, false);
controlPointsContainer = c.transform;
}
}
private void Start()
{
foreach (var mi in menuItems)
{
if (mi?.button == null) continue;
var captured = mi;
captured.button.onClick.AddListener(() => StartDrawing(captured));
}
if (exitDrawButton == null)
{
var ui = customFloorMapUI;
if (ui != null) exitDrawButton = ui.GetExitDrawButton();
}
if (exitDrawButton != null) exitDrawButton.onClick.AddListener(ExitDrawing);
initialized = true;
UpdateExitDrawButton();
}
public void StartDrawing(MenuItem item)
{
GuardInit();
PruneDead();
if (IsOpening(item.type) && !HasConstructedRoom())
{
Debug.LogWarning("[FloorMapLogic] Draw a Room first before adding openings.");
CurrentMode = Mode.Idle;
UpdateExitDrawButton();
return;
}
currentMenuItem = item;
if (itemByType.TryGetValue(item.type, out var existing) && existing != null)
{
activeUI = existing;
activeUI.ToggleDrawMode(true, true, false);
DisableAllExcept(activeUI);
if (registry.TryGetValue(activeUI, out var rt) && rt?.cpc != null)
{
int cp = rt.cpc.GetPointsList().Count;
CurrentMode = (cp >= MinPointsNeeded(rt.Type)) ? Mode.EditPoints : Mode.Draw;
if (CurrentMode == Mode.Draw) rt.cpc.CreatePathVisualization();
}
else
{
CurrentMode = Mode.Draw;
}
UpdateExitDrawButton();
return;
}
if (createItemOnStartDrawing)
{
activeUI = CreateItemInternal(item);
if (activeUI != null)
{
itemByType[item.type] = activeUI;
activeUI.ToggleDrawMode(true, true, false);
DisableAllExcept(activeUI);
if (registry.TryGetValue(activeUI, out var rt) && rt?.cpc != null)
{
int cp = rt.cpc.GetPointsList().Count;
CurrentMode = (cp >= MinPointsNeeded(rt.Type)) ? Mode.EditPoints : Mode.Draw;
if (CurrentMode == Mode.Draw) rt.cpc.CreatePathVisualization();
}
else
{
CurrentMode = Mode.Draw;
}
UpdateExitDrawButton();
return;
}
}
DisableAllExcept(null);
CurrentMode = Mode.Draw;
UpdateExitDrawButton();
}
public void ExitDrawing()
{
PruneDead();
CurrentMode = Mode.Idle;
currentMenuItem = null;
if (activeUI != null) activeUI.ToggleDrawMode(false, true, true);
activeUI = null;
// Hide all CPCs
foreach (var kv in new List<UIBaseItem>(registry.Keys))
{
var ui = kv;
if (ui == null) { registry.Remove(kv); continue; }
ui.ToggleDrawMode(false, true, true);
}
UpdateExitDrawButton();
}
public void AppendPointToActiveItem(Vector3 worldPoint)
{
if (CurrentMode != Mode.Draw) return;
if (activeUI == null)
{
if (currentMenuItem == null) return;
if (IsOpening(currentMenuItem.type) && !HasConstructedRoom())
return;
activeUI = CreateItemInternal(currentMenuItem);
if (activeUI == null) return;
itemByType[currentMenuItem.type] = activeUI;
activeUI.ToggleDrawMode(true, true, false);
CurrentMode = Mode.Draw;
}
PruneDead();
if (!registry.TryGetValue(activeUI, out var rt) || rt.cpc == null) return;
var grid = rt.cpc.GetGrid();
if (grid == null) return;
Vector3 normalized = worldPoint;
try
{
var p = grid.GetNormalizedPosition(worldPoint);
normalized = new Vector3(p.x, worldPoint.y, p.y);
}
catch
{
// if GetNormalizedPosition returns Vector3 in build,just do:
// normalized = grid.GetNormalizedPosition(worldPoint);
}
int countBefore = rt.cpc.GetPointsList().Count;
int minNeeded = MinPointsNeeded(rt.Type);
bool sendEventNow = (countBefore + 1) >= minNeeded;
rt.cpc.CreateControlPointBasedOnNormalizedPosition(
normalized,
useThickness: false,
index: uint.MaxValue,
sendEvent: sendEventNow
);
activeUI.ToggleDrawMode(true, true, false);
if (sendEventNow)
{
TriggerRebuild(rt);
}
else
{
rt.cpc.CreatePathVisualization();
}
if (CurrentMode == Mode.Idle) CurrentMode = Mode.EditPoints;
UpdateExitDrawButton();
}
public void CommitActiveItem()
{
if (activeUI != null) activeUI.ToggleDrawMode(false, true, true);
activeUI = null;
CurrentMode = Mode.Idle;
UpdateExitDrawButton();
}
public IEnumerable<UIBaseItem> EnumerateUIItems() => registry.Keys;
public void ClearAll(bool keepContainers = true)
{
foreach (var kv in registry)
{
if (kv.Value?.ui != null) Destroy(kv.Value.ui.gameObject);
if (kv.Value?.cpc != null) Destroy(kv.Value.cpc.gameObject);
if (kv.Value?.drawer != null && kv.Value.drawer.GO != null) Destroy(kv.Value.drawer.GO);
}
registry.Clear();
itemByType.Clear();
if (!keepContainers)
{
if (roomsContainer != null) Destroy(roomsContainer.gameObject);
if (openingsContainer != null) Destroy(openingsContainer.gameObject);
if (controlPointsContainer != null) Destroy(controlPointsContainer.gameObject);
}
activeUI = null;
CurrentMode = Mode.Idle;
UpdateExitDrawButton();
}
private static int MinPointsNeeded(FloorMapItemType t)
=> (t == FloorMapItemType.Room || t == FloorMapItemType.Outside) ? 3 : 2;
private UIBaseItem CreateItemInternal(MenuItem mi)
{
if (mi == null || mi.uiItemPrefab == null)
{
Debug.LogError($"[FloorMapLogic] MenuItem not configured ({mi?.type.ToString() ?? "null"})");
return null;
}
Transform uiParent = uiItemsContainer != null ? uiItemsContainer : null;
GameObject uiGo = uiParent != null ? Instantiate(mi.uiItemPrefab, uiParent) : Instantiate(mi.uiItemPrefab);
var ui = uiGo.GetComponent<UIBaseItem>();
if (ui == null)
{
Debug.LogError("[FloorMapLogic] UI prefab must include UIBaseItem.");
Destroy(uiGo);
return null;
}
ui.sequencingItemType = mi.type;
if (serializer == null || serializer.controlPointControllerPrefab == null)
{
Debug.LogError("[FloorMapLogic] FloorMapSerializer.controlPointControllerPrefab is not set in scene.");
Destroy(uiGo);
return null;
}
GameObject cpcGo = Instantiate(serializer.controlPointControllerPrefab, controlPointsContainer);
cpcGo.name = $"CPC_{mi.type}_{Guid.NewGuid().ToString().Substring(0, 4)}";
var cpc = cpcGo.GetComponent<ControlPointsController>();
if (cpc == null)
{
Debug.LogError("[FloorMapLogic] CPC prefab must include ControlPointsController.");
Destroy(uiGo); Destroy(cpcGo);
return null;
}
if (mi.type == FloorMapItemType.Door || mi.type == FloorMapItemType.Window || mi.type == FloorMapItemType.Opening)
cpc.snapToPathLines = true;
GameObject ctrlPrefab = ResolveControllerPrefab(mi.type);
if (ctrlPrefab == null)
{
Debug.LogError($"[FloorMapLogic] Missing controller prefab for {mi.type} on FloorMapSerializer.");
Destroy(uiGo); Destroy(cpcGo);
return null;
}
Transform geomParent = (mi.type == FloorMapItemType.Room || mi.type == FloorMapItemType.Outside)
? roomsContainer : openingsContainer;
GameObject ctrlGo = Instantiate(ctrlPrefab, geomParent);
var drawer = ctrlGo.GetComponent<IObjectDrawer>();
if (drawer == null)
{
Debug.LogError("[FloorMapLogic] Controller prefab must implement IObjectDrawer.");
Destroy(uiGo); Destroy(cpcGo); Destroy(ctrlGo);
return null;
}
ui.cpc = cpc;
ui.drawer = drawer;
drawer.Cpc = cpc;
drawer.UI = ui;
drawer.Init();
var item = new ItemRuntime { ui = ui, cpc = cpc, drawer = drawer, Type = mi.type };
registry[ui] = item;
itemByType[mi.type] = ui;
HookUiEvents(ui);
return ui;
}
private GameObject ResolveControllerPrefab(FloorMapItemType type)
{
if (!useSerializerPrefabs || serializer == null) return null;
switch (type)
{
case FloorMapItemType.Room: return serializer.roomControllerPrefab;
case FloorMapItemType.Opening: return serializer.openingControllerPrefab;
case FloorMapItemType.Door: return serializer.openingControllerPrefab;
case FloorMapItemType.Window: return serializer.openingControllerPrefab;
default: return null;
}
}
private void HookUiEvents(UIBaseItem ui)
{
ui.OnChangeSettings += (data, type) =>
{
if (!registry.TryGetValue(ui, out var rt)) return;
TriggerRebuild(rt);
};
ui.OnDrawModeToggled += (item, active) =>
{
if (!registry.ContainsKey(ui)) return;
if (active)
{
activeUI = ui;
CurrentMode = Mode.EditPoints;
DisableAllExcept(activeUI);
}
else
{
if (activeUI == ui) activeUI = null;
if (activeUI == null) CurrentMode = Mode.Idle;
}
UpdateExitDrawButton();
};
ui.OnIsolate += (item, active) =>
{
foreach (var kv in registry)
{
bool visible = kv.Key == ui || !active;
var go = kv.Value.drawer?.GO;
if (go != null) go.SetActive(visible);
}
};
ui.OnDuplicate += (data, type) =>
{
var mi = menuItems.Find(m => m.type == type);
var dupe = CreateItemInternal(mi);
if (dupe == null) return;
dupe.SetData(data);
if (registry.TryGetValue(dupe, out var rt)) TriggerRebuild(rt, true);
};
StartCoroutine(WatchForDeletion(ui));
}
private static bool IsOpening(FloorMapItemType t)
{
return t == FloorMapItemType.Door
|| t == FloorMapItemType.Window
|| t == FloorMapItemType.Opening;
}
private bool HasConstructedRoom()
{
foreach (var kv in registry)
{
var rt = kv.Value;
if (rt == null) continue;
if (rt.Type == FloorMapItemType.Room || rt.Type == FloorMapItemType.Outside)
{
try
{
var pts = rt.cpc?.GetPointsList();
if (pts != null && pts.Count >= 3)
return true;
}
catch { }
}
}
return false;
}
private IEnumerator WatchForDeletion(UIBaseItem ui)
{
while (ui != null) yield return null;
PruneDead();
UpdateExitDrawButton();
}
private void TriggerRebuild(ItemRuntime rt, bool sendRepositionOpeningsEvent = true)
{
var data = rt.ui.GetData();
rt.ui.SetData(data);
rt.ui.ToggleDrawMode(false, true, true); // exit draw mode
rt.cpc?.ReSnapControlPoints();
}
private void DisableAllExcept(UIBaseItem keep)
{
PruneDead();
var items = new List<UIBaseItem>(registry.Keys);
foreach (var ui in items)
{
if (ui == null || ui == keep) continue;
if (!registry.TryGetValue(ui, out var r) || r.cpc == null) continue;
ui.ToggleDrawMode(false, true, false);
}
UpdateExitDrawButton();
}
private void UpdateExitDrawButton()
{
if (exitDrawButton != null)
exitDrawButton.gameObject.SetActive(activeUI != null || CurrentMode != Mode.Idle);
}
private void GuardInit()
{
if (!initialized)
throw new InvalidOperationException("FloorMapLogic not initialized. Ensure Start() ran and containers are assigned.");
}
private void PruneDead()
{
var deadKeys = new List<UIBaseItem>();
foreach (var kv in registry)
{
var ui = kv.Key;
var rt = kv.Value;
if (ui == null || rt == null || rt.cpc == null || rt.drawer == null || rt.drawer.GO == null)
{
deadKeys.Add(kv.Key);
}
}
foreach (var k in deadKeys)
{
if (k == activeUI) activeUI = null;
if (k != null) itemByType.Remove(k.sequencingItemType);
registry.Remove(k);
}
var deadTypes = new List<FloorMapItemType>();
foreach (var p in itemByType)
{
if (p.Value == null) deadTypes.Add(p.Key);
}
foreach (var t in deadTypes) itemByType.Remove(t);
}
public UIBaseItem CreateItemFromData(FloorMapItem data)
{
var t = data.GetItemType();
// ---------- SPECIAL HANDLING FOR OPENINGS ----------
if (t == FloorMapItemType.Opening)
{
// Find the first room in registry (openings must belong to a room)
foreach (var kv in registry)
{
if (kv.Value.drawer is ProceduralRoom pr)
{
// Convert FloorMapItem -> GenericOpening
ProceduralRoom.GenericOpening go = new ProceduralRoom.GenericOpening
{
type = ProceduralRoom.GenericOpening.OpeningType.Opening,
width = data.width,
height = data.height,
yPos = data.ypos,
hasWindow = data.hasWindow,
windowFrameSize = data.windowFrameSize,
windowSizeH = data.windowSizeH,
windowSizeV = data.windowSizeV,
windowSubDivH = data.windowSubDivH,
windowSubDivV = data.windowSubDivV,
// xPos = midpoint from saved points (basic approximation)
xPos = (data.normalizedPositions != null && data.normalizedPositions.Count > 0)
? data.normalizedPositions[0].x
: 0f
};
if (pr.openings == null)
pr.openings = new List<ProceduralRoom.GenericOpening>();
pr.openings.Add(go);
pr.GenerateWalls(); // force rebuild with opening cutouts
Debug.Log("[FloorMapLogic] Opening loaded and added to room walls.");
return null; // no prefab UI for openings
}
}
Debug.LogWarning("[FloorMapLogic] No room found for Opening. Skipping.");
return null;
}
// ---------- NORMAL FLOW FOR OTHER TYPES ----------
var mi = menuItems.Find(m => m != null && m.type == t);
if (mi == null)
{
Debug.LogError("[FloorMapLogic] No MenuItem configured for type " + t);
return null;
}
var ui = CreateItemInternal(mi);
if (ui == null || ui.cpc == null || ui.drawer == null)
{
Debug.LogError("[FloorMapLogic] CreateItemInternal failed for type " + t);
return null;
}
ui.SetData(data);
var points = data.normalizedPositions ?? new List<Vector3>();
var dirs = data.directions ?? new List<Vector3>();
uint idx = 0;
for (int i = 0; i < points.Count; i++)
{
var cp = ui.cpc.CreateControlPointBasedOnNormalizedPosition(points[i], false, idx++, false);
if (i < dirs.Count) cp.dir = dirs[i];
}
ui.cpc.CreatePathVisualization();
ui.cpc.ReSnapControlPoints();
ui.ToggleDrawMode(false, true, true);
if (ui.drawer.GO == null)
{
ui.drawer.Init();
if (ui.drawer.GO == null)
{
Debug.LogWarning("[FloorMapLogic] Drawer.GO is still null after Init() for type " + t);
}
}
if (registry.TryGetValue(ui, out var rt))
TriggerRebuild(rt, true);
return ui;
}
public void SaveToJson(string fileName)
{
if (string.IsNullOrEmpty(fileName))
{
Debug.LogError("[FloorMapLogic] SaveToJson: filename is empty");
return;
}
// Build FloorMapV2 object
var v2 = new FloorMapV2
{
version = "v2",
floors = new List<FloorMapLevel>(),
settings = (BuildingSettings)AppController.Instance.GetBuildingSettings()
};
var level = new FloorMapLevel(null);
level.GenerateUniqueId();
var spaces = new List<FloorMapItem>();
foreach (var ui in EnumerateUIItems())
{
spaces.Add(ui.GetData()); // uses normalizedPositions from CPC
}
level.spaces = spaces;
v2.floors.Add(level);
// Serialize with Exoa.Json (same as plugin)
string json = JsonConvert.SerializeObject(
v2, Formatting.Indented,
new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }
);
// Save via SaveSystem
SaveSystem.Create(SaveSystem.Mode.FILE_SYSTEM)
.SaveFileItem(fileName, HDSettings.EXT_FLOORMAP_FOLDER, json, (saved) =>
{
Debug.Log("[FloorMapLogic] Saved file: " + fileName);
});
}
// =====================
// LOAD
// =====================
public void LoadFromJson(string json, bool clearScene = true)
{
if (clearScene)
ClearAll();
// Deserialize using your DataModel
DataModel.FloorMapV2 file = DataModel.DeserializeFloorMapJsonFile(json);
if (file.floors == null || file.floors.Count == 0)
{
Debug.LogError("[FloorMapLogic] No floors found in JSON.");
return;
}
// Collect all items from all floors
List<DataModel.FloorMapItem> items = new List<DataModel.FloorMapItem>();
foreach (var floor in file.floors)
{
if (floor.spaces != null)
items.AddRange(floor.spaces);
}
// ---- PASS 1: Rooms ----
foreach (var item in items)
{
if (item.GetItemType() == DataModel.FloorMapItemType.Room)
CreateItemFromData(item);
}
// ---- PASS 2: Doors / Windows / Openings ----
foreach (var item in items)
{
var type = item.GetItemType();
if (type == DataModel.FloorMapItemType.Door ||
type == DataModel.FloorMapItemType.Window ||
type == DataModel.FloorMapItemType.Opening)
{
CreateItemFromData(item);
}
}
Debug.Log("[FloorMapLogic] Load complete. Rooms + openings restored.");
}
}
`
so here's my main logic code that handle the floor drawing loading and saving and iam wiring it with my own UI
but i noticed something is very weird and i tried to solve but it didn't work
which after i load the scene every thing will be correct doors windows room ... only the openings they are not appearing idk what's the issue of it