@export var air_speed := 3.0
@export var acceleration := 20.0
-@export var jump_speed := 6.0
@export var rotation_speed := 10.0
-@export var fall_speed := 1.2
@export var idle_timeout := 5.0
@export var hard_landing_limit := 10.0
@export_group("Physics")
-@export var push_force := 1.5
+@export var push_force := 5.0
@export_group("Camera")
-@export_range(1.0, 10.0) var camera_distance := 2.5
+@export_range(1.0, 10.0) var camera_distance := 2.0
@export_range(0.0, 1.0) var mouse_sensitivity := 0.15
@export_range(0.0, 1.0) var mouse_sensitivity_x := 1.0
@export_range(0.0, 1.0) var mouse_sensitivity_y := 0.5
@onready var _debug: CanvasLayer = %debug
@onready var _camera_pivot: Node3D = %camera_pivot
@onready var _camera: Camera3D = %camera
+@onready var _camera_spring: SpringArm3D = %spring
@onready var _skin: AnimatedSkin = %skin
+@onready var _dust: GPUParticles3D = %dust
var _last_movement_direction := rotation
+var _floor_normal := Vector3.ONE
var _camera_input_direction := Vector2.ZERO
enum {CAMERA_MOUSE_INPUT, CAMERA_JOYSTICK_INPUT}
var _camera_input_method := CAMERA_MOUSE_INPUT
var _idle_time: float = 0.0
-var _player_speed: float = walk_speed
+var _player_speed: float = 0
func _ready() -> void:
- $camera_pivot/SpringArm3D.spring_length = camera_distance
_debug.draw.add_vector(self, "velocity", 1, 1, Color(0,1,0,1))
+ _debug.draw.add_vector(self, "_floor_normal", 1, 1, Color(0, 0, 1, 1))
_debug.draw.add_vector(self, "_last_movement_direction", 1, 1, Color(1,0,0,1))
_debug.stats.add_property(self, "velocity", "length")
_debug.stats.add_property(self, "_idle_time", "round")
+
+ _camera_spring.spring_length = camera_distance
+ _player_speed = jog_speed
+ _skin.set_grounded()
func _physics_process(delta: float) -> void:
func _input(event: InputEvent):
if event.is_action_pressed("player_run"):
- _player_speed = jog_speed
- elif event.is_action_released("player_run"):
_player_speed = walk_speed
+ elif event.is_action_released("player_run"):
+ _player_speed = jog_speed
if event.is_action_pressed("player_attack") and velocity.length() > jog_speed * .75:
_player_speed = charge_speed
+ elif event.is_action_released("player_attack"):
+ _player_speed = jog_speed
# Get the XZ input direction based on player's input relative to the camera
# reset mouse movement vector if mouse input
if _camera_input_method == CAMERA_MOUSE_INPUT:
_camera_input_direction = Vector2.ZERO
+
+ # change spring length depending on player speed
+ _camera_spring.spring_length = lerp(
+ _camera_spring.spring_length, camera_distance + velocity.length() / 4, delta
+ )
func _process_player_on_floor(delta: float):
var move_direction := _get_player_move_direction()
# if we're not stuck, then it's okay to set the velocity
- velocity = velocity.move_toward(move_direction * _player_speed, acceleration * delta)
+ _floor_normal = get_floor_normal()
+ var ground_angle := move_direction.angle_to(_floor_normal * Vector3(1, 0, 1))
+ #print(str(ground_angle))
+ velocity = velocity.move_toward(move_direction * (_player_speed + ground_angle), acceleration * delta)
var movement_speed := Vector3(velocity.x, 0, velocity.z).length()
# also, if we're moving, we're not idle
if move_direction.length() < 0.2:
if velocity == Vector3.ZERO:
_idle_time += delta
+ if _idle_time > idle_timeout:
+ _skin.set_idle()
else:
_last_movement_direction = move_direction
_idle_time = 0.0
+ _skin.set_grounded()
# if camera is unlocked, rotate whole skin to face movement direction
- # else, rotate to face camera pivot global Y direction
var skin_target_angle := Vector3.BACK.signed_angle_to(_last_movement_direction, Vector3.UP)
_skin.global_rotation.y = lerp_angle(
_skin.global_rotation.y,
skin_target_angle,
rotation_speed * delta
)
- _skin.set_movement_speed(movement_speed)
+
+ # lean into player momentum just a little bit
+ _skin.rotation.z = lerp_angle(
+ _skin.rotation.z,
+ clamp(_last_movement_direction.signed_angle_to(velocity, Vector3.UP) * movement_speed * 0.08, -PI/4, PI/ 4),
+ rotation_speed * delta * 0.25
+ )
+
+ # let skin know how fast player is moving along the ground
+ _skin.set_grounded_speed(movement_speed)
# timescale tweaking for fun effect!
- if movement_speed >= charge_speed * 0.75:
+ if _player_speed == charge_speed:
_skin.set_timescale(2.0)
else:
_skin.set_timescale(1.0)
func _process_player(delta: float) -> void:
if is_on_floor():
_process_player_on_floor(delta)
+
+ _dust.emitting = velocity.length() > (0.75 * charge_speed) and is_on_floor()
+ _dust.amount_ratio = velocity.length()
else:
+ _skin.set_falling()
velocity += get_gravity() * air_speed * delta
# now actually move!
- var movement_speed := velocity.length()
+ var velocity_length := velocity.length()
move_and_slide()
-
+
# handle collisions
for i in get_slide_collision_count():
var c := get_slide_collision(i)
if c.get_collider() is RigidBody3D:
var col: RigidBody3D = c.get_collider()
- col.apply_central_impulse(-c.get_normal() * push_force * movement_speed / col.mass)
+ col.apply_central_impulse(-c.get_normal() * velocity_length * push_force)