// 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 { private const int CellSize = 64; private const int GridWidth = 64; // 64 格×64 格 = 4096×4096 像素覆盖范围 private const int GridHeight = 64; 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 / CellSize), 0, GridWidth - 1); int cy = Mathf.Clamp((int)(pos.Y / 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) / CellSize), 0, GridWidth - 1); int maxCx = Mathf.Clamp((int)((center.X + radius) / CellSize), 0, GridWidth - 1); int minCy = Mathf.Clamp((int)((center.Y - radius) / CellSize), 0, GridHeight - 1); int maxCy = Mathf.Clamp((int)((center.Y + radius) / 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(); } }