@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