added scrath stage editor
This commit is contained in:
parent
c4e3723e07
commit
bfab987ae7
@ -7,7 +7,7 @@ Material:
|
|||||||
m_CorrespondingSourceObject: {fileID: 0}
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_Name: ContactCollider_v2
|
m_Name: ObstacleCollider_v2
|
||||||
m_Shader: {fileID: -6465566751694194690, guid: 0ca531f654dad4a6ca8be40f93612d9e, type: 3}
|
m_Shader: {fileID: -6465566751694194690, guid: 0ca531f654dad4a6ca8be40f93612d9e, type: 3}
|
||||||
m_Parent: {fileID: 0}
|
m_Parent: {fileID: 0}
|
||||||
m_ModifiedSerializedProperties: 0
|
m_ModifiedSerializedProperties: 0
|
||||||
@ -47,7 +47,7 @@ Material:
|
|||||||
- _QueueOffset: 0
|
- _QueueOffset: 0
|
||||||
- _SideDarkness: 0.6
|
- _SideDarkness: 0.6
|
||||||
m_Colors:
|
m_Colors:
|
||||||
- _BaseColor: {r: 1, g: 0.5529412, b: 0.5647059, a: 1}
|
- _BaseColor: {r: 0.3990299, g: 0.5432971, b: 0.5754717, a: 1}
|
||||||
m_BuildTextureStacks: []
|
m_BuildTextureStacks: []
|
||||||
m_AllowLocking: 1
|
m_AllowLocking: 1
|
||||||
--- !u!114 &1069900037742449899
|
--- !u!114 &1069900037742449899
|
||||||
8
Assets/# Prototype/Stages.meta
Normal file
8
Assets/# Prototype/Stages.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 78ca007dda30545739e1fce83a853093
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
22
Assets/# Prototype/Stages/StageDefinition.asset
Normal file
22
Assets/# Prototype/Stages/StageDefinition.asset
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
%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: []
|
||||||
8
Assets/# Prototype/Stages/StageDefinition.asset.meta
Normal file
8
Assets/# Prototype/Stages/StageDefinition.asset.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bb896366809bc439b8be4d0f1158dc6f
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@ -298,7 +298,7 @@ GameObject:
|
|||||||
m_Component:
|
m_Component:
|
||||||
- component: {fileID: 7939123536182165016}
|
- component: {fileID: 7939123536182165016}
|
||||||
m_Layer: 30
|
m_Layer: 30
|
||||||
m_Name: '# Contact_Prototype_BASE'
|
m_Name: '# Obstacle_Prototype_BASE'
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
m_Icon: {fileID: 0}
|
m_Icon: {fileID: 0}
|
||||||
m_NavMeshLayer: 0
|
m_NavMeshLayer: 0
|
||||||
@ -74,7 +74,7 @@ PrefabInstance:
|
|||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 8449749668273782584, guid: 64620cfd283d648b5b4d9c9a4fac7751, type: 3}
|
- target: {fileID: 8449749668273782584, guid: 64620cfd283d648b5b4d9c9a4fac7751, type: 3}
|
||||||
propertyPath: m_Name
|
propertyPath: m_Name
|
||||||
value: Contact_Cube_Prototype
|
value: Obstacle_Cube_Prototype
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
m_RemovedComponents: []
|
m_RemovedComponents: []
|
||||||
m_RemovedGameObjects: []
|
m_RemovedGameObjects: []
|
||||||
@ -12,10 +12,9 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: 8f4d583fd594418e8ecea0731be75f23, type: 3}
|
m_Script: {fileID: 11500000, guid: 8f4d583fd594418e8ecea0731be75f23, type: 3}
|
||||||
m_Name: Dash - Cadence Profile
|
m_Name: Dash - Cadence Profile
|
||||||
m_EditorClassIdentifier: Assembly-CSharp::Intrepid.Gameplay.Entities.Player.Sentinel.Cadence.CadenceProfile
|
m_EditorClassIdentifier: Assembly-CSharp::Intrepid.Gameplay.Entities.Player.Sentinel.Cadence.CadenceProfile
|
||||||
totalTimeMS: 400
|
strained: 0.25
|
||||||
strainedEndP: 0.25
|
aligned: 0.65
|
||||||
alignedEndP: 0.65
|
resonant: 1
|
||||||
resonantEndP: 1
|
|
||||||
strainedSettlement: 1
|
strainedSettlement: 1
|
||||||
alignedSettlement: 0.5
|
alignedSettlement: 0.5
|
||||||
resonantSettlement: 0
|
resonantSettlement: 0
|
||||||
|
|||||||
@ -16,4 +16,4 @@ MonoBehaviour:
|
|||||||
unstableThresholdP: 0.7
|
unstableThresholdP: 0.7
|
||||||
criticalThresholdP: 0.35
|
criticalThresholdP: 0.35
|
||||||
collapsedRecoveryStability: 25
|
collapsedRecoveryStability: 25
|
||||||
naturalRecoveryPerSecond: 10
|
naturalRecoveryPerSecond: 2
|
||||||
|
|||||||
3
Assets/Scripts/Editor/GardensStageEditor.meta
Normal file
3
Assets/Scripts/Editor/GardensStageEditor.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 93fdd19587ea435285157d73a25958f0
|
||||||
|
timeCreated: 1781500522
|
||||||
109
Assets/Scripts/Editor/GardensStageEditor/README.md
Normal file
109
Assets/Scripts/Editor/GardensStageEditor/README.md
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
# 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.
|
||||||
3
Assets/Scripts/Editor/GardensStageEditor/README.md.meta
Normal file
3
Assets/Scripts/Editor/GardensStageEditor/README.md.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 494fb84451f94e218111409283a7f795
|
||||||
|
timeCreated: 1781500522
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
# 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.
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ffb9c2df7a0f4b3ca08544979287cf20
|
||||||
|
timeCreated: 1781500522
|
||||||
281
Assets/Scripts/Editor/GardensStageEditor/StageDefinition.cs
Normal file
281
Assets/Scripts/Editor/GardensStageEditor/StageDefinition.cs
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Editor.GardensStageEditor
|
||||||
|
{
|
||||||
|
public enum StageLogicalLayer
|
||||||
|
{
|
||||||
|
Structural = 0,
|
||||||
|
Design = 1,
|
||||||
|
Camera = 2,
|
||||||
|
Markers = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StructuralCellKind
|
||||||
|
{
|
||||||
|
Empty = 0,
|
||||||
|
Ground = 1,
|
||||||
|
Wall = 2,
|
||||||
|
Hole = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StageObjectKind
|
||||||
|
{
|
||||||
|
Trap = 0,
|
||||||
|
Door = 1,
|
||||||
|
Interactable = 2,
|
||||||
|
Portal = 3,
|
||||||
|
CameraZone = 4,
|
||||||
|
Spawn = 5,
|
||||||
|
Elevator = 6,
|
||||||
|
Marker = 7,
|
||||||
|
Custom = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
[CreateAssetMenu(menuName = "GON/Editor/Stage Definition", fileName = "StageDefinition")]
|
||||||
|
public sealed class StageDefinition : ScriptableObject
|
||||||
|
{
|
||||||
|
public string stageId = "stage_new";
|
||||||
|
public float cellSize = 1f;
|
||||||
|
public float floorHeight = 6f;
|
||||||
|
public int activeFloorIndex;
|
||||||
|
|
||||||
|
public List<StageFloorDefinition> floors = new();
|
||||||
|
public List<StageObjectDefinition> objects = new();
|
||||||
|
|
||||||
|
public StageFloorDefinition ActiveFloor
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
EnsureDefaults();
|
||||||
|
if (activeFloorIndex < 0) activeFloorIndex = 0;
|
||||||
|
if (activeFloorIndex >= floors.Count) activeFloorIndex = floors.Count - 1;
|
||||||
|
return floors[activeFloorIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnsureDefaults()
|
||||||
|
{
|
||||||
|
floors ??= new List<StageFloorDefinition>();
|
||||||
|
objects ??= new List<StageObjectDefinition>();
|
||||||
|
|
||||||
|
if (floors.Count == 0)
|
||||||
|
{
|
||||||
|
floors.Add(new StageFloorDefinition
|
||||||
|
{
|
||||||
|
floorId = "F0"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cellSize <= 0f)
|
||||||
|
{
|
||||||
|
cellSize = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (floorHeight <= 0f)
|
||||||
|
{
|
||||||
|
floorHeight = 6f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public StageFloorDefinition GetFloor(string floorId)
|
||||||
|
{
|
||||||
|
EnsureDefaults();
|
||||||
|
foreach (var stageFloorDefinition in floors)
|
||||||
|
{
|
||||||
|
if (stageFloorDefinition != null && stageFloorDefinition.floorId == floorId)
|
||||||
|
{
|
||||||
|
return stageFloorDefinition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsFloor(string floorId)
|
||||||
|
{
|
||||||
|
return GetFloor(floorId) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string CreateUniqueObjectId(StageObjectKind kind)
|
||||||
|
{
|
||||||
|
objects ??= new List<StageObjectDefinition>();
|
||||||
|
|
||||||
|
var prefix = kind.ToString().ToLowerInvariant();
|
||||||
|
var index = 1;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var candidate = prefix + "_" + index.ToString("000");
|
||||||
|
var exists = false;
|
||||||
|
foreach (var stageObjectDefinition in objects)
|
||||||
|
{
|
||||||
|
if (stageObjectDefinition != null && stageObjectDefinition.id == candidate)
|
||||||
|
{
|
||||||
|
exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!exists)
|
||||||
|
{
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class StageFloorDefinition
|
||||||
|
{
|
||||||
|
public string floorId = "F0";
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private List<LogicalCell> cells = new();
|
||||||
|
|
||||||
|
public List<LogicalCell> Cells
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
cells ??= new List<LogicalCell>();
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infinite/sparse floor: every integer coordinate is valid.
|
||||||
|
public bool IsInside(int x, int y)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StructuralCellKind GetCell(int x, int y)
|
||||||
|
{
|
||||||
|
int index = FindCellIndex(x, y);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
return StructuralCellKind.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cells[index].structural;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCell(int x, int y, StructuralCellKind kind)
|
||||||
|
{
|
||||||
|
var index = FindCellIndex(x, y);
|
||||||
|
if (kind == StructuralCellKind.Empty)
|
||||||
|
{
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
Cells.RemoveAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicalCell cell = new LogicalCell
|
||||||
|
{
|
||||||
|
x = x,
|
||||||
|
y = y,
|
||||||
|
structural = kind
|
||||||
|
};
|
||||||
|
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
Cells[index] = cell;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Cells.Add(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FillRect(RectInt rect, StructuralCellKind kind)
|
||||||
|
{
|
||||||
|
for (var y = rect.yMin; y < rect.yMax; y++)
|
||||||
|
{
|
||||||
|
for (var x = rect.xMin; x < rect.xMax; x++)
|
||||||
|
{
|
||||||
|
SetCell(x, y, kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetUsedBounds(out RectInt bounds)
|
||||||
|
{
|
||||||
|
if (Cells.Count == 0)
|
||||||
|
{
|
||||||
|
bounds = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var minX = Cells[0].x;
|
||||||
|
var maxX = Cells[0].x;
|
||||||
|
var minY = Cells[0].y;
|
||||||
|
var maxY = Cells[0].y;
|
||||||
|
|
||||||
|
for (var i = 1; i < Cells.Count; i++)
|
||||||
|
{
|
||||||
|
var cell = Cells[i];
|
||||||
|
minX = Mathf.Min(minX, cell.x);
|
||||||
|
maxX = Mathf.Max(maxX, cell.x);
|
||||||
|
minY = Mathf.Min(minY, cell.y);
|
||||||
|
maxY = Mathf.Max(maxY, cell.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
bounds = new RectInt(minX, minY, maxX - minX + 1, maxY - minY + 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int FindCellIndex(int x, int y)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Cells.Count; i++)
|
||||||
|
{
|
||||||
|
var cell = Cells[i];
|
||||||
|
if (cell.x == x && cell.y == y)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct LogicalCell
|
||||||
|
{
|
||||||
|
public int x;
|
||||||
|
public int y;
|
||||||
|
public StructuralCellKind structural;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class StageObjectDefinition
|
||||||
|
{
|
||||||
|
public string id;
|
||||||
|
public StageLogicalLayer layer;
|
||||||
|
public StageObjectKind kind;
|
||||||
|
public string typeId;
|
||||||
|
public string prefabId;
|
||||||
|
public string floorId;
|
||||||
|
|
||||||
|
public int x;
|
||||||
|
public int y;
|
||||||
|
public int width = 1;
|
||||||
|
public int height = 1;
|
||||||
|
|
||||||
|
public bool cutsGround;
|
||||||
|
public bool blocksMovement;
|
||||||
|
public string notes;
|
||||||
|
|
||||||
|
public RectInt Bounds => new(x, y, Mathf.Max(1, width), Mathf.Max(1, height));
|
||||||
|
|
||||||
|
public bool ContainsCell(int cellX, int cellY)
|
||||||
|
{
|
||||||
|
var bounds = Bounds;
|
||||||
|
return cellX >= bounds.xMin && cellX < bounds.xMax && cellY >= bounds.yMin && cellY < bounds.yMax;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 778f9d79eb2c410482efdc25b248dd46
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Editor.GardensStageEditor
|
||||||
|
{
|
||||||
|
internal static class StageEditorColors
|
||||||
|
{
|
||||||
|
public static readonly Color Background = new(0.13f, 0.13f, 0.13f, 1f);
|
||||||
|
public static readonly Color GridMinor = new(1f, 1f, 1f, 0.08f);
|
||||||
|
public static readonly Color GridMajor = new(1f, 1f, 1f, 0.18f);
|
||||||
|
public static readonly Color GridBorder = new(1f, 1f, 1f, 0.28f);
|
||||||
|
public static readonly Color PreviewFill = new(1f, 1f, 1f, 0.18f);
|
||||||
|
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 Structural(StructuralCellKind kind)
|
||||||
|
{
|
||||||
|
switch (kind)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color Layer(StageLogicalLayer layer)
|
||||||
|
{
|
||||||
|
switch (layer)
|
||||||
|
{
|
||||||
|
case StageLogicalLayer.Design:
|
||||||
|
return new Color(0.85f, 0.35f, 0.25f, 0.75f);
|
||||||
|
case StageLogicalLayer.Camera:
|
||||||
|
return new Color(0.25f, 0.48f, 0.95f, 0.35f);
|
||||||
|
case StageLogicalLayer.Markers:
|
||||||
|
return new Color(0.85f, 0.75f, 0.25f, 0.75f);
|
||||||
|
case StageLogicalLayer.Structural:
|
||||||
|
default:
|
||||||
|
return new Color(0.35f, 0.7f, 0.45f, 0.75f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fa76e08c40c94bf3a8e3aa57f849200d
|
||||||
|
timeCreated: 1781500522
|
||||||
46
Assets/Scripts/Editor/GardensStageEditor/StageEditorState.cs
Normal file
46
Assets/Scripts/Editor/GardensStageEditor/StageEditorState.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using System;
|
||||||
|
using Intrepid.Gameplay.Prototyping;
|
||||||
|
|
||||||
|
namespace Editor.GardensStageEditor
|
||||||
|
{
|
||||||
|
public sealed class StageEditorState
|
||||||
|
{
|
||||||
|
public StageLogicalLayer activeLayer = StageLogicalLayer.Structural;
|
||||||
|
public StagePaletteEntry activePaletteEntry;
|
||||||
|
public StageObjectDefinition selectedObject;
|
||||||
|
|
||||||
|
public event Action Changed;
|
||||||
|
|
||||||
|
public void ClearListeners()
|
||||||
|
{
|
||||||
|
Changed = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetActiveLayer(StageLogicalLayer layer)
|
||||||
|
{
|
||||||
|
activeLayer = layer;
|
||||||
|
NotifyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPaletteEntry(StagePaletteEntry entry)
|
||||||
|
{
|
||||||
|
activePaletteEntry = entry;
|
||||||
|
if (entry != null)
|
||||||
|
{
|
||||||
|
activeLayer = entry.layer;
|
||||||
|
}
|
||||||
|
NotifyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectObject(StageObjectDefinition obj)
|
||||||
|
{
|
||||||
|
selectedObject = obj;
|
||||||
|
NotifyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NotifyChanged()
|
||||||
|
{
|
||||||
|
Changed?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b999d27637ff4b45bb5fbdf1ef4de17b
|
||||||
295
Assets/Scripts/Editor/GardensStageEditor/StageEditorWindow.cs
Normal file
295
Assets/Scripts/Editor/GardensStageEditor/StageEditorWindow.cs
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Intrepid.Gameplay.Prototyping;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.UIElements;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
namespace Editor.GardensStageEditor
|
||||||
|
{
|
||||||
|
public sealed class StageEditorWindow : EditorWindow
|
||||||
|
{
|
||||||
|
private StageDefinition stage;
|
||||||
|
private StageEditorState state;
|
||||||
|
private List<StagePaletteEntry> palette;
|
||||||
|
|
||||||
|
private VisualElement topBar;
|
||||||
|
private StageMapCanvas canvas;
|
||||||
|
private StageInspectorPanel inspector;
|
||||||
|
private StagePalettePanel palettePanel;
|
||||||
|
|
||||||
|
[MenuItem("Tools/Gardens Of Nil/Stage Editor")]
|
||||||
|
public static void Open()
|
||||||
|
{
|
||||||
|
var window = GetWindow<StageEditorWindow>();
|
||||||
|
window.titleContent = new GUIContent("Gardens Stage Editor");
|
||||||
|
window.minSize = new Vector2(900, 600);
|
||||||
|
window.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateGUI()
|
||||||
|
{
|
||||||
|
state ??= new StageEditorState();
|
||||||
|
state.ClearListeners();
|
||||||
|
|
||||||
|
if (palette is null)
|
||||||
|
{
|
||||||
|
palette = StagePaletteEntry.CreateBuiltInPalette();
|
||||||
|
if (state.activePaletteEntry is null && palette.Count > 0)
|
||||||
|
{
|
||||||
|
state.activePaletteEntry = palette[0];
|
||||||
|
state.activeLayer = palette[0].layer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootVisualElement.Clear();
|
||||||
|
rootVisualElement.style.flexDirection = FlexDirection.Column;
|
||||||
|
|
||||||
|
topBar = new VisualElement
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
flexDirection = FlexDirection.Row,
|
||||||
|
alignItems = Align.Center,
|
||||||
|
paddingLeft = 8,
|
||||||
|
paddingRight = 8,
|
||||||
|
paddingTop = 6,
|
||||||
|
paddingBottom = 6,
|
||||||
|
borderBottomWidth = 1,
|
||||||
|
borderBottomColor = new Color(1f, 1f, 1f, 0.12f)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
rootVisualElement.Add(topBar);
|
||||||
|
|
||||||
|
var main = new VisualElement
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
flexGrow = 1f,
|
||||||
|
flexDirection = FlexDirection.Row
|
||||||
|
}
|
||||||
|
};
|
||||||
|
rootVisualElement.Add(main);
|
||||||
|
|
||||||
|
canvas = new StageMapCanvas(this, state);
|
||||||
|
canvas.SetStage(stage);
|
||||||
|
main.Add(canvas);
|
||||||
|
|
||||||
|
inspector = new StageInspectorPanel(this, state);
|
||||||
|
inspector.SetStage(stage);
|
||||||
|
main.Add(inspector);
|
||||||
|
|
||||||
|
palettePanel = new StagePalettePanel(state, palette);
|
||||||
|
rootVisualElement.Add(palettePanel);
|
||||||
|
|
||||||
|
state.Changed += delegate
|
||||||
|
{
|
||||||
|
canvas?.RequestRepaint();
|
||||||
|
inspector?.Rebuild();
|
||||||
|
};
|
||||||
|
|
||||||
|
RebuildTopBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MutateStage(string undoName, System.Action mutation)
|
||||||
|
{
|
||||||
|
if (stage is null || mutation is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Undo.RecordObject(stage, undoName);
|
||||||
|
mutation();
|
||||||
|
stage.EnsureDefaults();
|
||||||
|
EditorUtility.SetDirty(stage);
|
||||||
|
|
||||||
|
canvas?.RequestRepaint();
|
||||||
|
inspector?.Rebuild();
|
||||||
|
|
||||||
|
RebuildTopBar();
|
||||||
|
Repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RebuildTopBar()
|
||||||
|
{
|
||||||
|
if (topBar is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
topBar.Clear();
|
||||||
|
|
||||||
|
var stageField = new ObjectField("Stage")
|
||||||
|
{
|
||||||
|
objectType = typeof(StageDefinition),
|
||||||
|
value = stage,
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
minWidth = 300
|
||||||
|
}
|
||||||
|
};
|
||||||
|
stageField.RegisterValueChangedCallback(evt =>
|
||||||
|
{
|
||||||
|
SetStage(evt.newValue as StageDefinition);
|
||||||
|
});
|
||||||
|
topBar.Add(stageField);
|
||||||
|
|
||||||
|
var newButton = new Button(CreateNewStageAsset)
|
||||||
|
{
|
||||||
|
text = "New",
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
marginLeft = 6
|
||||||
|
}
|
||||||
|
};
|
||||||
|
topBar.Add(newButton);
|
||||||
|
|
||||||
|
if (stage is not null)
|
||||||
|
{
|
||||||
|
stage.EnsureDefaults();
|
||||||
|
|
||||||
|
var floorNames = new List<string>();
|
||||||
|
foreach (var stageFloorDefinition in stage.floors)
|
||||||
|
{
|
||||||
|
floorNames.Add(stageFloorDefinition.floorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var floorPopup = new PopupField<string>("Floor", floorNames, Mathf.Clamp(stage.activeFloorIndex, 0, floorNames.Count - 1))
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
marginLeft = 10,
|
||||||
|
minWidth = 170
|
||||||
|
}
|
||||||
|
};
|
||||||
|
floorPopup.RegisterValueChangedCallback(evt =>
|
||||||
|
{
|
||||||
|
var index = floorNames.IndexOf(evt.newValue);
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
MutateStage("Change active floor", delegate { stage.activeFloorIndex = index; });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
topBar.Add(floorPopup);
|
||||||
|
|
||||||
|
var addFloor = new Button(AddFloor)
|
||||||
|
{
|
||||||
|
text = "+ Floor",
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
marginLeft = 4
|
||||||
|
}
|
||||||
|
};
|
||||||
|
topBar.Add(addFloor);
|
||||||
|
|
||||||
|
var validate = new Button(ValidateStage)
|
||||||
|
{
|
||||||
|
text = "Validate",
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
marginLeft = 12
|
||||||
|
}
|
||||||
|
};
|
||||||
|
topBar.Add(validate);
|
||||||
|
|
||||||
|
var save = new Button(delegate
|
||||||
|
{
|
||||||
|
EditorUtility.SetDirty(stage);
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
Debug.Log("Saved StageDefinition: " + AssetDatabase.GetAssetPath(stage));
|
||||||
|
})
|
||||||
|
{
|
||||||
|
text = "Save Asset",
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
marginLeft = 4
|
||||||
|
}
|
||||||
|
};
|
||||||
|
topBar.Add(save);
|
||||||
|
}
|
||||||
|
|
||||||
|
var spacer = new VisualElement
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
flexGrow = 1f
|
||||||
|
}
|
||||||
|
};
|
||||||
|
topBar.Add(spacer);
|
||||||
|
|
||||||
|
var hint = new Label("Infinite grid · LMB select/place/paint · RMB/MMB pan · Wheel zoom")
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
opacity = 0.75f
|
||||||
|
}
|
||||||
|
};
|
||||||
|
topBar.Add(hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetStage(StageDefinition newStage)
|
||||||
|
{
|
||||||
|
stage = newStage;
|
||||||
|
stage?.EnsureDefaults();
|
||||||
|
state.SelectObject(null);
|
||||||
|
canvas?.SetStage(stage);
|
||||||
|
inspector?.SetStage(stage);
|
||||||
|
|
||||||
|
RebuildTopBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateNewStageAsset()
|
||||||
|
{
|
||||||
|
var path = EditorUtility.SaveFilePanelInProject(
|
||||||
|
"Create Stage Definition",
|
||||||
|
"StageDefinition",
|
||||||
|
"asset",
|
||||||
|
"Choose where to create the new StageDefinition asset."
|
||||||
|
);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var asset = CreateInstance<StageDefinition>();
|
||||||
|
asset.stageId = System.IO.Path.GetFileNameWithoutExtension(path).ToLowerInvariant();
|
||||||
|
asset.EnsureDefaults();
|
||||||
|
|
||||||
|
AssetDatabase.CreateAsset(asset, path);
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
AssetDatabase.Refresh();
|
||||||
|
|
||||||
|
SetStage(asset);
|
||||||
|
Selection.activeObject = asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddFloor()
|
||||||
|
{
|
||||||
|
if (stage is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MutateStage("Add floor", delegate
|
||||||
|
{
|
||||||
|
var index = stage.floors.Count;
|
||||||
|
var floor = new StageFloorDefinition
|
||||||
|
{
|
||||||
|
floorId = "F" + index
|
||||||
|
};
|
||||||
|
|
||||||
|
stage.floors.Add(floor);
|
||||||
|
stage.activeFloorIndex = stage.floors.Count - 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateStage()
|
||||||
|
{
|
||||||
|
var messages = StageValidator.Validate(stage);
|
||||||
|
var report = StageValidator.Format(messages);
|
||||||
|
Debug.Log(report);
|
||||||
|
EditorUtility.DisplayDialog("Stage Validation", report, "OK");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f7065bab1fc14f53909281036dbb6368
|
||||||
258
Assets/Scripts/Editor/GardensStageEditor/StageInspectorPanel.cs
Normal file
258
Assets/Scripts/Editor/GardensStageEditor/StageInspectorPanel.cs
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
namespace Editor.GardensStageEditor
|
||||||
|
{
|
||||||
|
public sealed class StageInspectorPanel : VisualElement
|
||||||
|
{
|
||||||
|
private readonly StageEditorWindow owner;
|
||||||
|
private readonly StageEditorState state;
|
||||||
|
private StageDefinition stage;
|
||||||
|
|
||||||
|
public StageInspectorPanel(StageEditorWindow owner, StageEditorState state)
|
||||||
|
{
|
||||||
|
this.owner = owner;
|
||||||
|
this.state = state;
|
||||||
|
|
||||||
|
style.width = 300;
|
||||||
|
style.minWidth = 260;
|
||||||
|
style.maxWidth = 380;
|
||||||
|
style.paddingLeft = 8;
|
||||||
|
style.paddingRight = 8;
|
||||||
|
style.paddingTop = 8;
|
||||||
|
style.paddingBottom = 8;
|
||||||
|
style.borderLeftWidth = 1;
|
||||||
|
style.borderLeftColor = new Color(1f, 1f, 1f, 0.12f);
|
||||||
|
style.flexShrink = 0;
|
||||||
|
|
||||||
|
state.Changed += Rebuild;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetStage(StageDefinition newStage)
|
||||||
|
{
|
||||||
|
stage = newStage;
|
||||||
|
Rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rebuild()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
|
||||||
|
var title = new Label("Inspector")
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
unityFontStyleAndWeight = FontStyle.Bold,
|
||||||
|
marginBottom = 8
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Add(title);
|
||||||
|
|
||||||
|
if (stage is null)
|
||||||
|
{
|
||||||
|
Add(new HelpBox("Select or create a StageDefinition asset.", HelpBoxMessageType.Info));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stage.EnsureDefaults();
|
||||||
|
|
||||||
|
if (state.selectedObject is not null && stage.objects.Contains(state.selectedObject))
|
||||||
|
{
|
||||||
|
BuildObjectInspector(state.selectedObject);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state.selectedObject = null;
|
||||||
|
BuildStageInspector();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildStageInspector()
|
||||||
|
{
|
||||||
|
var section = new Label("Stage")
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
unityFontStyleAndWeight = FontStyle.Bold
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Add(section);
|
||||||
|
|
||||||
|
var stageId = new TextField("Stage Id")
|
||||||
|
{
|
||||||
|
value = stage.stageId
|
||||||
|
};
|
||||||
|
stageId.RegisterValueChangedCallback(evt => owner.MutateStage("Change stage id", delegate { stage.stageId = evt.newValue; }));
|
||||||
|
Add(stageId);
|
||||||
|
|
||||||
|
var cellSize = new FloatField("Cell Size")
|
||||||
|
{
|
||||||
|
value = stage.cellSize
|
||||||
|
};
|
||||||
|
cellSize.RegisterValueChangedCallback(evt => owner.MutateStage("Change cell size", delegate { stage.cellSize = Mathf.Max(0.01f, evt.newValue); }));
|
||||||
|
Add(cellSize);
|
||||||
|
|
||||||
|
var floorHeight = new FloatField("Floor Height")
|
||||||
|
{
|
||||||
|
value = stage.floorHeight
|
||||||
|
};
|
||||||
|
floorHeight.RegisterValueChangedCallback(evt => owner.MutateStage("Change floor height", delegate { stage.floorHeight = Mathf.Max(0.01f, evt.newValue); }));
|
||||||
|
Add(floorHeight);
|
||||||
|
|
||||||
|
var spacer = new VisualElement
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
height = 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Add(spacer);
|
||||||
|
|
||||||
|
var floor = stage.ActiveFloor;
|
||||||
|
var floorSection = new Label("Active Floor")
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
unityFontStyleAndWeight = FontStyle.Bold
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Add(floorSection);
|
||||||
|
|
||||||
|
var floorId = new TextField("Floor Id")
|
||||||
|
{
|
||||||
|
value = floor.floorId
|
||||||
|
};
|
||||||
|
floorId.RegisterValueChangedCallback(evt => owner.MutateStage("Change floor id", delegate
|
||||||
|
{
|
||||||
|
var oldId = floor.floorId;
|
||||||
|
floor.floorId = string.IsNullOrEmpty(evt.newValue) ? oldId : evt.newValue;
|
||||||
|
foreach (var stageObjectDefinition in stage.objects)
|
||||||
|
{
|
||||||
|
if (stageObjectDefinition is not null && stageObjectDefinition.floorId == oldId)
|
||||||
|
{
|
||||||
|
stageObjectDefinition.floorId = floor.floorId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
Add(floorId);
|
||||||
|
|
||||||
|
Add(new Label("Structural Cells: " + floor.Cells.Count));
|
||||||
|
|
||||||
|
Add(floor.TryGetUsedBounds(out var bounds)
|
||||||
|
? new Label("Used Bounds: x " + bounds.xMin + ".." + bounds.xMax + ", y " + bounds.yMin + ".." +
|
||||||
|
bounds.yMax)
|
||||||
|
: new Label("Used Bounds: empty"));
|
||||||
|
|
||||||
|
Add(new HelpBox("The grid is infinite/sparse. The stage size emerges from painted cells and logical objects.", HelpBoxMessageType.Info));
|
||||||
|
Add(new HelpBox("Editor plane uses X/Y. Later, the stage baker can map editor Y to Unity Z and floor index to Unity Y.", HelpBoxMessageType.Info));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildObjectInspector(StageObjectDefinition obj)
|
||||||
|
{
|
||||||
|
var section = new Label("Selected Object")
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
unityFontStyleAndWeight = FontStyle.Bold
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Add(section);
|
||||||
|
|
||||||
|
var id = new TextField("Id")
|
||||||
|
{
|
||||||
|
value = obj.id
|
||||||
|
};
|
||||||
|
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);
|
||||||
|
|
||||||
|
var typeId = new TextField("Type Id")
|
||||||
|
{
|
||||||
|
value = obj.typeId
|
||||||
|
};
|
||||||
|
typeId.RegisterValueChangedCallback(evt => owner.MutateStage("Change type id", delegate { obj.typeId = evt.newValue; }));
|
||||||
|
Add(typeId);
|
||||||
|
|
||||||
|
var prefabId = new TextField("Prefab Id")
|
||||||
|
{
|
||||||
|
value = obj.prefabId
|
||||||
|
};
|
||||||
|
prefabId.RegisterValueChangedCallback(evt => owner.MutateStage("Change prefab id", delegate { obj.prefabId = evt.newValue; }));
|
||||||
|
Add(prefabId);
|
||||||
|
|
||||||
|
var x = new IntegerField("X")
|
||||||
|
{
|
||||||
|
value = obj.x
|
||||||
|
};
|
||||||
|
x.RegisterValueChangedCallback(evt => owner.MutateStage("Move object x", delegate { obj.x = evt.newValue; }));
|
||||||
|
Add(x);
|
||||||
|
|
||||||
|
var y = new IntegerField("Y")
|
||||||
|
{
|
||||||
|
value = obj.y
|
||||||
|
};
|
||||||
|
y.RegisterValueChangedCallback(evt => owner.MutateStage("Move object y", delegate { obj.y = evt.newValue; }));
|
||||||
|
Add(y);
|
||||||
|
|
||||||
|
var width = new IntegerField("Width")
|
||||||
|
{
|
||||||
|
value = obj.width
|
||||||
|
};
|
||||||
|
width.RegisterValueChangedCallback(evt => owner.MutateStage("Resize object width", delegate { obj.width = Mathf.Max(1, evt.newValue); }));
|
||||||
|
Add(width);
|
||||||
|
|
||||||
|
var height = new IntegerField("Height")
|
||||||
|
{
|
||||||
|
value = obj.height
|
||||||
|
};
|
||||||
|
height.RegisterValueChangedCallback(evt => owner.MutateStage("Resize object height", delegate { obj.height = Mathf.Max(1, evt.newValue); }));
|
||||||
|
Add(height);
|
||||||
|
|
||||||
|
var cutsGround = new Toggle("Cuts Ground")
|
||||||
|
{
|
||||||
|
value = obj.cutsGround
|
||||||
|
};
|
||||||
|
cutsGround.RegisterValueChangedCallback(evt => owner.MutateStage("Toggle cuts ground", delegate { obj.cutsGround = evt.newValue; }));
|
||||||
|
Add(cutsGround);
|
||||||
|
|
||||||
|
var blocksMovement = new Toggle("Blocks Movement")
|
||||||
|
{
|
||||||
|
value = obj.blocksMovement
|
||||||
|
};
|
||||||
|
blocksMovement.RegisterValueChangedCallback(evt => owner.MutateStage("Toggle blocks movement", delegate { obj.blocksMovement = evt.newValue; }));
|
||||||
|
Add(blocksMovement);
|
||||||
|
|
||||||
|
var notes = new TextField("Notes")
|
||||||
|
{
|
||||||
|
multiline = true,
|
||||||
|
value = obj.notes
|
||||||
|
};
|
||||||
|
notes.RegisterValueChangedCallback(evt => owner.MutateStage("Change object notes", delegate { obj.notes = evt.newValue; }));
|
||||||
|
Add(notes);
|
||||||
|
|
||||||
|
var delete = new Button(delegate
|
||||||
|
{
|
||||||
|
owner.MutateStage("Delete object", delegate
|
||||||
|
{
|
||||||
|
stage.objects.Remove(obj);
|
||||||
|
state.SelectObject(null);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
{
|
||||||
|
text = "Delete Object",
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
marginTop = 8
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Add(delete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a1b3226b742b4e5e94cab859de25494a
|
||||||
466
Assets/Scripts/Editor/GardensStageEditor/StageMapCanvas.cs
Normal file
466
Assets/Scripts/Editor/GardensStageEditor/StageMapCanvas.cs
Normal file
@ -0,0 +1,466 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
namespace Editor.GardensStageEditor
|
||||||
|
{
|
||||||
|
public sealed class StageMapCanvas : VisualElement
|
||||||
|
{
|
||||||
|
private const float MinCellPixels = 12f;
|
||||||
|
private const float MaxCellPixels = 96f;
|
||||||
|
|
||||||
|
private readonly StageEditorWindow owner;
|
||||||
|
private readonly StageEditorState state;
|
||||||
|
|
||||||
|
private StageDefinition stage;
|
||||||
|
private Vector2 pan = new(40f, 40f);
|
||||||
|
private float cellPixels = 32f;
|
||||||
|
|
||||||
|
private bool isPanning;
|
||||||
|
private bool isPainting;
|
||||||
|
private Vector2 panStartMouse;
|
||||||
|
private Vector2 panStartValue;
|
||||||
|
private Vector2Int dragStartCell;
|
||||||
|
private Vector2Int dragCurrentCell;
|
||||||
|
|
||||||
|
public StageMapCanvas(StageEditorWindow owner, StageEditorState state)
|
||||||
|
{
|
||||||
|
this.owner = owner;
|
||||||
|
this.state = state;
|
||||||
|
|
||||||
|
focusable = true;
|
||||||
|
style.flexGrow = 1f;
|
||||||
|
style.backgroundColor = StageEditorColors.Background;
|
||||||
|
|
||||||
|
generateVisualContent += OnGenerateVisualContent;
|
||||||
|
|
||||||
|
RegisterCallback<MouseDownEvent>(OnMouseDown);
|
||||||
|
RegisterCallback<MouseMoveEvent>(OnMouseMove);
|
||||||
|
RegisterCallback<MouseUpEvent>(OnMouseUp);
|
||||||
|
RegisterCallback<WheelEvent>(OnWheel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetStage(StageDefinition newStage)
|
||||||
|
{
|
||||||
|
stage = newStage;
|
||||||
|
stage?.EnsureDefaults();
|
||||||
|
MarkDirtyRepaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestRepaint()
|
||||||
|
{
|
||||||
|
MarkDirtyRepaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWheel(WheelEvent evt)
|
||||||
|
{
|
||||||
|
if (stage is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mouse = evt.localMousePosition;
|
||||||
|
var before = ScreenToGridFloat(mouse);
|
||||||
|
|
||||||
|
var delta = -evt.delta.y * 0.08f;
|
||||||
|
cellPixels = Mathf.Clamp(cellPixels * (1f + delta), MinCellPixels, MaxCellPixels);
|
||||||
|
|
||||||
|
var after = ScreenToGridFloat(mouse);
|
||||||
|
pan += (after - before) * cellPixels;
|
||||||
|
|
||||||
|
evt.StopPropagation();
|
||||||
|
MarkDirtyRepaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMouseDown(MouseDownEvent evt)
|
||||||
|
{
|
||||||
|
Focus();
|
||||||
|
|
||||||
|
if (stage is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
evt.StopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evt.button != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryScreenToCell(evt.localMousePosition, out Vector2Int cell))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LMB always tries to select an existing logical object first.
|
||||||
|
// This removes the need for a Select tool.
|
||||||
|
var hit = FindObjectAt(cell, false);
|
||||||
|
if (hit != null)
|
||||||
|
{
|
||||||
|
state.SelectObject(hit);
|
||||||
|
MarkDirtyRepaint();
|
||||||
|
evt.StopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SelectObject(null);
|
||||||
|
|
||||||
|
// Empty space uses the active palette item to paint/place.
|
||||||
|
dragStartCell = cell;
|
||||||
|
dragCurrentCell = cell;
|
||||||
|
isPainting = true;
|
||||||
|
this.CaptureMouse();
|
||||||
|
|
||||||
|
evt.StopPropagation();
|
||||||
|
MarkDirtyRepaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMouseMove(MouseMoveEvent evt)
|
||||||
|
{
|
||||||
|
if (isPanning)
|
||||||
|
{
|
||||||
|
pan = panStartValue + evt.localMousePosition - panStartMouse;
|
||||||
|
evt.StopPropagation();
|
||||||
|
MarkDirtyRepaint();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPainting)
|
||||||
|
{
|
||||||
|
if (TryScreenToCell(evt.localMousePosition, out Vector2Int cell))
|
||||||
|
{
|
||||||
|
dragCurrentCell = cell;
|
||||||
|
MarkDirtyRepaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
evt.StopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMouseUp(MouseUpEvent evt)
|
||||||
|
{
|
||||||
|
if (isPanning)
|
||||||
|
{
|
||||||
|
isPanning = false;
|
||||||
|
if (this.HasMouseCapture())
|
||||||
|
{
|
||||||
|
this.ReleaseMouse();
|
||||||
|
}
|
||||||
|
|
||||||
|
evt.StopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPainting || evt.button != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPainting = false;
|
||||||
|
if (this.HasMouseCapture())
|
||||||
|
{
|
||||||
|
this.ReleaseMouse();
|
||||||
|
}
|
||||||
|
|
||||||
|
var rect = GetDragRect();
|
||||||
|
ApplyPalette(rect);
|
||||||
|
|
||||||
|
evt.StopPropagation();
|
||||||
|
MarkDirtyRepaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyPalette(RectInt rect)
|
||||||
|
{
|
||||||
|
StageFloorDefinition floor = stage?.ActiveFloor;
|
||||||
|
if (floor is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry = state.activePaletteEntry;
|
||||||
|
if (entry is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.paintsStructural)
|
||||||
|
{
|
||||||
|
owner.MutateStage("Paint " + entry.displayName, delegate
|
||||||
|
{
|
||||||
|
floor.FillRect(rect, entry.structuralKind);
|
||||||
|
});
|
||||||
|
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));
|
||||||
|
|
||||||
|
owner.MutateStage("Place " + entry.displayName, delegate
|
||||||
|
{
|
||||||
|
var obj = new StageObjectDefinition
|
||||||
|
{
|
||||||
|
id = stage.CreateUniqueObjectId(entry.objectKind),
|
||||||
|
layer = entry.layer,
|
||||||
|
kind = entry.objectKind,
|
||||||
|
typeId = entry.typeId,
|
||||||
|
prefabId = entry.prefabId,
|
||||||
|
floorId = floor.floorId,
|
||||||
|
x = objectRect.x,
|
||||||
|
y = objectRect.y,
|
||||||
|
width = Mathf.Max(1, objectRect.width),
|
||||||
|
height = Mathf.Max(1, objectRect.height),
|
||||||
|
cutsGround = entry.cutsGround,
|
||||||
|
blocksMovement = entry.blocksMovement
|
||||||
|
};
|
||||||
|
|
||||||
|
stage.objects.Add(obj);
|
||||||
|
state.SelectObject(obj);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private StageObjectDefinition FindObjectAt(Vector2Int cell, bool activeLayerOnly)
|
||||||
|
{
|
||||||
|
if (stage is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var floorId = stage.ActiveFloor.floorId;
|
||||||
|
for (var i = stage.objects.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var obj = stage.objects[i];
|
||||||
|
if (obj is null || obj.floorId != floorId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeLayerOnly && obj.layer != state.activeLayer)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.ContainsCell(cell.x, cell.y))
|
||||||
|
{
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGenerateVisualContent(MeshGenerationContext mgc)
|
||||||
|
{
|
||||||
|
var p = mgc.painter2D;
|
||||||
|
FillRect(p, contentRect, StageEditorColors.Background);
|
||||||
|
|
||||||
|
if (stage is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stage.EnsureDefaults();
|
||||||
|
var floor = stage.ActiveFloor;
|
||||||
|
if (floor is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawInfiniteGrid(p);
|
||||||
|
DrawCells(p, floor);
|
||||||
|
DrawObjects(p, floor.floorId);
|
||||||
|
DrawOriginAxes(p);
|
||||||
|
DrawPreview(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawCells(Painter2D p, StageFloorDefinition floor)
|
||||||
|
{
|
||||||
|
foreach (var cell in floor.Cells)
|
||||||
|
{
|
||||||
|
var color = StageEditorColors.Structural(cell.structural);
|
||||||
|
if (color.a <= 0f)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rect = CellRect(cell.x, cell.y);
|
||||||
|
FillRect(p, rect, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawObjects(Painter2D p, string floorId)
|
||||||
|
{
|
||||||
|
foreach (var obj in stage.objects)
|
||||||
|
{
|
||||||
|
if (obj is null || obj.floorId != floorId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rect = GridRect(obj.Bounds);
|
||||||
|
var color = StageEditorColors.Layer(obj.layer);
|
||||||
|
FillRect(p, rect, color);
|
||||||
|
StrokeRect(p, rect, color.WithAlpha(Mathf.Min(1f, color.a + 0.25f)), 2f);
|
||||||
|
|
||||||
|
if (state.selectedObject == obj)
|
||||||
|
{
|
||||||
|
StrokeRect(p, rect.Expanded(2f), StageEditorColors.SelectionStroke, 3f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawInfiniteGrid(Painter2D p)
|
||||||
|
{
|
||||||
|
var r = contentRect;
|
||||||
|
var minX = Mathf.FloorToInt((r.xMin - pan.x) / cellPixels) - 1;
|
||||||
|
var maxX = Mathf.CeilToInt((r.xMax - pan.x) / cellPixels) + 1;
|
||||||
|
var minY = Mathf.FloorToInt((r.yMin - pan.y) / cellPixels) - 1;
|
||||||
|
var maxY = Mathf.CeilToInt((r.yMax - pan.y) / cellPixels) + 1;
|
||||||
|
|
||||||
|
for (var x = minX; x <= maxX; x++)
|
||||||
|
{
|
||||||
|
var sx = pan.x + x * cellPixels;
|
||||||
|
var c = x % 4 == 0 ? StageEditorColors.GridMajor : StageEditorColors.GridMinor;
|
||||||
|
DrawLine(p, new Vector2(sx, r.yMin), new Vector2(sx, r.yMax), c, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var y = minY; y <= maxY; y++)
|
||||||
|
{
|
||||||
|
var sy = pan.y + y * cellPixels;
|
||||||
|
var c = y % 4 == 0 ? StageEditorColors.GridMajor : StageEditorColors.GridMinor;
|
||||||
|
DrawLine(p, new Vector2(r.xMin, sy), new Vector2(r.xMax, sy), c, 1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawOriginAxes(Painter2D p)
|
||||||
|
{
|
||||||
|
var r = contentRect;
|
||||||
|
var sx = pan.x;
|
||||||
|
var sy = pan.y;
|
||||||
|
|
||||||
|
if (sx >= r.xMin && sx <= r.xMax)
|
||||||
|
{
|
||||||
|
DrawLine(p, new Vector2(sx, r.yMin), new Vector2(sx, r.yMax), StageEditorColors.GridBorder, 2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sy >= r.yMin && sy <= r.yMax)
|
||||||
|
{
|
||||||
|
DrawLine(p, new Vector2(r.xMin, sy), new Vector2(r.xMax, sy), StageEditorColors.GridBorder, 2f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPreview(Painter2D p)
|
||||||
|
{
|
||||||
|
if (!isPainting || state.activePaletteEntry is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rect = GetDragRect();
|
||||||
|
var entry = state.activePaletteEntry;
|
||||||
|
|
||||||
|
if (!entry.paintsStructural && rect is { width: 1, height: 1 })
|
||||||
|
{
|
||||||
|
rect = new RectInt(rect.x, rect.y, Mathf.Max(1, entry.defaultSize.x), Mathf.Max(1, entry.defaultSize.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
var screenRect = GridRect(rect);
|
||||||
|
FillRect(p, screenRect, StageEditorColors.PreviewFill);
|
||||||
|
StrokeRect(p, screenRect, StageEditorColors.PreviewStroke, 2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RectInt GetDragRect()
|
||||||
|
{
|
||||||
|
var minX = Mathf.Min(dragStartCell.x, dragCurrentCell.x);
|
||||||
|
var maxX = Mathf.Max(dragStartCell.x, dragCurrentCell.x);
|
||||||
|
var minY = Mathf.Min(dragStartCell.y, dragCurrentCell.y);
|
||||||
|
var maxY = Mathf.Max(dragStartCell.y, dragCurrentCell.y);
|
||||||
|
return new RectInt(minX, minY, maxX - minX + 1, maxY - minY + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryScreenToCell(Vector2 screen, out Vector2Int cell)
|
||||||
|
{
|
||||||
|
var grid = ScreenToGridFloat(screen);
|
||||||
|
cell = new Vector2Int(Mathf.FloorToInt(grid.x), Mathf.FloorToInt(grid.y));
|
||||||
|
return stage?.ActiveFloor is not null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 ScreenToGridFloat(Vector2 screen)
|
||||||
|
{
|
||||||
|
return new Vector2((screen.x - pan.x) / cellPixels, (screen.y - pan.y) / cellPixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Rect CellRect(int x, int y)
|
||||||
|
{
|
||||||
|
return new Rect(pan.x + x * cellPixels, pan.y + y * cellPixels, cellPixels, cellPixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Rect GridRect(RectInt rect)
|
||||||
|
{
|
||||||
|
return new Rect(pan.x + rect.x * cellPixels, pan.y + rect.y * cellPixels, rect.width * cellPixels, rect.height * cellPixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FillRect(Painter2D p, Rect rect, Color color)
|
||||||
|
{
|
||||||
|
p.fillColor = color;
|
||||||
|
p.BeginPath();
|
||||||
|
p.MoveTo(new Vector2(rect.xMin, rect.yMin));
|
||||||
|
p.LineTo(new Vector2(rect.xMax, rect.yMin));
|
||||||
|
p.LineTo(new Vector2(rect.xMax, rect.yMax));
|
||||||
|
p.LineTo(new Vector2(rect.xMin, rect.yMax));
|
||||||
|
p.ClosePath();
|
||||||
|
p.Fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StrokeRect(Painter2D p, Rect rect, Color color, float width)
|
||||||
|
{
|
||||||
|
p.strokeColor = color;
|
||||||
|
p.lineWidth = width;
|
||||||
|
p.BeginPath();
|
||||||
|
p.MoveTo(new Vector2(rect.xMin, rect.yMin));
|
||||||
|
p.LineTo(new Vector2(rect.xMax, rect.yMin));
|
||||||
|
p.LineTo(new Vector2(rect.xMax, rect.yMax));
|
||||||
|
p.LineTo(new Vector2(rect.xMin, rect.yMax));
|
||||||
|
p.ClosePath();
|
||||||
|
p.Stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawLine(Painter2D p, Vector2 a, Vector2 b, Color color, float width)
|
||||||
|
{
|
||||||
|
p.strokeColor = color;
|
||||||
|
p.lineWidth = width;
|
||||||
|
p.BeginPath();
|
||||||
|
p.MoveTo(a);
|
||||||
|
p.LineTo(b);
|
||||||
|
p.Stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class StageEditorRectExtensions
|
||||||
|
{
|
||||||
|
public static Rect Expanded(this Rect rect, float amount)
|
||||||
|
{
|
||||||
|
return new Rect(rect.x - amount, rect.y - amount, rect.width + amount * 2f, rect.height + amount * 2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color WithAlpha(this Color color, float alpha)
|
||||||
|
{
|
||||||
|
color.a = alpha;
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8c584058841343debb04c00199a79393
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Editor.GardensStageEditor
|
||||||
|
{
|
||||||
|
public sealed class StagePaletteEntry
|
||||||
|
{
|
||||||
|
public string id;
|
||||||
|
public string displayName;
|
||||||
|
public StageLogicalLayer layer;
|
||||||
|
|
||||||
|
public bool paintsStructural;
|
||||||
|
public StructuralCellKind structuralKind;
|
||||||
|
|
||||||
|
public StageObjectKind objectKind;
|
||||||
|
public string typeId;
|
||||||
|
public string prefabId;
|
||||||
|
|
||||||
|
public Vector2Int defaultSize = Vector2Int.one;
|
||||||
|
public Vector2Int minSize = Vector2Int.one;
|
||||||
|
public Vector2Int maxSize = new(32, 32);
|
||||||
|
public bool resizable = true;
|
||||||
|
public bool cutsGround;
|
||||||
|
public bool blocksMovement;
|
||||||
|
|
||||||
|
public static StagePaletteEntry Structural(string id, string name, StructuralCellKind kind)
|
||||||
|
{
|
||||||
|
return new StagePaletteEntry
|
||||||
|
{
|
||||||
|
id = id,
|
||||||
|
displayName = name,
|
||||||
|
layer = StageLogicalLayer.Structural,
|
||||||
|
paintsStructural = true,
|
||||||
|
structuralKind = kind,
|
||||||
|
defaultSize = Vector2Int.one,
|
||||||
|
minSize = Vector2Int.one,
|
||||||
|
maxSize = new Vector2Int(64, 64),
|
||||||
|
resizable = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StagePaletteEntry Object(string id, string name, StageLogicalLayer layer, StageObjectKind kind, string typeId, Vector2Int defaultSize)
|
||||||
|
{
|
||||||
|
return new StagePaletteEntry
|
||||||
|
{
|
||||||
|
id = id,
|
||||||
|
displayName = name,
|
||||||
|
layer = layer,
|
||||||
|
paintsStructural = false,
|
||||||
|
objectKind = kind,
|
||||||
|
typeId = typeId,
|
||||||
|
prefabId = string.Empty,
|
||||||
|
defaultSize = defaultSize,
|
||||||
|
minSize = Vector2Int.one,
|
||||||
|
maxSize = new Vector2Int(64, 64),
|
||||||
|
resizable = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<StagePaletteEntry> CreateBuiltInPalette()
|
||||||
|
{
|
||||||
|
var entries = new List<StagePaletteEntry>
|
||||||
|
{
|
||||||
|
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 door = Object("door_standard", "Door", StageLogicalLayer.Design, StageObjectKind.Door, "door_standard", new Vector2Int(1, 2));
|
||||||
|
door.blocksMovement = true;
|
||||||
|
entries.Add(door);
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0e7ed87816264200a12fcc4bf3584153
|
||||||
|
timeCreated: 1781500522
|
||||||
163
Assets/Scripts/Editor/GardensStageEditor/StagePalettePanel.cs
Normal file
163
Assets/Scripts/Editor/GardensStageEditor/StagePalettePanel.cs
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
namespace Editor.GardensStageEditor
|
||||||
|
{
|
||||||
|
public sealed class StagePalettePanel : VisualElement
|
||||||
|
{
|
||||||
|
private readonly StageEditorState state;
|
||||||
|
private readonly List<StagePaletteEntry> entries;
|
||||||
|
|
||||||
|
public StagePalettePanel(StageEditorState state, List<StagePaletteEntry> entries)
|
||||||
|
{
|
||||||
|
this.state = state;
|
||||||
|
this.entries = entries;
|
||||||
|
|
||||||
|
style.flexDirection = FlexDirection.Column;
|
||||||
|
style.minHeight = 110;
|
||||||
|
style.maxHeight = 170;
|
||||||
|
style.paddingLeft = 8;
|
||||||
|
style.paddingRight = 8;
|
||||||
|
style.paddingTop = 6;
|
||||||
|
style.paddingBottom = 6;
|
||||||
|
style.borderTopWidth = 1;
|
||||||
|
style.borderTopColor = new Color(1f, 1f, 1f, 0.12f);
|
||||||
|
|
||||||
|
state.Changed += Rebuild;
|
||||||
|
Rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Rebuild()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
|
||||||
|
var row = new VisualElement
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
flexDirection = FlexDirection.Row,
|
||||||
|
alignItems = Align.Center
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Add(row);
|
||||||
|
|
||||||
|
var title = new Label("Palette")
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
unityFontStyleAndWeight = FontStyle.Bold,
|
||||||
|
marginRight = 12
|
||||||
|
}
|
||||||
|
};
|
||||||
|
row.Add(title);
|
||||||
|
|
||||||
|
AddLayerButton(row, StageLogicalLayer.Structural);
|
||||||
|
AddLayerButton(row, StageLogicalLayer.Design);
|
||||||
|
AddLayerButton(row, StageLogicalLayer.Camera);
|
||||||
|
AddLayerButton(row, StageLogicalLayer.Markers);
|
||||||
|
|
||||||
|
var hint = new Label("LMB selects objects. Empty space paints/places selected palette item. RMB/MMB pans.")
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
opacity = 0.65f,
|
||||||
|
marginLeft = 12
|
||||||
|
}
|
||||||
|
};
|
||||||
|
row.Add(hint);
|
||||||
|
|
||||||
|
var scroll = new ScrollView(ScrollViewMode.Horizontal)
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
flexGrow = 1f,
|
||||||
|
marginTop = 8
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Add(scroll);
|
||||||
|
|
||||||
|
var itemRow = new VisualElement
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
flexDirection = FlexDirection.Row
|
||||||
|
}
|
||||||
|
};
|
||||||
|
scroll.Add(itemRow);
|
||||||
|
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
if (entry.layer != state.activeLayer)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var button = new Button(delegate { state.SetPaletteEntry(entry); })
|
||||||
|
{
|
||||||
|
text = entry.displayName,
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
minWidth = 120,
|
||||||
|
height = 48,
|
||||||
|
marginRight = 6,
|
||||||
|
unityTextAlign = TextAnchor.MiddleCenter
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (state.activePaletteEntry == entry)
|
||||||
|
{
|
||||||
|
button.style.borderBottomWidth = 3;
|
||||||
|
button.style.borderBottomColor = StageEditorColors.SelectionStroke;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemRow.Add(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddLayerButton(VisualElement row, StageLogicalLayer layer)
|
||||||
|
{
|
||||||
|
|
||||||
|
var button = new Button(delegate
|
||||||
|
{
|
||||||
|
state.SetActiveLayer(layer);
|
||||||
|
|
||||||
|
if (state.activePaletteEntry == null || state.activePaletteEntry.layer != layer)
|
||||||
|
{
|
||||||
|
var first = FindFirstEntryForLayer(layer);
|
||||||
|
if (first != null)
|
||||||
|
{
|
||||||
|
state.SetPaletteEntry(first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
text = layer.ToString(),
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
marginRight = 4
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (state.activeLayer == layer)
|
||||||
|
{
|
||||||
|
button.style.borderBottomWidth = 3;
|
||||||
|
button.style.borderBottomColor = StageEditorColors.SelectionStroke;
|
||||||
|
}
|
||||||
|
row.Add(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
private StagePaletteEntry FindFirstEntryForLayer(StageLogicalLayer layer)
|
||||||
|
{
|
||||||
|
foreach (var stagePaletteEntry in entries)
|
||||||
|
{
|
||||||
|
if (stagePaletteEntry.layer == layer)
|
||||||
|
{
|
||||||
|
return stagePaletteEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e9ffd5aa3cf34c84be81a3c01679fc3e
|
||||||
130
Assets/Scripts/Editor/GardensStageEditor/StageValidator.cs
Normal file
130
Assets/Scripts/Editor/GardensStageEditor/StageValidator.cs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Editor.GardensStageEditor
|
||||||
|
{
|
||||||
|
public enum StageValidationSeverity
|
||||||
|
{
|
||||||
|
Info,
|
||||||
|
Warning,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct StageValidationMessage
|
||||||
|
{
|
||||||
|
public readonly StageValidationSeverity severity;
|
||||||
|
public readonly string message;
|
||||||
|
|
||||||
|
public StageValidationMessage(StageValidationSeverity severity, string message)
|
||||||
|
{
|
||||||
|
this.severity = severity;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return severity + ": " + message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StageValidator
|
||||||
|
{
|
||||||
|
public static List<StageValidationMessage> Validate(StageDefinition stage)
|
||||||
|
{
|
||||||
|
var messages = new List<StageValidationMessage>();
|
||||||
|
|
||||||
|
if (stage is null)
|
||||||
|
{
|
||||||
|
messages.Add(new StageValidationMessage(StageValidationSeverity.Error, "No StageDefinition selected."));
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
stage.EnsureDefaults();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(stage.stageId))
|
||||||
|
{
|
||||||
|
messages.Add(new StageValidationMessage(StageValidationSeverity.Warning, "Stage id is empty."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stage.floors.Count == 0)
|
||||||
|
{
|
||||||
|
messages.Add(new StageValidationMessage(StageValidationSeverity.Error, "Stage has no floors."));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < stage.floors.Count; i++)
|
||||||
|
{
|
||||||
|
StageFloorDefinition floor = stage.floors[i];
|
||||||
|
if (floor is null)
|
||||||
|
{
|
||||||
|
messages.Add(new StageValidationMessage(StageValidationSeverity.Error, "Floor at index " + i + " is null."));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(floor.floorId))
|
||||||
|
{
|
||||||
|
messages.Add(new StageValidationMessage(StageValidationSeverity.Error, "Floor at index " + i + " has no floor id."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasSpawn = false;
|
||||||
|
var ids = new HashSet<string>();
|
||||||
|
for (var i = 0; i < stage.objects.Count; i++)
|
||||||
|
{
|
||||||
|
var obj = stage.objects[i];
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
messages.Add(new StageValidationMessage(StageValidationSeverity.Warning, "Object at index " + i + " is null."));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(obj.id))
|
||||||
|
{
|
||||||
|
messages.Add(new StageValidationMessage(StageValidationSeverity.Warning, "Object at index " + i + " has no id."));
|
||||||
|
}
|
||||||
|
else if (!ids.Add(obj.id))
|
||||||
|
{
|
||||||
|
messages.Add(new StageValidationMessage(StageValidationSeverity.Error, "Duplicate object id: " + obj.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
var floor = stage.GetFloor(obj.floorId);
|
||||||
|
if (floor is null)
|
||||||
|
{
|
||||||
|
messages.Add(new StageValidationMessage(StageValidationSeverity.Error, "Object " + obj.id + " references missing floor: " + obj.floorId));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.width <= 0 || obj.height <= 0)
|
||||||
|
{
|
||||||
|
messages.Add(new StageValidationMessage(StageValidationSeverity.Error, "Object " + obj.id + " has invalid size."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.kind == StageObjectKind.Spawn)
|
||||||
|
{
|
||||||
|
hasSpawn = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasSpawn)
|
||||||
|
{
|
||||||
|
messages.Add(new StageValidationMessage(StageValidationSeverity.Warning, "Stage has no player spawn marker."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messages.Count == 0)
|
||||||
|
{
|
||||||
|
messages.Add(new StageValidationMessage(StageValidationSeverity.Info, "No validation issues found."));
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Format(List<StageValidationMessage> messages)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
foreach (var stageValidationMessage in messages)
|
||||||
|
{
|
||||||
|
sb.AppendLine(stageValidationMessage.ToString());
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a32d1b16f27f4bfd90425ffa3fde2c89
|
||||||
@ -9,7 +9,7 @@
|
|||||||
"com.unity.2d.tilemap": "1.0.0",
|
"com.unity.2d.tilemap": "1.0.0",
|
||||||
"com.unity.2d.tilemap.extras": "6.0.2",
|
"com.unity.2d.tilemap.extras": "6.0.2",
|
||||||
"com.unity.2d.tooling": "1.0.3",
|
"com.unity.2d.tooling": "1.0.3",
|
||||||
"com.unity.cinemachine": "3.1.6",
|
"com.unity.cinemachine": "3.1.7",
|
||||||
"com.unity.collab-proxy": "2.12.4",
|
"com.unity.collab-proxy": "2.12.4",
|
||||||
"com.unity.ide.rider": "3.0.40",
|
"com.unity.ide.rider": "3.0.40",
|
||||||
"com.unity.ide.visualstudio": "2.0.27",
|
"com.unity.ide.visualstudio": "2.0.27",
|
||||||
|
|||||||
@ -117,7 +117,7 @@
|
|||||||
"url": "https://packages.unity.com"
|
"url": "https://packages.unity.com"
|
||||||
},
|
},
|
||||||
"com.unity.cinemachine": {
|
"com.unity.cinemachine": {
|
||||||
"version": "3.1.6",
|
"version": "3.1.7",
|
||||||
"depth": 0,
|
"depth": 0,
|
||||||
"source": "registry",
|
"source": "registry",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user