extends SceneTree ## 道具系统回归测试(时间驱动): ## L1 双发 → 拾取升级 L2 四发 → 直设 L3 六发(4 直射+2 斜射) ## → 直设 L4 十二发(6 直射+4×22°+2×30°) ## → 满级拾取 +500 分 → 死亡降一级 → 掉落标记敌机死亡产出道具 ## 注意:SceneTree._initialize 中 add_child 后节点 _ready 会在首帧才执行, ## 因此容器引用在首个 _process 里惰性获取。 ## 运行:godot --headless -s tests/powerup_test.gd const GLOBAL_TIMEOUT := 30.0 var _phase := 0 var _step := 0 var _phase_time := 0.0 var _time := 0.0 var _game: Game var _pbullets: Node2D var _powerups: Node2D var _score_before := 0 var _fails: Array[String] = [] func _initialize() -> void: _game = (load("res://scenes/game.tscn") as PackedScene).instantiate() root.add_child(_game) print("[powerup_test] game instantiated") func _fail(msg: String) -> void: _fails.append(msg) print("[FAIL] " + msg) func _next_phase() -> void: _phase += 1 _step = 0 _phase_time = 0.0 func _restart_step_timer() -> void: _step += 1 _phase_time = 0.0 ## 容器引用就绪前不进入任何阶段(_ready 在首帧执行) func _ensure_refs() -> bool: if _pbullets == null: _pbullets = _game.get_node_or_null("PlayerBullets") as Node2D _powerups = _game.get_node_or_null("Powerups") as Node2D return _pbullets != null and _powerups != null ## 清空玩家弹容器并返回清空前数量 func _count_and_clear_bullets() -> int: var children := _pbullets.get_children() var n := children.size() for bullet in children: _pbullets.remove_child(bullet) (bullet as Node).queue_free() return n func _spawn_pickup_at_player() -> void: var pickup := Powerup.new() pickup.position = _game.player.global_position _powerups.add_child(pickup) func _check_volley(expected: int, level_name: String) -> void: var n := _count_and_clear_bullets() Input.action_release("fire") if n != expected: _fail("%s volley=%d expected %d" % [level_name, n, expected]) else: print("[powerup_test] %s volley ok: %d bullets" % [level_name, n]) func _finish() -> bool: if _fails.is_empty(): print("[powerup_test] PASS") else: print("[powerup_test] FAIL: %d issue(s)" % _fails.size()) quit(0 if _fails.is_empty() else 1) return true func _process(delta: float) -> bool: _time += delta if _time > GLOBAL_TIMEOUT: _fail("global timeout in phase %d" % _phase) return _finish() if not _ensure_refs(): return false _phase_time += delta match _phase: 0: if _step == 0: Input.action_press("fire") _restart_step_timer() elif _phase_time >= 0.1: _check_volley(2, "L1") _next_phase() 1: match _step: 0: _spawn_pickup_at_player() _restart_step_timer() 1: if _phase_time >= 0.6: if _game.player.weapon_level != 2: _fail("pickup did not level up: level=%d" % _game.player.weapon_level) elif _powerups.get_child_count() != 0: _fail("pickup not consumed: %d left" % _powerups.get_child_count()) else: print("[powerup_test] pickup -> L2 ok") Input.action_press("fire") _restart_step_timer() 2: if _phase_time >= 0.1: _check_volley(4, "L2") _next_phase() 2: match _step: 0: _game.player.weapon_level = 3 Input.action_press("fire") _restart_step_timer() 1: if _phase_time >= 0.1: _check_volley(6, "L3") _game.player.weapon_level = 4 Input.action_press("fire") _restart_step_timer() 2: if _phase_time >= 0.1: _check_volley(12, "L4") _next_phase() 3: if _step == 0: _score_before = _game.score _spawn_pickup_at_player() _restart_step_timer() elif _phase_time >= 0.6: if _game.player.weapon_level != 4: _fail("capped pickup changed level: %d" % _game.player.weapon_level) elif _game.score != _score_before + 500: _fail("capped pickup score: %d -> %d (expected +500)" % [_score_before, _game.score]) else: print("[powerup_test] capped pickup +500 ok") _next_phase() 4: if _step == 0: _game.player.die() _restart_step_timer() elif _phase_time >= 0.1: if _game.player.weapon_level != 3: _fail("death downgrade: level=%d expected 3" % _game.player.weapon_level) else: print("[powerup_test] death downgrade 4->3 ok") _next_phase() 5: if _step == 0: var enemy := EnemySmall.new() enemy.setup(270.0, 200.0, 100.0) enemy.drops_powerup = true _game._enemies.add_child(enemy) enemy.apply_damage(99) _restart_step_timer() elif _phase_time >= 0.5: if _powerups.get_child_count() != 1: _fail("dropper enemy produced %d powerups (expected 1)" % _powerups.get_child_count()) else: print("[powerup_test] dropper spawn ok") _next_phase() 6: if _step == 0: # 清掉前面阶段残留的道具再验证 Boss 必掉 P for pickup in _powerups.get_children(): _powerups.remove_child(pickup) (pickup as Node).queue_free() var boss := _game.spawn_boss(1.0) boss.apply_damage(99999) _restart_step_timer() elif _phase_time >= 1.2: var kinds := [] for pickup in _powerups.get_children(): kinds.append((pickup as Powerup).kind) if kinds != [Powerup.Kind.POWER]: _fail("boss drop kinds=%s expected [POWER]" % str(kinds)) else: print("[powerup_test] boss always drops POWER ok") return _finish() return false