some adjustments on stage editor

This commit is contained in:
Nilton Constantino 2026-06-15 17:48:18 +01:00
parent bfab987ae7
commit 1a4b2443ff
No known key found for this signature in database
18 changed files with 1949 additions and 4417 deletions

View File

@ -1,22 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 778f9d79eb2c410482efdc25b248dd46, type: 3}
m_Name: StageDefinition
m_EditorClassIdentifier: Assembly-CSharp::Intrepid.Gameplay.Prototyping.StageDefinition
stageId: stagedefinition
cellSize: 1
floorHeight: 6
activeFloorIndex: 0
floors:
- floorId: F0
cells: []
objects: []

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: bb896366809bc439b8be4d0f1158dc6f
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because one or more lines are too long

View File

@ -1,109 +0,0 @@
# Gardens Stage Editor Starter Kit
Starter kit para um editor lógico de stages do **Gardens of Nil** dentro da Unity.
Este pacote contém somente:
- modelo de dados (`StageDefinition`, floors, células estruturais e objetos lógicos);
- janela de editor com UI Toolkit;
- grid 1x1 com pan/zoom;
- paleta por layer;
- criação/seleção/remoção de elementos lógicos;
- inspector simples;
- validação básica.
Não contém bake/rebake de scene, geração de colliders ou integração com Tiled.
## Instalação
Descompacte a pasta `GardensStageEditor` dentro de:
```txt
Assets/Editor/
```
O resultado deve ficar parecido com:
```txt
Assets/Editor/GardensStageEditor/
Data/
Editor/
README.md
```
Depois espere a Unity recompilar.
## Abrir o editor
Na Unity:
```txt
Gardens > Stage Editor
```
Na janela:
1. Clique em `New` para criar um `StageDefinition.asset`.
2. Escolha a layer na paleta inferior.
3. Use `Paint`, `Select`, `Erase` ou `Pan`.
4. Pinte o mapa lógico no canvas central.
5. Edite o elemento selecionado no inspector à direita.
6. Use `Validate` para checagens básicas.
## Controles
```txt
LMB: pintar / selecionar
RMB ou MMB: pan
Mouse wheel: zoom
```
## Modelo mental
O editor trabalha em um plano lógico 2D:
```txt
Editor X/Y
```
Mais tarde, o baker pode mapear isso para o mundo Unity:
```txt
Editor X -> Unity X
Editor Y -> Unity Z
Floor index -> Unity Y offset
```
## Layers iniciais
```txt
Structural: Ground, Wall, Hole, Erase Structural
Design: Falling Floor, Door, Interactable, Portal
Camera: Camera Zone
Markers: Player Spawn, Elevator, Marker
```
## Filosofia
A scene não é fonte da verdade.
```txt
StageDefinition.asset = source of truth
Generated scene/prefab = output futuro do baker
```
Este pacote só cuida do primeiro lado: autoria visual e dados lógicos.
## Próximos passos sugeridos
1. Transformar `StagePaletteEntry` em assets configuráveis, se necessário.
2. Adicionar hotkeys e melhor seleção/move/resize visual.
3. Adicionar overlays de erro no canvas.
4. Implementar o baker separado consumindo `StageDefinition`.
5. Depois, se fizer sentido, criar import/export Tiled apenas para o mapa lógico.
## Requisitos recomendados
Unity 2022.3 LTS ou superior. Unity 6 também deve funcionar.
O canvas usa UI Toolkit custom drawing (`Painter2D`). Em versões muito antigas da Unity, pode ser necessário trocar o canvas por IMGUI ou SceneView Handles.

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 494fb84451f94e218111409283a7f795
timeCreated: 1781500522

View File

@ -1,71 +0,0 @@
# Stage Model Notes
## StageDefinition
Fonte da verdade do stage lógico.
Contém:
- `stageId`
- `cellSize`
- `floorHeight`
- `floors`
- `objects`
## StageFloorDefinition
Um andar lógico do stage.
Contém:
- `floorId`
- `width`
- `height`
- células estruturais preenchidas
Células vazias não são serializadas explicitamente. Apenas células com `Ground`, `Wall` ou `Hole` são guardadas.
## Structural cells
A layer estrutural é cell-based.
```txt
Empty
Ground
Wall
Hole
```
## StageObjectDefinition
Objetos lógicos são object-based.
Exemplos:
- traps
- doors
- interactables
- portals
- camera zones
- spawn points
- elevators
- markers
Cada objeto tem:
- `id`
- `layer`
- `kind`
- `typeId`
- `prefabId`
- `floorId`
- `x`, `y`, `width`, `height`
- flags como `cutsGround` e `blocksMovement`
## Regra sugerida para bake futuro
```txt
GroundCollision = Structural.Ground - Structural.Hole - Objects(cutsGround)
```
Depois o baker pode compactar células em BoxColliders maiores.

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: ffb9c2df7a0f4b3ca08544979287cf20
timeCreated: 1781500522

