Skip to content

Instantly share code, notes, and snippets.

@PranavSK
Last active April 12, 2021 11:31
Show Gist options
  • Select an option

  • Save PranavSK/4039860ea4ab214f73d561b8d9f7afa8 to your computer and use it in GitHub Desktop.

Select an option

Save PranavSK/4039860ea4ab214f73d561b8d9f7afa8 to your computer and use it in GitHub Desktop.
extends StateMachine
const MAX_MOVE_SLIDES: = 6
const Ragdoll: = preload("states/ragdoll.gd")
const Falling: = preload("states/falling.gd")
const Grounded: = preload("states/grounded.gd")
const Crouched: = preload("states/crouched.gd")
const STATE_RAGDOLL: = "Ragdoll"
const STATE_FALLING: = "Falling"
const STATE_STAND_WALK: = "Walking"
const STATE_STAND_RUN: = "Running"
const STATE_STAND_SPRINT: = "Sprinting"
const STATE_CROUCH_WALK: = "CrouchedWalking"
const STATE_CROUCH_RUN: = "CrouchedRunning"
enum Gait { WALK, RUN, SPRINT }
# Setup
export var skeleton_node_path: NodePath
export var capsule_node_path: NodePath
export (float, 0.0, 50.0, 0.1) var terminal_speed: = 10.0
# Slopes with angle greater than this value are considered as walls.
export (float, 0.1, 90, 0.1) var floor_max_angle: = 40.0
var allowed_gait: int = Gait.SPRINT
var desired_gait: int = Gait.RUN
var velocity: = Vector3.ZERO
# World
var gravity_scale: = 1.0 setget _no_setter
var gravity_dir := Vector3.ZERO setget _no_setter
var up: = Vector3.ZERO setget _no_setter
var right: = Vector3.ZERO setget _no_setter
var forward: = Vector3.ZERO setget _no_setter
onready var character: KinematicBody = get_parent()
onready var skeleton: Skeleton = get_node_or_null(skeleton_node_path)
onready var capsule: CollisionShape = get_node_or_null(capsule_node_path)
func _physics_process(delta):
_update_orientation_vectors()
# Update the state machine
update(delta)
func move(input_direction: Vector2):
# TODO: Change animation direction based on this.
# TODO: Rootmotion setup
if get_current_state().has_method("move"):
var direction = right * input_direction.x + forward * input_direction.y
if direction.length_squared() > 1:
direction = direction.normalized()
get_current_state().move(direction)
func roll():
pass
func crouch():
pass
func uncrouch():
pass
func set_ragdoll():
change_state(STATE_RAGDOLL)
func is_ragdoll() -> bool:
return get_current_state() is Ragdoll
func set_falling() -> void:
change_state(STATE_FALLING)
func is_falling() -> bool:
return get_current_state() is Falling
func set_grounded() -> void:
if desired_gait == Gait.SPRINT and allowed_gait >= Gait.SPRINT:
change_state(STATE_STAND_SPRINT)
elif desired_gait == Gait.RUN and allowed_gait >= Gait.RUN:
change_state(STATE_STAND_RUN)
else:
change_state(STATE_STAND_WALK)
func is_grounded() -> bool:
return get_current_state() is Grounded
func set_crouched() -> void:
if desired_gait == Gait.RUN and allowed_gait >= Gait.RUN:
change_state(STATE_CROUCH_RUN)
else:
change_state(STATE_CROUCH_WALK)
func is_crouched() -> bool:
return get_current_state() is Crouched
func _update_orientation_vectors():
var state = PhysicsServer.body_get_direct_state(character.get_rid())
if not state.total_gravity.is_equal_approx(Vector3.ZERO):
gravity_dir = state.total_gravity.normalized()
gravity_scale = state.total_gravity.length()
else:
gravity_dir = ProjectSettings.get_setting("physics/3d/default_gravity_vector")
gravity_scale = ProjectSettings.get_setting("physics/3d/default_gravity")
up = -gravity_dir
var plane = Plane(up, 0.0)
right = plane.project(Vector3.RIGHT).normalized()
forward = plane.project(Vector3.FORWARD).normalized()
func _get_configuration_warning():
if not get_parent() is KinematicBody:
return "Please use this as a child to a KinematicBody!"
else: return ""
func _no_setter(_value):
assert(false, "The property has no valid setter!!")
extends Node
var locomotor
func _enter_tree():
locomotor = get_parent()
locomotor.register_state(name, self)
func _exit_tree():
locomotor.unregister_state(name)
func _apply_velocity(floor_snap: = Vector3.ZERO, stop_on_slope: = false):
locomotor.character.velocity = locomotor.character.move_and_slide_with_snap(
locomotor.velocity,
floor_snap,
locomotor.up,
stop_on_slope,
locomotor.MAX_MOVE_SLIDES,
deg2rad(locomotor.floor_max_angle),
false
)
func _apply_gravity(delta: float) -> void:
var curr_up_speed = locomotor.velocity.dot(locomotor.up)
var up_speed_change = locomotor.terminal_speed - curr_up_speed
var max_up_speed_change = locomotor.gravity_scale * delta
up_speed_change = clamp(
up_speed_change, -max_up_speed_change, max_up_speed_change
)
locomotor.velocity += up_speed_change * locomotor.gravity_dir
extends "grounded.gd"
export (float) var crouched_height
func on_enter():
pass
func on_exit():
pass
extends "move_state.gd"
# Ragdoll
export var ragdoll_on_land: = false
export (float, 0.0, 100.0, 0.1) var ragdoll_on_land_speed: = 20.0
# Breakfall
export var breakfall_on_land: = false
export (float, 0.0, 100.0, 0.1) var breakfall_on_land_speed: = 20.0
func on_update(delta: float) -> void:
.on_update(delta)
if (locomotor.character.is_on_floor()):
var fall_speed = locomotor.velocity.dot(locomotor.gravity_dir)
if (ragdoll_on_land and fall_speed >= ragdoll_on_land_speed):
locomotor.set_ragdoll()
elif (breakfall_on_land and fall_speed >= breakfall_on_land_speed):
locomotor.roll()
else:
locomotor.set_grounded()
extends "move_state.gd"
export (float, 0.0, 100.0, 0.1) var max_snap_speed: = 50.0
export (float, 0.0, 16.0, 0.1) var floor_check_distance: = 2.0
func on_update(delta: float) -> void:
# no gravity is applied since floor snap is used.
var floor_snap
if (locomotor.velocity.length_squared() > max_snap_speed * max_snap_speed):
floor_snap = Vector3.ZERO
else:
floor_snap = -locomotor.up * floor_check_distance
var floor_normal = locomotor.character.get_floor_normal()
var current_move_velocity: = Plane(
floor_normal, 0.0).project(locomotor.velocity
)
var desired_move_velocity = (
Plane(floor_normal, 0.0).project(desired_direction).normalized() *
move_speed
)
var move_velocity_change = desired_move_velocity - current_move_velocity
var max_move_speed_change = (
acceleration * delta
if move_speed >= move_velocity_change.length() or
is_zero_approx(desired_move_velocity.length())
else friction * delta
)
locomotor.velocity += MathUtils.clamp_magnitude(
move_velocity_change, max_move_speed_change
)
_apply_velocity(floor_snap, true) # TODO: Support moving platforms
if not locomotor.character.is_on_floor():
# TODO: implement coyote time
locomotor.set_falling()
extends "character_state.gd"
export (float, 0.0, 100.0, 0.1) var move_speed: = 1.0
export (float, 0.0, 100.0, 0.1) var acceleration: = 1.0
export (float, 0.0, 100.0, 0.1) var friction: = 1.0
var desired_direction: = Vector3.ZERO
func move(direction: Vector3) -> void:
desired_direction = direction
func on_update(delta: float) -> void:
# Apply gravity
_apply_gravity(delta)
# Apply movement
var desired_move_velocity = desired_direction * move_speed
var move_velocity_change = desired_move_velocity - locomotor.velocity
var max_move_speed_change = (
acceleration * delta
if move_speed >= move_velocity_change.length() or
is_zero_approx(desired_move_velocity.length())
else friction * delta
)
locomotor.velocity += MathUtils.clamp_magnitude(
move_velocity_change, max_move_speed_change
)
_apply_velocity()
extends "character_state.gd"
func on_enter():
# 1. Disable capsule collision and enable mesh physics simulation.
# 2. Stop any active animations
pass
func on_exit():
# 1. If ragdoll is on ground change state to walking, and play get up anim.
# Else, change to falling and set the velocity to match last ragdoll
# velocity.
# 3. Re-enable capsule collision and disable mesh physics simulation.
pass
func on_update(delta):
# 1. Set the last ragdoll speed from the physics simulation of mesh.
# 2. Use ragdoll velocity to scale joint strength for physical animation.
# for the pin joint - param/bias = ranged map from velocity,
# param/damping and param/impulse_clamp are 0.0.
# 3. Disable gravity if falling too fast (above 4000?). This should also
# prvent the ragdoll from falling through the floor.
# 4. _set_character_location_during_ragdoll(delta)
pass
func _set_character_location_during_ragdoll(delta):
# 1. Set the pelvis as target location.
# 2. Determine if ragdoll is facing up or down and set the target rotation
# accordingly.
# 3. Raycast downward (along gravity) from the target location to offset the
# target location, preventing the lower half of the capsule from going
# through the floor when the ragdoll is laying on the ground.
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment