forked from Shardstone/trail-into-darkness
First commit on my server, yey!
This commit is contained in:
643
Packages/com.jovian.zonesystem/Editor/ZoneEditorWindow.cs
Normal file
643
Packages/com.jovian.zonesystem/Editor/ZoneEditorWindow.cs
Normal file
@@ -0,0 +1,643 @@
|
||||
#if UNITY_EDITOR
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Jovian.ZoneSystem.Editor {
|
||||
/// <summary>
|
||||
/// Main Zone Editor window.
|
||||
/// Open via: Window → Zone System → Zone Editor
|
||||
/// </summary>
|
||||
public class ZoneEditorWindow : EditorWindow {
|
||||
|
||||
private string _exportPath = "Assets/StreamingAssets/zones.json";
|
||||
|
||||
// ── Create form state ───────────────────────────────────────────
|
||||
private bool _showCreateForm;
|
||||
private string _newZoneName = "New Zone";
|
||||
private ZoneShape _newZoneShape = ZoneShape.Square;
|
||||
|
||||
// ── Edit state ──────────────────────────────────────────────────
|
||||
private ZoneInstance _editingZone;
|
||||
private SerializedObject _editingSO;
|
||||
private bool _isUnsavedNewData;
|
||||
private bool _hasUnsavedChanges;
|
||||
private string _saveError;
|
||||
private ZoneShape _shapeOnEditStart;
|
||||
|
||||
// ── Scroll / foldouts ───────────────────────────────────────────
|
||||
private Vector2 _scrollPos;
|
||||
private bool _showExportFoldout = true;
|
||||
|
||||
// ── GUI ──────────────────────────────────────────────────────────
|
||||
private void OnGUI() {
|
||||
DrawHeader();
|
||||
|
||||
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos);
|
||||
|
||||
if(_editingZone != null) {
|
||||
DrawEditSection();
|
||||
} else {
|
||||
DrawCreateButton();
|
||||
if(_showCreateForm) {
|
||||
DrawCreateForm();
|
||||
}
|
||||
EditorGUILayout.Space(6);
|
||||
DrawSceneZonesList();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(6);
|
||||
DrawExportSection();
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private void OnFocus() {
|
||||
ValidateEditingState();
|
||||
Repaint();
|
||||
}
|
||||
private void OnHierarchyChange() {
|
||||
ValidateEditingState();
|
||||
Repaint();
|
||||
}
|
||||
private void OnSelectionChange() {
|
||||
ValidateEditingState();
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private void ValidateEditingState() {
|
||||
if(_editingZone == null && _editingSO != null) {
|
||||
_editingSO = null;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("Window/Zone System/Zone Editor")]
|
||||
public static void Open() {
|
||||
GetWindow<ZoneEditorWindow>("Zone Editor");
|
||||
}
|
||||
|
||||
public static void OpenAndEdit(ZoneInstance zone) {
|
||||
ZoneEditorWindow window = GetWindow<ZoneEditorWindow>("Zone Editor");
|
||||
window.EnterEditMode(zone);
|
||||
}
|
||||
|
||||
// ── Header ──────────────────────────────────────────────────────
|
||||
|
||||
private void DrawHeader() {
|
||||
Color prevBg = GUI.backgroundColor;
|
||||
GUI.backgroundColor = new Color(0.2f, 0.2f, 0.3f);
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
GUI.backgroundColor = prevBg;
|
||||
|
||||
EditorGUILayout.LabelField("🗺 Zone System Editor", new GUIStyle(EditorStyles.boldLabel) {
|
||||
fontSize = 14,
|
||||
normal = { textColor = new Color(0.8f, 0.9f, 1f) }
|
||||
});
|
||||
EditorGUILayout.LabelField("Define map zones for encounter difficulty and chance.",
|
||||
EditorStyles.miniLabel);
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.Space(4);
|
||||
}
|
||||
|
||||
// ── Create Button + Dropdown ────────────────────────────────────
|
||||
|
||||
private void DrawCreateButton() {
|
||||
GUI.backgroundColor = new Color(0.3f, 0.7f, 0.3f);
|
||||
if(GUILayout.Button("➕ Create New Zone", GUILayout.Height(30))) {
|
||||
_showCreateForm = !_showCreateForm;
|
||||
}
|
||||
GUI.backgroundColor = Color.white;
|
||||
}
|
||||
|
||||
private void DrawCreateForm() {
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
_newZoneName = EditorGUILayout.TextField("Zone Name", _newZoneName);
|
||||
_newZoneShape = (ZoneShape)EditorGUILayout.EnumPopup("Shape", _newZoneShape);
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
EditorGUILayout.LabelField("All zone data can be edited after creation.",
|
||||
EditorStyles.miniLabel);
|
||||
|
||||
if(GUILayout.Button("Create & Edit", GUILayout.Height(26))) {
|
||||
CreateZoneInScene();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
// ── Edit Section ────────────────────────────────────────────────
|
||||
|
||||
private void EnterEditMode(ZoneInstance zone) {
|
||||
_editingZone = zone;
|
||||
_editingSO = zone.data != null ? new SerializedObject(zone.data) : null;
|
||||
_showCreateForm = false;
|
||||
_shapeOnEditStart = zone.data != null ? zone.data.shape : ZoneShape.Polygon;
|
||||
|
||||
Selection.activeGameObject = zone.gameObject;
|
||||
SceneView.FrameLastActiveSceneView();
|
||||
ZoneInstanceEditor.startEditingOnNextSelect = true;
|
||||
}
|
||||
|
||||
private void ExitEditMode() {
|
||||
// If exiting with unsaved new data, clean up the in-memory asset
|
||||
if(_isUnsavedNewData && _editingZone != null && _editingZone.data != null) {
|
||||
DestroyImmediate(_editingZone.data);
|
||||
_editingZone.data = null;
|
||||
}
|
||||
_editingZone = null;
|
||||
_editingSO = null;
|
||||
_isUnsavedNewData = false;
|
||||
_hasUnsavedChanges = false;
|
||||
_saveError = null;
|
||||
}
|
||||
|
||||
private void DrawEditSection() {
|
||||
// Validate that the zone still exists
|
||||
if(_editingZone == null || _editingZone.data == null) {
|
||||
ExitEditMode();
|
||||
return;
|
||||
}
|
||||
|
||||
// Back button
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Color prevBackBg = GUI.backgroundColor;
|
||||
GUI.backgroundColor = new Color(0.9f, 0.3f, 0.3f);
|
||||
if(GUILayout.Button("← Back", GUILayout.Height(36), GUILayout.Width(70))) {
|
||||
ExitEditMode();
|
||||
GUI.backgroundColor = prevBackBg;
|
||||
EditorGUILayout.EndHorizontal();
|
||||
return;
|
||||
}
|
||||
GUI.backgroundColor = prevBackBg;
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
ZoneData d = _editingZone.data;
|
||||
|
||||
// Zone header with color swatch
|
||||
Color prevBg = GUI.backgroundColor;
|
||||
GUI.backgroundColor = new Color(0.2f, 0.3f, 0.4f);
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
GUI.backgroundColor = prevBg;
|
||||
|
||||
string headerLabel = _isUnsavedNewData ? $"New Zone: {d.zoneName}" : $"Editing: {d.zoneName}";
|
||||
EditorGUILayout.LabelField(headerLabel, new GUIStyle(EditorStyles.boldLabel) {
|
||||
fontSize = 13,
|
||||
normal = { textColor = new Color(0.9f, 0.95f, 1f) }
|
||||
});
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
// Draw the ZoneData fields using SerializedObject
|
||||
if(_editingSO == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
_editingSO.Update();
|
||||
|
||||
// Identity
|
||||
EditorGUILayout.LabelField("Identity", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(_editingSO.FindProperty("zoneId"), new GUIContent("Zone ID"));
|
||||
EditorGUILayout.PropertyField(_editingSO.FindProperty("zoneName"), new GUIContent("Zone Name"));
|
||||
|
||||
// Track role changes to auto-apply color
|
||||
SerializedProperty rolePropForColor = _editingSO.FindProperty("role");
|
||||
ZoneRole roleBefore = (ZoneRole)rolePropForColor.enumValueIndex;
|
||||
EditorGUILayout.PropertyField(rolePropForColor, new GUIContent("Role"));
|
||||
ZoneRole roleAfter = (ZoneRole)rolePropForColor.enumValueIndex;
|
||||
if(roleBefore != roleAfter) {
|
||||
ZoneEditorSettings settings = FindSettings();
|
||||
_editingSO.FindProperty("debugColor").colorValue = settings.GetColorForRole(roleAfter);
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(_editingSO.FindProperty("priority"), new GUIContent("Priority"));
|
||||
EditorGUILayout.PropertyField(_editingSO.FindProperty("shape"), new GUIContent("Shape"));
|
||||
|
||||
SerializedProperty shapeProp = _editingSO.FindProperty("shape");
|
||||
if((ZoneShape)shapeProp.enumValueIndex == ZoneShape.Circle) {
|
||||
EditorGUILayout.PropertyField(_editingSO.FindProperty("circleRadius"), new GUIContent("Circle Radius"));
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(8);
|
||||
|
||||
// Role-specific fields
|
||||
SerializedProperty roleProp = _editingSO.FindProperty("role");
|
||||
ZoneRole role = (ZoneRole)roleProp.enumValueIndex;
|
||||
switch(role) {
|
||||
case ZoneRole.Base:
|
||||
EditorGUILayout.LabelField("Base Zone Settings", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(_editingSO.FindProperty("encounterTableId"), new GUIContent("Encounter Table ID"));
|
||||
EditorGUILayout.PropertyField(_editingSO.FindProperty("baseDifficultyTier"), new GUIContent("Difficulty Tier"));
|
||||
EditorGUILayout.PropertyField(_editingSO.FindProperty("baseEncounterChance"), new GUIContent("Encounter Chance"));
|
||||
break;
|
||||
case ZoneRole.Modifier:
|
||||
EditorGUILayout.LabelField("Modifier Zone Settings", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(_editingSO.FindProperty("encounterChanceMultiplier"), new GUIContent("Chance Multiplier"));
|
||||
EditorGUILayout.PropertyField(_editingSO.FindProperty("difficultyTierBonus"), new GUIContent("Difficulty Tier Bonus"));
|
||||
break;
|
||||
case ZoneRole.Override:
|
||||
EditorGUILayout.LabelField("Override Zone Settings", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(_editingSO.FindProperty("isSafeZone"), new GUIContent("Is Safe Zone"));
|
||||
if(!_editingSO.FindProperty("isSafeZone").boolValue) {
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_editingSO.FindProperty("overrideEncounterTableId"), new GUIContent("Encounter Table ID"));
|
||||
EditorGUILayout.PropertyField(_editingSO.FindProperty("overrideEncounterChance"), new GUIContent("Encounter Chance"));
|
||||
EditorGUILayout.PropertyField(_editingSO.FindProperty("overrideDifficultyTier"), new GUIContent("Difficulty Tier"));
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(_editingSO.ApplyModifiedProperties()) {
|
||||
_hasUnsavedChanges = true;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(8);
|
||||
|
||||
// Save button — shown for new unsaved zones or any modified existing zone
|
||||
if(_isUnsavedNewData || _hasUnsavedChanges) {
|
||||
// Show error if any
|
||||
if(!string.IsNullOrEmpty(_saveError)) {
|
||||
EditorGUILayout.HelpBox(_saveError, MessageType.Error);
|
||||
}
|
||||
|
||||
GUI.backgroundColor = new Color(0.3f, 0.7f, 0.3f);
|
||||
if(GUILayout.Button("💾 Save Zone", GUILayout.Height(30))) {
|
||||
SaveZoneData();
|
||||
}
|
||||
GUI.backgroundColor = Color.white;
|
||||
}
|
||||
|
||||
// Delete button at the bottom
|
||||
EditorGUILayout.Space(4);
|
||||
GUI.backgroundColor = new Color(0.9f, 0.3f, 0.3f);
|
||||
if(GUILayout.Button("🗑 Delete Zone", GUILayout.Height(27))) {
|
||||
ZoneInstance zone = _editingZone;
|
||||
ExitEditMode();
|
||||
DeleteZone(zone);
|
||||
}
|
||||
GUI.backgroundColor = Color.white;
|
||||
}
|
||||
|
||||
// ── Create ──────────────────────────────────────────────────────
|
||||
|
||||
private static ZoneEditorSettings FindSettings() {
|
||||
return ZoneEditorSettings.FindOrCreateSettings();
|
||||
}
|
||||
|
||||
private void CreateZoneInScene() {
|
||||
// Create in-memory ZoneData (saved when user clicks Save)
|
||||
ZoneEditorSettings settings = FindSettings();
|
||||
ZoneData data = CreateInstance<ZoneData>();
|
||||
data.zoneId = _newZoneName.ToLower().Replace(" ", "_");
|
||||
data.zoneName = _newZoneName;
|
||||
data.shape = _newZoneShape;
|
||||
data.debugColor = settings.GetColorForRole(data.role);
|
||||
data.polygon.AddRange(ShapeFactory.CreateDefault(_newZoneShape));
|
||||
|
||||
// Create the scene GameObject
|
||||
GameObject go = new GameObject(_newZoneName);
|
||||
Undo.RegisterCreatedObjectUndo(go, "Create Zone");
|
||||
|
||||
ZoneInstance inst = go.AddComponent<ZoneInstance>();
|
||||
inst.data = data;
|
||||
|
||||
// Try to parent under ZoneManager if it exists
|
||||
ZonesObjectHolder mgr = FindFirstObjectByType<ZonesObjectHolder>();
|
||||
if(mgr != null) {
|
||||
go.transform.SetParent(mgr.transform, true);
|
||||
}
|
||||
|
||||
_showCreateForm = false;
|
||||
_isUnsavedNewData = true;
|
||||
_saveError = null;
|
||||
EnterEditMode(inst);
|
||||
}
|
||||
|
||||
private void CreateDataForZone(ZoneInstance zone) {
|
||||
string zoneName = zone.gameObject.name;
|
||||
ZoneEditorSettings settings = FindSettings();
|
||||
|
||||
// Create in-memory ZoneData (not saved as asset yet)
|
||||
ZoneData data = CreateInstance<ZoneData>();
|
||||
data.zoneId = zoneName.ToLower().Replace(" ", "_");
|
||||
data.zoneName = zoneName;
|
||||
data.shape = ZoneShape.Polygon;
|
||||
data.debugColor = settings.GetColorForRole(data.role);
|
||||
data.polygon.AddRange(ShapeFactory.CreateDefault(ZoneShape.Polygon));
|
||||
|
||||
zone.data = data;
|
||||
_isUnsavedNewData = true;
|
||||
_saveError = null;
|
||||
EnterEditMode(zone);
|
||||
}
|
||||
|
||||
private void SaveZoneData() {
|
||||
ZoneData data = _editingZone.data;
|
||||
string zoneId = data.zoneId;
|
||||
string assetName = data.zoneName.Replace(" ", "_");
|
||||
|
||||
// Check for duplicate zoneId or asset name among existing ZoneData assets
|
||||
string[] existingGuids = AssetDatabase.FindAssets("t:ZoneData");
|
||||
foreach(string guid in existingGuids) {
|
||||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
ZoneData existing = AssetDatabase.LoadAssetAtPath<ZoneData>(path);
|
||||
if(existing == null || existing == data) {
|
||||
continue;
|
||||
}
|
||||
if(existing.zoneId == zoneId) {
|
||||
_saveError = $"A ZoneData asset with ID '{zoneId}' already exists at:\n{path}";
|
||||
return;
|
||||
}
|
||||
string existingAssetName = Path.GetFileNameWithoutExtension(path);
|
||||
if(existingAssetName == assetName) {
|
||||
_saveError = $"A ZoneData asset named '{assetName}' already exists at:\n{path}";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset polygon if shape type changed since edit started
|
||||
if(data.shape != _shapeOnEditStart) {
|
||||
data.polygon.Clear();
|
||||
data.polygon.AddRange(ShapeFactory.CreateDefault(data.shape));
|
||||
if(data.shape == ZoneShape.Circle) {
|
||||
data.circleRadius = ShapeFactory.DefaultRadius;
|
||||
}
|
||||
_editingZone.RebuildBoundsCache();
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
_shapeOnEditStart = data.shape;
|
||||
|
||||
if(_isUnsavedNewData) {
|
||||
// New zone — create the asset for the first time
|
||||
ZoneEditorSettings settings = FindSettings();
|
||||
string folder = settings != null ? settings.zoneDataFolder : "Assets/ZoneData";
|
||||
string soPath = $"{folder}/{assetName}.asset";
|
||||
string fullFolder = Path.GetFullPath(Path.Combine(Application.dataPath, "..", folder));
|
||||
Directory.CreateDirectory(fullFolder);
|
||||
|
||||
AssetDatabase.CreateAsset(data, soPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
// Rebuild the SerializedObject now that the asset is persisted
|
||||
_editingSO = new SerializedObject(data);
|
||||
_isUnsavedNewData = false;
|
||||
|
||||
Debug.Log($"[ZoneSystem] Created ZoneData '{data.zoneName}' at {soPath}");
|
||||
}
|
||||
else {
|
||||
// Existing zone — rename asset if needed, then save
|
||||
string currentPath = AssetDatabase.GetAssetPath(data);
|
||||
string currentAssetName = Path.GetFileNameWithoutExtension(currentPath);
|
||||
if(currentAssetName != assetName) {
|
||||
string renameError = AssetDatabase.RenameAsset(currentPath, assetName);
|
||||
if(!string.IsNullOrEmpty(renameError)) {
|
||||
_saveError = $"Failed to rename asset: {renameError}";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
EditorUtility.SetDirty(data);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
Debug.Log($"[ZoneSystem] Saved ZoneData '{data.zoneName}'");
|
||||
}
|
||||
|
||||
// Rename the GameObject to match the zone name
|
||||
Undo.RecordObject(_editingZone.gameObject, "Rename Zone GameObject");
|
||||
_editingZone.gameObject.name = data.zoneName;
|
||||
|
||||
EditorUtility.SetDirty(_editingZone);
|
||||
_hasUnsavedChanges = false;
|
||||
_saveError = null;
|
||||
}
|
||||
|
||||
// ── Scene Zones List ────────────────────────────────────────────
|
||||
|
||||
private void DrawSceneZonesList() {
|
||||
EditorGUILayout.LabelField("Scene Zones", EditorStyles.boldLabel);
|
||||
|
||||
// Show active map plane from ZoneManager
|
||||
ZonesObjectHolder mgr = FindFirstObjectByType<ZonesObjectHolder>();
|
||||
if(mgr != null) {
|
||||
EditorGUILayout.LabelField($"Map Plane: {mgr.mapPlane} (set on ZoneManager)",
|
||||
new GUIStyle(EditorStyles.miniLabel) { normal = { textColor = new Color(0.5f, 0.8f, 1f) } });
|
||||
}
|
||||
else {
|
||||
EditorGUILayout.HelpBox("No ZoneManager found in scene.", MessageType.Warning);
|
||||
}
|
||||
|
||||
ZoneInstance[] zones = FindObjectsByType<ZoneInstance>(FindObjectsSortMode.None)
|
||||
.OrderByDescending(z => z.data?.priority ?? 0)
|
||||
.ThenBy(z => z.data?.zoneName ?? "")
|
||||
.ToArray();
|
||||
|
||||
if(zones.Length == 0) {
|
||||
EditorGUILayout.HelpBox("No ZoneInstance objects found in the scene.", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(ZoneInstance zone in zones) {
|
||||
DrawZoneRow(zone);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawZoneRow(ZoneInstance zone) {
|
||||
if(zone.data == null) {
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.helpBox);
|
||||
|
||||
// Warning icon
|
||||
GUIContent warnIcon = EditorGUIUtility.IconContent("console.warnicon.sml");
|
||||
EditorGUILayout.LabelField(warnIcon, GUILayout.Width(18), GUILayout.Height(20));
|
||||
|
||||
EditorGUILayout.LabelField($"{zone.gameObject.name}: Missing ZoneData", EditorStyles.miniLabel);
|
||||
|
||||
// Add & Edit button — creates a ZoneData asset and enters edit mode
|
||||
if(GUILayout.Button("+ Add & Edit", GUILayout.Width(90), GUILayout.Height(20))) {
|
||||
CreateDataForZone(zone);
|
||||
}
|
||||
|
||||
GUI.backgroundColor = new Color(0.9f, 0.3f, 0.3f);
|
||||
if(GUILayout.Button("✕", GUILayout.Width(28), GUILayout.Height(20))) {
|
||||
if(EditorUtility.DisplayDialog("Delete Zone",
|
||||
$"Delete '{zone.gameObject.name}'? (no ZoneData asset to remove)", "Delete", "Cancel")) {
|
||||
Undo.DestroyObjectImmediate(zone.gameObject);
|
||||
}
|
||||
}
|
||||
GUI.backgroundColor = Color.white;
|
||||
EditorGUILayout.EndHorizontal();
|
||||
return;
|
||||
}
|
||||
|
||||
ZoneData d = zone.data;
|
||||
Color roleColor = FindSettings().GetColorForRole(d.role);
|
||||
|
||||
Color prevBg = GUI.backgroundColor;
|
||||
GUI.backgroundColor = (roleColor * 2f * 0.6f) + (Color.gray * 0.4f);
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.helpBox);
|
||||
GUI.backgroundColor = prevBg;
|
||||
|
||||
// Color swatch
|
||||
Rect swatchRect = GUILayoutUtility.GetRect(12, 20, GUILayout.Width(12));
|
||||
EditorGUI.DrawRect(swatchRect, roleColor * 3f);
|
||||
|
||||
// Info
|
||||
EditorGUILayout.BeginVertical();
|
||||
EditorGUILayout.LabelField($"{d.zoneName}", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField(BuildZoneSummaryString(d), EditorStyles.miniLabel);
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
// Select / Edit button
|
||||
if(GUILayout.Button("Select", GUILayout.Width(55), GUILayout.Height(36))) {
|
||||
EnterEditMode(zone);
|
||||
}
|
||||
|
||||
// Duplicate button
|
||||
if(GUILayout.Button("📋", GUILayout.Width(28), GUILayout.Height(36))) {
|
||||
DuplicateZone(zone);
|
||||
}
|
||||
|
||||
// Delete button
|
||||
GUI.backgroundColor = new Color(0.9f, 0.3f, 0.3f);
|
||||
if(GUILayout.Button("✕", GUILayout.Width(28), GUILayout.Height(36))) {
|
||||
DeleteZone(zone);
|
||||
}
|
||||
GUI.backgroundColor = prevBg;
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void DeleteZone(ZoneInstance zone) {
|
||||
string zoneName = zone.data != null ? zone.data.zoneName : zone.gameObject.name;
|
||||
string assetPath = zone.data != null ? AssetDatabase.GetAssetPath(zone.data) : null;
|
||||
|
||||
string message = $"Delete zone '{zoneName}'?";
|
||||
if(!string.IsNullOrEmpty(assetPath)) {
|
||||
message += $"\n\nThis will also delete the asset:\n{assetPath}";
|
||||
}
|
||||
|
||||
if(!EditorUtility.DisplayDialog("Delete Zone", message, "Delete", "Cancel")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!string.IsNullOrEmpty(assetPath)) {
|
||||
AssetDatabase.DeleteAsset(assetPath);
|
||||
}
|
||||
|
||||
Undo.DestroyObjectImmediate(zone.gameObject);
|
||||
}
|
||||
|
||||
private void DuplicateZone(ZoneInstance zone) {
|
||||
if(zone.data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ZoneData original = zone.data;
|
||||
ZoneEditorSettings settings = FindSettings();
|
||||
string folder = settings.zoneDataFolder;
|
||||
|
||||
// Create independent ZoneData copy
|
||||
ZoneData copy = CreateInstance<ZoneData>();
|
||||
EditorUtility.CopySerialized(original, copy);
|
||||
copy.zoneId = original.zoneId + "_" + System.Guid.NewGuid().ToString("N").Substring(0, 6);
|
||||
copy.zoneName = original.zoneName + " (Copy)";
|
||||
|
||||
string newName = original.zoneName.Replace(" ", "_") + "_Copy";
|
||||
string newPath = AssetDatabase.GenerateUniqueAssetPath(
|
||||
Path.Combine(folder, newName + ".asset"));
|
||||
|
||||
string fullFolder = Path.GetFullPath(Path.Combine(Application.dataPath, "..", folder));
|
||||
Directory.CreateDirectory(fullFolder);
|
||||
AssetDatabase.CreateAsset(copy, newPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
// Create scene GameObject
|
||||
GameObject duplicate = Instantiate(zone.gameObject, zone.transform.parent);
|
||||
duplicate.name = copy.zoneName;
|
||||
Undo.RegisterCreatedObjectUndo(duplicate, "Duplicate Zone");
|
||||
|
||||
ZoneInstance dupInstance = duplicate.GetComponent<ZoneInstance>();
|
||||
dupInstance.data = copy;
|
||||
dupInstance.RebuildBoundsCache();
|
||||
|
||||
// Offset slightly so it's not on top of the original
|
||||
ZonesObjectHolder mgr = FindFirstObjectByType<ZonesObjectHolder>();
|
||||
MapPlane plane = mgr != null ? mgr.mapPlane : MapPlane.XZ;
|
||||
Vector3 offset = MapPlaneUtility.UnprojectFromPlane(new Vector2(1f, 1f), plane, 0f);
|
||||
duplicate.transform.position += offset;
|
||||
|
||||
Debug.Log($"[ZoneSystem] Duplicated zone to {newPath}");
|
||||
}
|
||||
|
||||
private string BuildZoneSummaryString(ZoneData d) {
|
||||
switch(d.role) {
|
||||
case ZoneRole.Base:
|
||||
return $"Base | Priority {d.priority} | {d.baseDifficultyTier} | {d.baseEncounterChance:P0} | Table: {d.encounterTableId}";
|
||||
case ZoneRole.Modifier:
|
||||
return $"Modifier | Priority {d.priority} | Chance ×{d.encounterChanceMultiplier:F2} | Tier +{d.difficultyTierBonus}";
|
||||
case ZoneRole.Override:
|
||||
return d.isSafeZone
|
||||
? $"Override | Priority {d.priority} | ✓ SAFE"
|
||||
: $"Override | Priority {d.priority} | {d.overrideDifficultyTier} | {d.overrideEncounterChance:P0}";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
// ── Export Section ───────────────────────────────────────────────
|
||||
private void DrawExportSection() {
|
||||
_showExportFoldout = EditorGUILayout.BeginFoldoutHeaderGroup(_showExportFoldout, "Export Zones to JSON");
|
||||
if(_showExportFoldout) {
|
||||
EditorGUI.indentLevel++;
|
||||
_exportPath = EditorGUILayout.TextField("Output Path", _exportPath);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if(GUILayout.Button("Browse…", GUILayout.Width(70))) {
|
||||
string picked = EditorUtility.SaveFilePanel(
|
||||
"Save zones.json", Path.GetDirectoryName(_exportPath),
|
||||
Path.GetFileName(_exportPath), "json");
|
||||
if(!string.IsNullOrEmpty(picked)) {
|
||||
_exportPath = "Assets" + picked.Substring(Application.dataPath.Length);
|
||||
}
|
||||
}
|
||||
|
||||
GUI.backgroundColor = new Color(0.4f, 0.8f, 0.4f);
|
||||
if(GUILayout.Button("📦 Export Now", GUILayout.Height(24))) {
|
||||
ExportZones();
|
||||
}
|
||||
GUI.backgroundColor = Color.white;
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
EditorGUILayout.EndFoldoutHeaderGroup();
|
||||
}
|
||||
|
||||
private void ExportZones() {
|
||||
ZoneInstance[] instances = FindObjectsByType<ZoneInstance>(FindObjectsSortMode.None);
|
||||
ZonesObjectHolder mgr = FindFirstObjectByType<ZonesObjectHolder>();
|
||||
MapPlane plane = mgr != null ? mgr.mapPlane : MapPlane.XZ;
|
||||
ZoneExportRoot root = ZoneExporter.BuildExport(instances, plane);
|
||||
string json = ZoneExporter.ToJson(root);
|
||||
|
||||
string fullPath = Path.Combine(Application.dataPath, "../", _exportPath);
|
||||
fullPath = Path.GetFullPath(fullPath);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
||||
File.WriteAllText(fullPath, json);
|
||||
|
||||
AssetDatabase.Refresh();
|
||||
Debug.Log($"[ZoneSystem] Exported {root.zones.Count} zones → {fullPath}");
|
||||
EditorUtility.DisplayDialog("Zone Export",
|
||||
$"Successfully exported {root.zones.Count} zone(s) to:\n{_exportPath}", "OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user