@tool class_name Ground2D extends Node2D @export var scene_name := "" # 用于在 debug 时态下,指定进入的 portal @export_enum("left", "right", "1", "2", "3", "4", "5", "6", "7", "8", "9") var default_portal := "left" @export_group("Player", "player_") @export var player_y_fixed := true: set(val): player_y_fixed = val if is_node_ready(): _reset_player_y() @export var player_y := 70: set(val): player_y = val if is_node_ready(): _reset_player_y() @export var replace_player_to_portal := false: set(val): replace_player_to_portal = false if is_node_ready(): move_player_to_portal(default_portal) @export_group("Sound") @export_enum("none", "ghost", "硬地面", "室外", "crawling", "盒子猫") var footstep_type: String = "硬地面": set(val): footstep_type = val if is_node_ready(): _load_footstep_audio() # var main_scene := preload("res://scene/main.tscn") as PackedScene @onready var player_line = %PlayerLine2D as Line2D @onready var player = %MainPlayer as MainPlayer @onready var directional_light := %DirectionalLight2D as DirectionalLight2D @onready var bg_sprite = %BGSprite2D as Sprite2D @onready var foreground = %ParallaxForeground as ParallaxBackground @onready var camera_focus_marker = %CameraFocusMarker as CameraFocusMarker @onready var footstep_audio = %FootstepAudioPlayer as RandomAudioStreamPlayer const FOOTSTEP_AUDIO = { #"wood": preload("res://config/audio/footstep/footstep_wood.tres"), #"carpet": preload("res://config/audio/footstep/footstep_carpet.tres"), "concrete": preload("res://config/audio/footstep/footstep_concrete.tres"), #"grass": preload("res://config/audio/footstep/footstep_grass.tres"), #"snow": preload("res://config/audio/footstep/footstep_snow.tres"), "ghost": preload("res://config/audio/sfx/footstep_ghost.tres"), "硬地面": preload("res://config/audio/sfx/footstep_硬地面.tres"), "室外": preload("res://config/audio/sfx/footstep_室外.tres"), "crawling": preload("res://config/audio/sfx/footstep_crawling.tres"), # 待替换 "盒子猫": preload("res://config/audio/footstep/footstep_concrete.tres"), } var restarting = false func _enter_tree() -> void: # 仅在编辑器中调试时,通过 main 场景启动 if GlobalConfig.DEBUG and (not Engine.is_editor_hint()) and (not get_parent() is GroundLoader): print("restarting...") restarting= true _restart_from_main() return if camera_focus_marker: camera_focus_marker.enabled = true func _ready() -> void: if restarting: print("restarting: skip ground _ready()") return foreground.layer = GlobalConfig.CANVAS_LAYER_FG # 检查 scene_name 是否合法 scene_name = scene_name.strip_edges() if get_parent().name.begins_with("S") and (not scene_name or scene_name.length() != 7): printerr("scene_name is not valid") if Engine.is_editor_hint(): return # 隐藏 player_line player_line.visible = false _set_camera_and_player_boundary() _load_footstep_audio() # marker 默认就在 foucs player 状态 # camera_focus_marker.focus_node(player) # %ColorRectTop.visible = true # %ColorRectBottom.visible = true # 如果 debug 模式下不通过 GroundLoader 启动,则插入到 main 以下 _setup_player_light() func _restart_from_main(): # _enter_tree, wait for ready await ready if not ArchiveManager.archive: ArchiveManager.load_archive() if not GlobalConfigManager.config: ArchiveManager.load_config() ArchiveManager.archive.current_scene = scene_name ArchiveManager.archive.entrance_portal = default_portal # get_tree().change_scene_to_packed.call_deferred(main_scene) get_tree().change_scene_to_file.call_deferred("res://scene/main.tscn") func _reset_player_y(): # 从屏幕下边缘算起 if player_y_fixed: player.set_y_from_ground(158.0 - player_y) func _set_camera_and_player_boundary(): # set current_boarder by bg size var camera_rect = Rect2(0, -158, 564, 316) var player_rect = Rect2(0, -158, 564, 316) if bg_sprite.texture and not Engine.is_editor_hint(): var size = bg_sprite.texture.get_size() * bg_sprite.scale # camera rect var camera_size = size camera_size.x += bg_sprite.position.x camera_size = Vector2(max(564.0, camera_size.x), max(camera_size.y, 316.0)) var camera_upleft = Vector2(0, -camera_size.y / 2.0) camera_rect = Rect2(camera_upleft, camera_size) # player rect should be set centered, with 30px x padding player_rect.position.x = player_line.get_point_position(0).x + player_line.global_position.x player_rect.size.x = player_line.get_point_position(1).x - player_line.get_point_position(0).x SceneManager.set_camera_boundary(camera_rect) SceneManager.set_player_boundary(player_rect) if GlobalConfig.DEBUG: print("_set_camera_and_player_boundary:", camera_rect, player_rect) func _load_footstep_audio(): # foot step sound footstep_audio.audio_collections.clear() if footstep_type != "none": var audio = FOOTSTEP_AUDIO[footstep_type] as AudioStreamCollection footstep_audio.audio_collections.append(audio) func play_footstep_sound() -> void: if not footstep_audio.audio_collections.is_empty(): footstep_audio.play_random() func move_player_to_portal(portal_name: String) -> void: var node_path = NodePath("DeployLayer/portal_" + portal_name) var portal_node = get_node_or_null(node_path) as Portal2D if portal_node and player: player.global_position.x = portal_node.global_position.x if portal_name == "left": player.set_facing_direction(Vector2.RIGHT) elif portal_name == "right": player.set_facing_direction(Vector2.LEFT) _reset_player_y() if GlobalConfig.DEBUG: print("move player to portal:", portal_name, portal_node.global_position) elif player: printerr(scene_name, " portal not found: ", node_path) else: printerr("move_player_to_portal player not ready") # 传送后,重置 camera 位置 camera_focus_marker.reset_position_immediately() func _setup_player_light(): # 强制显示 directional_light directional_light.visible = true # 设置角色身上光源 if directional_light.blend_mode == Light2D.BLEND_MODE_SUB and directional_light.energy > 0.6: player.enable_light = true else: player.enable_light = false print("_setup_player_light player.enable_light=", player.enable_light)