You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							137 lines
						
					
					
						
							4.8 KiB
						
					
					
				
			
		
		
	
	
							137 lines
						
					
					
						
							4.8 KiB
						
					
					
				extends Node3D | 
						|
 | 
						|
@export var camera    : Camera | 
						|
@export var skeleton  : Skeleton3D | 
						|
@export var root_bone : BoneAttachment3D | 
						|
 | 
						|
@onready var player      := find_parent("Player") as Player | 
						|
@onready var anim_tree   := $AnimationTree as AnimationTree | 
						|
@onready var anim_player := anim_tree.get_node(anim_tree.anim_player) as AnimationPlayer | 
						|
 | 
						|
@onready var camera_default_transform := camera.transform | 
						|
 | 
						|
@onready var walk_forward_anim  := anim_player.get_animation("walk_forward") | 
						|
@onready var walk_backward_anim := anim_player.get_animation("walk_backward") | 
						|
 | 
						|
# TODO: @onready var walk_loop_length := walk_forward_anim.length | 
						|
 | 
						|
# Contains all the bones in the skeleton, keyed by name (e.g. "LowerArm_L"). | 
						|
var bones := {  } | 
						|
 | 
						|
# Whether the player's body is currently turning to match up with the camera rotation. | 
						|
var is_turning := false | 
						|
 | 
						|
# Current amount the body is turned due to walking sideways. | 
						|
var body_yaw := 0.0 # radians | 
						|
 | 
						|
 | 
						|
func _ready() -> void: | 
						|
	var add_bone := func(bone: BoneAttachment3D) -> void: | 
						|
		bone.override_pose = true | 
						|
		bones[bone.name] = bone | 
						|
 | 
						|
	add_bone.call(root_bone) | 
						|
	for child in root_bone.find_children("*", "BoneAttachment3D"): | 
						|
		add_bone.call(child as BoneAttachment3D) | 
						|
 | 
						|
 | 
						|
func _process(delta: float) -> void: | 
						|
	reset_transforms() | 
						|
	handle_turning(delta) | 
						|
	handle_looking_animation(delta) | 
						|
	handle_walking_animation(delta) | 
						|
 | 
						|
 | 
						|
func reset_transforms() -> void: | 
						|
	for bone_name in bones: | 
						|
		var bone := bones[bone_name] as BoneAttachment3D | 
						|
		bone.transform = skeleton.get_bone_pose(bone.bone_idx) | 
						|
	camera.transform = camera_default_transform | 
						|
 | 
						|
 | 
						|
func handle_turning(delta: float) -> void: | 
						|
	const TURN_BEGIN := 60.0 # Start turning when camera is rotated this much. | 
						|
	const TURN_END   :=  5.0 # Stop turning when body is this close to camera rotation. | 
						|
	const TURN_SPEED :=  6.0 | 
						|
 | 
						|
	var yaw := camera.current_yaw # Camera yaw relative to player yaw. | 
						|
	if player.is_moving || abs(yaw) > deg_to_rad(TURN_BEGIN): | 
						|
		is_turning = true | 
						|
	if is_turning: | 
						|
		var yaw_delta = sign(yaw) * min(abs(yaw), (abs(yaw) * TURN_SPEED) * delta) | 
						|
		player.rotate_y(yaw_delta) | 
						|
		camera.current_yaw -= yaw_delta | 
						|
		if abs(camera.current_yaw) < deg_to_rad(TURN_END): | 
						|
			is_turning = false | 
						|
 | 
						|
 | 
						|
