254 lines
7.5 KiB
GDScript
254 lines
7.5 KiB
GDScript
@tool
|
|
class_name Ground2D extends Node2D
|
|
|
|
# Constants
|
|
const DEFAULT_PLAYER_Y := 70
|
|
const CAMERA_MIN_WIDTH := 564.0
|
|
const CAMERA_MIN_HEIGHT := 316.0
|
|
const PLAYER_PADDING_X := 30.0
|
|
|
|
const FOOTSTEP_AUDIO = {
|
|
"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/sfx/footstep_meow.tres"),
|
|
}
|
|
|
|
# Exports
|
|
@export var scene_name := ""
|
|
# 用于在 debug 时态下,指定进入的 portal
|
|
@export_enum("left", "right", "1", "2", "3", "4", "5", "6", "7", "8", "9")
|
|
var default_portal := "left"
|
|
@export var display_hud := true
|
|
@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 := DEFAULT_PLAYER_Y:
|
|
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()
|
|
|
|
# Nodes
|
|
@onready var player_line := %PlayerLine2D as Line2D
|
|
@onready var reenter_lock := %PlayerReenterLock as PlayerReenterLock
|
|
@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
|
|
|
|
# State
|
|
var restarting := false
|
|
|
|
|
|
func _enter_tree() -> void:
|
|
# 仅在编辑器中调试时,通过 main 场景启动
|
|
if not Engine.is_editor_hint() and not (get_parent() is GroundLoader):
|
|
_handle_restart()
|
|
return
|
|
if camera_focus_marker:
|
|
camera_focus_marker.enabled = true
|
|
|
|
|
|
func _ready() -> void:
|
|
if restarting:
|
|
print("restarting: skip ground _ready()")
|
|
return
|
|
_validate_scene_name()
|
|
_set_camera_and_player_boundary()
|
|
if Engine.is_editor_hint():
|
|
return
|
|
_setup_scene()
|
|
_setup_runtime()
|
|
|
|
|
|
func _handle_restart() -> void:
|
|
print("restarting... set GlobalConfig.DEBUG = true")
|
|
restarting = true
|
|
GlobalConfig.DEBUG = true
|
|
_restart_from_main()
|
|
|
|
|
|
func _setup_scene() -> void:
|
|
foreground.layer = GlobalConfig.CANVAS_LAYER_FG
|
|
player_line.visible = false
|
|
|
|
|
|
func _validate_scene_name() -> void:
|
|
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")
|
|
|
|
|
|
func _setup_runtime() -> void:
|
|
_load_footstep_audio()
|
|
_setup_player_light()
|
|
SceneManager.toggle_hud_display(display_hud)
|
|
|
|
|
|
func _restart_from_main() -> void:
|
|
# _enter_tree, wait for ready
|
|
await ready
|
|
_ensure_managers_loaded()
|
|
_update_archive_for_restart()
|
|
get_tree().change_scene_to_file.call_deferred("res://scene/main.tscn")
|
|
|
|
|
|
func _ensure_managers_loaded() -> void:
|
|
if not ArchiveManager.archive:
|
|
ArchiveManager.load_archive()
|
|
if not GlobalConfigManager.config:
|
|
ArchiveManager.load_config()
|
|
|
|
|
|
func _update_archive_for_restart() -> void:
|
|
var archive = ArchiveManager.archive
|
|
if archive:
|
|
archive.current_scene = scene_name
|
|
archive.entrance_portal = default_portal
|
|
|
|
|
|
func get_player() -> MainPlayer:
|
|
if player:
|
|
return player
|
|
if GlobalConfig.DEBUG:
|
|
print("[Ground] get_player before ready")
|
|
return get_node_or_null("MainPlayer") as MainPlayer
|
|
|
|
|
|
func get_camera() -> CameraFocusMarker:
|
|
if camera_focus_marker:
|
|
return camera_focus_marker
|
|
if GlobalConfig.DEBUG:
|
|
print("[Ground] get_camera before ready")
|
|
return get_node_or_null("CameraFocusMarker") as CameraFocusMarker
|
|
|
|
|
|
func reset_player_y() -> void:
|
|
# 从屏幕下边缘算起
|
|
if player_y_fixed:
|
|
var p = get_player()
|
|
if p:
|
|
p.set_y_from_ground(158.0 - player_y)
|
|
|
|
|
|
func _set_camera_and_player_boundary() -> void:
|
|
var camera_rect = _calculate_camera_rect()
|
|
var player_rect = _calculate_player_rect()
|
|
if GlobalConfig.DEBUG:
|
|
print("try to _set_camera_and_player_boundary as:", camera_rect, player_rect)
|
|
_apply_camera_limits(camera_rect)
|
|
_apply_player_boundary(player_rect)
|
|
|
|
|
|
func _calculate_camera_rect() -> Rect2:
|
|
var camera_rect = Rect2(0, -158, CAMERA_MIN_WIDTH, CAMERA_MIN_HEIGHT)
|
|
if bg_sprite.texture:
|
|
var size = bg_sprite.texture.get_size() * bg_sprite.scale
|
|
var camera_size = size
|
|
camera_size.x += bg_sprite.position.x
|
|
camera_size = Vector2(
|
|
max(CAMERA_MIN_WIDTH, camera_size.x), max(camera_size.y, CAMERA_MIN_HEIGHT)
|
|
)
|
|
var camera_upleft = Vector2(0, -camera_size.y / 2.0)
|
|
camera_rect = Rect2(camera_upleft, camera_size)
|
|
return camera_rect
|
|
|
|
|
|
func _calculate_player_rect() -> Rect2:
|
|
var player_rect = Rect2(0, -158, CAMERA_MIN_WIDTH, CAMERA_MIN_HEIGHT)
|
|
if player_line:
|
|
var line_start = player_line.get_point_position(0)
|
|
var line_end = player_line.get_point_position(1)
|
|
player_rect.position.x = line_start.x + player_line.global_position.x
|
|
player_rect.size.x = line_end.x - line_start.x
|
|
return player_rect
|
|
|
|
|
|
func _apply_camera_limits(camera_rect: Rect2) -> void:
|
|
var camera_marker = get_camera()
|
|
if camera_marker:
|
|
camera_marker.limit_left = camera_rect.position.x
|
|
camera_marker.limit_right = camera_rect.position.x + camera_rect.size.x
|
|
camera_marker.limit_top = camera_rect.position.y
|
|
camera_marker.limit_bottom = camera_rect.position.y + camera_rect.size.y
|
|
|
|
|
|
func _apply_player_boundary(player_rect: Rect2) -> void:
|
|
var p = get_player()
|
|
if p:
|
|
p.player_movement_rect = player_rect
|
|
|
|
|
|
func _load_footstep_audio() -> void:
|
|
footstep_audio.audio_collections.clear()
|
|
if footstep_type != "none" and FOOTSTEP_AUDIO.has(footstep_type):
|
|
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 portal_node = _get_portal_node(portal_name)
|
|
var mov_player = get_player() as MainPlayer
|
|
if not portal_node:
|
|
if mov_player:
|
|
printerr(scene_name, " portal not found: portal_", portal_name)
|
|
else:
|
|
printerr("move_player_to_portal player not ready")
|
|
return
|
|
if not mov_player:
|
|
printerr("move_player_to_portal player not ready")
|
|
return
|
|
_position_player_at_portal(mov_player, portal_node, portal_name)
|
|
reset_player_y()
|
|
print("[ground] move player to portal:", portal_name, portal_node.global_position)
|
|
|
|
|
|
func _get_portal_node(portal_name: String) -> Portal2D:
|
|
var node_path = NodePath("DeployLayer/portal_" + portal_name)
|
|
return get_node_or_null(node_path) as Portal2D
|
|
|
|
|
|
func _position_player_at_portal(
|
|
mov_player: MainPlayer, portal_node: Portal2D, portal_name: String
|
|
) -> void:
|
|
mov_player.global_position.x = portal_node.global_position.x
|
|
|
|
if portal_name == "left":
|
|
mov_player.set_facing_direction(Vector2.RIGHT)
|
|
elif portal_name == "right":
|
|
mov_player.set_facing_direction(Vector2.LEFT)
|
|
|
|
|
|
func _setup_player_light() -> void:
|
|
# 强制显示 directional_light
|
|
directional_light.visible = true
|
|
# 设置角色身上光源
|
|
var should_enable_light = (
|
|
directional_light.blend_mode == Light2D.BLEND_MODE_SUB and directional_light.energy > 0.6
|
|
)
|
|
player.enable_light = should_enable_light
|
|
print("_setup_player_light player.enable_light=", player.enable_light)
|