## ZoneManager — 地面效果区域管理(Autoload: ZoneManager) ## 权威来源:architecture_design.md §8 ADR-R5-N1(P6-N71 tick 用 while + -=) ## SoA PackedFloat32Array stride=8;基于 SpatialGrid 空间索引(禁用 Area2D) ## [0]=cx [1]=cy [2]=radius [3]=status_id [4]=duration [5]=tick_interval [6]=tick_accum [7]=owner_id ## ## 注:本 GDScript 外壳承载 tick 热循环为 S5 功能验收路径(P-S5-04); ## C# 子节点 ZoneManagerCs 驱动的压力路径(P-S5-ZM-01,64 区×1000 敌 <1ms)留作后续优化, ## 与项目既有 C# 回退策略(R-08)一致。 extends Node const ZONE_STRIDE: int = 8 const MAX_ZONES: int = 64 var _zone_data: PackedFloat32Array = PackedFloat32Array() var _active_zones: int = 0 func _ready() -> void: _zone_data.resize(MAX_ZONES * ZONE_STRIDE) ## 生成一个地面效果区域;满员时顶掉最旧(index 0 前移) func spawn_zone(cx: float, cy: float, radius: float, status_id: int, duration: float, tick_interval: float, owner_id: int) -> void: if _active_zones >= MAX_ZONES: # 顶掉最旧 Zone(index 0),整体前移(O(N),N≤64) for j in range(ZONE_STRIDE, _active_zones * ZONE_STRIDE): _zone_data[j - ZONE_STRIDE] = _zone_data[j] _active_zones -= 1 var base: int = _active_zones * ZONE_STRIDE _zone_data[base + 0] = cx _zone_data[base + 1] = cy _zone_data[base + 2] = radius _zone_data[base + 3] = float(status_id) _zone_data[base + 4] = duration _zone_data[base + 5] = maxf(0.01, tick_interval) _zone_data[base + 6] = 0.0 # tick_accum _zone_data[base + 7] = float(owner_id) _active_zones += 1 func _physics_process(delta: float) -> void: if _active_zones == 0: return var i: int = 0 while i < _active_zones: var base: int = i * ZONE_STRIDE _zone_data[base + 4] -= delta # duration 倒计时 _zone_data[base + 6] += delta # tick_accum 累积 # 速率限制:外层 if 守卫 + 内层 while + -= tick_interval(P6-N71,保留余量精度) # SpatialGrid.query_circle 仅在 if 守卫内查询一次,不在 while 内重复查询 if _zone_data[base + 6] >= _zone_data[base + 5]: var cx: float = _zone_data[base + 0] var cy: float = _zone_data[base + 1] var rad: float = _zone_data[base + 2] var sid: int = int(_zone_data[base + 3]) var oid: int = int(_zone_data[base + 7]) var targets: PackedInt32Array = SpatialGrid.query_circle(Vector2(cx, cy), rad) while _zone_data[base + 6] >= _zone_data[base + 5]: _zone_data[base + 6] -= _zone_data[base + 5] # -= tick_interval 保留余量 for t_id in targets: StatusManager.apply(t_id, sid, 1, -1.0, oid) # stacks=1, duration=默认, owner if _zone_data[base + 4] <= 0.0: # duration 耗尽 → swap-and-pop 移除 + VFX 淡出 VFXManager.play("zone_expire", Vector2(_zone_data[base + 0], _zone_data[base + 1])) if i < _active_zones - 1: var last: int = (_active_zones - 1) * ZONE_STRIDE for k in ZONE_STRIDE: _zone_data[base + k] = _zone_data[last + k] _active_zones -= 1 else: i += 1 func get_active_count() -> int: return _active_zones func reset() -> void: _active_zones = 0 # 惰性清理:SoA 数据不清零,_active_zones 控制有效范围