@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(): 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 @export var player_movement_rect := Rect2(50, -500, 1400, 1000) @export var velocity_ratio := 1.0 @export var action_locked := false: set(val): action_locked = 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 var _scale = current_animation_config["scale"] sprite.scale = Vector2(_scale, _scale) _play_animation() 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: 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"] # 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 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(): 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: # # 绘制阴影,暂不启用 # 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