Iterate upon controls.

This commit is contained in:
blujai831 2025-03-29 02:12:43 -07:00
parent 147806124b
commit 09b25cf945
No known key found for this signature in database
GPG Key ID: DDC31A0363AA5E66
8 changed files with 288 additions and 85 deletions

View File

@ -130,6 +130,8 @@ var _freeze_lerp_requested := false
var _freeze_lerp_target := Vector3.ZERO
var _carrying: Node3D = null
@onready var _shapecast_object := PhysicsShapeQueryParameters3D.new()
#endregion
#region Public Properties
@ -165,7 +167,7 @@ var impetus := Vector3.ZERO:
if value.is_zero_approx():
impetus = Vector3.ZERO
else:
impetus = value.normalized()
impetus = value.limit_length()
## World-space direction the character "wants" / is "trying" to move in.
var global_impetus: Vector3:
get():
@ -173,7 +175,7 @@ var global_impetus: Vector3:
if gi.is_zero_approx():
return Vector3.ZERO
else:
return gi.normalized()
return gi.limit_length()
set(value):
impetus = (global_position + value)*global_transform
## Whether the character is "trying" to jump.
@ -440,7 +442,7 @@ func _reorient() -> void:
match _current_state_properties.yaw_orientation:
CharacterStateProperties.OrientationMode.MANUAL:
# If manual, do not modify yaw.
target_forward = -basis.z
target_forward = -global_basis.z
CharacterStateProperties.OrientationMode.FOLLOW_VELOCITY:
# If follow velocity, face velocity direction.
if !linear_velocity.is_zero_approx():
@ -452,14 +454,14 @@ func _reorient() -> void:
CharacterStateProperties.OrientationMode.FOLLOW_NORMAL:
# If follow normal, face away from the wall if there is one.
# Else, mimic manual.
target_forward = wall_normal if _touching_wall else -basis.z
target_forward = wall_normal if _touching_wall else -global_basis.z
# Determine target pitch.
var target_upward := Vector3.ZERO
var old_target_forward := target_forward
match _current_state_properties.pitch_orientation:
CharacterStateProperties.OrientationMode.MANUAL:
# If manual, do not modify pitch.
target_upward = basis.y
target_upward = global_basis.y
CharacterStateProperties.OrientationMode.FOLLOW_VELOCITY:
# If follow velocity, add influence from velocity to pitch.
target_upward = ground_normal
@ -525,29 +527,17 @@ func _do_standard_motion(delta: float) -> void:
# Check where we are trying to go.
var effective_impetus := _get_effective_impetus()
# Check whether in free mode.
var free_mode := (
_current_state_properties.physics_mode ==
CharacterStateProperties.PhysicsMode.FREE
)
# Use air physics iff either in free mode or not grounded (by state).
var use_air_physics := free_mode || !is_grounded()
var use_air_physics := air_physics_active()
# Calculate "volitional" velocity
# (component of velocity projected into the space
# that our impetus is allowed to affect).
var volitional_velocity := linear_velocity
if !free_mode:
volitional_velocity -= (
volitional_velocity.project(ground_normal)
)
# Determine which top speed to use.
var top_speed: float = (
_top_air_speed if use_air_physics else _top_ground_speed
)
var volitional_velocity := get_volitional_velocity()
# Check if we are exceeding top speed.
var too_fast := volitional_velocity.length() > top_speed
var too_fast := going_too_fast()
# Regardless, also check how close we are to top speed, 1.0 max.
var top_speed_proximity: float = clamp(
volitional_velocity.length()/top_speed, 0.0, 1.0
get_proportional_speed(), 0.0, 1.0
)
# Use no friction with air physics.
# Otherwise, calculate friction according to speed
@ -635,7 +625,7 @@ func _do_standard_motion(delta: float) -> void:
func _shapecast(from: Vector3, to: Vector3) -> Dictionary:
# prepare from/to sweep
var space_state := get_world_3d().direct_space_state
var query := PhysicsShapeQueryParameters3D.new()
var query := _shapecast_object
query.collide_with_areas = false
query.collide_with_bodies = true
query.collision_mask = 0xffffffff
@ -769,7 +759,10 @@ func _apply_jump_deceleration(delta: float) -> void:
## Whether the current state counts as grounded.
func is_grounded() -> bool:
return _current_state_properties.counts_as_grounded
return (
_current_state_properties &&
_current_state_properties.counts_as_grounded
)
## Whether there is a current contact that counts as ground.
func is_really_grounded() -> bool:
@ -971,6 +964,41 @@ func pick_up(what: Node3D, force: bool = false) -> void:
_carrying = what
force_change_state(&'pick-up')
## Whether our current physics mode is free mode.
func is_free_moving() -> bool:
return _current_state_properties && (
_current_state_properties.physics_mode ==
CharacterStateProperties.PhysicsMode.FREE
)
## Whether we currently move according to air physics.
## [br][br]
## If false, then we currently move according to ground physics.
func air_physics_active() -> bool:
return is_free_moving() || !is_grounded()
## Component of linear velocity that can be affected by impetus.
## [br][br]
## If we are free-moving, then this is simply our linear velocity.
## Otherwise, it equals linear velocity projected onto ground plane.
func get_volitional_velocity() -> Vector3:
if is_free_moving():
return linear_velocity
else:
return linear_velocity - linear_velocity.project(ground_normal)
## Top air speed if air physics active, else top ground speed.
func get_top_speed() -> float:
return _top_air_speed if air_physics_active() else _top_ground_speed
## Whether volitional velocity exceeds top speed.
func going_too_fast() -> bool:
return get_volitional_velocity().length() > get_top_speed()
## Current volitional speed as compared to (divided by) top speed.
func get_proportional_speed() -> float:
return get_volitional_velocity().length()/get_top_speed()
#endregion
#region Protected Methods

