diff --git a/content/scripts/dampened_camera.gd b/content/scripts/dampened_camera.gd index 02cf46c..30cb7de 100644 --- a/content/scripts/dampened_camera.gd +++ b/content/scripts/dampened_camera.gd @@ -1,7 +1,8 @@ class_name DampenedCamera3D extends Camera3D -@export var target : Node3D -@export var damping : bool +@export var target: Node3D +@export var damping: bool + # Called when the node enters the scene tree for the first time. func _ready() -> void: @@ -24,8 +25,10 @@ func _process(delta: float) -> void: target_pos.y = lerp(global_position.y, target_pos.y, 20 * delta) global_position = target_pos + func damp() -> void: damping = true + func donmp() -> void: damping = false diff --git a/content/scripts/player.gd b/content/scripts/player.gd index 8d6f48c..6badfc1 100644 --- a/content/scripts/player.gd +++ b/content/scripts/player.gd @@ -7,7 +7,7 @@ extends PhysicsBody3D ## The character will be blocked from moving up slopes steeper than this angle ## The character will be not be flagged as 'grounded' when stood on slopes steeper than this angle -@export var slope_limit : float = 45 +@export var slope_limit: float = 45 ## The character will automatically adjust height to step over obstacles this high @export var step_height := 0.2 @@ -17,13 +17,13 @@ extends PhysicsBody3D @export var snap_to_ground_distance := 0.2 @export_group("Connector Nodes") -@export var head : Node3D -@export var body : Node3D -@export var camera : DampenedCamera3D +@export var head: Node3D +@export var body: Node3D +@export var camera: DampenedCamera3D ## A reference to the collision shape this physics body is using ## (It's just a bit easier rather than aquiring the reference via code) -@export var collision_shape : CollisionShape3D +@export var collision_shape: CollisionShape3D @export_group("Advanced") ## Stop movement under this distance, but only if the movement touches at least 2 steep slopes @@ -60,37 +60,38 @@ extends PhysicsBody3D ## accurate. 4 is a decent number for low poly terrain! @export var max_iteration_count := 4 - - var jump_pressed := false var gravity := 9.8 +var _velocity: Vector3 = Vector3() +var at_max_speed: bool = true -var _velocity : Vector3 = Vector3() -var at_max_speed : bool = true +var grounded: bool = false +var ground_normal: Vector3 +var steep_slope_normals: Array[Vector3] = [] +var total_stepped_height: float = 0 -var grounded : bool = false -var ground_normal : Vector3 -var steep_slope_normals : Array[Vector3] = [] -var total_stepped_height : float = 0 +var escape_pressed: int +var vertical_collisions: Array[KinematicCollision3D] +var lateral_collisions: Array[KinematicCollision3D] +var snap_collisions: Array[KinematicCollision3D] -var escape_pressed : int -var vertical_collisions : Array[KinematicCollision3D] -var lateral_collisions : Array[KinematicCollision3D] -var snap_collisions : Array[KinematicCollision3D] +enum MovementType { VERTICAL, LATERAL } -enum MovementType {VERTICAL, LATERAL} func _ready() -> void: lock_mouse() + func lock_mouse() -> void: Input.mouse_mode = Input.MOUSE_MODE_CAPTURED + func unlock_mouse() -> void: Input.mouse_mode = Input.MOUSE_MODE_VISIBLE + # TODO should this have an action associated? # TODO should it be in unhandled? func _input(event: InputEvent) -> void: @@ -100,6 +101,7 @@ func _input(event: InputEvent) -> void: head.rotate_x(-motion.relative.y * mouse_sensitivity) head.rotation.x = clamp(head.rotation.x, -1.4, 1.4) + # TODO should this be in unhandled input? # Input buffering? lol lmao func get_input() -> Vector2: @@ -113,16 +115,16 @@ func get_input() -> Vector2: escape_pressed = 1 elif !Input.is_key_pressed(KEY_ESCAPE): escape_pressed = 0 - + jump_pressed = Input.is_action_just_pressed("cc_jump") if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED: - return Vector2(0,0) + return Vector2(0, 0) if Input.is_action_just_pressed("cc_sprint"): at_max_speed = !at_max_speed - var input_dir : Vector2 = Vector2() + var input_dir: Vector2 = Vector2() if Input.is_action_pressed("cc_forward"): input_dir += Vector2.UP if Input.is_action_pressed("cc_backward"): @@ -136,6 +138,7 @@ func get_input() -> Vector2: # Local rotation is fine given the parent isn't rotating ever return input_dir.rotated(-body.rotation.y) + func _physics_process(delta: float) -> void: # Before Move var _desired_horz_velocity := get_input() @@ -164,7 +167,7 @@ func _physics_process(delta: float) -> void: # Entry point to moving -func move(intended_velocity : Vector3, delta : float) -> void: +func move(intended_velocity: Vector3, delta: float) -> void: var start_position := position var lateral_translation := horz(intended_velocity * delta) @@ -183,29 +186,45 @@ func move(intended_velocity : Vector3, delta : float) -> void: # An initial grounded check is important because ground normal is used # to detect seams with steep slopes; which often are collided with before the ground if vertical_translation.y <= 0: - var initial_grounded_collision := move_and_collide(Vector3.DOWN * ground_cast_distance, true, depenetration_margin) + var initial_grounded_collision := move_and_collide( + Vector3.DOWN * ground_cast_distance, true, depenetration_margin + ) if initial_grounded_collision: - if initial_grounded_collision.get_normal(0).angle_to(Vector3.UP) < deg_to_rad(slope_limit): + if ( + initial_grounded_collision.get_normal(0).angle_to(Vector3.UP) + < deg_to_rad(slope_limit) + ): grounded = true ground_normal = initial_grounded_collision.get_normal(0) # === Iterate Movement Laterally var lateral_iterations := 0 while lateral_translation.length() > 0 and lateral_iterations < max_iteration_count: - - lateral_translation = move_iteration(MovementType.LATERAL, lateral_collisions, initial_lateral_translation, lateral_translation) + lateral_translation = move_iteration( + MovementType.LATERAL, + lateral_collisions, + initial_lateral_translation, + lateral_translation + ) lateral_iterations += 1 # De-jitter by just ignoring lateral movement # (multiple steep slopes have been collided, but movement is very small) - if steep_slope_normals.size() > 1 and horz(position - start_position).length() < steep_slope_jitter_reduce: + if ( + steep_slope_normals.size() > 1 + and horz(position - start_position).length() < steep_slope_jitter_reduce + ): position = start_position # === Iterate Movement Vertically var vertical_iterations := 0 while vertical_translation.length() > 0 and vertical_iterations < max_iteration_count: - - vertical_translation = move_iteration(MovementType.VERTICAL, vertical_collisions, initial_vertical_translation, vertical_translation) + vertical_translation = move_iteration( + MovementType.VERTICAL, + vertical_collisions, + initial_vertical_translation, + vertical_translation + ) vertical_iterations += 1 # Don't include step height in actual velocity @@ -236,14 +255,23 @@ func move(intended_velocity : Vector3, delta : float) -> void: var ground_snap_iterations := 0 var ground_snap_translation := Vector3.DOWN * snap_to_ground_distance while ground_snap_translation.length() > 0 and ground_snap_iterations < max_iteration_count: - - ground_snap_translation = move_iteration(MovementType.VERTICAL, snap_collisions, Vector3.DOWN, ground_snap_translation) + ground_snap_translation = move_iteration( + MovementType.VERTICAL, snap_collisions, Vector3.DOWN, ground_snap_translation + ) ground_snap_iterations += 1 # Decide whether to keep the snap or not if snap_collisions.is_empty(): - var after_snap_ground_test := move_and_collide(Vector3.DOWN * ground_cast_distance, true, depenetration_margin) - if after_snap_ground_test and after_snap_ground_test.get_normal(0).angle_to(Vector3.UP) < deg_to_rad(slope_limit): + var after_snap_ground_test := move_and_collide( + Vector3.DOWN * ground_cast_distance, true, depenetration_margin + ) + if ( + after_snap_ground_test + and ( + after_snap_ground_test.get_normal(0).angle_to(Vector3.UP) + < deg_to_rad(slope_limit) + ) + ): # There was no snap collisions, but there is ground underneath # This can be due to an edge case where the snap movement falls through the ground # Why does this check not fall through the ground? I don't know @@ -252,7 +280,10 @@ func move(intended_velocity : Vector3, delta : float) -> void: else: # No snap collisions and no floor, reset position = before_snap_pos - elif !(snap_collisions[snap_collisions.size() - 1].get_normal(0).angle_to(Vector3.UP) < deg_to_rad(slope_limit)): + elif !( + snap_collisions[snap_collisions.size() - 1].get_normal(0).angle_to(Vector3.UP) + < deg_to_rad(slope_limit) + ): # Collided with steep ground, reset position = before_snap_pos else: @@ -261,9 +292,13 @@ func move(intended_velocity : Vector3, delta : float) -> void: # Moves are composed of multiple iterates # In each iteration, move until collision, then calculate and return the next movement -func move_iteration(movement_type: MovementType, collision_array : Array, initial_direction: Vector3, translation: Vector3) -> Vector3: - - var collisions : KinematicCollision3D +func move_iteration( + movement_type: MovementType, + collision_array: Array, + initial_direction: Vector3, + translation: Vector3 +) -> Vector3: + var collisions: KinematicCollision3D # If Lateral movement, try stepping if movement_type == MovementType.LATERAL: @@ -274,7 +309,7 @@ func move_iteration(movement_type: MovementType, collision_array : Array, initia var current_step_height := step_height var step_up_collisions := move_and_collide(Vector3.UP * step_height, false, 0) - if (step_up_collisions): + if step_up_collisions: current_step_height = step_up_collisions.get_travel().length() var raised_forward_collisions := move_and_collide(translation, false, 0) var down_collision := move_and_collide(Vector3.DOWN * current_step_height, false, 0) @@ -282,17 +317,19 @@ func move_iteration(movement_type: MovementType, collision_array : Array, initia # Only step if the step algorithm landed on a walkable surface # AND the walk lands on a non-walkable surface # This stops stepping up ramps - if (down_collision and - down_collision.get_normal(0).angle_to(Vector3.UP) < deg_to_rad(slope_limit) and - walk_test_collision and - !walk_test_collision.get_normal(0).angle_to(Vector3.UP) < deg_to_rad(slope_limit)): + if ( + down_collision + and down_collision.get_normal(0).angle_to(Vector3.UP) < deg_to_rad(slope_limit) + and walk_test_collision + and !walk_test_collision.get_normal(0).angle_to(Vector3.UP) < deg_to_rad(slope_limit) + ): do_step = true - if do_step: # Keep track of stepepd distance to cancel it out later + if do_step: # Keep track of stepepd distance to cancel it out later total_stepped_height += position.y - temp_position.y collisions = raised_forward_collisions camera.damp() - else: # Reset and move normally + else: # Reset and move normally position = temp_position collisions = move_and_collide(translation, false, depenetration_margin) @@ -322,8 +359,8 @@ func move_iteration(movement_type: MovementType, collision_array : Array, initia # These values shouldn't be calculated every frame; they only need to change # when the user defines the slope limit # But I'm lazy :) - var min_block_angle : float - var max_block_angle : float + var min_block_angle: float + var max_block_angle: float if movement_type == MovementType.LATERAL: min_block_angle = deg_to_rad(slope_limit) if grounded: @@ -334,7 +371,6 @@ func move_iteration(movement_type: MovementType, collision_array : Array, initia min_block_angle = 0 max_block_angle = deg_to_rad(slope_limit) - # This algorithm for determining where to move on a collisions uses "projection plane" # Whatever surface the character hits, we generate a blocking "plane" that we will slide along # @@ -351,11 +387,16 @@ func move_iteration(movement_type: MovementType, collision_array : Array, initia # If collision happens on the "side" of the cylinder, treat it as a vertical # wall in all cases (we use the tangent of the cylinder) - if (movement_type == MovementType.LATERAL and - (collision_point.y > (collision_shape.global_position.y - cylinder.height / 2) + bottom_height)): - projection_normal = collision_shape.global_position - collision_point - projection_normal.y = 0 - projection_normal = projection_normal.normalized() + if ( + movement_type == MovementType.LATERAL + and ( + collision_point.y + > (collision_shape.global_position.y - cylinder.height / 2) + bottom_height + ) + ): + projection_normal = collision_shape.global_position - collision_point + projection_normal.y = 0 + projection_normal = projection_normal.normalized() # Otherwise, determine if the surface is a blocking surface elif surface_angle >= min_block_angle and surface_angle <= max_block_angle: @@ -386,11 +427,13 @@ func move_iteration(movement_type: MovementType, collision_array : Array, initia var continued_translation := projection_plane.project(collisions.get_remainder()) var initial_influenced_translation := projection_plane.project(initial_direction) - var next_translation : Vector3 + var next_translation: Vector3 if initial_influenced_translation.dot(continued_translation) >= 0: next_translation = continued_translation else: - next_translation = initial_influenced_translation.normalized() * continued_translation.length() + next_translation = ( + initial_influenced_translation.normalized() * continued_translation.length() + ) # See same_surface_adjust_distance if next_translation.normalized() == translation.normalized(): @@ -399,16 +442,17 @@ func move_iteration(movement_type: MovementType, collision_array : Array, initia return next_translation -func already_touched_slope_close_match(normal : Vector3) -> bool: +func already_touched_slope_close_match(normal: Vector3) -> bool: for steep_slope_normal in steep_slope_normals: if steep_slope_normal.distance_squared_to(normal) < 0.001: return true return false + # I wrote this a while ago in Unity # I ported it here but I only have a vague grasp of how it works -func relative_slope_normal(slope_normal : Vector3, lateral_desired_direction : Vector3) -> Vector3: +func relative_slope_normal(slope_normal: Vector3, lateral_desired_direction: Vector3) -> Vector3: var slope_normal_horz := horz(slope_normal) var angle_to_straight := slope_normal_horz.angle_to(-lateral_desired_direction) var angle_to_up := slope_normal.angle_to(Vector3.UP) @@ -435,8 +479,10 @@ func relative_slope_normal(slope_normal : Vector3, lateral_desired_direction : V return emulated_normal.normalized() + func horz(value: Vector3) -> Vector3: - return Vector3(value.x,0,value.z) + return Vector3(value.x, 0, value.z) + func vert(value: Vector3) -> Vector3: - return Vector3(0,value.y,0) + return Vector3(0, value.y, 0) diff --git a/content/scripts/smoother.gd b/content/scripts/smoother.gd index efac48d..9ca4cc3 100644 --- a/content/scripts/smoother.gd +++ b/content/scripts/smoother.gd @@ -1,9 +1,10 @@ extends Node3D -@export var target : Node3D +@export var target: Node3D var currTransform: Transform3D var prevTransform: Transform3D + # Called when the node enters the scene tree for the first time. func _ready() -> void: currTransform = Transform3D() @@ -26,6 +27,7 @@ func _process(_delta: float) -> void: transform = new_transform + func _physics_process(_delta: float) -> void: prevTransform = currTransform currTransform = target.global_transform