Files
spellforge/scripts/autoloads/minion_manager.gd
T
2026-07-20 10:56:52 +08:00

104 lines
3.8 KiB
GDScript
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.
## MinionManager — 玩家召唤物管理(Autoload: MinionManager
## 权威来源:architecture_design.md §5.12 + ADR-R4-N4(独立管理器,方案 B)
## 数量上限小(MAX_MINIONS=20ADR-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