Files
spellforge/csharp/systems/SpatialGridCs.cs
T
2026-07-20 10:56:52 +08:00

99 lines
3.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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<int>[] _grid = new List<int>[GridTotal];
// dirty-list:本帧有变化的格子索引
private readonly HashSet<int> _dirty = new();
// 实体位置缓存:entity_id → cell_index
private readonly Dictionary<int, int> _entityCell = new();
private GodotObject _gdSpatialGrid;
public override void _Ready()
{
for (int i = 0; i < GridTotal; i++)
_grid[i] = new List<int>(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<int>(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();
}
}