ManiaMap.Godot
Procedural generation of metroidvania style maps for Godot .NET.
RoomNode2D.cs
1using Godot;
2using MPewsey.ManiaMap;
4using System;
5
7{
13 [Tool]
14 [GlobalClass]
15 [Icon(ManiaMapResources.Icons.RoomNode2DIcon)]
16 public partial class RoomNode2D : Node2D, IRoomNode
17 {
23 [Signal] public delegate void OnCellAreaEnteredEventHandler(CellArea2D cell, Node collision);
24 public Error EmitOnCellAreaEntered(CellArea2D cell, Node collision) => EmitSignal(SignalName.OnCellAreaEntered, cell, collision);
25
31 [Signal] public delegate void OnCellAreaExitedEventHandler(CellArea2D cell, Node collision);
32 public Error EmitOnCellAreaExited(CellArea2D 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 Vector2 _cellSize = new Vector2(96, 96);
55 [Export(PropertyHint.Range, "0,100,1,or_greater")] public Vector2 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 = Mathf.Max(value, 1);
80 this.SizeActiveCells();
81 EmitOnCellGridChanged();
82 }
83
84 private void SetCellSizeField(ref Vector2 field, Vector2 value)
85 {
86 field = new Vector2(Mathf.Max(value.X, 0.001f), Mathf.Max(value.Y, 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.CellGrid2DGizmo.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.RoomNode2DToolbar.DisplayCells;
130 }
131
132 private static CellActivity CellEditMode()
133 {
134 if (!Editor.ManiaMapPlugin.PluginIsValid())
135 return CellActivity.None;
136 return Editor.ManiaMapPlugin.Current.RoomNode2DToolbar.CellEditMode;
137 }
138
139 private void ProcessEditCellInputs()
140 {
141 if (!DisplayCells())
142 return;
143
144 if (Input.IsMouseButtonPressed(MouseButton.Left) && MouseIsInsideMainScreen())
145 {
146 if (!MouseButtonPressed)
147 MouseButtonDownPosition = GetViewport().GetMousePosition();
148
149 MouseButtonPressed = true;
150 return;
151 }
152
153 if (MouseButtonPressed && MouseIsInsideMainScreen())
154 {
155 var startIndex = GlobalPositionToCellIndex(MouseButtonDownPosition);
156 var endIndex = GlobalPositionToCellIndex(GetViewport().GetMousePosition());
157 this.SetCellActivities(startIndex, endIndex, CellEditMode());
158 EmitOnCellGridChanged();
159 }
160
161 MouseButtonPressed = false;
162 }
163
164 private static bool MouseIsInsideMainScreen()
165 {
166 var mainScreen = EditorInterface.Singleton.GetEditorMainScreen();
167 var mousePosition = mainScreen.GetViewport().GetMousePosition();
168 var mainScreenRect = new Rect2(mainScreen.GlobalPosition, mainScreen.Size);
169 return mainScreenRect.Encloses(new Rect2(mousePosition, Vector2.Zero));
170 }
171#endif
172
182 public static RoomNode2D CreateInstance(Uid id, LayoutPack layoutPack, PackedScene scene, Node parent, bool assignLayoutPosition = false)
183 {
184 var roomLayout = layoutPack.Layout.Rooms[id];
185 var roomState = layoutPack.LayoutState.RoomStates[id];
186 var collisionMask = layoutPack.Settings.CellCollisionMask;
187 return CreateInstance(scene, parent, layoutPack, roomLayout, roomState, collisionMask, assignLayoutPosition);
188 }
189
200 public static RoomNode2D CreateInstance(PackedScene scene, Node parent, LayoutPack layoutPack,
201 Room roomLayout, RoomState roomState, uint cellCollisionMask, bool assignLayoutPosition)
202 {
203 var room = scene.Instantiate<RoomNode2D>();
204 room.Initialize(layoutPack, roomLayout, roomState, cellCollisionMask, assignLayoutPosition);
205 parent.AddChild(room);
206 return room;
207 }
208
221 public bool Initialize(LayoutPack layoutPack, Room roomLayout, RoomState roomState,
222 uint cellCollisionMask, bool assignLayoutPosition)
223 {
224 if (IsInitialized)
225 return false;
226
227 LayoutPack = layoutPack;
228 RoomLayout = roomLayout;
229 RoomState = roomState;
230
231 if (assignLayoutPosition)
233
234 CreateCellAreas(cellCollisionMask);
235 IsInitialized = true;
236 return true;
237 }
238
243 public Vector2I FindClosestActiveCellIndex(Vector2 position)
244 {
245 var fastIndex = GlobalPositionToCellIndex(position);
246
247 if (this.CellIndexExists(fastIndex.X, fastIndex.Y) && ActiveCells[fastIndex.X][fastIndex.Y])
248 return fastIndex;
249
250 var index = Vector2I.Zero;
251 var minDistance = float.PositiveInfinity;
252
253 for (int i = 0; i < ActiveCells.Count; i++)
254 {
255 var row = ActiveCells[i];
256
257 for (int j = 0; j < row.Count; j++)
258 {
259 if (row[j])
260 {
261 var delta = CellCenterGlobalPosition(i, j) - position;
262 var distance = delta.LengthSquared();
263
264 if (distance < minDistance)
265 {
266 minDistance = distance;
267 index = new Vector2I(i, j);
268 }
269 }
270 }
271 }
272
273 return index;
274 }
275
283 public DoorDirection FindClosestDoorDirection(int row, int column, Vector2 position)
284 {
285 Span<DoorDirection> directions = stackalloc DoorDirection[]
286 {
287 DoorDirection.North,
288 DoorDirection.East,
289 DoorDirection.South,
290 DoorDirection.West,
291 };
292
293 Span<Vector2> vectors = stackalloc Vector2[]
294 {
295 new Vector2(0, -1),
296 new Vector2(1, 0),
297 new Vector2(0, 1),
298 new Vector2(-1, 0),
299 };
300
301 var index = 0;
302 var maxDistance = float.NegativeInfinity;
303 var delta = (position - CellCenterGlobalPosition(row, column)) / CellSize;
304
305 for (int i = 0; i < vectors.Length; i++)
306 {
307 var distance = delta.Dot(vectors[i]);
308
309 if (distance > maxDistance)
310 {
311 maxDistance = distance;
312 index = i;
313 }
314 }
315
316 return directions[index];
317 }
318
323 private void CreateCellAreas(uint cellCollisionMask)
324 {
325 for (int i = 0; i < ActiveCells.Count; i++)
326 {
327 var row = ActiveCells[i];
328
329 for (int j = 0; j < row.Count; j++)
330 {
331 if (row[j])
332 CellArea2D.CreateInstance(i, j, this, cellCollisionMask);
333 }
334 }
335 }
336
341 {
342 var position = RoomLayout.Position;
343 Position = new Vector2(CellSize.X * position.Y, CellSize.Y * position.X);
344 }
345
351 public Vector2 CellCenterGlobalPosition(int row, int column)
352 {
353 return CellCenterLocalPosition(row, column) + GlobalPosition;
354 }
355
361 public Vector2 CellCenterLocalPosition(int row, int column)
362 {
363 return new Vector2(column * CellSize.X, row * CellSize.Y) + 0.5f * CellSize;
364 }
365
371 public Vector2I GlobalPositionToCellIndex(Vector2 position)
372 {
373 return LocalPositionToCellIndex(position - GlobalPosition);
374 }
375
381 public Vector2I LocalPositionToCellIndex(Vector2 position)
382 {
383 var column = Mathf.FloorToInt(position.X / CellSize.X);
384 var row = Mathf.FloorToInt(position.Y / CellSize.Y);
385
386 if (this.CellIndexExists(row, column))
387 return new Vector2I(row, column);
388
389 return new Vector2I(-1, -1);
390 }
391 }
392}
Provides area and body entering and exiting detection for a cell.
Definition: CellArea2D.cs:12
static CellArea2D CreateInstance(int row, int column, RoomNode2D room, uint collisionMask)
Creates a new instance with child collision shape and adds it as a child of the specified room.
Definition: CellArea2D.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 2D room.
Definition: RoomNode2D.cs:17
Godot.Collections.Array< Godot.Collections.Array< bool > > ActiveCells
Definition: RoomNode2D.cs:58
void MoveToLayoutPosition()
Moves the room to its position in the Layout.
Definition: RoomNode2D.cs:340
Vector2I LocalPositionToCellIndex(Vector2 position)
Returns the row (x) and column (y) index corresponding to the specified local position....
Definition: RoomNode2D.cs:381
static RoomNode2D 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: RoomNode2D.cs:182
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: RoomNode2D.cs:221
static RoomNode2D CreateInstance(PackedScene scene, Node parent, LayoutPack layoutPack, Room roomLayout, RoomState roomState, uint cellCollisionMask, bool assignLayoutPosition)
Creates and initializes an instance of a room.
Definition: RoomNode2D.cs:200
void CreateCellAreas(uint cellCollisionMask)
Creates CellArea2D for all active cells.
Definition: RoomNode2D.cs:323
Vector2I GlobalPositionToCellIndex(Vector2 position)
Returns the row (x) and column (y) index corresponding to the specified global position....
Definition: RoomNode2D.cs:371
delegate void OnCellGridChangedEventHandler()
Signal emitted when the cell grid size or cell sizes are set.
Vector2 CellCenterGlobalPosition(int row, int column)
Returns the cell center global position for the specified cell index.
Definition: RoomNode2D.cs:351
delegate void OnCellAreaEnteredEventHandler(CellArea2D cell, Node collision)
Signal emitted when a cell area is entered by a tracked area or body.
RoomTemplateResource RoomTemplate
Definition: RoomNode2D.cs:41
Vector2 CellCenterLocalPosition(int row, int column)
Returns the cell center local position for the specified cell index.
Definition: RoomNode2D.cs:361
Vector2I FindClosestActiveCellIndex(Vector2 position)
Returns the closest active cell index to the specified global position.
Definition: RoomNode2D.cs:243
delegate void OnCellAreaExitedEventHandler(CellArea2D cell, Node collision)
Signal emitted when a cell area is exited by a tracked area or body.
DoorDirection FindClosestDoorDirection(int row, int column, Vector2 position)
Returns the closest door direction based on the cell index and specified global position....
Definition: RoomNode2D.cs:283
Vector2 CellSize
The width and height of the room cells.
Definition: RoomNode2D.cs:55
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