Files
rthythm_archor_game/tests/test_dash_overlap_separation.gd
T
2026-07-06 22:59:47 -07:00

131 lines
4.6 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
## 2026-07-06 策划契约:冲刺穿人机制保留,但其余时间玩家不得与敌人重叠。
## 旧行为:dash_through 落点嵌在敌人体内时,恢复碰撞的逻辑"干等分离"——
## 嵌着就无限期保持幽灵态,玩家可长时间站在小怪/Boss 身体里。
## 新行为:嵌入时沿冲刺方向每物理帧温和推出(保留"从远侧穿出"手感),
## 推清后恢复身体碰撞。
const ENEMY_BODY_BIT := 1 << 6
var failures: Array[String] = []
func _init() -> void:
_run.call_deferred()
func _run() -> void:
root.size = Vector2i(1152, 648)
var settings := root.get_node_or_null("GameSettings")
if settings != null:
settings.call("set_difficulty", &"easy")
await _check_embedded_dash_gets_separated()
await _check_clean_dash_restores_immediately()
_finish()
func _make_actors() -> Dictionary:
var player := (load("res://scenes/characters/player.tscn") as PackedScene).instantiate() as CharacterBody2D
root.add_child(player)
var minion := (load("res://scenes/enemies/minion.tscn") as PackedScene).instantiate() as CharacterBody2D
root.add_child(minion)
var behavior := minion.get_node_or_null("MinionBehavior")
if behavior != null:
behavior.set("enabled", false)
minion.set("approach_direction", 0.0)
return {"player": player, "minion": minion}
func _dash_action() -> Resource:
var script: Script = load("res://resources/action_data.gd")
var action: Resource = script.new()
var action_tags: Array[StringName] = [&"dash_through"]
action.set("id", &"test_dash_embed")
action.set("action_tags", action_tags)
action.set("move_mult_x", 1.0)
action.set("startup_beats", 0.25)
action.set("active_beats", 0.25)
action.set("recovery_beats", 0.25)
return action
func _check_embedded_dash_gets_separated() -> void:
var actors := _make_actors()
var player := actors["player"] as CharacterBody2D
var minion := actors["minion"] as CharacterBody2D
await process_frame
player.global_position = Vector2(500.0, 300.0)
# 冲刺时长 0.75 拍 × 0.5s × 220px/s ≈ 82px:小怪摆在 80px 处,落点必然嵌入。
minion.global_position = Vector2(580.0, 300.0)
await physics_frame
var motion: Node = player.get_node("MotionExecutor")
motion.call("execute", _dash_action(), Vector2.RIGHT, 0.5, 220.0)
_expect((int(player.collision_mask) & ENEMY_BODY_BIT) == 0, "冲刺期间保持幽灵态(机制保留)")
# 裸场景无 handle_movement 驱动,手动按真实链路推进:tick → move_and_slide。
var finished := false
for _index: int in range(90):
player.velocity = motion.call("tick", 1.0 / 60.0)
player.move_and_slide()
await physics_frame
if not bool(motion.get("active")):
finished = true
break
_expect(finished, "冲刺应在 90 帧内结束")
# 新契约:嵌入不再无限期保持幽灵态——有限帧内推出并恢复碰撞。
var separated := false
for _index: int in range(45):
await physics_frame
if not bool(motion.call("is_ghosting")):
separated = true
break
_expect(separated, "落点嵌入后应在 45 帧内被推出并恢复碰撞(旧行为:无限期重叠)")
if separated:
_expect((int(player.collision_mask) & ENEMY_BODY_BIT) != 0, "分离后玩家身体碰撞恢复")
_expect(player.global_position.x > minion.global_position.x,
"应从冲刺方向的远侧穿出(不被弹回入口侧)")
var gap := absf(player.global_position.x - minion.global_position.x)
_expect(gap >= 18.0, "分离后身体不再重叠(间距 %.1f px ≥ 半宽和)" % gap)
player.free()
minion.free()
func _check_clean_dash_restores_immediately() -> void:
# 落点干净(前方无敌人)时行为与旧版一致:结束即恢复。
var player := (load("res://scenes/characters/player.tscn") as PackedScene).instantiate() as CharacterBody2D
root.add_child(player)
await process_frame
player.global_position = Vector2(500.0, 300.0)
var motion: Node = player.get_node("MotionExecutor")
motion.call("execute", _dash_action(), Vector2.RIGHT, 0.5, 220.0)
for _index: int in range(90):
player.velocity = motion.call("tick", 1.0 / 60.0)
player.move_and_slide()
await physics_frame
if not bool(motion.get("active")):
break
await physics_frame
await physics_frame
_expect(not bool(motion.call("is_ghosting")), "干净落点:冲刺结束立即恢复碰撞")
_expect((int(player.collision_mask) & ENEMY_BODY_BIT) != 0, "干净落点:敌人身体位复位")
player.free()
func _expect(condition: bool, label: String) -> void:
if not condition:
failures.append(label)
func _finish() -> void:
paused = false
if failures.is_empty():
print("PASS dash overlap separation")
quit(0)
else:
for failure: String in failures:
push_error(failure)
quit(1)