Files
hello-godot/tests/funnel_test.gd
T

298 lines
10 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.
extends SceneTree
## 浮游炮机型回归测试(时间驱动):
## A) 默认战斗机无浮游炮;GameSession 传递变体 → 新局为浮游炮机
## B) 等级 1/2/3/4 → 浮游炮 0/2/4/6 台
## C) 常态散开:侧位浮游炮激光打得中侧旁堡垒,打不中中线堡垒
## D) 按 I 聚焦:中线高血堡垒被合体粗激光快速击毁
## E) focus 输入映射含 I 键
## 运行:godot --headless -s tests/funnel_test.gd
const GLOBAL_TIMEOUT := 40.0
var _phase := 0
var _step := 0
var _phase_time := 0.0
var _time := 0.0
var _game: Game
var _fort_center: EnemyFortress
var _fort_left: EnemyFortress
var _fort_right: EnemyFortress
var _fort_focus: EnemyFortress
var _fails: Array[String] = []
func _initialize() -> void:
GameSession.plane_variant = 0
_game = (load("res://scenes/game.tscn") as PackedScene).instantiate()
root.add_child(_game)
print("[funnel_test] game A instantiated (fighter)")
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:
GameSession.plane_variant = 0
if _fails.is_empty():
print("[funnel_test] PASS")
else:
print("[funnel_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()
_game.clear_enemy_bullets()
# 防止上一阶段流弹干扰断言
_game.player.invuln = 6.0
func _spawn_fortress(x: float, y: float, hp: int) -> EnemyFortress:
var fortress := EnemyFortress.new()
fortress.setup(x, y, 1.0)
_game._enemies.add_child(fortress)
fortress.hp = hp
return fortress
func _process(delta: float) -> bool:
_time += delta
if _time > GLOBAL_TIMEOUT:
_fail("global timeout in phase %d" % _phase)
return _finish()
if _game._enemies == null:
return false
_phase_time += delta
match _phase:
0:
# A: 战斗机默认无浮游炮
if _step == 0:
if _game.player._funnels.size() != 0:
_fail("fighter has funnels: %d" % _game.player._funnels.size())
else:
print("[funnel_test] fighter has no funnels ok")
_restart_step()
elif _phase_time >= 0.1:
_next_phase()
1:
# A2: 切换变体 → 新局生效(旧 game 立即移除,避免跨实例物理污染)
if _step == 0:
GameSession.plane_variant = 1
var game_b := (load("res://scenes/game.tscn") as PackedScene).instantiate()
root.add_child(game_b)
_game.queue_free()
_game = game_b
_restart_step()
elif _game.player != null:
if _game.player.variant != Player.Variant.FUNNEL:
_fail("variant not applied: %d" % _game.player.variant)
elif _game.player._funnels.size() != 0:
_fail("L1 should have 0 funnels, got %d" % _game.player._funnels.size())
else:
print("[funnel_test] variant applied, L1=0 funnels ok")
_next_phase()
2:
# B: 等级→浮游炮数 + 展开位置分散(两两间距 ≥30px)
if _step == 0:
for level in [2, 3, 4]:
_game.player.weapon_level = level
_game.player._sync_funnels()
var expected: int = (level - 1) * 2
if _game.player._funnels.size() != expected:
_fail("L%d funnels=%d expected %d" % [level, _game.player._funnels.size(), expected])
if _fails.is_empty():
print("[funnel_test] L2/L3/L4 = 2/4/6 funnels ok")
_restart_step()
elif _phase_time >= 0.9:
var positions: Array = []
for funnel in _game.player._funnels:
positions.append((funnel as Node2D).position)
var min_dist := 9999.0
for a in positions.size():
for b in range(a + 1, positions.size()):
min_dist = minf(min_dist, (positions[a] as Vector2).distance_to(positions[b]))
if min_dist < 30.0:
_fail("funnels not spread: min pairwise dist %.1f" % min_dist)
else:
print("[funnel_test] spread formation min pairwise dist %.1f ok" % min_dist)
_next_phase()
3:
# E: focus 映射含 I(73)
if _step == 0:
var focus_ok := false
for event in InputMap.action_get_events("focus"):
if event is InputEventKey and (event as InputEventKey).physical_keycode == 73:
focus_ok = true
if not focus_ok:
_fail("focus action missing I key")
else:
print("[funnel_test] focus=I mapping ok")
_next_phase()
4:
# C: 散开覆盖(L2:±38 侧位)。目标放高处、玩家放低处,保证足够照射窗口
match _step:
0:
_clear_field()
_game.player.weapon_level = 2
_game.player._sync_funnels()
_game.player.position = Vector2(270.0, 700.0)
_fort_center = _spawn_fortress(270.0, 150.0, 60) # 仅机体激光 18dps
_fort_left = _spawn_fortress(234.0, 150.0, 10) # 左浮游炮位(-36)7dps
_fort_right = _spawn_fortress(306.0, 150.0, 10) # 右浮游炮位(+36)
Input.action_press("fire")
_restart_step()
1:
if _phase_time >= 2.2:
var center_stands := is_instance_valid(_fort_center) and not _fort_center.dead
var left_dead := (not is_instance_valid(_fort_left)) or _fort_left.dead
var right_dead := (not is_instance_valid(_fort_right)) or _fort_right.dead
if center_stands and left_dead and right_dead:
print("[funnel_test] spread: sides killed, center survives (hp=%d) ok" % [_fort_center.hp])
else:
_fail("spread outcome wrong: center_stands=%s L=%s R=%s" % [str(center_stands), str(left_dead), str(right_dead)])
Input.action_release("fire")
_next_phase()
5:
# D: 聚焦粗激光(L4 合体 = (18+6×7)×1.5 = 90dps)
match _step:
0:
_clear_field()
_game.player.weapon_level = 4
_game.player._sync_funnels()
_game.player.position = Vector2(270.0, 700.0)
_fort_focus = _spawn_fortress(270.0, 150.0, 60) # 60hp / 90dps ≈ 0.67s
Input.action_press("fire")
Input.action_press("focus")
_restart_step()
1:
var killed := (not is_instance_valid(_fort_focus)) or _fort_focus.dead
if killed:
var dps := _game.player._focus_beam.dps
if dps < 89.0 or dps > 91.0:
_fail("focus dps=%.1f expected 90.0" % dps)
elif _phase_time < 1.6:
print("[funnel_test] focused beam dps=%.0f, killed 60hp in %.2fs ok" % [dps, _phase_time])
else:
_fail("focused beam too slow: %.2fs" % _phase_time)
Input.action_release("fire")
Input.action_release("focus")
_next_phase()
elif _phase_time > 3.0:
_fail("focused beam failed to kill (hp=%d)" % _fort_focus.hp)
Input.action_release("fire")
Input.action_release("focus")
_next_phase()
6:
# E2: 聚焦期间移动速度减半(0.5s 期望位移 ~85px,全速为 170px)
match _step:
0:
_game.player.position = Vector2(100.0, 700.0)
Input.action_press("fire")
Input.action_press("focus")
Input.action_press("move_right")
_restart_step()
1:
if _phase_time >= 0.5:
Input.action_release("move_right")
var dx := _game.player.position.x - 100.0
if dx < 45.0 or dx > 125.0:
_fail("focus speed wrong: dx=%.0f px in 0.5s (expect ~85)" % dx)
else:
print("[funnel_test] focus half-speed ok (dx=%.0f px)" % dx)
Input.action_release("fire")
Input.action_release("focus")
_next_phase()
7:
# E3: 侧身帧状态切换;机头不歪不变量(无旋转、无缩放变换)
match _step:
0:
Input.action_press("move_left")
_restart_step()
1:
if _phase_time >= 0.5:
var state := _game.player._bank_state
if state != -1:
_fail("bank state=%d expected -1 (left)" % state)
elif absf(_game.player.sprite.rotation) > 0.001 or absf(_game.player.sprite.scale.x - 1.0) > 0.001:
_fail("bank must be sprite-swap only: rot=%.3f scale.x=%.3f" % [_game.player.sprite.rotation, _game.player.sprite.scale.x])
else:
print("[funnel_test] bank-left state ok (nose straight, no transform)")
Input.action_release("move_left")
_restart_step()
2:
if _phase_time >= 0.6:
if _game.player._bank_state != 0:
_fail("bank not restored: state=%d" % _game.player._bank_state)
else:
print("[funnel_test] bank restored to neutral ok")
_next_phase()
8:
# E4: 战斗机(P-38)侧身帧真实切换(素材已生成时应换贴图)
match _step:
0:
var fighter := Player.new()
fighter.variant = Player.Variant.FIGHTER
fighter.position = Vector2(270.0, 700.0)
_game.add_child(fighter)
Input.action_press("move_left")
_restart_step()
1:
if _phase_time >= 0.5:
var p := _game.get_child(_game.get_child_count() - 1) as Player
if p == null:
_fail("fighter not found in tree")
elif p._tex_bank_left == null:
_fail("P-38-bank-left.png not loaded (missing art?)")
elif p.sprite.texture != p._tex_bank_left:
_fail("bank texture not swapped for fighter")
elif p._bank_state != -1:
_fail("fighter bank state=%d expected -1" % p._bank_state)
else:
print("[funnel_test] fighter P-38 bank texture swap ok")
Input.action_release("move_left")
_next_phase()
9:
# E5: 雷光(FUNNEL)侧身帧真实切换 + 平飞帧已是美术 PNG
match _step:
0:
var funnel_p := Player.new()
funnel_p.variant = Player.Variant.FUNNEL
funnel_p.position = Vector2(270.0, 700.0)
_game.add_child(funnel_p)
Input.action_press("move_right")
_restart_step()
1:
if _phase_time >= 0.5:
var p := _game.get_child(_game.get_child_count() - 1) as Player
if p == null:
_fail("funnel player not found in tree")
elif p._tex_neutral != null and p._tex_neutral == Player.TEX_FUNNEL:
_fail("funnel neutral still SVG placeholder (plane_funnel.png not loaded?)")
elif p._tex_bank_right == null:
_fail("plane_funnel-bank-right.png not loaded (missing art?)")
elif p.sprite.texture != p._tex_bank_right:
_fail("bank texture not swapped for funnel variant")
elif p._bank_state != 1:
_fail("funnel bank state=%d expected 1" % p._bank_state)
else:
print("[funnel_test] funnel plane_funnel bank texture swap ok")
Input.action_release("move_right")
return _finish()
return false