extends Node2D # transfer_mode 意味着检测 portal_key,然后显示 @export var transfer_mode := true # 脚步声周期时间 @export var footstep_wait_time := 0.5 # 追击速度 @export var speed := 50 # 转身范围,超过这个范围/在这个范围内不 hidden 都会转身 @export var trun_back_detact_x := 150 # 抓取范围,优先 catch_front 播放 猪头怪抓盒子猫 # 其次 catch_nearby 播放 猪头怪抓盒子猫后段 @export var catch_front_x_range := Vector2(55 - 3, 55 + 3) @export var catch_nearby_x_range := Vector2(-10, 55) # 观测范围,在观测范围内进入盒子仍旧会被抓 @export var watching_x := 180 # 允许被抓后重开游戏 var allow_restart_game := true @onready var sfx_footstep = $SfxFootstep as Sfx2D @onready var sprite2d = $AnimatedSprite2D as AnimatedSprite2D # 检测到猫钻进盒子的过程 var cat_hiding_detacted = false var footstep_timer := Timer.new() var player var camera var catty_canvas_layer # 运动&检测, 初始为 false var move_and_detact = false: set(val): move_and_detact = val toggle_footstep_sfx(move_and_detact) if val and is_node_ready(): sprite2d.play("猪头怪走路") # 启用的瞬间先执行检测 _physics_process(0.0) func _ready() -> void: SceneManager.get_ground().ready.connect(_on_ground_ready) # 抓盒子猫后,传送回初始场景,跳过其 intro 过程(从纸面逐渐进入黑板粉笔空间) ArchiveManager.archive.set_global_entry("c02_boxcat_skip_intro", true) func _on_ground_ready(): player = SceneManager.get_player() camera = SceneManager.get_camera_marker() catty_canvas_layer = SceneManager.get_ground().get_node("盒子猫CanvasLayer") var archive = ArchiveManager.archive as AssembledArchive var portal = archive.entrance_portal var chasing_started = ArchiveManager.get_global_value("c02_boxcat_chasing_started", false) if archive.current_scene == "c02_s13": # 开始追逐后,禁用二楼两只 if chasing_started and not transfer_mode: queue_free() return elif not chasing_started: if transfer_mode: queue_free() return if portal == "left" and name != &"追猫猪头怪_左侧": queue_free() return if portal != "left" and name != &"追猫猪头怪_右侧": queue_free() return # c02_s13 开始追逐; call_deferred 延时设置 ArchiveManager.archive.set_global_entry.call_deferred( "c02_boxcat_chasing_started", true ) elif not chasing_started: # 如果没有开始追逐,且不是 13 号场景(二楼),则销毁 queue_free() return # TODO 高度对齐 position.y = player.position.y # 脚步声 timer footstep_timer.one_shot = false footstep_timer.autostart = false footstep_timer.wait_time = footstep_wait_time footstep_timer.timeout.connect(_on_footstep_timer_timeout) add_child(footstep_timer) if transfer_mode: visible = false var portal_node = ( SceneManager.get_ground().get_node("DeployLayer/portal_" + portal) as Portal2D ) global_position.x = portal_node.global_position.x # 禁用传送过来的 portal 3s _disable_portal_a_few_seconds(portal_node) # 3 秒后传送 get_tree().create_timer(1.0).timeout.connect(_do_transfer) if GlobalConfig.DEBUG: print("猪头怪 Transfer to: " + portal) else: move_and_detact = true var footstep_count := 0 func _on_footstep_timer_timeout() -> void: sfx_footstep.play() footstep_count += 1 if footstep_count % 2 == 0: # shake camera camera.shake_camera(4.0, 5.0) # 启用/禁用脚步声 func toggle_footstep_sfx(play := true): if play: footstep_timer.start() else: footstep_timer.stop() # 禁用传送过来的 portal 3s func _disable_portal_a_few_seconds(portal_node: Portal2D): portal_node.enabled = false await create_tween().tween_interval(3.0).finished portal_node.enabled = true func _do_transfer(): if player.global_position.x > global_position.x: _toggle_flip_h() modulate.a = 0.0 visible = true var tween = create_tween() tween.tween_property(self, "modulate:a", 1.0, 2.0) await tween.finished move_and_detact = true # 转身追击 func turn_back(): move_and_detact = false # 呼吸一帧,然后转身 sprite2d.play("猪头怪呼吸") await sprite2d.animation_looped _toggle_flip_h() move_and_detact = true func _toggle_flip_h(): sprite2d.flip_h = !sprite2d.flip_h sprite2d.offset.x = -sprite2d.offset.x # 抓小猫 func do_catch(front: bool): move_and_detact = false SceneManager.freeze_player(0) # 抓小猫时 lock palyer SceneManager.lock_player() # 呼吸一帧,然后抓取 sprite2d.play("猪头怪呼吸") await sprite2d.animation_looped player.hide_sprite = true if front: # 优先 catch_front 播放 猪头怪抓盒子猫 sprite2d.play("猪头怪抓盒子猫") else: # 其次 catch_nearby 播放 猪头怪抓盒子猫后段 sprite2d.play("猪头怪抓盒子猫后段") # TODO 音效 SceneManager.pop_debug_dialog_info("音效", "抓取过程。 从捡小猫开始=" + str(front)) await sprite2d.animation_finished # 等待动画播放后秒,再检查是否允许重开游戏 if allow_restart_game: SceneManager.get_ground_loader().transition_to_scene("c02_s12", "left") func _physics_process(delta: float) -> void: if not move_and_detact: return var player_pos = player.global_position # 玩家到猪头怪面前的距离(有符号) var signed_x_diff = player_pos.x - global_position.x # 默认向左, flip_h 后向右 if not sprite2d.flip_h: signed_x_diff *= -1 # 猫是否在盒子里 var is_cat_hideen = catty_canvas_layer.crawling # 检查隐藏小猫,在观测范围内进入盒子仍旧会被抓 if signed_x_diff < 0 or signed_x_diff > watching_x: # 如果离开一定范围,重置 cat_hiding_detacted cat_hiding_detacted = false else: cat_hiding_detacted = cat_hiding_detacted or not is_cat_hideen # 检查是否抓取 if cat_hiding_detacted or not is_cat_hideen: if signed_x_diff > catch_front_x_range.x and signed_x_diff < catch_front_x_range.y: # 优先 catch_front 播放 猪头怪抓盒子猫 do_catch(true) return elif signed_x_diff > catch_nearby_x_range.x and signed_x_diff < catch_nearby_x_range.y: # 其次 catch_nearby 播放 猪头怪抓盒子猫后段 do_catch(false) return # 检查是否需要转身,超过这个范围/在这个范围内不 hidden 都会转身 if signed_x_diff < 0 and trun_back_detact_x < -signed_x_diff: turn_back() return # 更新追击位置 var mov_delta = speed * delta if not sprite2d.flip_h: mov_delta *= -1 global_position.x += mov_delta