Compare commits

...

3 Commits

Author SHA1 Message Date
Nilton Constantino
2a23d4cf9c
add stair and ground movement profile 2026-06-17 09:59:15 +01:00
Nilton Constantino
e9d7d39f2b
some effort on the editor 2026-06-17 08:26:48 +01:00
Nilton Constantino
1a4b2443ff
some adjustments on stage editor 2026-06-16 10:18:27 +01:00
59 changed files with 19246 additions and 2349 deletions

View File

@ -0,0 +1,18 @@
%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: f70bf8524ee54f76ba35bd5c28995d83, type: 3}
m_Name: Prefab Table
m_EditorClassIdentifier: Assembly-CSharp-Editor::Editor.GardensStageEditor.PrefabTable
groundSlabPrefab: {fileID: 4088177062756800262, guid: 761db1bde191e401fa6ec87acd053bbf, type: 3}
groundRampFrontPrefab: {fileID: 116909126457648545, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
groundRampLeftPrefab: {fileID: 1762350532007512996, guid: 378324f09bdf84db99e602efb4b23fa3, type: 3}
groundRampRightPrefab: {fileID: 778490509801794073, guid: d5e0f7c6e96fe4425a061863751bc639, type: 3}

View File

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

View File

@ -5,18 +5,34 @@ using UnityEngine;
namespace __Prototype.Scripts
{
public class TestGroundMorion : GroundProfileMotion
public class TestGroundMorion : GroundProfileMotionBehaviour
{
[GroupConfigurations, SerializeField] private Vector3 localDirection = Vector3.right;
[GroupConfigurations, SerializeField] private float speed = 2f;
[GroupConfigurations, SerializeField] private float maxDistance = 4f;
[GroupConfigurations, SerializeField] private bool pingPong = true;
[GroupConfigurations, SerializeField] private bool moveOnStart;
public override Vector3 Velocity { get; protected set; }
public override Vector3 DeltaPosition { get; protected set; }
public override bool IsMoving { get; protected set; }
public override bool IsStatic { get; protected set; } = false;
public Vector3 Velocity
{
get => GroundProfileMotion.Velocity;
private set => GroundProfileMotion.Velocity = value;
}
public Vector3 DeltaPosition
{
get => GroundProfileMotion.DeltaPosition;
private set => GroundProfileMotion.DeltaPosition = value;
}
public bool IsMoving
{
get => GroundProfileMotion.IsMoving;
private set => GroundProfileMotion.IsMoving = value;
}
public bool IsStatic
{
get => GroundProfileMotion.IsStatic;
private set => GroundProfileMotion.IsStatic = value;
}
private Vector3 _startPosition;
private Vector3 _lastPosition;
@ -69,7 +85,7 @@ namespace __Prototype.Scripts
}
else
{
next = _startPosition + worldDirection * Mathf.Sign(distanceFromStart) * maxDistance;
next = _startPosition + worldDirection * (Mathf.Sign(distanceFromStart) * maxDistance);
Stop();
}
}

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: []

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,14 @@ PrefabInstance:
serializedVersion: 3
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 101776470001583037, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: movementProfiles.Array.size
value: 1
objectReference: {fileID: 0}
- target: {fileID: 101776470001583037, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: 'movementProfiles.Array.data[0]'
value:
objectReference: {fileID: 5801231790879681971}
- target: {fileID: 221717111884494654, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_Mesh
value:
@ -90,7 +98,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 6524376507900539265, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_Name
value: Ground_Ramp_Prototype
value: Ground_Ramp_Front_Prototype
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
@ -99,6 +107,9 @@ PrefabInstance:
- targetCorrespondingSourceObject: {fileID: 6524376507900539265, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
insertIndex: -1
addedObject: {fileID: 116909126457648545}
- targetCorrespondingSourceObject: {fileID: 6524376507900539265, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
insertIndex: -1
addedObject: {fileID: 5801231790879681971}
m_SourcePrefab: {fileID: 100100000, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
--- !u!114 &2719093777726527478 stripped
MonoBehaviour:
@ -134,3 +145,23 @@ MonoBehaviour:
sizeZ: 1
x: 1
z: 1
--- !u!114 &5801231790879681971
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9140983697215042600}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bdd4ad30e2d149b58a2f84fa9a5b5fbc, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Intrepid.Core.Grounding.GroundProfileMovementBehaviour
id: 9d7dfeaecb7e4204ba7dd29dede5075d
groundProfileMovement:
speedMultiplier: 1
accelerationMultiplier: 1
decelerationMultiplier: 1
uphillSpeedMultiplier: 0.75
downhillSpeedMultiplier: 1
sidewaysSpeedMultiplier: 0.9

View File

@ -0,0 +1,63 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1001 &1867437227332939269
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 2829319849928503959, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_Mesh
value:
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalRotation.w
value: 0.7071068
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalRotation.y
value: -0.7071068
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: -90
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 9140983697215042600, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_Name
value: Ground_Ramp_Left_Prototype
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 378324f09bdf84db99e602efb4b23fa3
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,63 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1001 &815882455729688504
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 2829319849928503959, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_Mesh
value:
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalRotation.w
value: 0.7071068
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalRotation.y
value: 0.7071068
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 90
objectReference: {fileID: 0}
- target: {fileID: 4113289733881740216, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 9140983697215042600, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}
propertyPath: m_Name
value: Ground_Ramp_Right_Prototype
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 321f39f98806c4db7a3deff6d508bde1, type: 3}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: d5e0f7c6e96fe4425a061863751bc639
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,171 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1001 &3530962555909840662
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 101776470001583037, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: movementProfiles.Array.size
value: 1
objectReference: {fileID: 0}
- target: {fileID: 101776470001583037, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: 'movementProfiles.Array.data[0]'
value:
objectReference: {fileID: 1964621128587179894}
- target: {fileID: 221717111884494654, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_Mesh
value:
objectReference: {fileID: 0}
- target: {fileID: 2109134711161568785, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2109134711161568785, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2109134711161568785, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2109134711161568785, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2109134711161568785, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2109134711161568785, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2109134711161568785, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2109134711161568785, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2109134711161568785, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2109134711161568785, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3352223124200162652, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3352223124200162652, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalPosition.y
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 3352223124200162652, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4990331727855981778, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalRotation.w
value: 0.9238795
objectReference: {fileID: 0}
- target: {fileID: 4990331727855981778, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalRotation.x
value: -0.38268346
objectReference: {fileID: 0}
- target: {fileID: 4990331727855981778, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4990331727855981778, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 6392326082573612157, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_Size.y
value: 0.05
objectReference: {fileID: 0}
- target: {fileID: 6392326082573612157, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_Size.z
value: 1.4142
objectReference: {fileID: 0}
- target: {fileID: 6392326082573612157, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_Center.y
value: -0.025
objectReference: {fileID: 0}
- target: {fileID: 6524376507900539265, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
propertyPath: m_Name
value: Ground_Stair_Front_Prototype
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents:
- targetCorrespondingSourceObject: {fileID: 6524376507900539265, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
insertIndex: -1
addedObject: {fileID: 2575929596551944822}
- targetCorrespondingSourceObject: {fileID: 6524376507900539265, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
insertIndex: -1
addedObject: {fileID: 1964621128587179894}
m_SourcePrefab: {fileID: 100100000, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
--- !u!114 &3525235759526726985 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 138583005792719455, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
m_PrefabInstance: {fileID: 3530962555909840662}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f20bd3bb11c64d00a0429af5fe4bd33f, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Intrepid.Gameplay.Prototyping.PrototypeModel
--- !u!1 &7749360569717010071 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: 6524376507900539265, guid: 84ef2ec4df393420d973aa876a8778b6, type: 3}
m_PrefabInstance: {fileID: 3530962555909840662}
m_PrefabAsset: {fileID: 0}
--- !u!114 &2575929596551944822
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7749360569717010071}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3a3b1270c87c4456bdc61721ca19132f, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Intrepid.Gameplay.Prototyping.GroundStairModelPrototype
displayDebug: 1
prototypeModel: {fileID: 3525235759526726985}
sizeX: 1
sizeZ: 1
x: 1
z: 1
--- !u!114 &1964621128587179894
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7749360569717010071}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bdd4ad30e2d149b58a2f84fa9a5b5fbc, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Intrepid.Core.Grounding.GroundProfileMovementBehaviour
id: afbca706aa91413387e401bb77925f70
groundProfileMovement:
speedMultiplier: 1
accelerationMultiplier: 1
decelerationMultiplier: 1
uphillSpeedMultiplier: 0.5
downhillSpeedMultiplier: 1
sidewaysSpeedMultiplier: 0.9

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: da66eef8ff1ce4c3eaf46c09ad26e70b
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,63 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1001 &3382806052188099058
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalRotation.w
value: 0.7071068
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalRotation.y
value: -0.7071068
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: -90
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3608453355090094120, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_Mesh
value:
objectReference: {fileID: 0}
- target: {fileID: 7749360569717010071, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_Name
value: Ground_Stair_Left_Prototype
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c11973af2c21642658a917c1253a53f4
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,63 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1001 &8988140421347768202
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalRotation.w
value: 0.7071068
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalRotation.y
value: 0.7071068
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 90
objectReference: {fileID: 0}
- target: {fileID: 3190054200416215303, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3608453355090094120, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_Mesh
value:
objectReference: {fileID: 0}
- target: {fileID: 7749360569717010071, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}
propertyPath: m_Name
value: Ground_Stair_Right_Prototype
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: da66eef8ff1ce4c3eaf46c09ad26e70b, type: 3}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3e0d56cfe99b4417a8f7c924adc9581e
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -10,23 +10,31 @@ PrefabInstance:
m_Modifications:
- target: {fileID: 369205274101430871, guid: 64620cfd283d648b5b4d9c9a4fac7751, type: 3}
propertyPath: m_LocalPosition.y
value: 0.5
value: 1.5
objectReference: {fileID: 0}
- target: {fileID: 3020532133659471693, guid: 64620cfd283d648b5b4d9c9a4fac7751, type: 3}
propertyPath: m_Size.x
value: 2
value: 3
objectReference: {fileID: 0}
- target: {fileID: 3020532133659471693, guid: 64620cfd283d648b5b4d9c9a4fac7751, type: 3}
propertyPath: m_Size.y
value: 1
value: 3
objectReference: {fileID: 0}
- target: {fileID: 3020532133659471693, guid: 64620cfd283d648b5b4d9c9a4fac7751, type: 3}
propertyPath: m_Size.z
value: 2
value: 3.5
objectReference: {fileID: 0}
- target: {fileID: 3020532133659471693, guid: 64620cfd283d648b5b4d9c9a4fac7751, type: 3}
propertyPath: m_Center.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3020532133659471693, guid: 64620cfd283d648b5b4d9c9a4fac7751, type: 3}
propertyPath: m_Center.z
value: 0.25
objectReference: {fileID: 0}
- target: {fileID: 5094209182163755379, guid: 64620cfd283d648b5b4d9c9a4fac7751, type: 3}
propertyPath: m_Enabled
value: 0
value: 1
objectReference: {fileID: 0}
- target: {fileID: 6166895172714925370, guid: 64620cfd283d648b5b4d9c9a4fac7751, type: 3}
propertyPath: m_Mesh
@ -74,7 +82,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 8449749668273782584, guid: 64620cfd283d648b5b4d9c9a4fac7751, type: 3}
propertyPath: m_Name
value: Obstacle_Cube_Prototype
value: Obstacle_Wall_Prototype
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
@ -101,16 +109,18 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: ccf30362a82f4d67958b61569d4cf1b4, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Intrepid.Gameplay.Prototyping.ContactCubeModelPrototype
displayDebug: 0
displayDebug: 1
prototypeModel: {fileID: 8402728050198132523}
sizeX: 1
sizeY: 1
sizeY: 3
sizeZ: 1
offset: 1
offset_x: {x: 1, y: 1}
offset_y: {x: 1.5, y: 1}
x: 1
y: 1
y: 3
z: 1
contact_offset: 1
contact_offset_x: {x: 1, y: 1}
contact_offset_y: {x: 1.5, y: 1}
--- !u!114 &8402728050198132523 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 3563105585321411375, guid: 64620cfd283d648b5b4d9c9a4fac7751, type: 3}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e6846a45a2ff48599d8e2a83e5a5d0e3
timeCreated: 1781601092

View File

@ -0,0 +1,75 @@
using System;
using Intrepid.Diagnostics;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Editor.GardensStageEditor.Baker
{
namespace Editor.GardensStageEditor
{
public static class StageBaker
{
private const string GeneratedRootName = "Generated";
public static GameObject Bake(StageDefinition stage, PrefabTable prefabTable)
{
if (stage.IsNull())
{
DLogger.LogWarn("Cannot bake: no StageDefinition selected.");
return null;
}
stage.EnsureDefaults();
var scene = SceneManager.GetActiveScene();
var generatedRoot = EnsureGeneratedRoot(scene);
Undo.RegisterCreatedObjectUndo(generatedRoot, "Create Generated Root");
var stageRoot = EnsureStageRoot(stage, generatedRoot);
StageBuilder.BuildStage(new StageBuilderRequest
{
Stage = stage,
Root = stageRoot,
PrefabTable = prefabTable
});
Undo.RegisterCreatedObjectUndo(stageRoot, "Create Stage Root");
EditorSceneManager.MarkSceneDirty(scene);
Selection.activeGameObject = stageRoot;
DLogger.LogDebug($"Bake started for stage '{stage.Id}'. State root is ready.");
return stageRoot;
}
private static GameObject EnsureStageRoot(StageDefinition stage, GameObject generatedRoot)
{
foreach (Transform goTransform in generatedRoot.transform)
{
if (goTransform.gameObject.name == stage.Id)
{
goTransform.gameObject.name = $"{goTransform.gameObject.name}_bkp";
goTransform.gameObject.SetActive(false);
}
}
var go = new GameObject(stage.Id);
go.transform.SetParent(generatedRoot.transform);
return go;
}
private static GameObject EnsureGeneratedRoot(Scene scene)
{
foreach (var gameObject in scene.GetRootGameObjects())
{
if (gameObject.name == GeneratedRootName)
{
return gameObject;
}
}
return new GameObject(GeneratedRootName);
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3e84387a0ce04c489edeca68df66e1c4
timeCreated: 1781549085

View File

@ -0,0 +1,135 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Intrepid.Diagnostics;
using Intrepid.Gameplay.Prototyping;
using UnityEditor;
using UnityEngine;
namespace Editor.GardensStageEditor.Baker
{
public struct StageBuilderRequest
{
public StageDefinition Stage { get; set; }
public GameObject Root { get; set; }
public PrefabTable PrefabTable { get; set; }
}
public static class StageBuilder
{
public static void BuildStage(StageBuilderRequest request)
{
foreach (var stageFloorDefinition in request.Stage.floors)
{
var cellsGS = new HashSet<Vector2Int>();
var cellsGRF = new HashSet<Vector2Int>();
var cellsGRR = new HashSet<Vector2Int>();
var cellsGRL = new HashSet<Vector2Int>();
foreach (var logicalCell in stageFloorDefinition.Cells)
{
switch (logicalCell.structural)
{
case StructuralCellKind.Empty:
break;
case StructuralCellKind.Ground_Slab:
cellsGS.Add(new Vector2Int(logicalCell.x, logicalCell.y));
break;
case StructuralCellKind.Ground_Ramp_Front:
cellsGRF.Add(new Vector2Int(logicalCell.x, logicalCell.y));
break;
case StructuralCellKind.Ground_Ramp_Left:
cellsGRL.Add(new Vector2Int(logicalCell.x, logicalCell.y));
break;
case StructuralCellKind.Ground_Ramp_Right:
cellsGRR.Add(new Vector2Int(logicalCell.x, logicalCell.y));
break;
case StructuralCellKind.Wall:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
var floorRootTransform = new GameObject(stageFloorDefinition.floorId)
{
transform =
{
parent = request.Root.transform,
position = new Vector3(0, stageFloorDefinition.FloorHeight, 0)
}
}.transform;
InstantiateGround(
request.PrefabTable.GroundSlabPrefab,
floorRootTransform,
cellsGS,
StraightAddressSize
);
InstantiateGround(
request.PrefabTable.GroundRampFrontPrefab,
floorRootTransform,
cellsGRF,
StraightAddressSize
);
InstantiateGround(
request.PrefabTable.GroundRampLeftPrefab,
floorRootTransform,
cellsGRL,
InverseAddressSize
);
InstantiateGround(
request.PrefabTable.GroundRampRightPrefab,
floorRootTransform,
cellsGRR,
InverseAddressSize
);
}
}
private static Vector2Int StraightAddressSize(RectInt rect)
{
return new Vector2Int(rect.width, rect.height);
}
private static Vector2Int InverseAddressSize(RectInt rect)
{
return new Vector2Int(rect.height, rect.width);
}
private static void InstantiateGround<G>(
G prefab, Transform parent,
HashSet<Vector2Int> set,
Func<RectInt, Vector2Int> addressSize
) where G: GroundModelPrototype
{
var releaseName = new string(typeof(G).Name.Where(char.IsUpper).ToArray());
if (prefab.IsNull())
{
DLogger.LogError($"Prefab is not configured in PrefabTable: {releaseName}");
return;
}
foreach (var rect in StageBuilderHelpers.BuildRectanglesFromCellsBest(set))
{
if (PrefabUtility.InstantiatePrefab(prefab, parent) is not G g)
{
DLogger.LogError($"Failed to instantiate prefab: {prefab.name}");
continue;
}
StageBuilderHelpers.ResetTransform(g.transform);
g.name = $"{releaseName} ({rect.x}, {rect.y}) {rect.width}x{rect.height}";
g.transform.localPosition = new Vector3(
rect.x + (rect.width - 1) * 0.5f,
0,
-(rect.y + (rect.height - 1) * 0.5f)
);
g.SetSize(addressSize(rect));
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 571d9dd73a024147b211247e52b60a2a
timeCreated: 1781593722

View File

@ -0,0 +1,153 @@
using System.Collections.Generic;
using UnityEngine;
namespace Editor.GardensStageEditor.Baker
{
public static class StageBuilderHelpers
{
public static void ResetTransform(Transform transform)
{
// transform.rotation = Quaternion.identity;
transform.localScale = Vector3.one;
}
public static List<RectInt> BuildRectanglesFromCellsBest(HashSet<Vector2Int> cells)
{
var result = new List<RectInt>();
var remaining = new HashSet<Vector2Int>(cells);
while (remaining.Count > 0)
{
var rect = FindLargestRectGlobal(remaining);
result.Add(rect);
RemoveRectCells(rect, remaining);
}
return result;
}
public static List<RectInt> BuildRectanglesFromCells(HashSet<Vector2Int> cells)
{
var result = new List<RectInt>();
var remaining = new HashSet<Vector2Int>(cells);
while (remaining.Count > 0)
{
var start = FindTopLeftMostCell(remaining);
var rect = FindBestRectFromStart(start, remaining);
result.Add(rect);
RemoveRectCells(rect, remaining);
}
return result;
}
private static RectInt FindLargestRectGlobal(HashSet<Vector2Int> remaining)
{
var best = new RectInt();
var bestArea = 0;
foreach (var cell in remaining)
{
var rect = FindBestRectFromStart(cell, remaining);
var area = rect.width * rect.height;
if (area > bestArea)
{
best = rect;
bestArea = area;
}
}
return best;
}
private static RectInt FindBestRectFromStart(Vector2Int start, HashSet<Vector2Int> remaining)
{
var bestWidth = 1;
var bestHeight = 1;
var bestArea = 1;
var currentMinWidth = int.MaxValue;
var height = 0;
while (true)
{
var rowWidth = FindRowWidth(start.x, start.y + height, remaining);
if (rowWidth == 0)
{
break;
}
currentMinWidth = Mathf.Min(currentMinWidth, rowWidth);
height++;
var area = currentMinWidth * height;
if (area > bestArea)
{
bestArea = area;
bestWidth = currentMinWidth;
bestHeight = height;
}
}
return new RectInt(start.x, start.y, bestWidth, bestHeight);
}
private static int FindRowWidth(int x, int y, HashSet<Vector2Int> remaining)
{
var width = 0;
while (remaining.Contains(new Vector2Int(x + width, y)))
{
width++;
}
return width;
}
private static Vector2Int FindTopLeftMostCell(HashSet<Vector2Int> cells)
{
var found = false;
var best = Vector2Int.zero;
foreach (var cell in cells)
{
if (!found)
{
best = cell;
found = true;
continue;
}
if (cell.y < best.y)
{
best = cell;
continue;
}
if (cell.y == best.y && cell.x < best.x)
{
best = cell;
}
}
return best;
}
private static void RemoveRectCells(RectInt rect, HashSet<Vector2Int> remaining)
{
for (var y = rect.yMin; y < rect.yMax; y++)
{
for (var x = rect.xMin; x < rect.xMax; x++)
{
remaining.Remove(new Vector2Int(x, y));
}
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b3cf864dedfb4a929e087eba87cfd7f5
timeCreated: 1781598891

View File

@ -0,0 +1,20 @@
using Intrepid.Gameplay.Prototyping;
using Intrepid.Utilities;
using UnityEngine;
namespace Editor.GardensStageEditor
{
[CreateAssetMenu(fileName = "New Prefab Table", menuName = "GON/Editor/Prefab Table")]
public class PrefabTable : ScriptableObject
{
[GroupInjections, SerializeField] private GroundSlabModelPrototype groundSlabPrefab;
[GroupInjections, SerializeField] private GroundRampModelPrototype groundRampFrontPrefab;
[GroupInjections, SerializeField] private GroundRampModelPrototype groundRampLeftPrefab;
[GroupInjections, SerializeField] private GroundRampModelPrototype groundRampRightPrefab;
public GroundSlabModelPrototype GroundSlabPrefab => groundSlabPrefab;
public GroundRampModelPrototype GroundRampFrontPrefab => groundRampFrontPrefab;
public GroundRampModelPrototype GroundRampLeftPrefab => groundRampLeftPrefab;
public GroundRampModelPrototype GroundRampRightPrefab => groundRampRightPrefab;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f70bf8524ee54f76ba35bd5c28995d83
timeCreated: 1781596138

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

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using Intrepid.Utilities;
using UnityEditor.ShaderGraph.Internal;
using UnityEngine;
namespace Editor.GardensStageEditor
@ -15,28 +17,35 @@ namespace Editor.GardensStageEditor
public enum StructuralCellKind
{
Empty = 0,
Ground = 1,
Wall = 2,
Hole = 3
Ground_Slab = 10,
Ground_Ramp_Front = 11,
Ground_Ramp_Left = 12,
Ground_Ramp_Right = 13,
Wall = 20,
}
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")]
public sealed class StageDefinition : ScriptableObject
public sealed class StageDefinition : SOIdentification
{
public string stageId = "stage_new";
public float cellSize = 1f;
public float floorHeight = 6f;
public int activeFloorIndex;
@ -95,7 +104,7 @@ namespace Editor.GardensStageEditor
public bool ContainsFloor(string floorId)
{
return GetFloor(floorId) != null;
return GetFloor(floorId) is not null;
}
public string CreateUniqueObjectId(StageObjectKind kind)
@ -131,7 +140,8 @@ namespace Editor.GardensStageEditor
public sealed class StageFloorDefinition
{
public string floorId = "F0";
public float FloorHeight;
[SerializeField]
private List<LogicalCell> cells = new();
@ -152,7 +162,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 +184,7 @@ namespace Editor.GardensStageEditor
return;
}
LogicalCell cell = new LogicalCell
var cell = new LogicalCell
{
x = x,
y = y,
@ -270,6 +280,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 +292,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,3 +1,4 @@
using Intrepid.Diagnostics;
using UnityEngine;
namespace Editor.GardensStageEditor
@ -12,19 +13,28 @@ 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_Slab => FromHtml("55E0FF"),
StructuralCellKind.Ground_Ramp_Front => FromHtml("55E0FF"),
StructuralCellKind.Ground_Ramp_Left => FromHtml("55E0FF"),
StructuralCellKind.Ground_Ramp_Right => FromHtml("55E0FF"),
StructuralCellKind.Wall => FromHtml("668B93"),
_ => new Color(0f, 0f, 0f, 0f)
};
}
public static Color Layer(StageLogicalLayer layer)

View File

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using Intrepid.Gameplay.Prototyping;
using System.IO;
using Editor.GardensStageEditor.Baker.Editor.GardensStageEditor;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
@ -9,6 +11,7 @@ namespace Editor.GardensStageEditor
{
public sealed class StageEditorWindow : EditorWindow
{
private PrefabTable prefabTable;
private StageDefinition stage;
private StageEditorState state;
private List<StagePaletteEntry> palette;
@ -17,6 +20,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()
@ -91,7 +95,7 @@ namespace Editor.GardensStageEditor
RebuildTopBar();
}
public void MutateStage(string undoName, System.Action mutation)
public void MutateStage(string undoName, Action mutation)
{
if (stage is null || mutation is null)
{
@ -118,6 +122,21 @@ namespace Editor.GardensStageEditor
}
topBar.Clear();
var prefabTableField = new ObjectField("Prefab Table")
{
objectType = typeof(PrefabTable),
value = prefabTable,
style =
{
minWidth = 200
}
};
prefabTableField.RegisterValueChangedCallback(evt =>
{
SetPrefabTable(evt.newValue as PrefabTable);
});
topBar.Add(prefabTableField);
var stageField = new ObjectField("Stage")
{
@ -125,7 +144,7 @@ namespace Editor.GardensStageEditor
value = stage,
style =
{
minWidth = 300
minWidth = 200
}
};
stageField.RegisterValueChangedCallback(evt =>
@ -191,7 +210,7 @@ namespace Editor.GardensStageEditor
}
};
topBar.Add(validate);
var save = new Button(delegate
{
EditorUtility.SetDirty(stage);
@ -206,18 +225,31 @@ namespace Editor.GardensStageEditor
}
};
topBar.Add(save);
var bake = new Button(BakeStage)
{
text = "Bake",
style =
{
marginLeft = 4
}
};
topBar.Add(bake);
}
topBar.Add(AddSpacer());
var spacer = new VisualElement
mouseCellLabel = new Label("Mouse: —")
{
style =
{
flexGrow = 1f
opacity = 0.85f,
marginRight = 16f
}
};
topBar.Add(spacer);
topBar.Add(mouseCellLabel);
var hint = new Label("Infinite grid · LMB select/place/paint · RMB/MMB pan · Wheel zoom")
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 +259,30 @@ namespace Editor.GardensStageEditor
topBar.Add(hint);
}
private static VisualElement AddSpacer()
{
return new VisualElement
{
style =
{
flexGrow = 1f
}
};
}
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;
@ -237,12 +293,19 @@ namespace Editor.GardensStageEditor
RebuildTopBar();
}
private void SetPrefabTable(PrefabTable newPrefabTable)
{
prefabTable = newPrefabTable;
RebuildTopBar();
}
private void CreateNewStageAsset()
{
var path = EditorUtility.SaveFilePanelInProject(
"Create Stage Definition",
"StageDefinition",
"new-stage.stage-definition",
"asset",
"Choose where to create the new StageDefinition asset."
);
@ -253,7 +316,7 @@ namespace Editor.GardensStageEditor
}
var asset = CreateInstance<StageDefinition>();
asset.stageId = System.IO.Path.GetFileNameWithoutExtension(path).ToLowerInvariant();
asset.Id = Path.GetFileNameWithoutExtension(path).ToLowerInvariant();
asset.EnsureDefaults();
AssetDatabase.CreateAsset(asset, path);
@ -276,7 +339,8 @@ namespace Editor.GardensStageEditor
var index = stage.floors.Count;
var floor = new StageFloorDefinition
{
floorId = "F" + index
floorId = "F" + index,
FloorHeight = stage.floors.Count == 0 ? 0.0f : stage.floors[^1].FloorHeight
};
stage.floors.Add(floor);
@ -291,5 +355,32 @@ namespace Editor.GardensStageEditor
Debug.Log(report);
EditorUtility.DisplayDialog("Stage Validation", report, "OK");
}
private void BakeStage()
{
if (stage is null)
{
EditorUtility.DisplayDialog(
"Bake Stage",
"Select or create a StageDefinition before baking.",
"OK"
);
return;
}
if (prefabTable is null)
{
EditorUtility.DisplayDialog(
"Bake Stage",
"Select a PrefabTable before baking.",
"OK"
);
return;
}
StageBaker.Bake(stage, prefabTable);
}
}
}

View File

@ -77,14 +77,7 @@ namespace Editor.GardensStageEditor
}
};
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
@ -165,13 +158,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 +189,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 +279,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,3 +1,4 @@
using System;
using UnityEngine;
using UnityEngine.UIElements;
@ -7,6 +8,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 +26,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)
{
@ -29,6 +46,7 @@ namespace Editor.GardensStageEditor
focusable = true;
style.flexGrow = 1f;
style.overflow = Overflow.Hidden;
style.backgroundColor = StageEditorColors.Background;
generateVisualContent += OnGenerateVisualContent;
@ -36,6 +54,7 @@ namespace Editor.GardensStageEditor
RegisterCallback<MouseDownEvent>(OnMouseDown);
RegisterCallback<MouseMoveEvent>(OnMouseMove);
RegisterCallback<MouseUpEvent>(OnMouseUp);
RegisterCallback<MouseLeaveEvent>(OnMouseLeave);
RegisterCallback<WheelEvent>(OnWheel);
}
@ -58,6 +77,8 @@ namespace Editor.GardensStageEditor
return;
}
UpdateHoverCell(evt.localMousePosition);
var mouse = evt.localMousePosition;
var before = ScreenToGridFloat(mouse);
@ -74,6 +95,7 @@ namespace Editor.GardensStageEditor
private void OnMouseDown(MouseDownEvent evt)
{
Focus();
UpdateHoverCell(evt.localMousePosition);
if (stage is null)
{
@ -83,10 +105,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 +115,240 @@ 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;
case DragMode.None:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void OnMouseUp(MouseUpEvent evt)
{
if (isPanning)
switch (dragMode)
{
isPanning = false;
if (this.HasMouseCapture())
case DragMode.None:
return;
case DragMode.Paint when evt.button == 0:
{
this.ReleaseMouse();
var rect = GetDragRect();
ApplyPalette(rect);
break;
}
evt.StopPropagation();
return;
case DragMode.Pan:
case DragMode.MoveObject:
case DragMode.ResizeObject:
break;
default:
throw new ArgumentOutOfRangeException();
}
if (!isPainting || evt.button != 0)
{
return;
}
isPainting = false;
if (this.HasMouseCapture())
{
this.ReleaseMouse();
}
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;
@ -194,21 +362,12 @@ namespace Editor.GardensStageEditor
if (entry.paintsStructural)
{
owner.MutateStage("Paint " + entry.displayName, delegate
{
floor.FillRect(rect, entry.structuralKind);
});
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));
var objectRect = GetPaletteObjectRect(entry, rect);
owner.MutateStage("Place " + entry.displayName, delegate
{
@ -228,11 +387,40 @@ 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 +451,31 @@ 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;
@ -281,13 +494,74 @@ namespace Editor.GardensStageEditor
}
DrawInfiniteGrid(p);
DrawCells(p, floor);
DrawObjects(p, floor.floorId);
DrawLowerFloors(p);
DrawCells(p, floor, 1f);
DrawObjects(p, floor.floorId, 1f, true);
DrawOriginAxes(p);
DrawHoverCell(p);
DrawPreview(p);
}
private void DrawLowerFloors(Painter2D p)
{
if (stage is null || stage.floors is null)
{
return;
}
private void DrawCells(Painter2D p, StageFloorDefinition floor)
var activeIndex = Mathf.Clamp(stage.activeFloorIndex, 0, stage.floors.Count - 1);
for (var i = 0; i < activeIndex; i++)
{
var lowerFloor = stage.floors[i];
if (lowerFloor is null)
{
continue;
}
var alpha = GetLowerFloorAlpha(i, activeIndex);
DrawCells(p, lowerFloor, alpha);
DrawObjects(p, lowerFloor.floorId, alpha, false);
}
}
private float GetLowerFloorAlpha(int floorIndex, int activeFloorIndex)
{
var distance = activeFloorIndex - floorIndex;
// Floor imediatamente abaixo aparece mais forte.
// Floors mais distantes ficam mais apagados.
return Mathf.Clamp(0.38f - ((distance - 1) * 0.10f), 0.10f, 0.38f);
}
private void FillTriangle(Painter2D p, Vector2 a, Vector2 b, Vector2 c, Color color)
{
p.fillColor = color;
p.BeginPath();
p.MoveTo(a);
p.LineTo(b);
p.LineTo(c);
p.ClosePath();
p.Fill();
}
private void StrokeTriangle(Painter2D p, Vector2 a, Vector2 b, Vector2 c, Color color, float width)
{
p.strokeColor = color;
p.lineWidth = width;
p.BeginPath();
p.MoveTo(a);
p.LineTo(b);
p.LineTo(c);
p.ClosePath();
p.Stroke();
}
private void DrawCells(Painter2D p, StageFloorDefinition floor, float alphaMultiplier)
{
foreach (var cell in floor.Cells)
{
@ -297,12 +571,103 @@ namespace Editor.GardensStageEditor
continue;
}
color = color.WithAlpha(color.a * alphaMultiplier);
var rect = CellRect(cell.x, cell.y);
FillRect(p, rect, color);
DrawStructuralIcon(p, cell.structural, rect);
}
}
private void DrawObjects(Painter2D p, string floorId)
private void DrawStructuralIcon(Painter2D p, StructuralCellKind kind, Rect rect)
{
if (cellPixels < 18f)
{
return;
}
var pad = Mathf.Clamp(rect.width * 0.18f, 3f, 8f);
var inner = rect.Expanded(-pad);
var ink = Color.black.WithAlpha(0.55f);
var light = Color.white.WithAlpha(0.45f);
var line = Mathf.Clamp(cellPixels * 0.08f, 1.5f, 4f);
switch (kind)
{
case StructuralCellKind.Ground_Slab:
StrokeRect(p, inner, ink, line);
DrawLine(p, new Vector2(inner.xMin, inner.center.y), new Vector2(inner.xMax, inner.center.y), light,
line);
break;
case StructuralCellKind.Ground_Ramp_Front:
FillTriangle(
p,
new Vector2(inner.xMin, inner.yMax),
new Vector2(inner.xMax, inner.yMax),
new Vector2(inner.center.x, inner.yMin),
light
);
StrokeTriangle(
p,
new Vector2(inner.xMin, inner.yMax),
new Vector2(inner.xMax, inner.yMax),
new Vector2(inner.center.x, inner.yMin),
ink,
line
);
break;
case StructuralCellKind.Ground_Ramp_Left:
FillTriangle(
p,
new Vector2(inner.xMax, inner.yMin),
new Vector2(inner.xMin, inner.center.y),
new Vector2(inner.xMax, inner.yMax),
light
);
StrokeTriangle(
p,
new Vector2(inner.xMax, inner.yMin),
new Vector2(inner.xMin, inner.center.y),
new Vector2(inner.xMax, inner.yMax),
ink,
line
);
break;
case StructuralCellKind.Ground_Ramp_Right:
FillTriangle(
p,
new Vector2(inner.xMin, inner.yMin),
new Vector2(inner.xMax, inner.center.y),
new Vector2(inner.xMin, inner.yMax),
light
);
StrokeTriangle(
p,
new Vector2(inner.xMin, inner.yMin),
new Vector2(inner.xMax, inner.center.y),
new Vector2(inner.xMin, inner.yMax),
ink,
line
);
break;
case StructuralCellKind.Wall:
StrokeRect(p, inner, ink, line);
DrawLine(p, new Vector2(inner.xMin, inner.yMin), new Vector2(inner.xMax, inner.yMax), ink, line);
DrawLine(p, new Vector2(inner.xMax, inner.yMin), new Vector2(inner.xMin, inner.yMax), ink, line);
break;
case StructuralCellKind.Empty:
break;
default:
throw new ArgumentOutOfRangeException(nameof(kind), kind, null);
}
}
private void DrawObjects(Painter2D p, string floorId, float alphaMultiplier, bool allowSelection)
{
foreach (var obj in stage.objects)
{
@ -313,16 +678,90 @@ namespace Editor.GardensStageEditor
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);
color = color.WithAlpha(color.a * alphaMultiplier);
if (state.selectedObject == obj)
FillRect(p, rect, color);
StrokeRect(p, rect, color.WithAlpha(Mathf.Min(1f, color.a + 0.25f * alphaMultiplier)), 2f);
DrawObjectIcon(p, obj.kind, rect);
if (allowSelection && state.selectedObject == obj)
{
StrokeRect(p, rect.Expanded(2f), StageEditorColors.SelectionStroke, 3f);
if (obj.IsResizable)
{
DrawResizeHandle(p, obj);
}
}
}
}
private void DrawObjectIcon(Painter2D p, StageObjectKind kind, Rect rect)
{
if (cellPixels < 18f)
{
return;
}
var pad = Mathf.Clamp(Mathf.Min(rect.width, rect.height) * 0.2f, 4f, 12f);
var inner = rect.Expanded(-pad);
var ink = Color.black.WithAlpha(0.65f);
var light = Color.white.WithAlpha(0.7f);
var line = Mathf.Clamp(cellPixels * 0.08f, 1.5f, 4f);
switch (kind)
{
case StageObjectKind.Trap_FallingFloor:
DrawLine(p, new Vector2(inner.xMin, inner.center.y), new Vector2(inner.center.x, inner.yMin), ink,
line);
DrawLine(p, new Vector2(inner.center.x, inner.yMin), new Vector2(inner.xMax, inner.yMax), ink,
line);
break;
case StageObjectKind.DoorHorizontal:
DrawLine(p, new Vector2(inner.xMin, inner.center.y), new Vector2(inner.xMax, inner.center.y), light,
line);
DrawLine(p, new Vector2(inner.xMin, inner.yMin), new Vector2(inner.xMin, inner.yMax), ink, line);
DrawLine(p, new Vector2(inner.xMax, inner.yMin), new Vector2(inner.xMax, inner.yMax), ink, line);
break;
case StageObjectKind.DoorVertical:
DrawLine(p, new Vector2(inner.center.x, inner.yMin), new Vector2(inner.center.x, inner.yMax), light,
line);
DrawLine(p, new Vector2(inner.xMin, inner.yMin), new Vector2(inner.xMax, inner.yMin), ink, line);
DrawLine(p, new Vector2(inner.xMin, inner.yMax), new Vector2(inner.xMax, inner.yMax), ink, line);
break;
case StageObjectKind.CameraBoundary:
DrawCornerBrackets(p, inner, ink, line);
break;
}
}
private void DrawCornerBrackets(Painter2D p, Rect rect, Color color, float width)
{
var l = Mathf.Min(rect.width, rect.height) * 0.25f;
DrawLine(p, new Vector2(rect.xMin, rect.yMin), new Vector2(rect.xMin + l, rect.yMin), color, width);
DrawLine(p, new Vector2(rect.xMin, rect.yMin), new Vector2(rect.xMin, rect.yMin + l), color, width);
DrawLine(p, new Vector2(rect.xMax, rect.yMin), new Vector2(rect.xMax - l, rect.yMin), color, width);
DrawLine(p, new Vector2(rect.xMax, rect.yMin), new Vector2(rect.xMax, rect.yMin + l), color, width);
DrawLine(p, new Vector2(rect.xMin, rect.yMax), new Vector2(rect.xMin + l, rect.yMax), color, width);
DrawLine(p, new Vector2(rect.xMin, rect.yMax), new Vector2(rect.xMin, rect.yMax - l), color, width);
DrawLine(p, new Vector2(rect.xMax, rect.yMax), new Vector2(rect.xMax - l, rect.yMax), color, width);
DrawLine(p, new Vector2(rect.xMax, rect.yMax), new Vector2(rect.xMax, rect.yMax - l), color, width);
}
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 +802,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);
@ -411,7 +861,15 @@ namespace Editor.GardensStageEditor
private Rect GridRect(RectInt rect)
{
return new Rect(pan.x + rect.x * cellPixels, pan.y + rect.y * cellPixels, rect.width * cellPixels, rect.height * cellPixels);
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)
@ -463,4 +921,4 @@ namespace Editor.GardensStageEditor
return color;
}
}
}
}

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)
};
}
@ -61,27 +73,56 @@ namespace Editor.GardensStageEditor
{
var entries = new List<StagePaletteEntry>
{
Structural("struct_ground", "Ground", StructuralCellKind.Ground),
Structural("struct_ground_slab", "Ground - Slab", StructuralCellKind.Ground_Slab),
Structural("struct_ground_ramp_front", "Ground - RF", StructuralCellKind.Ground_Ramp_Front),
Structural("struct_ground_ramp_left", "Ground - RL", StructuralCellKind.Ground_Ramp_Left),
Structural("struct_ground_ramp_right", "Ground - RR", StructuralCellKind.Ground_Ramp_Right),
Structural("struct_wall", "Wall", StructuralCellKind.Wall),
Structural("struct_hole", "Hole", StructuralCellKind.Hole),
Structural("struct_empty", "Erase Structural", StructuralCellKind.Empty)
// leave it as last as possible
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 +1,2 @@
fileFormatVersion: 2
guid: 0e7ed87816264200a12fcc4bf3584153
timeCreated: 1781500522
guid: 0e7ed87816264200a12fcc4bf3584153

View File

@ -40,12 +40,7 @@ namespace Editor.GardensStageEditor
}
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."));

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Intrepid.Core.Entities.Messages;
using Intrepid.Core.Grounding;
using Intrepid.Diagnostics;
@ -11,6 +12,8 @@ namespace Intrepid.Core.Entities
{
public sealed class EntityMotorTopDown : MonoBehaviour
{
private const float FlatGroundNormalThreshold = 0.999f;
[GroupInjections, SerializeField] private Entity entity;
[GroupConfigurations, SerializeField] private LayerMask groundMask;
@ -29,32 +32,42 @@ namespace Intrepid.Core.Entities
[GroupConfigurations, SerializeField] private float fallAcceleration = 35f;
[GroupConfigurations, SerializeField] private float maxFallSpeed = 18f;
private readonly List<RaycastHit> _groundHits = new(16);
private RaycastHit _groundHit;
private Vector3 Origin { get; set; }
private Vector3 End { get; set; }
private bool HasHit { get; set; }
private Vector3 _inheritedGroundVelocity;
private float _fallVerticalSpeed;
private RaycastHit _groundHit;
private float GroundProbeRadius => IsFalling || IsFallingToAbyss
? groundProbeRadiusFalling
: groundProbeRadiusGrounded;
public GroundProfiles CurrentGroundProfiles { get; private set; } = GroundProfiles.None;
public float MaxSlopeAngle => maxSlopeAngle;
[GroupDebug, ShowInInspector, ReadOnly]
public Vector3 LastSafeGroundPosition { get; private set; }
[GroupDebug, ShowInInspector, ReadOnly]
public bool HasStableGroundNormal { get; private set; }
[GroupDebug, ShowInInspector, ReadOnly]
public Vector3 StableGroundNormal { get; private set; }
[GroupDebug, ShowInInspector, ReadOnly]
public bool IsFalling { get; private set; }
[GroupDebug, ShowInInspector, ReadOnly]
public bool IsFallingToAbyss { get; private set; }
[GroupDebug, ShowInInspector, ReadOnly]
public bool IsGrounded { get; private set; }
[GroupDebug, ShowInInspector, ReadOnly]
public Vector3 InheritedGroundVelocity => _inheritedGroundVelocity;
[GroupDebug, ShowInInspector, ReadOnly]
public float InheritedGroundVelocityTimer { get; private set; }
@ -357,7 +370,7 @@ namespace Intrepid.Core.Entities
RegisterStableGroundNormal(normal);
}
}
private Vector3 ResolveMovementVelocity(Vector3 velocity)
{
if (velocity.sqrMagnitude <= GonConstants.CommonZeroSqrt)
@ -370,10 +383,67 @@ namespace Intrepid.Core.Entities
return velocity;
}
return ProjectVelocityOnPlane(
var projectedVelocity = ProjectVelocityOnPlane(
velocity,
StableGroundNormal
);
return ApplyGroundMovementProfile(
projectedVelocity,
StableGroundNormal
);
}
private Vector3 ApplyGroundMovementProfile(
Vector3 velocity,
Vector3 groundNormal)
{
if (!IsGrounded || !CurrentGroundProfiles.TryGetGroundProfileMovement(out var movement))
{
return velocity;
}
if (velocity.sqrMagnitude <= GonConstants.CommonZeroSqrt)
{
return Vector3.zero;
}
var downhill = Vector3.ProjectOnPlane(
Vector3.down,
groundNormal.normalized
);
if (downhill.sqrMagnitude <= GonConstants.CommonZeroSqrt)
{
return velocity * movement.SpeedMultiplier;
}
downhill.Normalize();
var moveDirection = velocity.normalized;
var downhillFactor = Vector3.Dot(moveDirection, downhill);
float directionalMultiplier;
if (downhillFactor > 0f)
{
directionalMultiplier = Mathf.Lerp(
movement.SidewaysSpeedMultiplier,
movement.DownhillSpeedMultiplier,
downhillFactor
);
}
else
{
directionalMultiplier = Mathf.Lerp(
movement.SidewaysSpeedMultiplier,
movement.UphillSpeedMultiplier,
-downhillFactor
);
}
return velocity * (movement.SpeedMultiplier * directionalMultiplier);
}
private void RegisterStableGroundNormal(Vector3 normal)
@ -392,55 +462,134 @@ namespace Intrepid.Core.Entities
return TryGetGroundSurfaceAt(position, out _, out _, out _);
}
private bool TryGetBestGroundSurfaceHit(
Vector3 position,
float checkDistance,
out RaycastHit hit,
out Vector3 normal,
out GroundProfiles groundProfiles)
{
hit = default;
normal = default;
groundProfiles = GroundProfiles.None;
Origin = position + Vector3.up * groundCheckHeight;
End = Origin + Vector3.down * checkDistance;
_groundHits.Clear();
var hitCount = PhysicUtils.SphereCast3DAll(
Origin,
GroundProbeRadius,
Vector3.down,
_groundHits,
tags: null,
maxDistance: checkDistance,
layerMask: groundMask,
query: QueryTriggerInteraction.Ignore
);
HasHit = hitCount > 0;
if (hitCount == 0)
{
return false;
}
var found = false;
var bestHit = default(RaycastHit);
var bestNormal = Vector3.zero;
var bestProfiles = GroundProfiles.None;
var bestIsSlope = false;
var bestDistance = float.PositiveInfinity;
foreach (var candidateHit in _groundHits)
{
if (candidateHit.collider.IsNull())
{
continue;
}
var surface = candidateHit.collider.GetComponentInParent<GroundSurface>();
if (surface.IsNull())
{
continue;
}
var candidateNormal = surface.Normal;
if (!IsWalkable(candidateNormal))
{
continue;
}
var candidateIsSlope = IsSlope(candidateNormal);
var candidateDistance = candidateHit.distance;
var shouldReplace =
!found ||
// any walkable slope wins a plane
candidateIsSlope && !bestIsSlope ||
// if both are slopes or planes take the closest one
candidateIsSlope == bestIsSlope &&
candidateDistance < bestDistance;
if (!shouldReplace)
{
continue;
}
found = true;
bestHit = candidateHit;
bestNormal = candidateNormal;
bestProfiles = surface.GroundProfiles;
bestIsSlope = candidateIsSlope;
bestDistance = candidateDistance;
}
if (!found)
{
return false;
}
hit = bestHit;
normal = bestNormal;
groundProfiles = bestProfiles;
return true;
}
private bool TryGetGroundSurfaceAt(
Vector3 position,
out RaycastHit hit,
out Vector3 normal,
out GroundProfiles groundProfiles)
{
normal = default;
groundProfiles = GroundProfiles.None;
if (!TryGetGroundAt(position, out hit))
{
return false;
}
var surface = hit.collider.GetComponentInParent<GroundSurface>();
if (surface.IsNull())
{
return false;
}
normal = surface.Normal;
if (!IsWalkable(normal))
{
return false;
}
groundProfiles = surface.GroundProfiles;
return true;
return TryGetBestGroundSurfaceHit(
position,
groundCheckDistance,
out hit,
out normal,
out groundProfiles
);
}
private bool TryGetGroundAt(Vector3 position, out RaycastHit hit)
{
Origin = position + Vector3.up * groundCheckHeight;
End = Origin + Vector3.down * groundCheckDistance;
HasHit = Physics.SphereCast(
Origin,
GroundProbeRadius,
Vector3.down,
out hit,
return TryGetBestGroundSurfaceHit(
position,
groundCheckDistance,
groundMask,
QueryTriggerInteraction.Ignore
out hit,
out _,
out _
);
return HasHit;
}
private bool TryAnyGroundSurfaceBelowAt(
@ -449,43 +598,18 @@ namespace Intrepid.Core.Entities
out Vector3 normal,
out GroundProfiles groundProfiles)
{
normal = default;
groundProfiles = GroundProfiles.None;
var origin = position + Vector3.up * groundCheckHeight;
HasHit = Physics.SphereCast(
origin,
GroundProbeRadius,
Vector3.down,
out hit,
return TryGetBestGroundSurfaceHit(
position,
fallingGroundCheckDistance,
groundMask,
QueryTriggerInteraction.Ignore
out hit,
out normal,
out groundProfiles
);
}
if (!HasHit)
{
return false;
}
var surface = hit.collider.GetComponentInParent<GroundSurface>();
if (surface.IsNull())
{
return false;
}
normal = surface.Normal;
if (!IsWalkable(normal))
{
return false;
}
groundProfiles = surface.GroundProfiles;
return true;
private static bool IsSlope(Vector3 normal)
{
return normal.y < FlatGroundNormalThreshold;
}
private bool IsWalkable(Vector3 normal)

View File

@ -2,7 +2,7 @@ using Intrepid.Utilities;
namespace Intrepid.Core.Grounding
{
public abstract class GroundProfile : Identification
public abstract class GroundProfileBehaviour : Identification
{
}

View File

@ -1,12 +0,0 @@
using UnityEngine;
namespace Intrepid.Core.Grounding
{
public abstract class GroundProfileMotion : GroundProfile
{
public abstract Vector3 Velocity { get; protected set; }
public abstract Vector3 DeltaPosition { get; protected set; }
public abstract bool IsMoving { get; protected set; }
public abstract bool IsStatic { get; protected set; }
}
}

View File

@ -0,0 +1,11 @@
using UnityEngine;
namespace Intrepid.Core.Grounding
{
public class GroundProfileMotionBehaviour : GroundProfileBehaviour
{
[SerializeField] private GroundProfileMotion groundProfileMotion;
public GroundProfileMotion GroundProfileMotion => groundProfileMotion;
}
}

View File

@ -0,0 +1,11 @@
using UnityEngine;
namespace Intrepid.Core.Grounding
{
public class GroundProfileMovementBehaviour : GroundProfileBehaviour
{
[SerializeField] private GroundProfileMovement groundProfileMovement = GroundProfileMovement.NoModifiers;
public GroundProfileMovement GroundProfileMovement => groundProfileMovement;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bdd4ad30e2d149b58a2f84fa9a5b5fbc
timeCreated: 1781681309

View File

@ -1,19 +1,129 @@
using System;
using Intrepid.Utilities;
using Sirenix.OdinInspector;
using UnityEngine;
namespace Intrepid.Core.Grounding
{
[Serializable]
public abstract class GroundProfile
{
}
[Serializable, GroupConfigurations("Motion Profile"), HideLabel]
public sealed class GroundProfileMotion : GroundProfile
{
[SerializeField] private Vector3 velocity;
[SerializeField] private Vector3 deltaPosition;
[SerializeField] private bool isMoving;
[SerializeField] private bool isStatic;
public Vector3 Velocity
{
get => velocity;
set => velocity = value;
}
public Vector3 DeltaPosition
{
get => deltaPosition;
set => deltaPosition = value;
}
public bool IsMoving
{
get => isMoving;
set => isMoving = value;
}
public bool IsStatic
{
get => isStatic;
set => isStatic = value;
}
}
[Serializable, GroupConfigurations("Movement Profile"), HideLabel]
public class GroundProfileMovement : GroundProfile
{
[SerializeField] private float speedMultiplier;
[SerializeField] private float accelerationMultiplier;
[SerializeField] private float decelerationMultiplier;
[SerializeField] private float uphillSpeedMultiplier;
[SerializeField] private float downhillSpeedMultiplier;
[SerializeField] private float sidewaysSpeedMultiplier;
public float SpeedMultiplier
{
get => speedMultiplier;
protected set => speedMultiplier = value;
}
public float AccelerationMultiplier
{
get => accelerationMultiplier;
protected set => accelerationMultiplier = value;
}
public float DecelerationMultiplier
{
get => decelerationMultiplier;
protected set => decelerationMultiplier = value;
}
public float UphillSpeedMultiplier
{
get => uphillSpeedMultiplier;
protected set => uphillSpeedMultiplier = value;
}
public float DownhillSpeedMultiplier
{
get => downhillSpeedMultiplier;
protected set => downhillSpeedMultiplier = value;
}
public float SidewaysSpeedMultiplier
{
get => sidewaysSpeedMultiplier;
protected set => sidewaysSpeedMultiplier = value;
}
public static GroundProfileMovement NoModifiers => new()
{
SpeedMultiplier = 1f,
AccelerationMultiplier = 1f,
DecelerationMultiplier = 1f,
UphillSpeedMultiplier = 1f,
DownhillSpeedMultiplier = 1f,
SidewaysSpeedMultiplier = 1f,
};
public static GroundProfileMovement operator *(GroundProfileMovement a, GroundProfileMovement b)
{
return new GroundProfileMovement
{
SpeedMultiplier = a.SpeedMultiplier * b.SpeedMultiplier,
AccelerationMultiplier = a.AccelerationMultiplier * b.AccelerationMultiplier,
DecelerationMultiplier = a.DecelerationMultiplier * b.DecelerationMultiplier,
UphillSpeedMultiplier = a.UphillSpeedMultiplier * b.UphillSpeedMultiplier,
DownhillSpeedMultiplier = a.DownhillSpeedMultiplier * b.DownhillSpeedMultiplier,
SidewaysSpeedMultiplier = a.SidewaysSpeedMultiplier * b.SidewaysSpeedMultiplier,
};
}
}
public readonly struct GroundProfiles
{
public static readonly GroundProfiles None = new(false);
private readonly bool _isSafe;
private GroundProfileMotion GroundProfileMotion { get; }
private GroundProfileMovement GroundProfileMovement { get; }
public bool IsSafe => CheckIfThisProfileIsSafe();
public GroundProfiles(bool isSafe, GroundProfileMotion groundProfileMotion = null)
public GroundProfiles(bool isSafe,
GroundProfileMotion groundProfileMotion = null,
GroundProfileMovement groundProfileMovement = null)
{
_isSafe = isSafe;
GroundProfileMotion = groundProfileMotion;
GroundProfileMovement = groundProfileMovement;
}
private bool CheckIfThisProfileIsSafe()
@ -28,7 +138,7 @@ namespace Intrepid.Core.Grounding
private bool TryGet<T>(T value, out T profile) where T: GroundProfile
{
if (value.IsNull())
if (value is null)
{
profile = null;
return false;
@ -42,5 +152,10 @@ namespace Intrepid.Core.Grounding
{
return TryGet(GroundProfileMotion, out profile);
}
public bool TryGetGroundProfileMovement(out GroundProfileMovement profile)
{
return TryGet(GroundProfileMovement, out profile);
}
}
}

View File

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Intrepid.Utilities;
using Sirenix.OdinInspector;
using UnityEngine;
@ -13,8 +15,8 @@ namespace Intrepid.Core.Grounding
{
[GroupInjections, SerializeField] private BoxCollider boxCollider;
[GroupConfigurations("Profiles"), SerializeField] private bool safe = true;
[GroupConfigurations("Profiles"), SerializeField] private GroundProfileMotion groundProfileMotion;
[GroupConfigurations("Profiles"), SerializeField] private GroundProfileMotionBehaviour motionProfile;
[GroupConfigurations("Profiles"), SerializeField] private List<GroundProfileMovementBehaviour> movementProfiles = new();
[GroupDebug, ShowInInspector, ReadOnly] public Vector3 Position { get; private set; }
[GroupDebug, ShowInInspector, ReadOnly] public Vector3 Normal { get; private set; }
@ -38,7 +40,12 @@ namespace Intrepid.Core.Grounding
[GroupDebug, Button(ButtonSizes.Medium)]
private void RefreshProfiles()
{
GroundProfiles = new GroundProfiles(safe, groundProfileMotion);
var groundProfileMovement = movementProfiles.Count == 0
? null
: movementProfiles
.Select(behaviour => behaviour.GroundProfileMovement)
.Aggregate(GroundProfileMovement.NoModifiers, (a, b) => a * b);
GroundProfiles = new GroundProfiles(safe, motionProfile?.GroundProfileMotion, groundProfileMovement);
}
[Button(ButtonSizes.Medium)]

View File

@ -12,19 +12,24 @@ namespace Intrepid.Gameplay.Prototyping
[GroupConfigurations, SerializeField, ReadOnly] private int sizeX = 1;
[GroupConfigurations, SerializeField, ReadOnly] private int sizeY = 1;
[GroupConfigurations, SerializeField, ReadOnly] private int sizeZ = 1;
[GroupConfigurations, SerializeField, ReadOnly] private float offset = 1f;
[GroupConfigurations, SerializeField, ReadOnly] private Vector2 offset_x = new(1f, 1.5f);
[GroupConfigurations, SerializeField, ReadOnly] private Vector2 offset_y = new(1f, 1.5f);
[GroupDebug, SerializeField, Min(1)] private int x = 1;
[GroupDebug, SerializeField, Min(1)] private int y = 1;
[GroupDebug, SerializeField, Min(1)] private int z = 1;
[GroupDebug, SerializeField, Range(0f, 1f)] private float contact_offset = 1f;
[GroupDebug, SerializeField] private Vector2 contact_offset_x = new(1f, 1.5f);
[GroupDebug, SerializeField] private Vector2 contact_offset_y = new(1f, 1.5f);
protected override void DebugResize()
{
sizeX = Mathf.Max(1, x);
sizeY = Mathf.Max(1, y);
sizeZ = Mathf.Max(1, z);
offset = Mathf.Clamp01(contact_offset);
offset_x.x = Mathf.Clamp(contact_offset_x.x, 0f, 2f);
offset_x.y = Mathf.Clamp(contact_offset_x.y, 0f, 2f);
offset_y.x = Mathf.Clamp(contact_offset_y.x, 0f, 2f);
offset_y.y = Mathf.Clamp(contact_offset_y.y, 0f, 2f);
}
protected override void Generate()
@ -41,9 +46,11 @@ namespace Intrepid.Gameplay.Prototyping
vertices.ToArray(),
triangles.ToArray(),
uvs.ToArray()));
PrototypeModel.BoxCollider.size = new Vector3(sizeX + offset, sizeY, sizeZ + offset);
PrototypeModel.BoxCollider.center = Vector3.zero;
var offsetX = offset_x.x + offset_x.y;
var offsetY = offset_y.x + offset_y.y;
PrototypeModel.BoxCollider.size = new Vector3(sizeX + offsetX, sizeY, sizeZ + offsetY);
PrototypeModel.BoxCollider.center = new Vector3((offset_x.x - offset_x.y) / 2f, 0f, (offset_y.x - offset_y.y) / 2f);
PrototypeModel.transform.localPosition = new Vector3(0f, sizeY / 2f, 0f);
}
}

View File

@ -1,6 +1,9 @@
using UnityEngine;
namespace Intrepid.Gameplay.Prototyping
{
public abstract class GroundModelPrototype : ModelPrototype
{
public abstract void SetSize(Vector2Int size);
}
}

View File

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using Intrepid.Utilities;
using Sirenix.OdinInspector;
@ -14,6 +13,13 @@ namespace Intrepid.Gameplay.Prototyping
[GroupDebug, SerializeField, Min(1)] private int x = 1;
[GroupDebug, SerializeField, Min(1)] private int z = 1;
public override void SetSize(Vector2Int size)
{
x = size.x;
z = size.y;
SizeFitting();
}
protected override void DebugResize()
{
sizeX = Mathf.Max(1, x);

View File

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using Intrepid.Utilities;
using Sirenix.OdinInspector;
@ -16,6 +15,13 @@ namespace Intrepid.Gameplay.Prototyping
[GroupDebug, SerializeField, Min(1)] private int x = 1;
[GroupDebug, SerializeField, Min(1)] private int z = 1;
public override void SetSize(Vector2Int size)
{
x = size.x;
z = size.y;
SizeFitting();
}
protected override void DebugResize()
{
sizeX = Mathf.Max(1, x);

View File

@ -0,0 +1,116 @@
using System.Collections.Generic;
using Intrepid.Utilities;
using Sirenix.OdinInspector;
using UnityEngine;
namespace Intrepid.Gameplay.Prototyping
{
public class GroundStairModelPrototype : GroundModelPrototype
{
[GroupConfigurations, SerializeField, ReadOnly] private int sizeX = 1;
[GroupConfigurations, SerializeField, ReadOnly] private int sizeZ = 1;
[GroupDebug, SerializeField, Min(1)] private int x = 1;
[GroupDebug, SerializeField, Min(1)] private int z = 1;
public override void SetSize(Vector2Int size)
{
x = size.x;
z = size.y;
SizeFitting();
}
protected override void DebugResize()
{
x = Mathf.Max(1, x);
z = Mathf.Max(1, z);
sizeX = x;
sizeZ = z;
}
protected override void Generate()
{
// PrototypeModel.BuildMesh(BuildRampMeshData(new Vector3(sizeX, sizeZ * .75f, sizeZ)));
//
// PrototypeModel.BoxCollider.transform.localEulerAngles = new Vector3(-36.87f, 0f, 0f);
// PrototypeModel.BoxCollider.center = new Vector3(0f, -0.025f, 0f);
// PrototypeModel.BoxCollider.size = new Vector3(sizeX, 0.05f, sizeZ * 1.25f);
//
// PrototypeModel.transform.localPosition = new Vector3(0f, sizeZ * .5f, 0f);
PrototypeModel.BuildMesh(BuildRampMeshData(new Vector3(sizeX, sizeZ, sizeZ)));
PrototypeModel.BoxCollider.transform.localEulerAngles = new Vector3(-45f, 0f, 0f);
PrototypeModel.BoxCollider.center = new Vector3(0f, -0.025f, 0f);
PrototypeModel.BoxCollider.size = new Vector3(sizeX, 0.05f, sizeZ * 1.4142f);
PrototypeModel.transform.localPosition = new Vector3(0f, sizeZ * .5f, 0f);
}
private static MeshData BuildRampMeshData(Vector3 size)
{
var halfX = size.x * 0.5f;
var halfY = size.y * 0.5f;
var halfZ = size.z * 0.5f;
var bottomY = -halfY;
var topY = halfY;
var countX = Mathf.Max(1, Mathf.RoundToInt(size.x));
var countZ = Mathf.Max(1, Mathf.RoundToInt(size.z));
var vertices = new List<Vector3>();
var triangles = new List<int>();
var uvs = new List<Vector2>();
var rampStep = new Vector3(
0f,
size.y / countZ,
size.z / countZ
);
// Ramp surface
MeshUtils.AddGridFace(
vertices,
triangles,
uvs,
origin: new Vector3(halfX, bottomY, -halfZ),
axisU: Vector3.left,
axisV: rampStep,
countU: countX,
countV: countZ
);
// Left
MeshUtils.AddTriangle(
vertices,
triangles,
uvs,
new Vector3(-halfX, bottomY, -halfZ),
new Vector3(-halfX, bottomY, halfZ),
new Vector3(-halfX, topY, halfZ),
new Vector2(0f, 0f),
new Vector2(size.z, 0f),
new Vector2(size.z, size.y)
);
// Right
MeshUtils.AddTriangle(
vertices,
triangles,
uvs,
new Vector3(halfX, bottomY, -halfZ),
new Vector3(halfX, topY, halfZ),
new Vector3(halfX, bottomY, halfZ),
new Vector2(0f, 0f),
new Vector2(size.z, size.y),
new Vector2(size.z, 0f)
);
return new MeshData(
vertices.ToArray(),
triangles.ToArray(),
uvs.ToArray()
);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3a3b1270c87c4456bdc61721ca19132f
timeCreated: 1781674851

View File

@ -43,7 +43,7 @@ namespace Intrepid.Gameplay.Prototyping
}
[GroupDebug, Button(ButtonSizes.Medium, ButtonStyle.FoldoutButton)]
private void SetSize()
protected void SizeFitting()
{
DebugResize();
if (PrototypeModel.IsNull()) return;