View File

@ -16,21 +16,25 @@ namespace Editor.GardensStageEditor
{
Empty = 0,
Ground = 1,
Wall = 2,
Hole = 3
Wall = 2
}
public enum StageObjectKind
{
Trap = 0,
Door = 1,
Interactable = 2,
Portal = 3,
CameraZone = 4,
Spawn = 5,
Elevator = 6,
Marker = 7,
Custom = 100
Trap_FallingFloor = 0,
DoorHorizontal = 10,
DoorVertical = 11,
// Interactable = 2,
// Portal = 3,
CameraBoundary = 40,
Spawn = 50,
// Elevator = 6,
// Marker = 7,
// Custom = 100
}
[CreateAssetMenu(menuName = "GON/Editor/Stage Definition", fileName = "StageDefinition")]
@ -95,7 +99,7 @@ namespace Editor.GardensStageEditor
public bool ContainsFloor(string floorId)
{
return GetFloor(floorId) != null;
return GetFloor(floorId) is not null;
}
public string CreateUniqueObjectId(StageObjectKind kind)
@ -152,7 +156,7 @@ namespace Editor.GardensStageEditor
public StructuralCellKind GetCell(int x, int y)
{
int index = FindCellIndex(x, y);
var index = FindCellIndex(x, y);
if (index < 0)
{
return StructuralCellKind.Empty;
@ -174,7 +178,7 @@ namespace Editor.GardensStageEditor
return;
}
LogicalCell cell = new LogicalCell
var cell = new LogicalCell
{
x = x,
y = y,
@ -270,6 +274,11 @@ namespace Editor.GardensStageEditor
public bool blocksMovement;
public string notes;
public bool IsResizable => IsKindResizable(kind);
public Vector2Int DefaultSize => GetDefaultSize(kind);
public Vector2Int MinSize => GetMinSize(kind);
public Vector2Int MaxSize => GetMaxSize(kind);
public RectInt Bounds => new(x, y, Mathf.Max(1, width), Mathf.Max(1, height));
public bool ContainsCell(int cellX, int cellY)
@ -277,5 +286,62 @@ namespace Editor.GardensStageEditor
var bounds = Bounds;
return cellX >= bounds.xMin && cellX < bounds.xMax && cellY >= bounds.yMin && cellY < bounds.yMax;
}
public void NormalizeSize()
{
if (!IsResizable)
{
var fixedSize = DefaultSize;
width = Mathf.Max(1, fixedSize.x);
height = Mathf.Max(1, fixedSize.y);
return;
}
var min = MinSize;
var max = MaxSize;
width = Mathf.Clamp(Mathf.Max(1, width), Mathf.Max(1, min.x), Mathf.Max(1, max.x));
height = Mathf.Clamp(Mathf.Max(1, height), Mathf.Max(1, min.y), Mathf.Max(1, max.y));
}
public static bool IsKindResizable(StageObjectKind kind)
{
return kind switch
{
StageObjectKind.Trap_FallingFloor => true,
StageObjectKind.CameraBoundary => true,
_ => false
};
}
public static Vector2Int GetDefaultSize(StageObjectKind kind)
{
return kind switch
{
StageObjectKind.Trap_FallingFloor => 2 * Vector2Int.one,
StageObjectKind.DoorVertical => new Vector2Int(1, 2),
StageObjectKind.DoorHorizontal => new Vector2Int(2, 1),
StageObjectKind.CameraBoundary => new Vector2Int(8, 5),
_ => Vector2Int.one
};
}
public static Vector2Int GetMinSize(StageObjectKind kind)
{
return kind switch
{
StageObjectKind.CameraBoundary => new Vector2Int(1, 1),
_ => GetDefaultSize(kind)
};
}
public static Vector2Int GetMaxSize(StageObjectKind kind)
{
return kind switch
{
StageObjectKind.Trap_FallingFloor => new Vector2Int(64, 64),
StageObjectKind.CameraBoundary => new Vector2Int(64, 64),
_ => GetDefaultSize(kind)
};
}
}
}

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 778f9d79eb2c410482efdc25b248dd46

