✨ Grid layer system
This commit is contained in:
		| @@ -1,31 +1,68 @@ | ||||
| #nullable enable | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using Godot; | ||||
|  | ||||
| namespace AceFieldNewHorizon.Scripts.System; | ||||
|  | ||||
| public enum GridLayer | ||||
| { | ||||
|     Ground,    // Base layer (e.g., terrain, floors) | ||||
|     Building,  // Main building layer | ||||
|     Decoration // Additional layer for decorations, effects, etc. | ||||
| } | ||||
|  | ||||
| public partial class GridManager : Node | ||||
| { | ||||
|     private Dictionary<Vector2I, Node2D> _grid = new(); | ||||
|     private Dictionary<GridLayer, Dictionary<Vector2I, Node2D>> _layers = new(); | ||||
|  | ||||
|     public bool IsCellFree(Vector2I cell) | ||||
|     public GridManager() | ||||
|     { | ||||
|         return !_grid.ContainsKey(cell); | ||||
|         // Initialize all layers | ||||
|         foreach (GridLayer layer in Enum.GetValues(typeof(GridLayer))) | ||||
|         { | ||||
|             _layers[layer] = new Dictionary<Vector2I, Node2D>(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public void OccupyCell(Vector2I cell, Node2D building) | ||||
|     public bool IsCellFree(Vector2I cell, GridLayer layer = GridLayer.Building) | ||||
|     { | ||||
|         _grid[cell] = building; | ||||
|         return !_layers[layer].ContainsKey(cell); | ||||
|     } | ||||
|  | ||||
|     public void FreeCell(Vector2I cell) | ||||
|     public void OccupyCell(Vector2I cell, Node2D building, GridLayer layer = GridLayer.Building) | ||||
|     { | ||||
|         _grid.Remove(cell); | ||||
|         _layers[layer][cell] = building; | ||||
|     } | ||||
|  | ||||
|     public Node2D? GetBuildingAtCell(Vector2I cell) | ||||
|     public void FreeCell(Vector2I cell, GridLayer layer = GridLayer.Building) | ||||
|     { | ||||
|         return _grid.GetValueOrDefault(cell); | ||||
|         if (_layers[layer].ContainsKey(cell)) | ||||
|             _layers[layer].Remove(cell); | ||||
|     } | ||||
|  | ||||
|     public Node2D? GetBuildingAtCell(Vector2I cell, GridLayer layer = GridLayer.Building) | ||||
|     { | ||||
|         return _layers[layer].GetValueOrDefault(cell); | ||||
|     } | ||||
|  | ||||
|     public bool IsAnyCellOccupied(Vector2I cell, params GridLayer[] layers) | ||||
|     { | ||||
|         if (layers.Length == 0) | ||||
|         { | ||||
|             // Check all layers if none specified | ||||
|             foreach (var layer in _layers.Values) | ||||
|             { | ||||
|                 if (layer.ContainsKey(cell)) return true; | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         foreach (var layer in layers) | ||||
|         { | ||||
|             if (_layers[layer].ContainsKey(cell)) return true; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| using System; | ||||
| using AceFieldNewHorizon.Scripts.Tiles; | ||||
| using Godot; | ||||
|  | ||||
| @@ -8,14 +9,16 @@ public partial class PlacementManager : Node2D | ||||
|     [Export] public GridManager Grid { get; set; } | ||||
|     [Export] public BuildingRegistry Registry { get; set; } | ||||
|  | ||||
|     private string _currentBuildingId = "miner"; | ||||
|  | ||||
|     private string _currentBuildingId = "wall"; | ||||
|     private GridLayer _currentLayer = GridLayer.Building; // Default layer | ||||
|     private Vector2I _hoveredCell; | ||||
|     private BaseTile _ghostBuilding; | ||||
|     private float _currentRotation = 0f; | ||||
|  | ||||
|     public void SetCurrentBuilding(string buildingId) | ||||
|     public void SetCurrentBuilding(string buildingId, GridLayer layer = GridLayer.Building) | ||||
|     { | ||||
|         _currentBuildingId = buildingId; | ||||
|         _currentLayer = layer; | ||||
|  | ||||
|         // Replace ghost immediately | ||||
|         if (_ghostBuilding == null) return; | ||||
| @@ -23,10 +26,17 @@ public partial class PlacementManager : Node2D | ||||
|         _ghostBuilding = null; | ||||
|     } | ||||
|  | ||||
|     // Add this field to PlacementManager | ||||
|     private float _currentRotation = 0f; | ||||
|     public void SetCurrentLayer(GridLayer layer) | ||||
|     { | ||||
|         _currentLayer = layer; | ||||
|         // Update ghost building if needed | ||||
|         if (_ghostBuilding != null) | ||||
|         { | ||||
|             _ghostBuilding.QueueFree(); | ||||
|             _ghostBuilding = null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Add this method to handle rotation | ||||
|     private void RotateGhost(bool reverse = false) | ||||
|     { | ||||
|         if (_ghostBuilding == null) return; | ||||
| @@ -56,6 +66,8 @@ public partial class PlacementManager : Node2D | ||||
|             _ghostBuilding = (BaseTile)scene.Instantiate(); | ||||
|             _ghostBuilding.SetGhostMode(true); | ||||
|             _ghostBuilding.RotationDegrees = _currentRotation; | ||||
|             _ghostBuilding.ZAsRelative = false; | ||||
|             _ghostBuilding.ZIndex = (int)_currentLayer; // Use layer as Z-index | ||||
|             AddChild(_ghostBuilding); | ||||
|         } | ||||
|  | ||||
| @@ -68,17 +80,18 @@ public partial class PlacementManager : Node2D | ||||
|             Position = centerPos, | ||||
|             CollideWithBodies = true, | ||||
|             CollideWithAreas = true, | ||||
|             CollisionMask = uint.MaxValue // check against all layers/masks | ||||
|             // Exclude = new Godot.Collections.Array<Rid>() // optional, see below | ||||
|             CollisionMask = uint.MaxValue | ||||
|         }; | ||||
|  | ||||
|         var collision = spaceState.IntersectPoint(query, 8); | ||||
|         var canPlace = Grid.IsCellFree(_hoveredCell) && collision.Count == 0; | ||||
|          | ||||
|         // Check if the current cell is free in the current layer | ||||
|         var canPlace = Grid.IsCellFree(_hoveredCell, _currentLayer) &&  | ||||
|                       !Grid.IsAnyCellOccupied(_hoveredCell, GetBlockingLayers()); | ||||
|  | ||||
|         _ghostBuilding.Position = placementPos; | ||||
|         _ghostBuilding.SetGhostMode(canPlace); | ||||
|  | ||||
|  | ||||
|         // Left click to place | ||||
|         if (Input.IsActionPressed("build_tile") && canPlace) | ||||
|         { | ||||
| @@ -87,26 +100,42 @@ public partial class PlacementManager : Node2D | ||||
|  | ||||
|             _ghostBuilding.FinalizePlacement(); | ||||
|             _ghostBuilding.RotationDegrees = _currentRotation; | ||||
|             _ghostBuilding.ZIndex = (int)_currentLayer; | ||||
|  | ||||
|             var buildingData = Registry.GetBuilding(_currentBuildingId); | ||||
|             Grid.OccupyCell(_hoveredCell, _ghostBuilding); | ||||
|             Grid.OccupyCell(_hoveredCell, _ghostBuilding, _currentLayer); | ||||
|  | ||||
|             if (buildingData is { BuildTime: > 0f }) | ||||
|                 _ghostBuilding.StartConstruction(buildingData.BuildTime); | ||||
|  | ||||
|             // Create new ghost for next placement | ||||
|             _ghostBuilding = (BaseTile)scene.Instantiate(); | ||||
|             _ghostBuilding.SetGhostMode(true); | ||||
|             _ghostBuilding.RotationDegrees = _currentRotation; | ||||
|             _ghostBuilding.ZAsRelative = false; | ||||
|             _ghostBuilding.ZIndex = (int)_currentLayer; | ||||
|             AddChild(_ghostBuilding); | ||||
|         } | ||||
|  | ||||
|         if (Input.IsActionPressed("destroy_tile") && !Grid.IsCellFree(_hoveredCell)) | ||||
|         if (Input.IsActionPressed("destroy_tile") && !Grid.IsCellFree(_hoveredCell, _currentLayer)) | ||||
|         { | ||||
|             // Right click to destroy | ||||
|             var building = Grid.GetBuildingAtCell(_hoveredCell); | ||||
|             // Right click to destroy from current layer | ||||
|             var building = Grid.GetBuildingAtCell(_hoveredCell, _currentLayer); | ||||
|             if (building == null) return; | ||||
|             building.QueueFree(); | ||||
|             Grid.FreeCell(_hoveredCell); | ||||
|             Grid.FreeCell(_hoveredCell, _currentLayer); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Define which layers should block placement | ||||
|     private GridLayer[] GetBlockingLayers() | ||||
|     { | ||||
|         return _currentLayer switch | ||||
|         { | ||||
|             GridLayer.Ground => new[] { GridLayer.Ground }, | ||||
|             GridLayer.Building => new[] { GridLayer.Ground, GridLayer.Building }, | ||||
|             GridLayer.Decoration => new[] { GridLayer.Ground, GridLayer.Building, GridLayer.Decoration }, | ||||
|             _ => [] | ||||
|         }; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user