ManiaMap.Godot
Procedural generation of metroidvania style maps for Godot .NET.
RoomNode3D.cs
1using Godot;
2using MPewsey.ManiaMap;
4using System;
5
7{
13 [Tool]
14 [GlobalClass]
15 [Icon(ManiaMapResources.Icons.RoomNode3DIcon)]
16 public partial class RoomNode3D : Node3D, IRoomNode
17 {
23 [Signal] public delegate void OnCellAreaEnteredEventHandler(CellArea3D cell, Node collision);
24 public Error EmitOnCellAreaEntered(CellArea3D cell, Node collision) => EmitSignal(SignalName.OnCellAreaEntered, cell, collision);
25
31 [Signal] public delegate void OnCellAreaExitedEventHandler(CellArea3D cell, Node collision);
32 public Error EmitOnCellAreaExited(CellArea3D cell, Node collection) => EmitSignal(SignalName.OnCellAreaExited, cell, collection);
33
37 [Signal] public delegate void OnCellGridChangedEventHandler();
38 public Error EmitOnCellGridChanged() => EmitSignal(SignalName.OnCellGridChanged);
39
41 [Export] public RoomTemplateResource RoomTemplate { get; set; }
42
43 private int _rows = 1;
45 [Export(PropertyHint.Range, "1,10,1,or_greater")] public int Rows { get => _rows; set => SetSizeField(ref _rows, value); }
46
47 private int _columns = 1;
49 [Export(PropertyHint.Range, "1,10,1,or_greater")] public int Columns { get => _columns; set => SetSizeField(ref _columns, value); }
50
51 private Vector3 _cellSize = Vector3.One;
55 [Export(PropertyHint.Range, "0,100,0.001,or_greater")] public Vector3 CellSize { get => _cellSize; set => SetCellSizeField(ref _cellSize, value); }
56
58 [Export] public Godot.Collections.Array<Godot.Collections.Array<bool>> ActiveCells { get; set; } = new Godot.Collections.Array<Godot.Collections.Array<bool>>();
59
61 public LayoutPack LayoutPack { get; private set; }
62
64 public Room RoomLayout { get; private set; }
65
67 public RoomState RoomState { get; private set; }
68
70 public bool IsInitialized { get; private set; }
71
72#if TOOLS
73 private bool MouseButtonPressed { get; set; }
74 private Vector2 MouseButtonDownPosition { get; set; }
75#endif
76
77 private void SetSizeField(ref int field, int value)
78 {
79 field = value;
80 this.SizeActiveCells();
81 EmitOnCellGridChanged();
82 }
83
84 private void SetCellSizeField(ref Vector3 field, Vector3 value)
85 {
86 field = new Vector3(Mathf.Max(value.X, 0.001f), Mathf.Max(value.Y, 0.001f), Mathf.Max(value.Z, 0.001f));
87 EmitOnCellGridChanged();
88 }
89
90 public override void _Ready()
91 {
92 base._Ready();
93
94#if TOOLS
95 if (Engine.IsEditorHint())
96 {
97 this.SizeActiveCells();
98 Editor.CellGrid3DGizmo.CreateInstance(this);
99 return;
100 }
101#endif
102
103 if (!IsInitialized)
104 throw new RoomNotInitializedException($"Room is not initialized: {this}");
105 }
106
107 public override void _ValidateProperty(Godot.Collections.Dictionary property)
108 {
109 base._ValidateProperty(property);
110 var name = property["name"].AsStringName();
111 var usage = property["usage"].As<PropertyUsageFlags>();
112
113 if (name == PropertyName.ActiveCells)
114 property["usage"] = (int)(usage & ~PropertyUsageFlags.Editor);
115 }
116
117#if TOOLS
118 public override void _Process(double delta)
119 {
120 base._Process(delta);
121
122 if (Engine.IsEditorHint())
123 ProcessEditCellInputs();
124 }
125
126 private static bool DisplayCells()
127 {
128 return Editor.ManiaMapPlugin.PluginIsValid()
129 && Editor.ManiaMapPlugin.Current.RoomNode3DToolbar.DisplayCells;
130 }
131
132 private static CellActivity CellEditMode()
133 {
134 if (!Editor.ManiaMapPlugin.PluginIsValid())
135 return CellActivity.None;
136 return Editor.ManiaMapPlugin.Current.RoomNode3DToolbar.CellEditMode;
137 }
138
139 private void ProcessEditCellInputs()
140 {
141 if (!DisplayCells())
142 return;
143
144 if (Input.IsMouseButtonPressed(MouseButton.Left) && CameraIsLookingDown() && MouseIsInsideMainScreen())
145 {
146 if (!MouseButtonPressed)
147 MouseButtonDownPosition = GetMousePosition();
148
149 MouseButtonPressed = true;
150 return;
151 }
152
153 if (MouseButtonPressed && CameraIsLookingDown() && MouseIsInsideMainScreen())
154 {
155 var mousePosition = GetMousePosition();
156 var startPosition = new Vector3(MouseButtonDownPosition.X, 0, MouseButtonDownPosition.Y);
157 var endPosition = new Vector3(mousePosition.X, 0, mousePosition.Y);
158 var startIndex = GlobalPositionToCellIndex(startPosition);
159 var endIndex = GlobalPositionToCellIndex(endPosition);
160 this.SetCellActivities(startIndex, endIndex, CellEditMode());
161 EmitOnCellGridChanged();
162 }
163
164 MouseButtonPressed = false;
165 }
166
167 private static Vector2 GetMousePosition()
168 {
169 var viewport = EditorInterface.Singleton.GetEditorViewport3D();
170 var camera = viewport.GetCamera3D();
171 var origin = camera.ProjectRayOrigin(viewport.GetMousePosition());
172 return new Vector2(origin.X, origin.Z);
173 }
174
175 private static bool CameraIsLookingDown()
176 {
177 var camera = EditorInterface.Singleton.GetEditorViewport3D().GetCamera3D();
178 var cameraDirection = camera.GlobalRotationDegrees;
179 return cameraDirection.IsEqualApprox(new Vector3(-90, 0, 0));
180 }
181
182 private static bool MouseIsInsideMainScreen()
183 {
184 var mainScreen = EditorInterface.Singleton.GetEditorMainScreen();
185 var mousePosition = mainScreen.GetViewport().GetMousePosition();
186 var mainScreenRect = new Rect2(mainScreen.GlobalPosition, mainScreen.Size);
187 return mainScreenRect.Encloses(new Rect2(mousePosition, Vector2.Zero));
188 }
189#endif
190
200 public static RoomNode3D CreateInstance(Uid id, LayoutPack layoutPack, PackedScene scene, Node parent, bool assignLayoutPosition = false)
201 {
202 var roomLayout = layoutPack.Layout.Rooms[id];
203 var roomState = layoutPack.LayoutState.RoomStates[id];
204 var collisionMask = layoutPack.Settings.CellCollisionMask;
205 return CreateInstance(scene, parent, layoutPack, roomLayout, roomState, collisionMask, assignLayoutPosition);
206 }
207
220 public static RoomNode3D CreateInstance(PackedScene scene, Node parent, LayoutPack layoutPack,
221 Room roomLayout, RoomState roomState, uint cellCollisionMask, bool assignLayoutPosition)
222 {
223 var room = scene.Instantiate<RoomNode3D>();
224 room.Initialize(layoutPack, roomLayout, roomState, cellCollisionMask, assignLayoutPosition);
225 parent.AddChild(room);
226 return room;
227 }
228
243 public bool Initialize(LayoutPack layoutPack, Room roomLayout, RoomState roomState,
244 uint cellCollisionMask, bool assignLayoutPosition)
245 {
246 if (IsInitialized)
247 return false;
248
249 LayoutPack = layoutPack;
250 RoomLayout = roomLayout;
251 RoomState = roomState;
252
253 if (assignLayoutPosition)
255
256 CreateCellAreas(cellCollisionMask);
257 IsInitialized = true;
258 return true;
259 }
260
265 private void CreateCellAreas(uint cellCollisionMask)
266 {
267 for (int i = 0; i < ActiveCells.Count; i++)
268 {
269 var row = ActiveCells[i];
270
271 for (int j = 0; j < row.Count; j++)
272 {
273 if (row[j])
274 CellArea3D.CreateInstance(i, j, this, cellCollisionMask);
275 }
276 }
277 }
278
284 public Vector3 CellCenterGlobalPosition(int row, int column)
285 {
286 return CellCenterLocalPosition(row, column) + GlobalPosition;
287 }
288
294 public Vector3 CellCenterLocalPosition(int row, int column)
295 {
296 return new Vector3(column * CellSize.X, 0, row * CellSize.Z) + 0.5f * CellSize;
297 }
298
304 public Vector2I GlobalPositionToCellIndex(Vector3 position)
305 {
306 return LocalPositionToCellIndex(position - GlobalPosition);
307 }
308
314 public Vector2I LocalPositionToCellIndex(Vector3 position)
315 {
316 var column = Mathf.FloorToInt(position.X / CellSize.X);
317 var row = Mathf.FloorToInt(position.Z / CellSize.Z);
318
319 if (this.CellIndexExists(row, column))
320 return new Vector2I(row, column);
321
322 return new Vector2I(-1, -1);
323 }
324
329 public Vector2I FindClosestActiveCellIndex(Vector3 position)
330 {
331 var fastIndex = GlobalPositionToCellIndex(position);
332
333 if (this.CellIndexExists(fastIndex.X, fastIndex.Y) && ActiveCells[fastIndex.X][fastIndex.Y])
334 return fastIndex;
335
336 var index = Vector2I.Zero;
337 var minDistance = float.PositiveInfinity;
338
339 for (int i = 0; i < ActiveCells.Count; i++)
340 {
341 var row = ActiveCells[i];
342
343 for (int j = 0; j < row.Count; j++)
344 {
345 if (row[j])
346 {
347 var delta = CellCenterGlobalPosition(i, j) - position;
348 var distance = new Vector2(delta.X, delta.Z).LengthSquared();
349
350 if (distance < minDistance)
351 {
352 minDistance = distance;
353 index = new Vector2I(i, j);
354 }
355 }
356 }
357 }
358
359 return index;
360 }
361
369 public DoorDirection FindClosestDoorDirection(int row, int column, Vector3 position)
370 {
371 Span<DoorDirection> directions = stackalloc DoorDirection[]
372 {
373 DoorDirection.North,
374 DoorDirection.East,
375 DoorDirection.South,
376 DoorDirection.West,
377 DoorDirection.Top,
378 DoorDirection.Bottom,
379 };
380
381 Span<Vector3> vectors = stackalloc Vector3[]
382 {
383 new Vector3(0, 0, -1),
384 new Vector3(1, 0, 0),
385 new Vector3(0, 0, 1),
386 new Vector3(-1, 0, 0),
387 new Vector3(0, 1, 0),
388 new Vector3(0, -1, 0),
389 };
390
391 var index = 0;
392 var maxDistance = float.NegativeInfinity;
393 var delta = (position - CellCenterGlobalPosition(row, column)) / CellSize;
394
395 for (int i = 0; i < vectors.Length; i++)
396 {
397 var distance = delta.Dot(vectors[i]);
398
399 if (distance > maxDistance)
400 {
401 maxDistance = distance;
402 index = i;
403 }
404 }
405
406 return directions[index];
407 }
408
413 {
414 var position = RoomLayout.Position;
415 Position = new Vector3(CellSize.X * position.Y, CellSize.Y * position.Z, CellSize.Z * position.X);
416 }
417 }
418}
Provides area and body entering and exiting detection for a cell.
Definition: CellArea3D.cs:12
static CellArea3D CreateInstance(int row, int column, RoomNode3D room, uint collisionMask)
Creates a new instance with child collision shape and adds it as a child of the specified room.
Definition: CellArea3D.cs:35
Raised if a room is not initialized when its on ready method is called.
Holds the current Layout and LayoutState.
Definition: LayoutPack.cs:13
LayoutState LayoutState
The layout state.
Definition: LayoutPack.cs:22
ManiaMapSettings Settings
The settings.
Definition: LayoutPack.cs:27
uint CellCollisionMask
The cell collision mask used for detecting objects entering or exiting a CellArea2D....
A node serving as the top level of a 3D room.
Definition: RoomNode3D.cs:17
static RoomNode3D CreateInstance(Uid id, LayoutPack layoutPack, PackedScene scene, Node parent, bool assignLayoutPosition=false)
Creates and initializes an instance of a room. The layout and layout state are assigned to the room b...
Definition: RoomNode3D.cs:200
Vector2I FindClosestActiveCellIndex(Vector3 position)
Returns the closest active cell index to the specified global position.
Definition: RoomNode3D.cs:329
delegate void OnCellAreaExitedEventHandler(CellArea3D cell, Node collision)
Signal emitted when a cell area is exited by a tracked area or body.
Godot.Collections.Array< Godot.Collections.Array< bool > > ActiveCells
Definition: RoomNode3D.cs:58
void MoveToLayoutPosition()
Moves the room to its position in the Layout.
Definition: RoomNode3D.cs:412
bool Initialize(LayoutPack layoutPack, Room roomLayout, RoomState roomState, uint cellCollisionMask, bool assignLayoutPosition)
Initializes the room. The room should be initialized before adding it to the scene tree since cell ch...
Definition: RoomNode3D.cs:243
delegate void OnCellAreaEnteredEventHandler(CellArea3D cell, Node collision)
Signal emitted when a cell area is entered by a tracked area or body.
void CreateCellAreas(uint cellCollisionMask)
Creates CellArea3D for all active cells.
Definition: RoomNode3D.cs:265
delegate void OnCellGridChangedEventHandler()
Signal emitted when the cell grid size or cell sizes are set.
Vector3 CellCenterLocalPosition(int row, int column)
Returns the cell center local position for the specified cell index.
Definition: RoomNode3D.cs:294
Vector3 CellSize
The width and height of the room cells.
Definition: RoomNode3D.cs:55
Vector2I GlobalPositionToCellIndex(Vector3 position)
Returns the row (x) and column (y) index corresponding to the specified global position....
Definition: RoomNode3D.cs:304
RoomTemplateResource RoomTemplate
Definition: RoomNode3D.cs:41
static RoomNode3D CreateInstance(PackedScene scene, Node parent, LayoutPack layoutPack, Room roomLayout, RoomState roomState, uint cellCollisionMask, bool assignLayoutPosition)
Creates and initializes an instance of a room.
Definition: RoomNode3D.cs:220
DoorDirection FindClosestDoorDirection(int row, int column, Vector3 position)
Returns the closest door direction based on the cell index and specified global position....
Definition: RoomNode3D.cs:369
Vector3 CellCenterGlobalPosition(int row, int column)
Returns the cell center global position for the specified cell index.
Definition: RoomNode3D.cs:284
Vector2I LocalPositionToCellIndex(Vector3 position)
Returns the row (x) and column (y) index corresponding to the specified local position....
Definition: RoomNode3D.cs:314
Provides a reference to a room scene and information for the room required by the procedural generato...
The interface for a room node.
Definition: IRoomNode.cs:9
CellActivity
Option for setting cell activities.
Definition: CellActivity.cs:7