View File

@ -1,3 +1,4 @@
using Intrepid.Diagnostics;
using UnityEngine;
namespace Editor.GardensStageEditor
@ -12,19 +13,25 @@ namespace Editor.GardensStageEditor
public static readonly Color PreviewStroke = new(1f, 1f, 1f, 0.9f);
public static readonly Color SelectionStroke = new(1f, 0.85f, 0.25f, 1f);
public static Color FromHtml(string html)
{
if (!html.StartsWith("#")) html = $"#{html}";
if (ColorUtility.TryParseHtmlString(html, out var color))
{
return color;
}
DLogger.LogDebug("color not supported");
return Color.magenta;
}
public static Color Structural(StructuralCellKind kind)
{
switch (kind)
return kind switch
{
case StructuralCellKind.Ground:
return new Color(0.28f, 0.52f, 0.35f, 0.8f);
case StructuralCellKind.Wall:
return new Color(0.38f, 0.38f, 0.42f, 0.9f);
case StructuralCellKind.Hole:
return new Color(0.05f, 0.05f, 0.05f, 0.9f);
default:
return new Color(0f, 0f, 0f, 0f);
}
StructuralCellKind.Ground => FromHtml("55E0FF"),
StructuralCellKind.Wall => FromHtml("668B93"),
_ => new Color(0f, 0f, 0f, 0f)
};
}
public static Color Layer(StageLogicalLayer layer)

View File

@ -17,6 +17,7 @@ namespace Editor.GardensStageEditor
private StageMapCanvas canvas;
private StageInspectorPanel inspector;
private StagePalettePanel palettePanel;
private Label mouseCellLabel;
[MenuItem("Tools/Gardens Of Nil/Stage Editor")]
public static void Open()
@ -217,7 +218,17 @@ namespace Editor.GardensStageEditor
};
topBar.Add(spacer);
var hint = new Label("Infinite grid · LMB select/place/paint · RMB/MMB pan · Wheel zoom")
mouseCellLabel = new Label("Mouse: —")
{
style =
{
opacity = 0.85f,
marginRight = 16f
}
};
topBar.Add(mouseCellLabel);
var hint = new Label("Infinite grid · LMB select/place/paint/move · selected resizable objects have a resize handle · RMB/MMB pan · Wheel zoom")
{
style =
{
@ -227,6 +238,19 @@ namespace Editor.GardensStageEditor
topBar.Add(hint);
}
public void SetMouseCell(Vector2Int? cell)
{
if (mouseCellLabel is null)
{
return;
}
mouseCellLabel.text = cell.HasValue
? "Mouse: X " + cell.Value.x + " Y " + cell.Value.y
: "Mouse: —";
}
private void SetStage(StageDefinition newStage)
{
stage = newStage;

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: f7065bab1fc14f53909281036dbb6368

View File

@ -165,13 +165,8 @@ namespace Editor.GardensStageEditor
id.RegisterValueChangedCallback(evt => owner.MutateStage("Change object id", delegate { obj.id = evt.newValue; }));
Add(id);
var layer = new EnumField("Layer", obj.layer);
layer.RegisterValueChangedCallback(evt => owner.MutateStage("Change object layer", delegate { obj.layer = (StageLogicalLayer)evt.newValue; }));
Add(layer);
var kind = new EnumField("Kind", obj.kind);
kind.RegisterValueChangedCallback(evt => owner.MutateStage("Change object kind", delegate { obj.kind = (StageObjectKind)evt.newValue; }));
Add(kind);
AddReadOnlyLine("Layer", obj.layer.ToString());
AddReadOnlyLine("Kind", obj.kind.ToString());
var typeId = new TextField("Type Id")
{
@ -201,19 +196,56 @@ namespace Editor.GardensStageEditor
y.RegisterValueChangedCallback(evt => owner.MutateStage("Move object y", delegate { obj.y = evt.newValue; }));
Add(y);
var width = new IntegerField("Width")
if (obj.IsResizable)
{
value = obj.width
};
width.RegisterValueChangedCallback(evt => owner.MutateStage("Resize object width", delegate { obj.width = Mathf.Max(1, evt.newValue); }));
Add(width);
var min = obj.MinSize;
var max = obj.MaxSize;
Add(new Label("Resizable: yes · Min " + min.x + "x" + min.y + " · Max " + max.x + "x" + max.y)
{
style =
{
opacity = 0.75f,
marginTop = 4,
marginBottom = 2
}
});
var height = new IntegerField("Height")
var width = new IntegerField("Width")
{
value = obj.width
};
width.RegisterValueChangedCallback(evt => owner.MutateStage("Resize object width", delegate
{
obj.width = evt.newValue;
obj.NormalizeSize();
}));
Add(width);
var height = new IntegerField("Height")
{
value = obj.height
};
height.RegisterValueChangedCallback(evt => owner.MutateStage("Resize object height", delegate
{
obj.height = evt.newValue;
obj.NormalizeSize();
}));
Add(height);
}
else
{
value = obj.height
};
height.RegisterValueChangedCallback(evt => owner.MutateStage("Resize object height", delegate { obj.height = Mathf.Max(1, evt.newValue); }));
Add(height);
var fixedSize = obj.DefaultSize;
Add(new Label("Size: " + fixedSize.x + "x" + fixedSize.y + " (fixed)")
{
style =
{
opacity = 0.75f,
marginTop = 4,
marginBottom = 2
}
});
Add(new HelpBox("This object kind has a fixed size. It can be moved, but it cannot be resized from the inspector or the grid.", HelpBoxMessageType.Info));
}
var cutsGround = new Toggle("Cuts Ground")
{
@ -254,5 +286,39 @@ namespace Editor.GardensStageEditor
};
Add(delete);
}
private void AddReadOnlyLine(string label, string value)
{
var row = new VisualElement
{
style =
{
flexDirection = FlexDirection.Row,
marginTop = 2,
marginBottom = 2
}
};
var left = new Label(label)
{
style =
{
width = 90,
opacity = 0.75f
}
};
row.Add(left);
var right = new Label(value)
{
style =
{
unityFontStyleAndWeight = FontStyle.Bold
}
};
row.Add(right);
Add(row);
}
}
}

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: a1b3226b742b4e5e94cab859de25494a

