xiandie/scene/player/main_player.gd

327 lines
11 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.

@tool
extends CharacterBody2D
class_name MainPlayer
@export_enum("吕萍", "吕萍爬行", "吕萍带小猫", "小蝶", "小小蝶") var character := "吕萍":
set(val):
character = val
current_animation_config = PlayerAnimationConfig.ANIMATION_CONFIG[val]
current_status = PlayerAnimationConfig.MOVEMENT_IDLE
if is_node_ready():
sprite.scale = current_animation_config["scale"]
# @export var shadow_color := Color(0.1, 0.1, 0.1, 0.7)
# var shadow_y := 0.0
@export var player_movement_rect := Rect2(50, -500, 1400, 1000)
@export var velocity_ratio := 1.0
# action_locked 用于设置界面等强制锁定action_freezed 用于查看物品等锁定
# action_locked 优先级高于 action_freezed
# action_locked 对应 lock 与 unlock 方法
@export var action_locked := false:
set(val):
action_locked = val
_process_action_lock()
# action_freezed 对应 freeze 与 release 方法
@export var action_freezed := false:
set(val):
action_freezed = val
_process_action_lock()
@export_enum("idle", "walking", "running") var current_status := 0:
set(val):
current_status = val
_play_animation()
@export var facing_direction := Vector2(1.0, -1.0):
set(val):
facing_direction = val
_play_animation()
var current_animation_config := PlayerAnimationConfig.ANIMATION_CONFIG[character] as Dictionary
@onready var footstep_timer = %FootstepTimer as Timer
@onready var sprite = %AnimatedSprite2D as AnimatedSprite2D
@onready var os_pivot = %OSPivot as Control
@onready var os_contaner = %PanelContainer as PanelContainer
@onready var os_label = %OSLabel as DialogueLabel
# # animation -> {frame -> {shadow polygon}}
# var animation_shadow_polygons = {}
func _ready() -> void:
os_contaner.modulate.a = 0.0
# set up animated sprite
sprite.scale = current_animation_config["scale"]
_play_animation()
if Engine.is_editor_hint():
return
footstep_timer.timeout.connect(_on_footstep_timer_timeout)
footstep_timer.stop()
# SceneManager.focus_player(self)
func _on_footstep_timer_timeout():
# ground node is sibling of the player node.
var ground = get_parent() as Ground2D
if ground:
ground.play_footstep_sound()
func _process_action_lock() -> void:
# reset status to idle or stay
if action_locked or action_freezed:
velocity = Vector2.ZERO
if (
current_status == PlayerAnimationConfig.MOVEMENT_WALKING
or current_status == PlayerAnimationConfig.MOVEMENT_RUNNING
):
current_status = PlayerAnimationConfig.MOVEMENT_IDLE
# 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
if direction.x:
new_facing_direction.x = direction.x
tmp_status = PlayerAnimationConfig.MOVEMENT_WALKING
if Input.is_action_pressed("run") and !current_animation_config["running_locked"]:
tmp_status = PlayerAnimationConfig.MOVEMENT_RUNNING
else:
tmp_status = PlayerAnimationConfig.MOVEMENT_IDLE
if new_facing_direction != facing_direction or tmp_status != current_status:
facing_direction = new_facing_direction
current_status = tmp_status
return true
return false
func set_facing_direction(direction: Vector2) -> void:
facing_direction = direction
func _play_animation() -> void:
if not sprite:
return
sprite.offset = current_animation_config["foot_offset"]
sprite.scale = current_animation_config["scale"]
# reset the os label position on animation changed.
_reset_os_and_shadow_position()
var config = current_animation_config[current_status]
if facing_direction.x > 0.0:
sprite.play(config[1])
if config.size() > 2:
sprite.offset += config[3]
else:
sprite.play(config[0])
if config.size() > 2:
sprite.offset += config[2]
match current_status:
PlayerAnimationConfig.MOVEMENT_IDLE:
footstep_timer.stop()
PlayerAnimationConfig.MOVEMENT_WALKING:
footstep_timer.wait_time = current_animation_config["walk_footstep"]
footstep_timer.start()
PlayerAnimationConfig.MOVEMENT_RUNNING:
footstep_timer.wait_time = current_animation_config["run_footstep"]
footstep_timer.start()
# 在编辑器中不播放动画
if Engine.is_editor_hint():
footstep_timer.stop()
sprite.stop()
# 显示 os 效果
os_contaner.modulate.a = 1.0
os_label.text = "os 测试文本"
func _get_speed(direction: Vector2) -> Vector2:
match current_status:
PlayerAnimationConfig.MOVEMENT_WALKING:
var speed_walking = current_animation_config["speed_walking"]
return Vector2(speed_walking * direction.x, 0.0)
PlayerAnimationConfig.MOVEMENT_RUNNING:
var speed_runnig = current_animation_config["speed_runnig"]
return Vector2(speed_runnig * direction.x, 0.0)
return Vector2(0, 0)
func _physics_process(_delta: float) -> void:
if action_locked or action_freezed or Engine.is_editor_hint():
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)
_check_status(direction)
var speed := _get_speed(direction) as Vector2
velocity.x = move_toward(velocity.x, speed.x, 300.0) * velocity_ratio
velocity.y = move_toward(velocity.y, speed.y, 300.0) * velocity_ratio
# var x = global_position.x
move_and_slide()
global_position = global_position.clamp(player_movement_rect.position, player_movement_rect.end)
# var delta_x = global_position.x - x
# if delta_x:
# SceneManager.player_moved_delta_x(delta_x)
_tweak_camera_marker()
# drag the camera marker against the player movement
# so there will be a better vision in front of the player.
func _tweak_camera_marker():
SceneManager.get_camera_marker().tweak_position(velocity, facing_direction)
var lock_mutex = Mutex.new()
var release_timer: SceneTreeTimer
# lock_time: the time to lock the player action. 0 means lock forever, thus the player will be locked until release_player is called.
func freeze_player(lock_time: float, action_animation: int, auto_quit: bool) -> void:
lock_mutex.lock()
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]
var animation = config[0]
if not animation and config.size() >= 4:
animation = config[4] if facing_direction.x > 0.0 else config[3]
sprite.scale = config[1] if config.size() > 1 else Vector2.ONE
sprite.offset = config[2] if config.size() > 2 else Vector2.ZERO
if animation and sprite.sprite_frames.has_animation(animation):
sprite.sprite_frames.set_animation_loop(animation, false)
sprite.play(animation)
if auto_quit:
# reset animation after one play
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()
func freeze_and_play(lock_time: float, animation: String, auto_quit: bool) -> void:
lock_mutex.lock()
if not action_freezed:
action_freezed = true
if animation and sprite.sprite_frames.has_animation(animation):
sprite.sprite_frames.set_animation_loop(animation, false)
sprite.play(animation)
if auto_quit:
# reset animation after one play
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()
func release_player():
release_timer = null
lock_mutex.lock()
action_freezed = false
# velocity_ratio = 1.0
lock_mutex.unlock()
# _play_animation()
# func _draw() -> void:
# # 绘制阴影,暂不启用
# 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
func _reset_os_and_shadow_position():
if sprite and sprite.animation:
# reset the os label position
# 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"])
# reset the shadow position
# shadow_y = size.y * 0.5
var os_tween: Tween
func pop_os(lines := []):
if os_tween:
os_tween.kill()
os_tween = create_tween()
os_label.text = ""
os_tween.tween_property(os_contaner, "modulate:a", 1.0, 0.2)
for line in lines:
var duration = max(min(4.0, line.text.length() * 0.2), 2.0) - 0.4
os_tween.tween_callback(_os_load_line.bind(line))
os_tween.tween_callback(os_label.type_out)
os_tween.tween_interval(duration)
os_tween.tween_property(os_contaner, "modulate:a", 0.0, 0.2)
func _os_load_line(line):
os_label.dialogue_line = line
# func walk_to(pos: Vector2, duration: float, auto_unfreeze := true) -> void:
# var tween = create_tween()
# action_freezed = true
# velocity = Vector2.ZERO
# current_status = PlayerAnimationConfig.MOVEMENT_WALKING
# facing_direction.x = 1.0 if pos.x > global_position.x else -1.0
# _play_animation()
# tween.tween_property(self, "global_position", pos, duration)
# if auto_unfreeze:
# tween.tween_callback(_after_walk_to)
# func _after_walk_to():
# action_freezed = false