// SpatialGridCs — 2D 空间哈希网格 C# 实现 // 职责:固定 Cell 网格 dirty-list 方案重建 + query_circle // 权威:architecture_design.md §4.3, ADR-L1 // // ADR-L1 规厙1:内层循环体内禁止 GodotObject.Call() / .Set() using Godot; using System; using System.Collections.Generic; public partial class SpatialGridCs : Node { // ⚠️ 必须与 GDScript 外壳 spatial_grid.gd 的 CELL_SIZE / GRID_COLS / GRID_ROWS / GRID_OFFSET // 逐一对齐;否则激活 C# 内核后两条路径格子映射不一致(负坐标塌陷 + 覆盖范围减半)。 private const int CellSize = 64; private const int GridWidth = 128; // 128×128 格 × 64px = 8192×8192 覆盖范围 private const int GridHeight = 128; private const int GridOffset = 4096; // 世界中心映射到网格中心(支持负坐标) private const int GridTotal = GridWidth * GridHeight; // 网格主体:一维数组模拟二维,直接索引 grid[x + y * GridWidth] private readonly List[] _grid = new List[GridTotal]; // dirty-list:本帧有变化的格子索引 private readonly HashSet _dirty = new(); // 实体位置缓存:entity_id → cell_index private readonly Dictionary _entityCell = new(); private GodotObject _gdSpatialGrid; public override void _Ready() { for (int i = 0; i < GridTotal; i++) _grid[i] = new List(4); _gdSpatialGrid = Engine.GetSingleton("SpatialGrid"); _gdSpatialGrid.Set("_cs_node", this); GD.Print($"[SpatialGridCs] 初始化完成,{GridWidth}×{GridHeight} 格,格子 {CellSize} px"); } // 插入或更新实体位置(由 EnemyManager 每帧调用) public void Insert(int entityId, Vector2 pos) { int cx = Mathf.Clamp((int)((pos.X + GridOffset) / CellSize), 0, GridWidth - 1); int cy = Mathf.Clamp((int)((pos.Y + GridOffset) / CellSize), 0, GridHeight - 1); int cellIdx = cx + cy * GridWidth; if (_entityCell.TryGetValue(entityId, out int oldCell)) { if (oldCell == cellIdx) return; // 未跨格 _grid[oldCell].Remove(entityId); _dirty.Add(oldCell); } _grid[cellIdx].Add(entityId); _entityCell[entityId] = cellIdx; _dirty.Add(cellIdx); } // 重建(仅清空 dirty 集合) public void Rebuild() => _dirty.Clear(); // 圆形查询:返回 center 半径 radius 内的所有 entity_id // ADR-L1 规厙1:内层 foreach 内禁止 Call() public int[] QueryCircle(Vector2 center, float radius) { int minCx = Mathf.Clamp((int)((center.X - radius + GridOffset) / CellSize), 0, GridWidth - 1); int maxCx = Mathf.Clamp((int)((center.X + radius + GridOffset) / CellSize), 0, GridWidth - 1); int minCy = Mathf.Clamp((int)((center.Y - radius + GridOffset) / CellSize), 0, GridHeight - 1); int maxCy = Mathf.Clamp((int)((center.Y + radius + GridOffset) / CellSize), 0, GridHeight - 1); var result = new List(32); float radSq = radius * radius; for (int cy = minCy; cy <= maxCy; cy++) for (int cx = minCx; cx <= maxCx; cx++) { // ADR-L1 规厙1:内层循环约束—不得调用 GodotObject foreach (int id in _grid[cx + cy * GridWidth]) result.Add(id); } return result.ToArray(); } // 移除实体 public void Remove(int entityId) { if (!_entityCell.TryGetValue(entityId, out int cell)) return; _grid[cell].Remove(entityId); _entityCell.Remove(entityId); _dirty.Add(cell); } // 清空(关卡重置) public void Clear() { foreach (var cell in _grid) cell.Clear(); _entityCell.Clear(); _dirty.Clear(); } }