View File

@ -1,57 +1,17 @@
class_name PlayerCharacterController extends Node
@export var camera: Camera3D = null
@export var character: Character = null
@export var camera_sensitivity: float = 2.0
@export var camera_lerp_speed: float = 4.0
@export var camera_distance: float = 5.0
@export var camera_min_distance: float = 2.0
@export var camera_max_distance: float = 10.0
@export var camera_pitch: float = -0.5
@export var camera_min_pitch: float = -1.0
@export var camera_max_pitch: float = -0.2
@export var character: Character
@export var camera: Camera3D
@onready var camera_yaw: float = (
character.global_rotation.y if character else 0.0
)
func _physics_process(delta: float) -> void:
if camera && character:
var look_input := Vector3(
Input.get_axis(&'look_left', &'look_right'),
Input.get_axis(&'look_down', &'look_up'),
Input.get_axis(&'zoom_in', &'zoom_out')
)
camera_distance = clamp(
camera_distance + PI*look_input.z*camera_sensitivity*delta,
camera_min_distance, camera_max_distance
)
camera_yaw = fposmod(
camera_yaw - look_input.x*camera_sensitivity*delta,
2.0*PI
)
camera_pitch = clamp(
camera_pitch + look_input.y*camera_sensitivity*delta,
camera_min_pitch, camera_max_pitch
)
var bas := Basis.IDENTITY.rotated(
Vector3.RIGHT, camera_pitch
).rotated(
Vector3.UP, camera_yaw
)
bas = Basis.looking_at(-bas.z, character.ground_normal)
var posn := character.global_position + camera_distance*bas.z
var lerp_weight: float = 1.0 - 0.5**(camera_lerp_speed*delta)
camera.global_basis = camera.global_basis.slerp(bas, lerp_weight)
camera.global_position = lerp(camera.global_position, posn, lerp_weight)
var move_input := Vector2(
Input.get_axis(&'move_left', &'move_right'),
Input.get_axis(&'move_forward', &'move_backward')
)
character.global_impetus = (
move_input.x*camera.basis.x +
move_input.y*camera.basis.z
)
character.jump_impetus = Input.is_action_pressed(&'jump')
character.action1_impetus = Input.is_action_pressed(&'action1')
character.action2_impetus = Input.is_action_pressed(&'action2')
func _physics_process(_delta: float) -> void:
var move_input := Vector2(
Input.get_axis(&'move_left', &'move_right'),
Input.get_axis(&'move_forward', &'move_backward')
)
character.global_impetus = (
move_input.x*camera.global_basis.x +
move_input.y*camera.global_basis.z
)
character.jump_impetus = Input.is_action_pressed(&'jump')
character.action1_impetus = Input.is_action_pressed(&'action1')
character.action2_impetus = Input.is_action_pressed(&'action2')

View File

@ -1 +1 @@
uid://d26dyumvg37cp
uid://16os114krms3

View File

