## MinionManager — 玩家召唤物管理(Autoload: MinionManager) ## 权威来源:architecture_design.md §5.12 + ADR-R4-N4(独立管理器,方案 B) ## 数量上限小(MAX_MINIONS=20,ADR-R4-N4/plan S5),不需 C# 热路径,用 Array[Dictionary] ## 炮台型(Stationary)AI:定点对范围内最近敌人开火 ## ## 注:plan S5 提到 "SoA stride=8",但 §5.12 权威实现明确用 Array[Dictionary] ## 且 "不需要 C# 热路径",故按 §5.12 实现;SoA 化为后续可选优化。 extends Node const MAX_MINIONS: int = 20 # ADR-R4-N4:同场最大召唤物,超出 FIFO 顶替最旧 const DEFAULT_LIFETIME: float = 30.0 ## 活跃召唤物:{ id, owner_id, pos, lifetime_rem, range, fire_interval, fire_cd, ## damage, bullet_speed, bullet_radius, bullet_lifetime, damage_type } var _minions: Array = [] var _next_id: int = 0 ## def: Dictionary(项目无 .tres,用字典描述召唤物面板) ## 可选键:lifetime, range, fire_interval, damage, bullet_speed, bullet_radius, ## bullet_lifetime, damage_type func spawn_minion(def: Dictionary, owner_id: int, pos: Vector2) -> int: if _minions.size() >= MAX_MINIONS: _expire(_minions[0]["id"]) # FIFO:顶掉最旧 var lifetime: float = float(def.get("lifetime", DEFAULT_LIFETIME)) if lifetime <= 0.0: lifetime = DEFAULT_LIFETIME var id: int = _next_id _next_id = (_next_id + 1) % 100000 var entry: Dictionary = { "id": id, "owner_id": owner_id, "pos": pos, "lifetime_rem": lifetime, "range": float(def.get("range", 350.0)), "fire_interval": maxf(0.05, float(def.get("fire_interval", 0.8))), "fire_cd": 0.0, "damage": float(def.get("damage", 4.0)), "bullet_speed": float(def.get("bullet_speed", 360.0)), "bullet_radius": float(def.get("bullet_radius", 5.0)), "bullet_lifetime": float(def.get("bullet_lifetime", 2.0)), "damage_type": int(def.get("damage_type", 0)), } _minions.append(entry) EventBus.emit(EventID.MINION_SPAWNED, { "minion_id": id, "owner_id": owner_id, "current_count": _minions.size(), }) return id func _physics_process(delta: float) -> void: var i: int = 0 while i < _minions.size(): var m: Dictionary = _minions[i] m["lifetime_rem"] -= delta if m["lifetime_rem"] <= 0.0: _expire(m["id"]) continue # 不自增 i:当前槽已被 remove_at 后移的元素填补 # 炮台 AI:冷却到点且范围内有敌人则朝最近敌人开火 m["fire_cd"] -= delta if m["fire_cd"] <= 0.0: var pos: Vector2 = m["pos"] var rng: float = m["range"] if SpatialGrid.query_circle(pos, rng).size() > 0: var tgt: Vector2 = EnemyManager.get_nearest_pos(pos, rng) var dir: Vector2 = (tgt - pos) dir = dir.normalized() if dir.length() > 0.001 else Vector2.RIGHT BulletManager.spawn_bullet( pos, dir * m["bullet_speed"], m["bullet_lifetime"], m["bullet_radius"], m["damage"], 1.0, m["damage_type"], m["owner_id"], 0, 0.0, {}) m["fire_cd"] = m["fire_interval"] i += 1 func _expire(minion_id: int) -> void: for i in _minions.size(): if _minions[i]["id"] == minion_id: var owner: int = _minions[i]["owner_id"] _minions.remove_at(i) EventBus.emit(EventID.MINION_EXPIRED, { "minion_id": minion_id, "owner_id": owner, "current_count": _minions.size(), }) return func recall_all(owner_id: int = -1) -> void: # owner_id<0 → 全部召回(Run 结束);否则仅该 owner var ids: Array = [] for m in _minions: if owner_id < 0 or m["owner_id"] == owner_id: ids.append(m["id"]) for mid in ids: _expire(mid) func get_count(owner_id: int = -1) -> int: if owner_id < 0: return _minions.size() var c: int = 0 for m in _minions: if m["owner_id"] == owner_id: c += 1 return c func reset() -> void: _minions.clear() _next_id = 0