extends SceneTree ## 地面单位回归测试(时间驱动): ## A) 独立堡垒:随地面滚动(~120px/s)、有开火产出敌弹 ## B) Game 集成:堡垒击杀 +300、船击杀 +800 ## C) 碰撞规则:玩家机体叠在堡垒上不掉命;玩家子弹真实命中堡垒 ## 运行:godot --headless -s tests/ground_test.gd const GLOBAL_TIMEOUT := 25.0 var _phase := 0 var _step := 0 var _phase_time := 0.0 var _time := 0.0 var _fortress: EnemyFortress var _ship: EnemyShip var _game: Game var _bullets: Node2D var _start_y := 0.0 var _bullet_time := 0.0 var _score_before := 0 var _lives_before := 3 var _fails: Array[String] = [] func _initialize() -> void: _bullets = Node2D.new() _bullets.add_to_group("enemy_bullets") root.add_child(_bullets) _fortress = EnemyFortress.new() _fortress.setup(270.0, 200.0, 1.0) root.add_child(_fortress) _start_y = 200.0 print("[ground_test] phase A: standalone fortress") 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() -> void: _step += 1 _phase_time = 0.0 func _finish() -> bool: if _fails.is_empty(): print("[ground_test] PASS") else: print("[ground_test] FAIL: %d issue(s)" % _fails.size()) quit(0 if _fails.is_empty() else 1) return true ## 清场(敌机+敌弹),保证后续断言确定性 func _clear_field() -> void: for enemy in _game._enemies.get_children(): (enemy as Node).queue_free() for bullet in _game._enemy_bullets.get_children(): bullet.queue_free() func _process(delta: float) -> bool: _time += delta if _time > GLOBAL_TIMEOUT: _fail("global timeout in phase %d" % _phase) return _finish() _phase_time += delta match _phase: 0: if _bullets.get_child_count() > 0: _bullet_time += delta if _phase_time >= 4.0: var moved := _fortress.position.y - _start_y if moved < 380.0 or moved > 620.0: _fail("fortress scroll wrong: moved %.0f px in 4s (expect ~480)" % moved) elif _bullet_time < 0.3: _fail("fortress fired too little: %.2fs" % _bullet_time) else: print("[ground_test] fortress scroll+fire ok (moved %.0f px, bullet %.1fs)" % [moved, _bullet_time]) _fortress.apply_damage(999) _next_phase() 1: if not is_instance_valid(_fortress): _next_phase() elif _phase_time > 2.0: _fail("fortress not freed after kill") _next_phase() 2: if _game == null: _game = (load("res://scenes/game.tscn") as PackedScene).instantiate() root.add_child(_game) elif _game._enemies != null: _ship = EnemyShip.new() _ship.setup(270.0, 300.0, 1.0) _game._enemies.add_child(_ship) var f2 := EnemyFortress.new() f2.setup(150.0, 300.0, 1.0) _game._enemies.add_child(f2) _score_before = _game.score f2.apply_damage(999) _next_phase() 3: if _phase_time >= 0.6: if _game.score != _score_before + 300: _fail("fortress score: %d -> %d (expected +300)" % [_score_before, _game.score]) else: print("[ground_test] fortress kill +300 ok") _score_before = _game.score _ship.apply_damage(999) _next_phase() 4: if _phase_time >= 0.6: if _game.score != _score_before + 800: _fail("ship score: %d -> %d (expected +800)" % [_score_before, _game.score]) else: print("[ground_test] ship kill +800 ok") _next_phase() 5: match _step: 0: _clear_field() _lives_before = _game.lives var f3 := EnemyFortress.new() f3.setup(_game.player.global_position.x, _game.player.global_position.y, 1.0) _game._enemies.add_child(f3) _restart_step() 1: if _phase_time >= 0.8: if _game.lives != _lives_before or not _game.player.active: _fail("ramming fortress hurt player: lives %d->%d active=%s" % [ _lives_before, _game.lives, str(_game.player.active)]) else: print("[ground_test] player overlaps fortress unharmed ok") _next_phase() 6: match _step: 0: _clear_field() var f4 := EnemyFortress.new() f4.setup(270.0, 300.0, 1.0) _game._enemies.add_child(f4) var bullet := PlayerBullet.new() bullet.direction = Vector2.UP (_game.get_node("PlayerBullets") as Node2D).add_child(bullet) bullet.position = Vector2(270.0, 430.0) _restart_step() 1: if _phase_time >= 0.7: if _game._enemies.get_child_count() == 0: _fail("fortress missing before bullet-hit check") else: var target := _game._enemies.get_child(0) as EnemyFortress if target.hp != 3: _fail("player bullet did not hit fortress: hp=%d expected 3" % target.hp) else: print("[ground_test] player bullet hits fortress ok") return _finish() return false