Files
spellforge/csharp/systems/SpatialGridCs.cs
T
joywayerandClaude Opus 4.8 7038505880 fix(csharp): SpatialGridCs 网格常量/坐标偏移对齐 GDScript 外壳(激活陷阱)
休眠的 C# 内核 SpatialGridCs 与权威外壳 spatial_grid.gd 三处不一致,
一旦按 ADR-L1 激活即塌:
- 网格尺寸 64×64 → 128×128(覆盖 4096²→8192²,与 GDScript 一致)
- 补 GridOffset=4096:Insert / QueryCircle 的格映射改为 (coord±radius+4096)/64,
  修复负坐标实体全塌到 0 格、覆盖范围减半的问题。
两骨架 BulletManagerCs/EnemyManagerCs 仅读 _active_count 打计时,无 SoA 常量对齐问题。
dotnet build 通过(0 警告 0 错误)。保持休眠(_cs_node 仍恒 null,GDScript fallback 运行)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 16:32:39 +08:00

102 lines
3.8 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
{
// ⚠️ 必须与 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<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 + 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<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();
}
}