View File

@ -7,6 +7,16 @@ namespace Editor.GardensStageEditor
{
private const float MinCellPixels = 12f;
private const float MaxCellPixels = 96f;
private const float ResizeHandlePixels = 12f;
private enum DragMode
{
None,
Pan,
Paint,
MoveObject,
ResizeObject
}
private readonly StageEditorWindow owner;
private readonly StageEditorState state;
@ -15,12 +25,18 @@ namespace Editor.GardensStageEditor
private Vector2 pan = new(40f, 40f);
private float cellPixels = 32f;
private bool isPanning;
private bool isPainting;
private DragMode dragMode = DragMode.None;
private Vector2 panStartMouse;
private Vector2 panStartValue;
private Vector2Int dragStartCell;
private Vector2Int dragCurrentCell;
private Vector2Int? hoverCell;
private StageObjectDefinition draggedObject;
private int objectStartX;
private int objectStartY;
private int objectStartWidth;
private int objectStartHeight;
public StageMapCanvas(StageEditorWindow owner, StageEditorState state)
{
@ -36,6 +52,7 @@ namespace Editor.GardensStageEditor
RegisterCallback<MouseDownEvent>(OnMouseDown);
RegisterCallback<MouseMoveEvent>(OnMouseMove);
RegisterCallback<MouseUpEvent>(OnMouseUp);
RegisterCallback<MouseLeaveEvent>(OnMouseLeave);
RegisterCallback<WheelEvent>(OnWheel);
}
@ -58,6 +75,8 @@ namespace Editor.GardensStageEditor
return;
}
UpdateHoverCell(evt.localMousePosition);
var mouse = evt.localMousePosition;
var before = ScreenToGridFloat(mouse);
@ -74,6 +93,7 @@ namespace Editor.GardensStageEditor
private void OnMouseDown(MouseDownEvent evt)
{
Focus();
UpdateHoverCell(evt.localMousePosition);
if (stage is null)
{
@ -83,10 +103,7 @@ namespace Editor.GardensStageEditor
// Pan is no longer a palette tool. It is always available through RMB/MMB.
if (evt.button == 2 || evt.button == 1)
{
isPanning = true;
panStartMouse = evt.localMousePosition;
panStartValue = pan;
this.CaptureMouse();
BeginPan(evt.localMousePosition);
evt.StopPropagation();
return;
}
@ -96,91 +113,229 @@ namespace Editor.GardensStageEditor
return;
}
if (!TryScreenToCell(evt.localMousePosition, out Vector2Int cell))
if (!TryScreenToCell(evt.localMousePosition, out var cell))
{
return;
}
// If a resizable selected object has its handle clicked, resize wins over move/select.
if (TryBeginResizeSelectedObject(evt.localMousePosition, cell))
{
evt.StopPropagation();
MarkDirtyRepaint();
return;
}
// LMB always tries to select an existing logical object first.
// This removes the need for a Select tool.
// If the user drags after selecting/hitting it, the same interaction moves it.
var hit = FindObjectAt(cell, false);
if (hit != null)
{
state.SelectObject(hit);
MarkDirtyRepaint();
BeginMoveObject(hit, cell);
evt.StopPropagation();
MarkDirtyRepaint();
return;
}
state.SelectObject(null);
// Empty space uses the active palette item to paint/place.
dragStartCell = cell;
dragCurrentCell = cell;
isPainting = true;
this.CaptureMouse();
BeginPaint(cell);
evt.StopPropagation();
MarkDirtyRepaint();
}
private void OnMouseMove(MouseMoveEvent evt)
{
if (isPanning)
{
pan = panStartValue + evt.localMousePosition - panStartMouse;
evt.StopPropagation();
MarkDirtyRepaint();
return;
}
UpdateHoverCell(evt.localMousePosition);
if (isPainting)
switch (dragMode)
{
if (TryScreenToCell(evt.localMousePosition, out Vector2Int cell))
{
dragCurrentCell = cell;
case DragMode.Pan:
pan = panStartValue + evt.localMousePosition - panStartMouse;
evt.StopPropagation();
MarkDirtyRepaint();
}
return;
evt.StopPropagation();
case DragMode.Paint:
if (TryScreenToCell(evt.localMousePosition, out var paintCell))
{
dragCurrentCell = paintCell;
MarkDirtyRepaint();
}
evt.StopPropagation();
return;
case DragMode.MoveObject:
if (TryScreenToCell(evt.localMousePosition, out var moveCell))
{
ApplyMoveObject(moveCell);
}
evt.StopPropagation();
return;
case DragMode.ResizeObject:
if (TryScreenToCell(evt.localMousePosition, out var resizeCell))
{
ApplyResizeObject(resizeCell);
}
evt.StopPropagation();
return;
}
}
private void OnMouseUp(MouseUpEvent evt)
{
if (isPanning)
{
isPanning = false;
if (this.HasMouseCapture())
{
this.ReleaseMouse();
}
evt.StopPropagation();
return;
}
if (!isPainting || evt.button != 0)
if (dragMode == DragMode.None)
{
return;
}
isPainting = false;
if (this.HasMouseCapture())
if (dragMode == DragMode.Paint && evt.button == 0)
{
this.ReleaseMouse();
var rect = GetDragRect();
ApplyPalette(rect);
}
var rect = GetDragRect();
ApplyPalette(rect);
EndDrag();
evt.StopPropagation();
MarkDirtyRepaint();
}
private void OnMouseLeave(MouseLeaveEvent evt)
{
if (!this.HasMouseCapture())
{
hoverCell = null;
owner.SetMouseCell(null);
MarkDirtyRepaint();
}
}
private void BeginPan(Vector2 mouse)
{
dragMode = DragMode.Pan;
panStartMouse = mouse;
panStartValue = pan;
this.CaptureMouse();
}
private void BeginPaint(Vector2Int cell)
{
dragMode = DragMode.Paint;
dragStartCell = cell;
dragCurrentCell = cell;
this.CaptureMouse();
}
private void BeginMoveObject(StageObjectDefinition obj, Vector2Int cell)
{
dragMode = DragMode.MoveObject;
draggedObject = obj;
dragStartCell = cell;
dragCurrentCell = cell;
objectStartX = obj.x;
objectStartY = obj.y;
objectStartWidth = obj.width;
objectStartHeight = obj.height;
this.CaptureMouse();
}
private bool TryBeginResizeSelectedObject(Vector2 mouse, Vector2Int cell)
{
var obj = state.selectedObject;
if (obj is null || !CanEditObjectOnActiveFloor(obj) || !obj.IsResizable)
{
return false;
}
if (!GetResizeHandleRect(obj).Contains(mouse))
{
return false;
}
dragMode = DragMode.ResizeObject;
draggedObject = obj;
dragStartCell = cell;
dragCurrentCell = cell;
objectStartX = obj.x;
objectStartY = obj.y;
objectStartWidth = obj.width;
objectStartHeight = obj.height;
this.CaptureMouse();
return true;
}
private void ApplyMoveObject(Vector2Int cell)
{
if (draggedObject is null || !CanEditObjectOnActiveFloor(draggedObject))
{
return;
}
dragCurrentCell = cell;
var dx = cell.x - dragStartCell.x;
var dy = cell.y - dragStartCell.y;
var newX = objectStartX + dx;
var newY = objectStartY + dy;
if (draggedObject.x == newX && draggedObject.y == newY)
{
return;
}
owner.MutateStage("Move object", delegate
{
draggedObject.x = newX;
draggedObject.y = newY;
});
}
private void ApplyResizeObject(Vector2Int cell)
{
if (draggedObject is null || !CanEditObjectOnActiveFloor(draggedObject) || !draggedObject.IsResizable)
{
return;
}
dragCurrentCell = cell;
var min = draggedObject.MinSize;
var max = draggedObject.MaxSize;
var newWidth = Mathf.Clamp(cell.x - objectStartX + 1, Mathf.Max(1, min.x), Mathf.Max(1, max.x));
var newHeight = Mathf.Clamp(cell.y - objectStartY + 1, Mathf.Max(1, min.y), Mathf.Max(1, max.y));
if (draggedObject.width == newWidth && draggedObject.height == newHeight)
{
return;
}
owner.MutateStage("Resize object", delegate
{
draggedObject.width = newWidth;
draggedObject.height = newHeight;
draggedObject.NormalizeSize();
});
}
private void EndDrag()
{
dragMode = DragMode.None;
draggedObject = null;
if (this.HasMouseCapture())
{
this.ReleaseMouse();
}
}
private void ApplyPalette(RectInt rect)
{
StageFloorDefinition floor = stage?.ActiveFloor;
var floor = stage?.ActiveFloor;
if (floor is null)
{
return;
@ -201,14 +356,7 @@ namespace Editor.GardensStageEditor
return;
}
var objectRect = rect;
if (rect is { width: 1, height: 1 })
{
objectRect = new RectInt(rect.x, rect.y, Mathf.Max(1, entry.defaultSize.x), Mathf.Max(1, entry.defaultSize.y));
}
objectRect.width = Mathf.Clamp(objectRect.width, Mathf.Max(1, entry.minSize.x), Mathf.Max(1, entry.maxSize.x));
objectRect.height = Mathf.Clamp(objectRect.height, Mathf.Max(1, entry.minSize.y), Mathf.Max(1, entry.maxSize.y));
var objectRect = GetPaletteObjectRect(entry, rect);
owner.MutateStage("Place " + entry.displayName, delegate
{
@ -228,11 +376,38 @@ namespace Editor.GardensStageEditor
blocksMovement = entry.blocksMovement
};
obj.NormalizeSize();
stage.objects.Add(obj);
state.SelectObject(obj);
});
}
private RectInt GetPaletteObjectRect(StagePaletteEntry entry, RectInt paintedRect)
{
var defaultSize = entry.defaultSize;
if (defaultSize.x <= 0 || defaultSize.y <= 0)
{
defaultSize = StageObjectDefinition.GetDefaultSize(entry.objectKind);
}
if (!StageObjectDefinition.IsKindResizable(entry.objectKind))
{
return new RectInt(paintedRect.x, paintedRect.y, Mathf.Max(1, defaultSize.x), Mathf.Max(1, defaultSize.y));
}
var objectRect = paintedRect;
if (paintedRect is { width: 1, height: 1 })
{
objectRect = new RectInt(paintedRect.x, paintedRect.y, Mathf.Max(1, defaultSize.x), Mathf.Max(1, defaultSize.y));
}
var min = StageObjectDefinition.GetMinSize(entry.objectKind);
var max = StageObjectDefinition.GetMaxSize(entry.objectKind);
objectRect.width = Mathf.Clamp(objectRect.width, Mathf.Max(1, min.x), Mathf.Max(1, max.x));
objectRect.height = Mathf.Clamp(objectRect.height, Mathf.Max(1, min.y), Mathf.Max(1, max.y));
return objectRect;
}
private StageObjectDefinition FindObjectAt(Vector2Int cell, bool activeLayerOnly)
{
if (stage is null)
@ -263,6 +438,30 @@ namespace Editor.GardensStageEditor
return null;
}
private bool CanEditObjectOnActiveFloor(StageObjectDefinition obj)
{
return stage is not null && obj is not null && stage.ActiveFloor is not null && obj.floorId == stage.ActiveFloor.floorId;
}
private void UpdateHoverCell(Vector2 localMousePosition)
{
if (stage is null || !TryScreenToCell(localMousePosition, out var cell))
{
hoverCell = null;
owner.SetMouseCell(null);
return;
}
if (hoverCell.HasValue && hoverCell.Value == cell)
{
return;
}
hoverCell = cell;
owner.SetMouseCell(cell);
MarkDirtyRepaint();
}
private void OnGenerateVisualContent(MeshGenerationContext mgc)
{
var p = mgc.painter2D;
@ -284,6 +483,7 @@ namespace Editor.GardensStageEditor
DrawCells(p, floor);
DrawObjects(p, floor.floorId);
DrawOriginAxes(p);
DrawHoverCell(p);
DrawPreview(p);
}
@ -319,10 +519,22 @@ namespace Editor.GardensStageEditor
if (state.selectedObject == obj)
{
StrokeRect(p, rect.Expanded(2f), StageEditorColors.SelectionStroke, 3f);
if (obj.IsResizable)
{
DrawResizeHandle(p, obj);
}
}
}
}
private void DrawResizeHandle(Painter2D p, StageObjectDefinition obj)
{
var handle = GetResizeHandleRect(obj);
FillRect(p, handle, StageEditorColors.SelectionStroke);
StrokeRect(p, handle, Color.black.WithAlpha(0.75f), 1f);
}
private void DrawInfiniteGrid(Painter2D p)
{
var r = contentRect;
@ -363,19 +575,30 @@ namespace Editor.GardensStageEditor
}
}
private void DrawHoverCell(Painter2D p)
{
if (!hoverCell.HasValue)
{
return;
}
var cell = hoverCell.Value;
StrokeRect(p, CellRect(cell.x, cell.y).Expanded(-2f), StageEditorColors.PreviewStroke.WithAlpha(0.45f), 1f);
}
private void DrawPreview(Painter2D p)
{
if (!isPainting || state.activePaletteEntry is null)
if (dragMode != DragMode.Paint || state.activePaletteEntry is null)
{
return;
}
var rect = GetDragRect();
var entry = state.activePaletteEntry;
if (!entry.paintsStructural && rect is { width: 1, height: 1 })
if (!entry.paintsStructural)
{
rect = new RectInt(rect.x, rect.y, Mathf.Max(1, entry.defaultSize.x), Mathf.Max(1, entry.defaultSize.y));
rect = GetPaletteObjectRect(entry, rect);
}
var screenRect = GridRect(rect);
@ -414,6 +637,13 @@ namespace Editor.GardensStageEditor
return new Rect(pan.x + rect.x * cellPixels, pan.y + rect.y * cellPixels, rect.width * cellPixels, rect.height * cellPixels);
}
private Rect GetResizeHandleRect(StageObjectDefinition obj)
{
var rect = GridRect(obj.Bounds);
var size = Mathf.Min(ResizeHandlePixels, Mathf.Max(8f, cellPixels * 0.35f));
return new Rect(rect.xMax - size, rect.yMax - size, size, size);
}
private void FillRect(Painter2D p, Rect rect, Color color)
{
p.fillColor = color;

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 8c584058841343debb04c00199a79393

View File

@ -19,7 +19,7 @@ namespace Editor.GardensStageEditor
public Vector2Int defaultSize = Vector2Int.one;
public Vector2Int minSize = Vector2Int.one;
public Vector2Int maxSize = new(32, 32);
public bool resizable = true;
public bool resizable;
public bool cutsGround;
public bool blocksMovement;
@ -38,9 +38,21 @@ namespace Editor.GardensStageEditor
resizable = true
};
}
public static StagePaletteEntry Object(string id, string name, StageLogicalLayer layer, StageObjectKind kind, string typeId, Vector2Int defaultSize)
public static StagePaletteEntry Object(
string id,
string name,
StageLogicalLayer layer,
StageObjectKind kind,
string typeId,
Vector2Int defaultSize = default,
Vector2Int min = default,
Vector2Int max = default)
{
var kindDefault = StageObjectDefinition.GetDefaultSize(kind);
var kindMin = StageObjectDefinition.GetMinSize(kind);
var kindMax = StageObjectDefinition.GetMaxSize(kind);
return new StagePaletteEntry
{
id = id,
@ -50,10 +62,10 @@ namespace Editor.GardensStageEditor
objectKind = kind,
typeId = typeId,
prefabId = string.Empty,
defaultSize = defaultSize,
minSize = Vector2Int.one,
maxSize = new Vector2Int(64, 64),
resizable = true
defaultSize = defaultSize == default ? kindDefault : defaultSize,
minSize = min == default ? kindMin : min,
maxSize = max == default ? kindMax : max,
resizable = StageObjectDefinition.IsKindResizable(kind)
};
}
@ -63,25 +75,49 @@ namespace Editor.GardensStageEditor
{
Structural("struct_ground", "Ground", StructuralCellKind.Ground),
Structural("struct_wall", "Wall", StructuralCellKind.Wall),
Structural("struct_hole", "Hole", StructuralCellKind.Hole),
Structural("struct_empty", "Erase Structural", StructuralCellKind.Empty)
};
var trap = Object("trap_falling_floor", "Falling Floor", StageLogicalLayer.Design, StageObjectKind.Trap, "falling_floor", Vector2Int.one);
trap.cutsGround = true;
trap.blocksMovement = true;
entries.Add(trap);
var trapFallingFloor = Object("trap_falling_floor",
"Trap (Falling Floor)",
StageLogicalLayer.Design,
StageObjectKind.Trap_FallingFloor,
"trap_falling_floor",
defaultSize: Vector2Int.one);
trapFallingFloor.cutsGround = true;
trapFallingFloor.blocksMovement = true;
entries.Add(trapFallingFloor);
var door = Object("door_standard", "Door", StageLogicalLayer.Design, StageObjectKind.Door, "door_standard", new Vector2Int(1, 2));
door.blocksMovement = true;
entries.Add(door);
var doorHorizontal = Object("door_horizontal_standard",
"Door Horizontal",
StageLogicalLayer.Design,
StageObjectKind.DoorHorizontal,
"door_horizontal_standard",
new Vector2Int(2, 1));
doorHorizontal.blocksMovement = true;
entries.Add(doorHorizontal);
var doorVertical = Object("door_vertical_standard",
"Door Vertical",
StageLogicalLayer.Design,
StageObjectKind.DoorVertical,
"door_vertical_standard",
new Vector2Int(1, 2));
doorVertical.blocksMovement = true;
entries.Add(doorVertical);
entries.Add(Object("interactable", "Interactable", StageLogicalLayer.Design, StageObjectKind.Interactable, "interactable", Vector2Int.one));
entries.Add(Object("portal", "Portal", StageLogicalLayer.Design, StageObjectKind.Portal, "portal", new Vector2Int(2, 2)));
entries.Add(Object("camera_zone", "Camera Zone", StageLogicalLayer.Camera, StageObjectKind.CameraZone, "camera_zone", new Vector2Int(8, 5)));
entries.Add(Object("spawn_player", "Player Spawn", StageLogicalLayer.Markers, StageObjectKind.Spawn, "player_spawn", Vector2Int.one));
entries.Add(Object("elevator", "Elevator", StageLogicalLayer.Markers, StageObjectKind.Elevator, "elevator", new Vector2Int(2, 2)));
entries.Add(Object("marker", "Marker", StageLogicalLayer.Markers, StageObjectKind.Marker, "marker", Vector2Int.one));
// entries.Add(Object("interactable", "Interactable", StageLogicalLayer.Design, StageObjectKind.Interactable, "interactable", Vector2Int.one));
// entries.Add(Object("portal", "Portal", StageLogicalLayer.Design, StageObjectKind.Portal, "portal", new Vector2Int(2, 2)));
entries.Add(Object("camera_boundary",
"Camera Boundary",
StageLogicalLayer.Camera,
StageObjectKind.CameraBoundary,
"camera_boundary",
defaultSize: new Vector2Int(8, 5),
min: new Vector2Int(8, 5)));
// entries.Add(Object("spawn_player", "Player Spawn", StageLogicalLayer.Markers, StageObjectKind.Spawn, "player_spawn", Vector2Int.one));
// entries.Add(Object("elevator", "Elevator", StageLogicalLayer.Markers, StageObjectKind.Elevator, "elevator", new Vector2Int(2, 2)));
// entries.Add(Object("marker", "Marker", StageLogicalLayer.Markers, StageObjectKind.Marker, "marker", Vector2Int.one));
return entries;
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 0e7ed87816264200a12fcc4bf3584153
timeCreated: 1781500522