extends CharacterBody2D class_name MainPlayer enum MOVEMENT_STATUS { IDLE, WALKING, RUNNING, LAYING_STAY, LAYING_MOVING, CLIMBING_STAY, CLIMBING, } @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 var current_status: MOVEMENT_STATUS @export var facing_direction := Vector2(1.0, -1.0) @export var is_laying := false: set(val): is_laying = val # reset the facing direction wether the player is laying or not. _reset_face_direction() if is_laying: is_climbing = false @export var is_climbing := false: set(val): is_climbing = val # reset the facing direction wether the player is climbing or not. _reset_face_direction() if is_climbing: is_laying = false @export var running_locked := false @export var speed_walking := 75.0 #55 @export var speed_runnig := 120.0 #125 @export var speed_laying := 50.0 @export var speed_climbing := 50.0 #const JUMP_VELOCITY = -400.0 @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: # _reset_face_direction() footstep_timer.timeout.connect(_on_footstep_timer_timeout) footstep_timer.stop() SceneManager.focus_player() os_contaner.modulate.a = 0.0 sprite.frame_changed.connect(queue_redraw) func _on_footstep_timer_timeout(): # ground node is sibling of the player node. var ground_loader = SceneManager.get_ground_loader() if ground_loader: ground_loader.play_footstep_sound() func _reset_face_direction() -> void: facing_direction = Vector2(1, -1) func _process_action_lock() -> void: # reset status to idle or stay if action_locked: velocity = Vector2.ZERO if current_status == MOVEMENT_STATUS.WALKING or current_status == MOVEMENT_STATUS.RUNNING: current_status = MOVEMENT_STATUS.IDLE elif current_status == MOVEMENT_STATUS.LAYING_MOVING: current_status = MOVEMENT_STATUS.LAYING_STAY elif current_status == MOVEMENT_STATUS.CLIMBING: current_status = MOVEMENT_STATUS.CLIMBING_STAY _play_animation() # 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 is_laying: if direction.x: new_facing_direction.x = direction.x tmp_status = MOVEMENT_STATUS.LAYING_MOVING else: tmp_status = MOVEMENT_STATUS.LAYING_STAY elif is_climbing: if direction.y: new_facing_direction.y = direction.y tmp_status = MOVEMENT_STATUS.CLIMBING else: tmp_status = MOVEMENT_STATUS.CLIMBING_STAY else: if direction.x: new_facing_direction.x = direction.x tmp_status = MOVEMENT_STATUS.WALKING if !running_locked and Input.is_action_pressed("run"): tmp_status = MOVEMENT_STATUS.RUNNING else: tmp_status = MOVEMENT_STATUS.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 # update status _check_status(direction) func _play_animation() -> void: # reset the os label position on animation changed. _reset_os_and_shadow_position() match current_status: MOVEMENT_STATUS.IDLE: if facing_direction.x > 0.0: sprite.play(&"idle_r") else: sprite.play(&"idle_l") footstep_timer.stop() MOVEMENT_STATUS.WALKING: if facing_direction.x > 0.0: sprite.play(&"walking_r") else: sprite.play(&"walking_l") footstep_timer.wait_time = 0.5 footstep_timer.start() MOVEMENT_STATUS.RUNNING: if facing_direction.x > 0.0: sprite.play(&"running_r") else: sprite.play(&"running_l") footstep_timer.wait_time = 7.0 / 10.0 / 2.0 footstep_timer.start() MOVEMENT_STATUS.LAYING_STAY: if facing_direction.x > 0.0: sprite.play(&"laying_stay_r") else: sprite.play(&"laying_stay_l") footstep_timer.stop() MOVEMENT_STATUS.LAYING_MOVING: if facing_direction.x > 0.0: sprite.play(&"laying_moving_r") else: sprite.play(&"laying_moving_l") footstep_timer.wait_time = 2.0 / 3.0 footstep_timer.start() # MOVEMENT_STATUS.CLIMBING_STAY: # sprite.play(&"climbing_stay") # footstep_timer.stop() # MOVEMENT_STATUS.CLIMBING: # if facing_direction.y > 0.0: # sprite.play(&"climbing_down") # else: # sprite.play(&"climbing_up") # footstep_timer.wait_time = 2.0 / 3.0 # footstep_timer.start() func _get_speed(direction: Vector2) -> Vector2: match current_status: MOVEMENT_STATUS.WALKING: return Vector2(speed_walking * direction.x, 0.0) MOVEMENT_STATUS.RUNNING: return Vector2(speed_runnig * direction.x, 0.0) MOVEMENT_STATUS.LAYING_MOVING: return Vector2(speed_laying * direction.x, 0.0) MOVEMENT_STATUS.CLIMBING: return Vector2(0, speed_climbing * direction.y) return Vector2(0, 0) func _physics_process(_delta: float) -> void: if action_locked: velocity = Vector2.ZERO return # Add the gravity. #if not is_on_floor(): #velocity += get_gravity() * delta #if Input.is_action_just_pressed("jump") and is_on_floor(): #velocity.y = JUMP_VELOCITY var x_direction := Input.get_axis("left", "right") var y_direction := Input.get_axis("up", "down") var direction := Vector2(x_direction, y_direction) if _check_status(direction): _play_animation() 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 := "") -> void: lock_mutex.lock() if not action_locked: action_locked = true if animation: sprite.play(animation) 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: pass # # 绘制阴影,咱不启用 # 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.animation: var texture = sprite.sprite_frames.get_frame_texture(sprite.animation, 0) as Texture2D var size = texture.get_size() # reset the os label position os_pivot.position.y = -size.y * 0.5 - 4.0 # 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