@ -1,8 +1,9 @@
[gd_scene load_steps=16 format=3 uid="uid://6wjqqijnie4p"]
[gd_scene load_steps=17 format=3 uid="uid://6wjqqijnie4p"]
[ext_resource type="Script" uid="uid://cmoaplk7fly6y" path="res://vfx/gameplay_camera.gd" id="1_0cecx"]
[ext_resource type="PackedScene" uid="uid://cp3qagrsuarl5" path="res://models/test/blender_test_map.blend" id="1_jy2qr"]
[ext_resource type="PackedScene" uid="uid://gis0gxap8i8t" path="res://characters/test_stick.tscn" id="2_hmvb4"]
[ext_resource type="Script" uid="uid://d26dyumvg37cp" path="res://characters/controllers/player_character_controller.gd" id="3_0cecx"]
[ext_resource type="Script" uid="uid://16os114krms3" path="res://characters/controllers/player_character_controller.gd" id="3_0cecx"]
[sub_resource type="Gradient" id="Gradient_hmvb4"]
offsets = PackedFloat32Array(0, 0.327869, 0.663934, 1)
@ -63,8 +64,11 @@ data = PackedVector3Array(10.1543, 0, -43.3539, 50, 0, -50, 14.3313, 0, -39.177,
[node name="Node3D" type="Node3D"]
[node name="Camera3D" type="Camera3D" parent="."]
[node name="Camera3D" type="Camera3D" parent="." node_paths=PackedStringArray("target")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -10.4229, 13.1742, -7.10402)
script = ExtResource("1_0cecx")
target = NodePath("../TestStick")
player_control = true
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_5t8ro")
@ -83,12 +87,12 @@ surface_material_override/0 = SubResource("StandardMaterial3D_fdbvi")
shape = SubResource("ConcavePolygonShape3D_jy2qr")
[node name="TestStick" parent="." instance=ExtResource("2_hmvb4")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.54375, 17.2871, -13.5584)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -12.7073, 17.2871, 0.809647)
[node name="PlayerCharacterController" type="Node" parent="TestStick" node_paths=PackedStringArray("camera", "character")]
[node name="PlayerCharacterController" type="Node" parent="TestStick" node_paths=PackedStringArray("character", "camera")]
script = ExtResource("3_0cecx")
camera = NodePath("../../Camera3D")
character = NodePath("..")
metadata/_custom_type_script = "uid://d26dyumvg37cp"
camera = NodePath("../../Camera3D")
metadata/_custom_type_script = "uid://16os114krms3"
[editable path="StaticBody3D/blender_test_map"]

View File

@ -0,0 +1,57 @@
class_name PrototypePlayerCharacterController extends Node
@export var camera: Camera3D = null
@export var character: Character = null
@export var camera_sensitivity: float = 2.0
@export var camera_lerp_speed: float = 4.0
@export var camera_distance: float = 5.0
@export var camera_min_distance: float = 2.0
@export var camera_max_distance: float = 10.0
@export var camera_pitch: float = -0.5
@export var camera_min_pitch: float = -1.0
@export var camera_max_pitch: float = -0.2
@onready var camera_yaw: float = (
character.global_rotation.y if character else 0.0
)
func _physics_process(delta: float) -> void:
if camera && character:
var look_input := Vector3(
Input.get_axis(&'look_left', &'look_right'),
Input.get_axis(&'look_down', &'look_up'),
Input.get_axis(&'zoom_in', &'zoom_out')
)
camera_distance = clamp(
camera_distance + PI*look_input.z*camera_sensitivity*delta,
camera_min_distance, camera_max_distance
)
camera_yaw = fposmod(
camera_yaw - look_input.x*camera_sensitivity*delta,
2.0*PI
)
camera_pitch = clamp(
camera_pitch + look_input.y*camera_sensitivity*delta,
camera_min_pitch, camera_max_pitch
)
var bas := Basis.IDENTITY.rotated(
Vector3.RIGHT, camera_pitch
).rotated(
Vector3.UP, camera_yaw
)
bas = Basis.looking_at(-bas.z, character.ground_normal)
var posn := character.global_position + camera_distance*bas.z
var lerp_weight: float = 1.0 - 0.5**(camera_lerp_speed*delta)
camera.global_basis = camera.global_basis.slerp(bas, lerp_weight)
camera.global_position = lerp(camera.global_position, posn, lerp_weight)
var move_input := Vector2(
Input.get_axis(&'move_left', &'move_right'),
Input.get_axis(&'move_forward', &'move_backward')
)
character.global_impetus = (
move_input.x*camera.global_basis.x +
move_input.y*camera.global_basis.z
)
character.jump_impetus = Input.is_action_pressed(&'jump')
character.action1_impetus = Input.is_action_pressed(&'action1')
character.action2_impetus = Input.is_action_pressed(&'action2')

View File

@ -0,0 +1 @@
uid://d26dyumvg37cp

152
vfx/gameplay_camera.gd Normal file
View File

