xiandie/scene/player/main_player.gd

274 lines
8.7 KiB
GDScript3
Raw Normal View History

@tool
2024-12-23 01:29:31 +00:00
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():
var _scale = current_animation_config["scale"]
sprite.scale = Vector2(_scale, _scale)
# @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)
@export var velocity_ratio := 1.0
2024-12-23 01:29:31 +00:00
@export var action_locked := false:
set(val):
action_locked = val
_process_action_lock()
@export_enum("idle", "walking", "running") var current_status := 0:
2024-12-23 01:29:31 +00:00
set(val):
current_status = val
_play_animation()
@export var facing_direction := Vector2(1.0, -1.0):
2024-12-23 01:29:31 +00:00
set(val):
facing_direction = val
_play_animation()
var current_animation_config := PlayerAnimationConfig.ANIMATION_CONFIG[character] as Dictionary
2024-12-23 01:29:31 +00:00
@onready var footstep_timer = %FootstepTimer as Timer
2024-12-23 01:29:31 +00:00
@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
2024-12-23 01:29:31 +00:00
2024-12-23 13:12:13 +00:00
# # animation -> {frame -> {shadow polygon}}
# var animation_shadow_polygons = {}
2024-12-23 01:29:31 +00:00
func _ready() -> void:
os_contaner.modulate.a = 0.0
# set up animated sprite
var _scale = current_animation_config["scale"]
sprite.scale = Vector2(_scale, _scale)
_play_animation()
footstep_timer.timeout.connect(_on_footstep_timer_timeout)
footstep_timer.stop()
2025-01-14 00:56:51 +00:00
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()
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
if action_locked:
velocity = Vector2.ZERO
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
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
2024-12-23 01:29:31 +00:00
else:
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
func set_facing_direction(direction: Vector2) -> void:
facing_direction = direction
2024-12-23 13:12:13 +00:00
2024-12-23 01:29:31 +00:00
func _play_animation() -> void:
if not sprite:
return
sprite.offset = current_animation_config["foot_offset"]
# 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]
2024-12-23 01:29:31 +00:00
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 测试文本"
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 _get_speed(direction: Vector2) -> Vector2:
match current_status:
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)
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:
if action_locked or Engine.is_editor_hint():
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)
_check_status(direction)
2024-12-23 01:29:31 +00:00
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
2024-12-23 01:29:31 +00:00
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:
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()
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():
CameraFocusMarker.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, animation: String, loop: bool, offset: Vector2) -> void:
lock_mutex.lock()
if not action_locked:
action_locked = true
if animation and sprite.sprite_frames.has_animation(animation):
sprite.sprite_frames.set_animation_loop(animation, loop)
sprite.offset = offset
sprite.play(animation)
if not loop:
# 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_locked = false
# velocity_ratio = 1.0
lock_mutex.unlock()
_play_animation()
# 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
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