104 lines
3.8 KiB
GDScript
104 lines
3.8 KiB
GDScript
## 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
|