@ -0,0 +1,152 @@
class_name GameplayCamera extends Camera3D
@export var target: Node3D
@export var target_distance: float = 5.0
@export var target_min_distance: float = 2.0
@export var target_max_distance: float = 10.0
@export var target_decentering_factor: float = 0.4
@export var follow_lerp_speed: float = 0.999
@export var turn_lerp_speed: float = 0.999
@export var fov_lerp_speed: float = 0.999
@export var clip_margin: float = 0.5
@export var gimbal_margin: float = 0.75
@export var player_control := false
@export var look_speed: float = 1.0
@export var look_lerp_speed: float = 0.99
@export var zoom_speed: float = 4.0
@export var mouse_sensitivity: float = 0.03125
@export var mouse_wheel_sensitivity: float = 5.0
@export var analog_sensitivity: float = 1.0
@export var analog_zoom_sensitivity: float = 1.0
var target_direction := Vector3.ZERO
var look_impetus := Vector3.ZERO
var look_velocity := Vector3.ZERO
@onready var _raycast_object := PhysicsRayQueryParameters3D.new()
func _ready() -> void:
snap_to_target()
func _update_mouse_mode() -> void:
if player_control:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
else:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
func _unhandled_input(event: InputEvent) -> void:
if !player_control: return
var move_event := event as InputEventMouseMotion
var button_event := event as InputEventMouseButton
if move_event:
look_impetus.x -= move_event.relative.x*mouse_sensitivity
look_impetus.y += move_event.relative.y*mouse_sensitivity
elif button_event:
match button_event.button_index:
MOUSE_BUTTON_WHEEL_UP:
look_impetus.z -= mouse_wheel_sensitivity
MOUSE_BUTTON_WHEEL_DOWN:
look_impetus.z += mouse_wheel_sensitivity
func _handle_analog_input() -> void:
if !player_control: return
look_impetus.x -= Input.get_axis(&'look_left', &'look_right')
look_impetus.y += Input.get_axis(&'look_down', &'look_up')
look_impetus.z += Input.get_axis(&'zoom_in', &'zoom_out')
func _respond_to_impetus(delta: float) -> void:
var gck: float = _gimbal_check()
if (
(look_impetus.y > 0.0 and gck < -gimbal_margin) ||
(look_impetus.y < 0.0 and gck > gimbal_margin)
):
look_impetus.y = 0.0
var desired_look_velocity := Vector3(
look_impetus.x*look_speed*target_distance,
look_impetus.y*look_speed*target_distance,
look_impetus.z*zoom_speed
)
look_impetus = look_impetus.limit_length()
look_velocity = lerp(look_velocity, desired_look_velocity, look_lerp_speed)
_try_go(
global_position +
global_basis.x*look_velocity.x*delta +
global_basis.y*look_velocity.y*delta
)
target_distance = clamp(
target_distance + look_velocity.z*delta,
target_min_distance, target_max_distance
)
look_impetus = Vector3.ZERO
func _try_go(where: Vector3) -> void:
_raycast_object.from = global_position
_raycast_object.to = where + (
(where - global_position).normalized()*clip_margin
)
var raycast_results := (
get_world_3d().direct_space_state.intersect_ray(_raycast_object)
)
if raycast_results.is_empty():
global_position = where
else:
global_position = raycast_results.position + (
raycast_results.normal*clip_margin
)
func snap_to_target() -> void:
if !target: return
target_direction = (
-target.global_basis.z - target.global_basis.y/2.0
).normalized()
_follow_target(0.0, true)
func _gimbal_check() -> float:
return target_direction.dot(target.global_basis.y)
func _follow_target(
delta: float,
snap: bool = false
) -> void:
if !target: return
var velocity := Vector3.ZERO
var top_speed: float = 30.0
if target is RigidBody3D:
velocity = (target as RigidBody3D).linear_velocity
if target is Character:
top_speed = (target as Character).get_top_speed()
var decentering: float = target_distance*target_decentering_factor
var target_position := (
target.global_position + decentering*target.global_basis.y
)
var target_offset := target_position - global_position
if !snap:
target_direction = target_offset.normalized()
var desired_position := target_position - target_distance*target_direction
_try_go(desired_position if snap else lerp(
global_position, desired_position,
1.0 - (1.0 - follow_lerp_speed)**delta
))
if abs(_gimbal_check()) <= lerp(gimbal_margin, 1.0, 0.5):
var desired_basis := Basis.looking_at(
target_offset,
target.global_basis.y
)
global_basis = desired_basis if snap else global_basis.slerp(
desired_basis,
1.0 - (1.0 - turn_lerp_speed)**delta
)
var desired_fov: float = min(
75.0 + 15.0*velocity.project(global_basis.z).length()/top_speed,
179.0
)
fov = desired_fov if snap else lerp(
fov, desired_fov,
1.0 - (1.0 - fov_lerp_speed)**delta
)
func _physics_process(delta: float) -> void:
_update_mouse_mode()
_handle_analog_input()
_respond_to_impetus(delta)
_follow_target(delta)

View File

@ -0,0 +1 @@
uid://cmoaplk7fly6y