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
|
|
|