Last active
June 6, 2024 19:43
-
-
Save levidavidmurray/d1dd566cb0071e8661a2e354d4f4fe6d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## Base class for all characters in the game. | |
class_name SC_Character | |
extends CharacterBody3D | |
############################## | |
## SIGNALS | |
############################## | |
signal died | |
signal spawned | |
############################## | |
## EXPORT VARIABLES | |
############################## | |
@export var base_move_speed: float = 7.0 | |
@export var sprint_speed_multiplier: float = 1.7 | |
@export var airborne_speed_multiplier: float = 0.6 | |
@export var move_acceleration: float = 20.0 | |
############################## | |
## ONREADY VARIABLES | |
############################## | |
@onready var state_chart: StateChart = %StateChart | |
@onready var health: HealthComponent = %HealthComponent | |
############################## | |
## VARIABLES | |
############################## | |
var target_velocity: Vector3 | |
var actual_velocity: Vector3 | |
var move_speed: float | |
var gravity: float | |
############################## | |
## LIFECYCLE METHODS | |
############################## | |
func _ready(): | |
move_speed = base_move_speed | |
health.died.connect(_on_died) | |
health.changed.connect(_on_health_changed) | |
state_chart.set_expression_property("health", health.health) | |
############################## | |
## STATE CALLBACKS | |
############################## | |
# === ALIVE === | |
func _on_alive_state_entered(): | |
spawned.emit() | |
# === GROUNDED === | |
func _on_grounded_state_physics_processing(delta: float): | |
if gravity < 0.0: | |
gravity = 0.0 | |
_handle_physics_process(delta) | |
if not is_on_floor(): | |
state_chart.send_event("airborne") | |
# === STANDING === | |
func _on_standing_state_entered(): | |
move_speed = base_move_speed | |
# === AIRBORNE === | |
func _on_airborne_state_entered(): | |
move_speed = base_move_speed * airborne_speed_multiplier | |
func _on_airborne_state_physics_processing(delta: float): | |
gravity -= 20.0 * delta | |
_handle_physics_process(delta) | |
if is_on_floor(): | |
state_chart.send_event("grounded") | |
############################## | |
## SIGNAL CALLBACKS | |
############################## | |
func _on_health_changed(value: int): | |
state_chart.set_expression_property("health", value) | |
func _on_died(): | |
state_chart.send_event("died") | |
died.emit() | |
############################## | |
## HELPER FUNCTIONS | |
############################## | |
func _handle_physics_process(delta: float): | |
target_velocity = transform.basis * target_velocity | |
actual_velocity = velocity.lerp(target_velocity, move_acceleration * delta) | |
actual_velocity.y = gravity | |
velocity = actual_velocity | |
move_and_slide() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class_name SC_Player | |
extends SC_Character | |
############################## | |
## EXPORT VARIABLES | |
############################## | |
@export_category("Movement") | |
@export var jump_speed: float = 6.0 | |
@export var crouch_speed_multiplier := 0.4 | |
@export var crouch_collider_height := 1.0 | |
@export var ladder_speed := 4.0 | |
@export_category("Camera") | |
@export var mouse_sensitivity := 700.0 | |
@export var camera_responsiveness := 80.0 | |
@export var vertical_angle_limit := 90.0 | |
############################## | |
## ONREADY VARIABLES | |
############################## | |
@onready var hotbar: Hotbar = %Hotbar | |
@onready var tps_rig: TPSRig = %TPSRig | |
@onready var camera: Camera3D = %Camera | |
@onready var collision: CollisionShape3D = %CollisionShape3D | |
@onready var head_check_ray: RayCast3D = %HeadCheckRay | |
@onready var head: Marker3D = %Head | |
@onready var player_input: PlayerInput = %PlayerInput | |
@onready var spectate_handler: SpectateHandler = %SpectateHandler | |
@onready var interact_handler: InteractHandler = %InteractHandler | |
@onready var inventory: Inventory = %Inventory | |
############################## | |
## VARIABLES | |
############################## | |
var default_collider_height: float | |
var collider_target_height: float | |
var input_mouse: Vector2 | |
var target_rotation: Vector3 | |
var jump_request_time: float | |
var was_on_floor_last_frame: bool | |
var move_tween: Tween | |
# Ladder stuff | |
var ladder: Ladder | |
var ladder_enter_pos: Vector3 | |
var ladder_exit_time: float | |
############################## | |
## GETTERS | |
############################## | |
var vertical_look_percent: float: | |
get: | |
return head.rotation_degrees.x / vertical_angle_limit | |
var jump_requested: bool: | |
get: | |
return G.get_time() - jump_request_time < 0.15 | |
############################## | |
## LIFECYCLE METHODS | |
############################## | |
func _ready(): | |
super() | |
state_chart.set_expression_property("is_crouching", false) | |
default_collider_height = (collision.shape as CapsuleShape3D).height | |
collider_target_height = default_collider_height | |
tps_rig.hide() | |
camera.make_current() | |
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED | |
tps_rig.activate(hotbar) | |
interact_handler.activate(camera, inventory, hotbar) | |
await G.wait(0.1) | |
UIManager.game_ui.show() | |
func _physics_process(delta): | |
was_on_floor_last_frame = is_on_floor() | |
if collision.shape.height != collider_target_height: | |
var shape = collision.shape as CapsuleShape3D | |
var y_delta = signf(collider_target_height - shape.height) * delta * 8.0 | |
var new_height = clampf(shape.height + y_delta, crouch_collider_height, default_collider_height) | |
y_delta = new_height - shape.height | |
tps_rig.position.y -= y_delta / 2.0 | |
head_check_ray.position.y -= y_delta / 2.0 | |
shape.height = new_height | |
############################## | |
## STATE CALLBACKS | |
############################## | |
# === ALIVE === | |
func _on_alive_state_entered(): | |
super() | |
tps_rig.set_ragdoll(false) | |
func _on_alive_state_processing(delta: float): | |
# Handle movement input | |
_set_target_velocity_from_input() | |
# Handle camera rotation | |
head.rotation.x = lerp_angle( | |
head.rotation.x, target_rotation.x, delta * camera_responsiveness | |
) | |
rotation.y = lerp_angle(rotation.y, target_rotation.y, delta * camera_responsiveness) | |
tps_rig.set_spine_blend_space(vertical_look_percent) | |
# Handle jump input buffer | |
if player_input.input_jump: | |
jump_request_time = G.get_time() | |
if player_input.input_interact: | |
interact_handler.interact() | |
player_input.input_jump = false | |
player_input.input_interact = false | |
func _on_alive_state_input(event): | |
if not event is InputEventMouseMotion: | |
return | |
if not Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: | |
return | |
# Set camera's target rotation based on mouse input | |
input_mouse = event.relative / mouse_sensitivity | |
target_rotation.x -= input_mouse.y | |
target_rotation.y -= input_mouse.x | |
var angle_limit = deg_to_rad(vertical_angle_limit) | |
target_rotation.x = clamp(target_rotation.x, -angle_limit, angle_limit) | |
# === DEAD === | |
func _on_dead_state_entered(): | |
tps_rig.velocity = Vector3.ZERO | |
tps_rig.is_crouching = false | |
tps_rig.set_ragdoll(true) | |
# === GROUNDED === | |
func _on_grounded_state_processing(delta: float): | |
var is_crouching = player_input.input_crouch | |
if not is_crouching: | |
is_crouching = head_check_ray.is_colliding() | |
state_chart.set_expression_property("is_crouching", is_crouching) | |
if jump_requested: | |
gravity = jump_speed | |
jump_request_time = 0.0 | |
func _on_grounded_state_physics_processing(delta: float): | |
super(delta) | |
tps_rig.velocity = velocity | |
_process_collisions() | |
# === CROUCHING === | |
func _on_crouching_state_entered(): | |
move_speed = base_move_speed * crouch_speed_multiplier | |
collider_target_height = crouch_collider_height | |
tps_rig.is_crouching = true | |
func _on_crouching_state_physics_processing(delta: float): | |
pass | |
func _on_crouching_state_exited(): | |
collider_target_height = default_collider_height | |
tps_rig.is_crouching = false | |
# === AIRBORNE === | |
func _on_airborne_state_processing(delta: float): | |
pass | |
func _on_airborne_state_physics_processing(delta: float): | |
super(delta) | |
_process_collisions() | |
# === LADDER === | |
func _on_ladder_state_entered(): | |
move_speed = ladder_speed | |
func _on_ladder_state_physics_processing(delta: float): | |
if move_tween and move_tween.is_running(): | |
return | |
# Move along the ladder in the direction of the camera | |
target_velocity = head.global_basis * target_velocity | |
actual_velocity = velocity.lerp(target_velocity, move_acceleration * delta) | |
velocity = actual_velocity | |
move_and_slide() | |
# Constrain planar movement to ladder | |
global_position.x = ladder_enter_pos.x | |
global_position.z = ladder_enter_pos.z | |
var dist_to_top = ladder.global_position.y - global_position.y | |
if dist_to_top < 0.70: | |
var top_pos = _to_collider_centered_pos(ladder.global_position) | |
move_tween = create_tween() | |
move_tween.tween_property(self, "global_position", top_pos, 0.25) | |
move_tween.finished.connect(func(): | |
state_chart.send_event("ladder_exit") | |
move_tween = null | |
) | |
elif is_on_floor() and not was_on_floor_last_frame: | |
state_chart.send_event("ladder_exit") | |
return | |
if jump_requested: | |
gravity = jump_speed | |
jump_request_time = 0.0 | |
state_chart.send_event("ladder_exit") | |
func _on_ladder_state_exited(): | |
ladder = null | |
############################## | |
## HELPER FUNCTIONS | |
############################## | |
func _process_collisions(): | |
for i in range(get_slide_collision_count()): | |
var col = get_slide_collision(i) | |
for k in range(col.get_collision_count()): | |
var body = col.get_collider(k) | |
if not body: | |
continue | |
if body is Ladder: | |
_collide_ladder(body) | |
func _collide_ladder(body: Ladder): | |
if vertical_look_percent < 0.2: | |
return | |
if global_position.y > body.global_position.y: | |
return | |
if G.get_time() - ladder_exit_time < 0.25: | |
return | |
ladder = body | |
ladder_enter_pos = global_position | |
state_chart.send_event("ladder_enter") | |
func _set_target_velocity_from_input(): | |
# Handle movement input | |
var move_input = player_input.input_move | |
target_velocity = Vector3(move_input.x, 0.0, move_input.y).normalized() * move_speed | |
func _to_collider_centered_pos(pos: Vector3) -> Vector3: | |
return pos + Vector3(0.0, default_collider_height / 2.0, 0.0) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment