初次提交

This commit is contained in:
2026-07-20 10:56:52 +08:00
commit 7bcc0026e0
462 changed files with 50191 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
// 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();
}
}
+1
View File
@@ -0,0 +1 @@
uid://d3j8ulrpquwkh