func handle_looking_animation(_delta: float) -> void: | 
						|
	const PITCH_FACTOR_NECK := 0.25 | 
						|
	const PITCH_FACTOR_HEAD := 0.35 | 
						|
 | 
						|
	var pitch := camera.current_pitch | 
						|
	bones["Neck"].rotate_x(-pitch * PITCH_FACTOR_NECK) | 
						|
	bones["Head"].rotate_x(-pitch * PITCH_FACTOR_HEAD) | 
						|
	camera.rotate_x(pitch * (1 - PITCH_FACTOR_NECK - PITCH_FACTOR_HEAD)) | 
						|
 | 
						|
	const YAW_FACTOR_LOWER_BODY := 0.06 | 
						|
	const YAW_FACTOR_UPPER_BODY := 0.18 | 
						|
	const YAW_FACTOR_NECK := 0.2 | 
						|
	const YAW_FACTOR_HEAD := 0.3 | 
						|
 | 
						|
	var yaw := camera.current_yaw | 
						|
	bones["LowerBody"].global_rotate(Vector3.UP, yaw * YAW_FACTOR_LOWER_BODY) | 
						|
	bones["UpperBody"].global_rotate(Vector3.UP, yaw * YAW_FACTOR_UPPER_BODY) | 
						|
	bones["Neck"].global_rotate(Vector3.UP, yaw * YAW_FACTOR_NECK) | 
						|
	bones["Head"].global_rotate(Vector3.UP, yaw * YAW_FACTOR_HEAD) | 
						|
	camera.global_rotate(Vector3.UP, yaw * (1 - YAW_FACTOR_LOWER_BODY - YAW_FACTOR_UPPER_BODY - YAW_FACTOR_NECK - YAW_FACTOR_HEAD)) | 
						|
 | 
						|
	# How much of the "ideal" camera rotation (rather than animation rotation) should be applied. | 
						|
	const CAMERA_FACTOR_IDEAL_PITCH := 1.0 # 0.7 | 
						|
	const CAMERA_FACTOR_IDEAL_YAW   := 1.0 # 0.8 | 
						|
	const CAMERA_FACTOR_IDEAL_ROLL  := 1.0 # 0.9 | 
						|
 | 
						|
	var global_yaw := player.rotation.y + yaw | 
						|
	camera.global_rotation.x = lerp_angle(camera.global_rotation.x, pitch, CAMERA_FACTOR_IDEAL_PITCH) | 
						|
	camera.global_rotation.y = lerp_angle(camera.global_rotation.y, global_yaw, CAMERA_FACTOR_IDEAL_YAW) | 
						|
	camera.global_rotation.z = lerp_angle(camera.global_rotation.z, 0, CAMERA_FACTOR_IDEAL_ROLL) | 
						|
 | 
						|
 | 
						|
func handle_walking_animation(delta: float) -> void: | 
						|
	var input := Input.get_vector("move_strafe_left", "move_strafe_right", "move_forward", "move_backward") | 
						|
	var is_on_floor := player.time_since_on_floor < 0.25 | 
						|
	var is_moving_forward := input.y <= 0 | 
						|
 | 
						|
	var walk_state : String | 
						|
	var walk_direction : String | 
						|
	var target_body_yaw : float | 
						|
 | 
						|
	if is_on_floor && player.is_moving: | 
						|
		walk_state = "move" | 
						|
	else: | 
						|
		walk_state = "idle" | 
						|
 | 
						|
	if is_moving_forward: | 
						|
		walk_direction = "forward" | 
						|
		target_body_yaw = -Vector2.UP.angle_to(input) | 
						|
	else: | 
						|
		walk_direction = "backward" | 
						|
		target_body_yaw = -Vector2.DOWN.angle_to(input) | 
						|
 | 
						|
	anim_tree["parameters/walk_state/transition_request"] = walk_state | 
						|
	anim_tree["parameters/walk_direction/transition_request"] = walk_direction | 
						|
 | 
						|
	const YAW_FACTOR_LOWER_BODY := 0.25 | 
						|
	const YAW_FACTOR_UPPER_BODY := 0.25 | 
						|
	const YAW_FACTOR_NECK       := 0.50 | 
						|
 | 
						|
	body_yaw += (target_body_yaw - body_yaw) * delta * 6 | 
						|
 | 
						|
	bones["Root"].global_rotate(Vector3.UP, body_yaw) | 
						|
	bones["LowerBody"].global_rotate(Vector3.UP, -body_yaw * YAW_FACTOR_LOWER_BODY) | 
						|
	bones["UpperBody"].global_rotate(Vector3.UP, -body_yaw * YAW_FACTOR_UPPER_BODY) | 
						|
	bones["Neck"].global_rotate(Vector3.UP, -body_yaw * YAW_FACTOR_NECK) | 
						|
 | 
						|
 | 
						|
static func angle_difference(from: float, to: float) -> float: | 
						|
	return fposmod(to - from + PI, TAU) - PI
 | 
						|
 |