2025-01-14 10:20:31 +00:00
|
|
|
|
@tool
|
2024-12-23 01:29:31 +00:00
|
|
|
|
extends CharacterBody2D
|
|
|
|
|
|
|
|
|
|
class_name MainPlayer
|
|
|
|
|
|
2025-05-21 20:16:27 +00:00
|
|
|
|
signal position_updated(global_pos: Vector2)
|
2025-03-20 10:00:53 +00:00
|
|
|
|
signal os_finished
|
2025-05-14 20:43:55 +00:00
|
|
|
|
signal animation_finished
|
2025-03-20 10:00:53 +00:00
|
|
|
|
|
2025-06-07 09:04:08 +00:00
|
|
|
|
@export var hide_sprite := false:
|
|
|
|
|
set(val):
|
|
|
|
|
hide_sprite = val
|
|
|
|
|
if is_node_ready():
|
|
|
|
|
sprite.visible = not hide_sprite
|
2025-05-30 11:05:06 +00:00
|
|
|
|
@export var enable_light := true:
|
|
|
|
|
set(val):
|
|
|
|
|
enable_light = val
|
|
|
|
|
if is_node_ready():
|
|
|
|
|
light.enabled = enable_light
|
2025-06-06 16:19:37 +00:00
|
|
|
|
@export var catty_light_energy := 0.7
|
2025-01-21 10:52:36 +00:00
|
|
|
|
@export var camera_marker: CameraFocusMarker
|
2025-06-05 14:01:47 +00:00
|
|
|
|
@export_enum("吕萍", "吕萍爬行", "吕萍带小猫", "吕萍推柜子", "小小蝶", "盒子猫") var character := "吕萍":
|
2025-01-14 10:20:31 +00:00
|
|
|
|
set(val):
|
|
|
|
|
character = val
|
2025-01-20 13:45:47 +00:00
|
|
|
|
# 使用 new,方便在 editor 中刷新新值
|
|
|
|
|
current_animation_config = PlayerAnimationConfig.new().ANIMATION_CONFIG[val]
|
2025-01-14 10:20:31 +00:00
|
|
|
|
current_status = PlayerAnimationConfig.MOVEMENT_IDLE
|
2025-01-15 04:02:11 +00:00
|
|
|
|
if is_node_ready():
|
2025-01-16 12:24:21 +00:00
|
|
|
|
sprite.scale = current_animation_config["scale"]
|
2025-01-14 10:20:31 +00:00
|
|
|
|
# @export var shadow_color := Color(0.1, 0.1, 0.1, 0.7)
|
|
|
|
|
# var shadow_y := 0.0
|
2024-12-23 13:12:13 +00:00
|
|
|
|
@export var player_movement_rect := Rect2(50, -500, 1400, 1000)
|
2024-12-27 07:56:45 +00:00
|
|
|
|
@export var velocity_ratio := 1.0
|
2025-01-20 13:45:47 +00:00
|
|
|
|
@export var running_locked := false
|
2025-05-21 20:16:27 +00:00
|
|
|
|
# action_locked 用于设置界面等强制锁定(禁止游戏场景内的任何操作),action_freezed 用于查看物品等锁定(主要用于禁止在场景中移动)
|
2025-01-16 12:24:21 +00:00
|
|
|
|
# action_locked 优先级高于 action_freezed
|
|
|
|
|
# action_locked 对应 lock 与 unlock 方法
|
2024-12-23 01:29:31 +00:00
|
|
|
|
@export var action_locked := false:
|
|
|
|
|
set(val):
|
|
|
|
|
action_locked = val
|
|
|
|
|
_process_action_lock()
|
2025-01-16 12:24:21 +00:00
|
|
|
|
# action_freezed 对应 freeze 与 release 方法
|
|
|
|
|
@export var action_freezed := false:
|
|
|
|
|
set(val):
|
|
|
|
|
action_freezed = val
|
|
|
|
|
_process_action_lock()
|
|
|
|
|
|
2025-01-14 10:20:31 +00:00
|
|
|
|
@export_enum("idle", "walking", "running") var current_status := 0:
|
2024-12-23 01:29:31 +00:00
|
|
|
|
set(val):
|
2025-01-14 10:20:31 +00:00
|
|
|
|
current_status = val
|
|
|
|
|
_play_animation()
|
|
|
|
|
@export var facing_direction := Vector2(1.0, -1.0):
|
2024-12-23 01:29:31 +00:00
|
|
|
|
set(val):
|
2025-01-14 10:20:31 +00:00
|
|
|
|
facing_direction = val
|
|
|
|
|
_play_animation()
|
|
|
|
|
|
2025-03-10 13:03:34 +00:00
|
|
|
|
@export var debug_freeze := 0:
|
|
|
|
|
set(val):
|
|
|
|
|
if not Engine.is_editor_hint():
|
|
|
|
|
return
|
|
|
|
|
debug_freeze = val
|
|
|
|
|
current_animation_config = PlayerAnimationConfig.new().ANIMATION_CONFIG[character]
|
|
|
|
|
if val > 3:
|
|
|
|
|
freeze_player(0, val, true)
|
|
|
|
|
release_player()
|
|
|
|
|
|
2025-01-20 13:45:47 +00:00
|
|
|
|
# 使用 new,方便在 editor 中刷新新值
|
|
|
|
|
var current_animation_config := (
|
|
|
|
|
PlayerAnimationConfig.new().ANIMATION_CONFIG[character] as Dictionary
|
|
|
|
|
)
|
2024-12-23 01:29:31 +00:00
|
|
|
|
|
2025-05-30 11:05:06 +00:00
|
|
|
|
@onready var light = $PointLight2D as PointLight2D
|
2025-06-05 14:01:47 +00:00
|
|
|
|
@onready var catty_light = $CattyPointLight2D as PointLight2D
|
2024-12-25 12:24:34 +00:00
|
|
|
|
@onready var footstep_timer = %FootstepTimer as Timer
|
2024-12-23 01:29:31 +00:00
|
|
|
|
@onready var sprite = %AnimatedSprite2D as AnimatedSprite2D
|
2025-01-12 06:02:00 +00:00
|
|
|
|
@onready var os_pivot = %OSPivot as Control
|
2025-01-12 11:36:41 +00:00
|
|
|
|
@onready var os_contaner = %PanelContainer as PanelContainer
|
2025-01-12 06:02:00 +00:00
|
|
|
|
@onready var os_label = %OSLabel as DialogueLabel
|
2024-12-23 01:29:31 +00:00
|
|
|
|
|
2024-12-23 13:12:13 +00:00
|
|
|
|
|
2025-01-14 10:20:31 +00:00
|
|
|
|
# # animation -> {frame -> {shadow polygon}}
|
|
|
|
|
# var animation_shadow_polygons = {}
|
2024-12-23 01:29:31 +00:00
|
|
|
|
func _ready() -> void:
|
2025-06-07 09:04:08 +00:00
|
|
|
|
sprite.visible = not hide_sprite
|
2025-05-30 11:05:06 +00:00
|
|
|
|
light.enabled = enable_light
|
2025-01-14 10:20:31 +00:00
|
|
|
|
os_contaner.modulate.a = 0.0
|
2025-01-15 04:02:11 +00:00
|
|
|
|
# set up animated sprite
|
2025-01-16 12:24:21 +00:00
|
|
|
|
sprite.scale = current_animation_config["scale"]
|
2025-01-14 10:20:31 +00:00
|
|
|
|
_play_animation()
|
2025-01-16 12:24:21 +00:00
|
|
|
|
if Engine.is_editor_hint():
|
|
|
|
|
return
|
2024-12-25 12:24:34 +00:00
|
|
|
|
footstep_timer.timeout.connect(_on_footstep_timer_timeout)
|
|
|
|
|
footstep_timer.stop()
|
2025-05-14 20:43:55 +00:00
|
|
|
|
sprite.animation_finished.connect(animation_finished.emit)
|
2025-01-21 07:58:21 +00:00
|
|
|
|
_check_character_status()
|
2025-06-05 14:01:47 +00:00
|
|
|
|
# 如果当前是 prop_小猫玩具完整 ,尝试点亮玩家的灯效;否则无需点亮
|
|
|
|
|
if SceneManager.get_current_prop(false) == "prop_小猫玩具完整":
|
|
|
|
|
set_catty_light(true)
|
2025-01-21 07:58:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _enter_tree() -> void:
|
2025-01-21 10:08:16 +00:00
|
|
|
|
if is_node_ready() and not Engine.is_editor_hint():
|
2025-01-21 07:58:21 +00:00
|
|
|
|
_check_character_status()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _check_character_status():
|
|
|
|
|
# 检查角色锁定状态
|
|
|
|
|
running_locked = ArchiveManager.archive.player_running_locked
|
2024-12-25 12:24:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_footstep_timer_timeout():
|
2024-12-26 13:58:37 +00:00
|
|
|
|
# ground node is sibling of the player node.
|
2025-01-15 04:02:11 +00:00
|
|
|
|
var ground = get_parent() as Ground2D
|
|
|
|
|
if ground:
|
|
|
|
|
ground.play_footstep_sound()
|
2024-12-23 01:29:31 +00:00
|
|
|
|
|
2024-12-23 13:12:13 +00:00
|
|
|
|
|
2024-12-23 01:29:31 +00:00
|
|
|
|
func _process_action_lock() -> void:
|
|
|
|
|
# reset status to idle or stay
|
2025-01-16 12:24:21 +00:00
|
|
|
|
if action_locked or action_freezed:
|
2024-12-27 07:56:45 +00:00
|
|
|
|
velocity = Vector2.ZERO
|
2025-01-14 10:20:31 +00:00
|
|
|
|
if (
|
|
|
|
|
current_status == PlayerAnimationConfig.MOVEMENT_WALKING
|
|
|
|
|
or current_status == PlayerAnimationConfig.MOVEMENT_RUNNING
|
|
|
|
|
):
|
|
|
|
|
current_status = PlayerAnimationConfig.MOVEMENT_IDLE
|
2024-12-23 01:29:31 +00:00
|
|
|
|
|
2024-12-23 13:12:13 +00:00
|
|
|
|
|
2024-12-23 01:29:31 +00:00
|
|
|
|
# return whether the player status or facing direction has changed.
|
|
|
|
|
func _check_status(direction) -> bool:
|
|
|
|
|
var tmp_status = current_status
|
|
|
|
|
var new_facing_direction := facing_direction
|
2025-01-14 10:20:31 +00:00
|
|
|
|
if direction.x:
|
|
|
|
|
new_facing_direction.x = direction.x
|
|
|
|
|
tmp_status = PlayerAnimationConfig.MOVEMENT_WALKING
|
2025-01-20 13:45:47 +00:00
|
|
|
|
if (
|
|
|
|
|
not running_locked
|
|
|
|
|
and Input.is_action_pressed("run")
|
|
|
|
|
and current_animation_config["can_run"]
|
|
|
|
|
):
|
2025-01-14 10:20:31 +00:00
|
|
|
|
tmp_status = PlayerAnimationConfig.MOVEMENT_RUNNING
|
2024-12-23 01:29:31 +00:00
|
|
|
|
else:
|
2025-01-14 10:20:31 +00:00
|
|
|
|
tmp_status = PlayerAnimationConfig.MOVEMENT_IDLE
|
2024-12-23 01:29:31 +00:00
|
|
|
|
if new_facing_direction != facing_direction or tmp_status != current_status:
|
|
|
|
|
facing_direction = new_facing_direction
|
|
|
|
|
current_status = tmp_status
|
|
|
|
|
return true
|
|
|
|
|
return false
|
|
|
|
|
|
2024-12-30 13:19:10 +00:00
|
|
|
|
|
2024-12-23 01:29:31 +00:00
|
|
|
|
func _play_animation() -> void:
|
2025-01-14 10:20:31 +00:00
|
|
|
|
if not sprite:
|
|
|
|
|
return
|
2025-01-15 04:02:11 +00:00
|
|
|
|
sprite.offset = current_animation_config["foot_offset"]
|
2025-01-16 12:24:21 +00:00
|
|
|
|
sprite.scale = current_animation_config["scale"]
|
2025-01-12 06:02:00 +00:00
|
|
|
|
# reset the os label position on animation changed.
|
2025-01-12 11:36:41 +00:00
|
|
|
|
_reset_os_and_shadow_position()
|
2025-01-20 13:45:47 +00:00
|
|
|
|
# 检查动画基础偏移
|
|
|
|
|
check_foot_offset()
|
|
|
|
|
# 进一步偏移+播放动画
|
2025-01-15 04:02:11 +00:00
|
|
|
|
var config = current_animation_config[current_status]
|
2025-06-05 14:01:47 +00:00
|
|
|
|
_sprite_play_with_auto_flip_h(config[0], config[1])
|
2025-01-14 10:20:31 +00:00
|
|
|
|
if facing_direction.x > 0.0:
|
2025-06-05 14:01:47 +00:00
|
|
|
|
if config.size() > 3:
|
2025-01-15 04:02:11 +00:00
|
|
|
|
sprite.offset += config[3]
|
2025-01-14 10:20:31 +00:00
|
|
|
|
else:
|
2025-01-15 04:02:11 +00:00
|
|
|
|
if config.size() > 2:
|
|
|
|
|
sprite.offset += config[2]
|
2025-01-20 13:45:47 +00:00
|
|
|
|
# 播放脚步音效
|
2024-12-23 01:29:31 +00:00
|
|
|
|
match current_status:
|
2025-01-14 10:20:31 +00:00
|
|
|
|
PlayerAnimationConfig.MOVEMENT_IDLE:
|
2024-12-25 12:24:34 +00:00
|
|
|
|
footstep_timer.stop()
|
2025-01-14 10:20:31 +00:00
|
|
|
|
PlayerAnimationConfig.MOVEMENT_WALKING:
|
|
|
|
|
footstep_timer.wait_time = current_animation_config["walk_footstep"]
|
2024-12-25 12:24:34 +00:00
|
|
|
|
footstep_timer.start()
|
2025-01-14 10:20:31 +00:00
|
|
|
|
PlayerAnimationConfig.MOVEMENT_RUNNING:
|
|
|
|
|
footstep_timer.wait_time = current_animation_config["run_footstep"]
|
2024-12-25 12:24:34 +00:00
|
|
|
|
footstep_timer.start()
|
2025-01-15 04:02:11 +00:00
|
|
|
|
# 在编辑器中不播放动画
|
|
|
|
|
if Engine.is_editor_hint():
|
|
|
|
|
footstep_timer.stop()
|
|
|
|
|
sprite.stop()
|
|
|
|
|
# 显示 os 效果
|
|
|
|
|
os_contaner.modulate.a = 1.0
|
|
|
|
|
os_label.text = "os 测试文本"
|
2024-12-23 01:29:31 +00:00
|
|
|
|
|
2024-12-23 13:12:13 +00:00
|
|
|
|
|
2025-06-05 14:01:47 +00:00
|
|
|
|
func _sprite_play_with_auto_flip_h(left_animation: String, right_animation: String) -> String:
|
|
|
|
|
if facing_direction.x > 0.0:
|
|
|
|
|
if right_animation:
|
|
|
|
|
sprite.flip_h = false
|
|
|
|
|
sprite.play(right_animation)
|
|
|
|
|
return right_animation
|
|
|
|
|
else:
|
|
|
|
|
sprite.flip_h = true
|
|
|
|
|
sprite.play(left_animation)
|
|
|
|
|
return left_animation
|
|
|
|
|
else:
|
|
|
|
|
if left_animation:
|
|
|
|
|
sprite.flip_h = false
|
|
|
|
|
sprite.play(left_animation)
|
|
|
|
|
return left_animation
|
|
|
|
|
else:
|
|
|
|
|
sprite.flip_h = true
|
|
|
|
|
sprite.play(right_animation)
|
|
|
|
|
return right_animation
|
|
|
|
|
|
|
|
|
|
|
2024-12-23 01:29:31 +00:00
|
|
|
|
func _get_speed(direction: Vector2) -> Vector2:
|
|
|
|
|
match current_status:
|
2025-01-14 10:20:31 +00:00
|
|
|
|
PlayerAnimationConfig.MOVEMENT_WALKING:
|
|
|
|
|
var speed_walking = current_animation_config["speed_walking"]
|
2024-12-23 01:29:31 +00:00
|
|
|
|
return Vector2(speed_walking * direction.x, 0.0)
|
2025-01-14 10:20:31 +00:00
|
|
|
|
PlayerAnimationConfig.MOVEMENT_RUNNING:
|
|
|
|
|
var speed_runnig = current_animation_config["speed_runnig"]
|
2024-12-23 01:29:31 +00:00
|
|
|
|
return Vector2(speed_runnig * direction.x, 0.0)
|
|
|
|
|
return Vector2(0, 0)
|
|
|
|
|
|
2024-12-23 13:12:13 +00:00
|
|
|
|
|
2024-12-23 01:29:31 +00:00
|
|
|
|
func _physics_process(_delta: float) -> void:
|
2025-01-31 10:44:30 +00:00
|
|
|
|
if action_locked or action_freezed or Engine.is_editor_hint() or not is_visible_in_tree():
|
2024-12-23 01:29:31 +00:00
|
|
|
|
velocity = Vector2.ZERO
|
|
|
|
|
return
|
|
|
|
|
var x_direction := Input.get_axis("left", "right")
|
|
|
|
|
var y_direction := Input.get_axis("up", "down")
|
|
|
|
|
var direction := Vector2(x_direction, y_direction)
|
2025-01-14 10:20:31 +00:00
|
|
|
|
_check_status(direction)
|
2024-12-23 01:29:31 +00:00
|
|
|
|
var speed := _get_speed(direction) as Vector2
|
2024-12-27 07:56:45 +00:00
|
|
|
|
velocity.x = move_toward(velocity.x, speed.x, 300.0) * velocity_ratio
|
|
|
|
|
velocity.y = move_toward(velocity.y, speed.y, 300.0) * velocity_ratio
|
2025-01-03 13:29:22 +00:00
|
|
|
|
# var x = global_position.x
|
2024-12-23 01:29:31 +00:00
|
|
|
|
move_and_slide()
|
2025-01-03 13:29:22 +00:00
|
|
|
|
global_position = global_position.clamp(player_movement_rect.position, player_movement_rect.end)
|
|
|
|
|
# var delta_x = global_position.x - x
|
|
|
|
|
# if delta_x:
|
2025-01-06 08:06:20 +00:00
|
|
|
|
# SceneManager.player_moved_delta_x(delta_x)
|
2024-12-23 01:29:31 +00:00
|
|
|
|
_tweak_camera_marker()
|
2025-05-21 20:16:27 +00:00
|
|
|
|
position_updated.emit(global_position)
|
2024-12-23 01:29:31 +00:00
|
|
|
|
|
2024-12-23 13:12:13 +00:00
|
|
|
|
|
2024-12-23 01:29:31 +00:00
|
|
|
|
# drag the camera marker against the player movement
|
|
|
|
|
# so there will be a better vision in front of the player.
|
|
|
|
|
func _tweak_camera_marker():
|
2025-01-21 10:52:36 +00:00
|
|
|
|
if camera_marker != null:
|
|
|
|
|
camera_marker.tweak_position(velocity, facing_direction)
|
2024-12-27 07:56:45 +00:00
|
|
|
|
|
|
|
|
|
|
2025-06-05 14:01:47 +00:00
|
|
|
|
func set_catty_light(enable := false):
|
2025-06-07 09:04:08 +00:00
|
|
|
|
# 如果没有允许光照,那么 catty 的光也不添加
|
|
|
|
|
if not enable_light:
|
|
|
|
|
return
|
2025-06-05 14:01:47 +00:00
|
|
|
|
var tween = create_tween()
|
|
|
|
|
if enable:
|
|
|
|
|
tween.tween_property(catty_light, "energy", catty_light_energy, 0.5)
|
|
|
|
|
else:
|
|
|
|
|
tween.tween_property(catty_light, "energy", 0.0, 0.5)
|
|
|
|
|
|
|
|
|
|
|
2024-12-30 13:19:10 +00:00
|
|
|
|
var lock_mutex = Mutex.new()
|
|
|
|
|
var release_timer: SceneTreeTimer
|
2024-12-27 07:56:45 +00:00
|
|
|
|
|
|
|
|
|
|
2025-05-14 20:43:55 +00:00
|
|
|
|
# 自动解除对应 animation 的 loop 状态
|
2024-12-30 13:19:10 +00:00
|
|
|
|
# lock_time: the time to lock the player action. 0 means lock forever, thus the player will be locked until release_player is called.
|
2025-01-16 12:24:21 +00:00
|
|
|
|
func freeze_player(lock_time: float, action_animation: int, auto_quit: bool) -> void:
|
2024-12-30 13:19:10 +00:00
|
|
|
|
lock_mutex.lock()
|
2025-01-16 12:24:21 +00:00
|
|
|
|
if not action_freezed:
|
|
|
|
|
action_freezed = true
|
|
|
|
|
if current_animation_config.has(action_animation):
|
|
|
|
|
# animation_name, scale, offset
|
|
|
|
|
var config = current_animation_config[action_animation]
|
2025-06-05 14:01:47 +00:00
|
|
|
|
var animation_l = config[0]
|
|
|
|
|
var animation_r = config[0]
|
2025-01-20 13:45:47 +00:00
|
|
|
|
sprite.scale = config[1]
|
|
|
|
|
sprite.offset = config[2]
|
2025-06-05 14:01:47 +00:00
|
|
|
|
if config.size() >= 5:
|
|
|
|
|
animation_l = config[3]
|
|
|
|
|
animation_r = config[4]
|
|
|
|
|
var playing_animation = _sprite_play_with_auto_flip_h(animation_l, animation_r)
|
|
|
|
|
if auto_quit:
|
|
|
|
|
# reset animation after one play
|
|
|
|
|
if sprite.sprite_frames.get_animation_loop(playing_animation):
|
|
|
|
|
sprite.animation_looped.connect(_play_animation, CONNECT_ONE_SHOT)
|
|
|
|
|
else:
|
2025-01-16 12:24:21 +00:00
|
|
|
|
sprite.animation_finished.connect(_play_animation, CONNECT_ONE_SHOT)
|
|
|
|
|
if lock_time:
|
|
|
|
|
if release_timer and release_timer.time_left > 0:
|
|
|
|
|
release_timer.time_left = max(lock_time, release_timer.time_left)
|
|
|
|
|
else:
|
|
|
|
|
release_timer = get_tree().create_timer(lock_time)
|
|
|
|
|
release_timer.timeout.connect(release_player)
|
|
|
|
|
lock_mutex.unlock()
|
|
|
|
|
|
|
|
|
|
|
2024-12-30 13:19:10 +00:00
|
|
|
|
func release_player():
|
|
|
|
|
release_timer = null
|
|
|
|
|
lock_mutex.lock()
|
2025-01-16 12:24:21 +00:00
|
|
|
|
action_freezed = false
|
2024-12-27 07:56:45 +00:00
|
|
|
|
# velocity_ratio = 1.0
|
2024-12-30 13:19:10 +00:00
|
|
|
|
lock_mutex.unlock()
|
2025-01-16 12:24:21 +00:00
|
|
|
|
# _play_animation()
|
2025-01-12 06:02:00 +00:00
|
|
|
|
|
|
|
|
|
|
2025-05-14 20:43:55 +00:00
|
|
|
|
# 强制播放特定动画
|
|
|
|
|
func force_play_animation(animation: String) -> void:
|
|
|
|
|
sprite.play(animation)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func set_facing_direction(direction: Vector2) -> void:
|
|
|
|
|
facing_direction = direction
|
2025-06-04 11:46:27 +00:00
|
|
|
|
_play_animation()
|
2025-05-14 20:43:55 +00:00
|
|
|
|
|
|
|
|
|
|
2025-01-14 10:20:31 +00:00
|
|
|
|
# func _draw() -> void:
|
|
|
|
|
# # 绘制阴影,暂不启用
|
2025-01-12 12:15:18 +00:00
|
|
|
|
# var animation = sprite.animation
|
|
|
|
|
# if not animation:
|
|
|
|
|
# return
|
|
|
|
|
# if not animation_shadow_polygons.has(animation):
|
|
|
|
|
# _build_shadow_polygons(animation)
|
|
|
|
|
# var animation_polygons = animation_shadow_polygons[animation]
|
|
|
|
|
# if animation_polygons.has(sprite.frame):
|
|
|
|
|
# draw_polygon(animation_polygons[sprite.frame], [shadow_color])
|
|
|
|
|
# else:
|
|
|
|
|
# printerr("No shadow polygon found for frame %d" % sprite.frame)
|
|
|
|
|
|
|
|
|
|
# func _build_shadow_polygons(animation):
|
|
|
|
|
# var frames = sprite.sprite_frames
|
|
|
|
|
# var coords_dict = {}
|
|
|
|
|
# for i in frames.get_frame_count(animation):
|
|
|
|
|
# var texture = frames.get_frame_texture(animation, i) as Texture2D
|
|
|
|
|
# if not texture:
|
|
|
|
|
# continue
|
|
|
|
|
# var image = texture.get_image()
|
|
|
|
|
# var x_min = 10000
|
|
|
|
|
# var x_max = -1
|
|
|
|
|
# for y in range(texture.get_height()):
|
|
|
|
|
# for x in range(texture.get_width()):
|
|
|
|
|
# var color = image.get_pixel(x, y)
|
|
|
|
|
# if color.a > 0.0:
|
|
|
|
|
# x_min = min(x_min, x)
|
|
|
|
|
# x_max = max(x_max, x)
|
|
|
|
|
# if x_min >= x_max:
|
|
|
|
|
# continue
|
|
|
|
|
# var oval_ab: Vector2
|
|
|
|
|
# oval_ab.x = (x_max - x_min) * 0.5
|
|
|
|
|
# oval_ab.y = max(3.0, oval_ab.x * 0.12)
|
|
|
|
|
# var x_offset = (x_max - x_min) * 0.5 + x_min - texture.get_width() * 0.5
|
|
|
|
|
# var coords: PackedVector2Array
|
|
|
|
|
# # build shadow oval shape with segments.
|
|
|
|
|
# var segments = 16
|
|
|
|
|
# for j in range(segments):
|
|
|
|
|
# var angle = PI * 2 / segments * j
|
|
|
|
|
# var x = cos(angle) * oval_ab.x + x_offset
|
|
|
|
|
# var y = sin(angle) * oval_ab.y + shadow_y
|
|
|
|
|
# coords.append(Vector2(x, y))
|
|
|
|
|
# coords_dict[i] = coords
|
|
|
|
|
# animation_shadow_polygons[animation] = coords_dict
|
2025-01-12 11:36:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _reset_os_and_shadow_position():
|
2025-01-14 10:20:31 +00:00
|
|
|
|
if sprite and sprite.animation:
|
2025-01-12 11:36:41 +00:00
|
|
|
|
# reset the os label position
|
2025-01-15 04:02:11 +00:00
|
|
|
|
# var texture = sprite.sprite_frames.get_frame_texture(sprite.animation, 0) as Texture2D
|
|
|
|
|
# var size = texture.get_size()
|
|
|
|
|
# os_pivot.position.y = -size.y * 0.5 * sprite.scale.x
|
|
|
|
|
os_pivot.position.y = -abs(current_animation_config["os_height"])
|
2025-01-12 11:36:41 +00:00
|
|
|
|
# reset the shadow position
|
2025-01-14 10:20:31 +00:00
|
|
|
|
# shadow_y = size.y * 0.5
|
2025-01-12 06:02:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var os_tween: Tween
|
2025-06-07 09:04:08 +00:00
|
|
|
|
var os_pausing_timer: SceneTreeTimer
|
2025-01-12 06:02:00 +00:00
|
|
|
|
|
|
|
|
|
|
2025-06-07 09:04:08 +00:00
|
|
|
|
func pop_os(lines := [], auto_freeze := true, auto_release := true):
|
2025-01-12 06:02:00 +00:00
|
|
|
|
if os_tween:
|
|
|
|
|
os_tween.kill()
|
2025-06-07 09:04:08 +00:00
|
|
|
|
if auto_freeze:
|
|
|
|
|
freeze_player(0, 3, false)
|
2025-01-12 06:02:00 +00:00
|
|
|
|
os_tween = create_tween()
|
|
|
|
|
os_label.text = ""
|
2025-01-12 11:36:41 +00:00
|
|
|
|
os_tween.tween_property(os_contaner, "modulate:a", 1.0, 0.2)
|
2025-01-12 06:02:00 +00:00
|
|
|
|
for line in lines:
|
2025-06-07 09:04:08 +00:00
|
|
|
|
var duration = max(min(4.0, line.text.length() * 0.2), 2.0) + 0.2
|
2025-05-30 11:05:06 +00:00
|
|
|
|
# var duration = max(min(4.0, line.text.length() * 0.2), 2.0) - 0.4
|
2025-06-07 09:04:08 +00:00
|
|
|
|
os_tween.tween_callback(_os_load_line.bind(line, duration))
|
|
|
|
|
os_tween.tween_interval(0.1)
|
2025-03-20 10:00:53 +00:00
|
|
|
|
os_tween.tween_callback(os_finished.emit)
|
2025-01-12 11:36:41 +00:00
|
|
|
|
os_tween.tween_property(os_contaner, "modulate:a", 0.0, 0.2)
|
2025-06-07 09:04:08 +00:00
|
|
|
|
if auto_release:
|
|
|
|
|
os_tween.tween_callback(release_player)
|
2025-01-12 06:02:00 +00:00
|
|
|
|
|
|
|
|
|
|
2025-06-07 09:04:08 +00:00
|
|
|
|
func _os_load_line(line, duration):
|
2025-01-12 06:02:00 +00:00
|
|
|
|
os_label.dialogue_line = line
|
2025-06-07 09:04:08 +00:00
|
|
|
|
os_label.type_out()
|
|
|
|
|
os_tween.pause()
|
|
|
|
|
if os_pausing_timer and os_pausing_timer.timeout.is_connected(os_tween.play):
|
|
|
|
|
os_pausing_timer.timeout.disconnect(os_tween.play)
|
|
|
|
|
os_pausing_timer = get_tree().create_timer(duration)
|
|
|
|
|
os_pausing_timer.timeout.connect(_on_os_line_timeout, CONNECT_ONE_SHOT)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_os_line_timeout(naturally := true):
|
|
|
|
|
if not naturally:
|
|
|
|
|
if os_pausing_timer.timeout.is_connected(_on_os_line_timeout):
|
|
|
|
|
os_pausing_timer.timeout.disconnect(_on_os_line_timeout)
|
|
|
|
|
if os_tween.is_valid():
|
|
|
|
|
os_label.text = ""
|
|
|
|
|
os_tween.play()
|
|
|
|
|
os_pausing_timer = null
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
|
|
|
if event.is_action_pressed("interact"):
|
|
|
|
|
if os_pausing_timer and os_pausing_timer.time_left > 0:
|
|
|
|
|
_on_os_line_timeout(false)
|
|
|
|
|
get_viewport().set_input_as_handled()
|
2025-01-16 12:24:21 +00:00
|
|
|
|
|
|
|
|
|
|
2025-01-20 13:45:47 +00:00
|
|
|
|
# animation -> offset_y
|
|
|
|
|
var foot_offset_dict = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func check_foot_offset():
|
|
|
|
|
var animation = sprite.animation
|
|
|
|
|
if (
|
|
|
|
|
current_animation_config.has("auto_foot_offset")
|
|
|
|
|
and current_animation_config["auto_foot_offset"]
|
|
|
|
|
):
|
|
|
|
|
sprite.offset = Vector2.ZERO
|
|
|
|
|
if not foot_offset_dict.has(animation):
|
|
|
|
|
var frame_y = sprite.sprite_frames.get_frame_texture(animation, 0).get_size().y
|
|
|
|
|
foot_offset_dict[animation] = frame_y * 0.5
|
|
|
|
|
# 从屏幕下边缘算起
|
|
|
|
|
global_position.y = 158.0 - foot_offset_dict[animation]
|
|
|
|
|
elif current_animation_config.has("foot_offset"):
|
|
|
|
|
sprite.offset = current_animation_config["foot_offset"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 从地面高度设置 y 坐标
|
|
|
|
|
func set_y_from_ground(player_y: float) -> void:
|
|
|
|
|
if (
|
|
|
|
|
current_animation_config.has("auto_foot_offset")
|
|
|
|
|
and current_animation_config["auto_foot_offset"]
|
|
|
|
|
):
|
|
|
|
|
# 无视地面 y,使用动画帧中的 y
|
|
|
|
|
if GlobalConfig.DEBUG:
|
|
|
|
|
print("set_y_from_ground: auto_foot_offset")
|
|
|
|
|
else:
|
|
|
|
|
global_position.y = player_y
|
2025-01-16 12:24:21 +00:00
|
|
|
|
|
|
|
|
|
|
2025-01-20 13:45:47 +00:00
|
|
|
|
func walk_to(pos: Vector2, duration: float) -> void:
|
|
|
|
|
var tween = create_tween()
|
|
|
|
|
velocity = Vector2.ZERO
|
|
|
|
|
if pos.x < global_position.x:
|
|
|
|
|
facing_direction.x = -1.0
|
|
|
|
|
elif pos.x > global_position.x:
|
|
|
|
|
facing_direction.x = 1.0
|
|
|
|
|
_check_status(facing_direction)
|
|
|
|
|
_play_animation()
|
|
|
|
|
if GlobalConfig.DEBUG:
|
|
|
|
|
print("walk_to:", pos, duration, " from:", global_position)
|
|
|
|
|
# 不同距离下,行走时长略做自适应
|
|
|
|
|
var time_cost = min(duration, global_position.distance_to(pos) / 50.0)
|
|
|
|
|
if time_cost < duration:
|
|
|
|
|
tween.tween_interval(duration - time_cost)
|
|
|
|
|
tween.tween_property(self, "global_position", pos, time_cost)
|
|
|
|
|
tween.tween_callback(_after_walk_to)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _after_walk_to():
|
|
|
|
|
velocity = Vector2.ZERO
|
|
|
|
|
current_status = PlayerAnimationConfig.MOVEMENT_IDLE
|
|
|
|
|
_play_animation()
|