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.
138 lines
4.8 KiB
138 lines
4.8 KiB
2 years ago
|
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
|