A game prototype built with Godot 4 exploring in-world inventory management mechanics.
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

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