Skip to content

Instantly share code, notes, and snippets.

@kennethrapp
Created April 12, 2024 22:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kennethrapp/354aaa5394a014228e38a3c9cd8063fc to your computer and use it in GitHub Desktop.
Save kennethrapp/354aaa5394a014228e38a3c9cd8063fc to your computer and use it in GitHub Desktop.
Godot 4 default player controller
class_name Player2D extends CharacterBody2D
#does: coyote time, peak jump, jump buffer
#needs: dash, wall climb
# jump physics:
# https://www.youtube.com/watch?v=IOe1aGY6hXA
# https://www.youtube.com/watch?v=hG9SzQxaCm8
# https://youtu.be/2S3g8CgBG1g
const INPUT_JUMP:String = "ui_accept"
const INPUT_WALK_LEFT:String = "ui_left"
const INPUT_WALK_RIGHT:String = "ui_right"
@export var acceleration: float = 250.0
@export var max_speed: float = 400.0
@export var terminal_v: float = 800.0
@export var friction: float = 650.0
@export var coyote_time_limit:float=0.2
@export var gravity_suspend_limit:float=0.2
@export var peak_jump_gravity:float=0.5
@export var jump_height: float = 100:
set(value):
jump_height = value
jump_velocity = get_jump_velocity(jump_height, jump_time_to_peak)
jump_gravity = get_jump_gravity(jump_height, jump_time_to_peak )
fall_gravity = get_fall_gravity(jump_height, jump_time_to_fall)
get:
return jump_height
@export var jump_time_to_peak: float = 0.30:
set(value):
jump_time_to_peak = value
jump_velocity = get_jump_velocity(jump_height, jump_time_to_peak)
jump_gravity = get_jump_gravity(jump_height, jump_time_to_peak )
get:
return jump_time_to_peak
@export var jump_time_to_fall: float = 0.25:
set(value):
jump_time_to_fall = value
fall_gravity = get_fall_gravity(jump_height, jump_time_to_fall)
get:
return jump_time_to_fall
@onready var jump_velocity: float = get_jump_velocity()
@onready var jump_gravity: float = get_jump_gravity()
@onready var fall_gravity: float = get_fall_gravity()
var _was_on_floor:bool=false
var gravity_multiplier:float=1.0
# true for the first frame of player landing
func was_on_floor()->bool:
return _was_on_floor
#can't tell if this is actually working
func is_jump_buffered(delta:float=get_physics_process_delta_time())->bool:
return is_falling() and test_move(transform, Vector2(0,velocity.y) * delta)
# true for the first frame of jumping
func is_jumping()->bool:
return !is_on_floor() and velocity.y < 0
func is_falling() -> bool:
return not is_on_floor() and velocity.y > 0
# true for the peak frame of a jump
func is_jump_peak()->bool:
var peak:bool = is_jumping() and is_zero_approx(abs(ceil(get_last_motion().y)))
return peak
func get_jump_velocity(height:float=jump_height,time_to_peak:float=jump_time_to_peak)->float:
return ((2.0 * height) / time_to_peak) * -1.0
func get_jump_gravity(height:float=jump_height,time_to_peak:float=jump_time_to_peak)->float:
return (-2.0 * height) / pow(time_to_peak,2.0) * -1.0
func get_fall_gravity(height:float = jump_height, time_to_fall: float = jump_time_to_fall)->float:
return (-2.0 * height) / pow(time_to_fall,2.0) * -1.0
func update_x_velocity(x_input:float, delta:float)->void:
if not is_zero_approx(x_input):
velocity.x = lerp(velocity.x, x_input * max_speed, (acceleration / 100) * delta)
else:
velocity.x = move_toward(velocity.x, 0.0, friction * delta)
func jump()->void:
velocity.y = get_jump_velocity()
func cancel_jump()->void:
velocity.y = 0
func apply_gravity(delta:float=get_physics_process_delta_time())->void:
if not is_zero_approx(gravity_multiplier):
var g:float = gravity_multiplier * (jump_gravity if velocity.y > 0 else fall_gravity) * delta
velocity.y = min(velocity.y + g, absf(terminal_v))
else:
velocity.y = 0
var suspend_gravity_timer:Timer=null
func create_suspend_gravity_timer(time:float=gravity_suspend_limit):
if suspend_gravity_timer:
return
suspend_gravity_timer= Timer.new()
suspend_gravity_timer.name = "SuspendGravityTimer"
suspend_gravity_timer.process_callback = Timer.TIMER_PROCESS_PHYSICS
suspend_gravity_timer.wait_time = time
suspend_gravity_timer.one_shot = true
suspend_gravity_timer.autostart = false
add_child(suspend_gravity_timer)
suspend_gravity_timer.timeout.connect(self.resume_gravity)
func tweak_gravity(duration:float, value:float=0)->void:
if duration > 0:
gravity_multiplier = value
suspend_gravity_timer.stop()
suspend_gravity_timer.wait_time = duration
suspend_gravity_timer.start()
func suspend_gravity(duration:float)->void:
tweak_gravity(duration,0)
func resume_gravity(resume_gravity_multiplier:float=1)->void:
gravity_multiplier=resume_gravity_multiplier
signal coyote_time_started
signal coyote_time_finished #I don't think this signal does anything
var coyote_timer:Timer=null
func create_coyote_timer(time: float = coyote_time_limit)->void:
if coyote_timer:
return
coyote_timer = Timer.new()
coyote_timer.name = "CoyoteTimer"
coyote_timer.process_callback = Timer.TIMER_PROCESS_PHYSICS
coyote_timer.wait_time = coyote_time_limit
coyote_timer.one_shot = true
coyote_timer.autostart = false
add_child(coyote_timer)
coyote_time_started.connect(self.on_coyote_timer_started)
coyote_timer.timeout.connect(self.on_coyote_timer_timeout)
func on_coyote_timer_started()->void:
suspend_gravity(coyote_time_limit)
coyote_timer.start()
func on_coyote_timer_timeout()->void:
coyote_time_finished.emit()
func has_coyote_time()->bool:
return coyote_timer.time_left > 0 and !coyote_timer.is_stopped
func _ready() -> void:
create_coyote_timer()
create_suspend_gravity_timer()
func _physics_process(delta:float=get_physics_process_delta_time())->void:
var _on_floor:bool = is_on_floor()
if is_jump_peak() and not has_coyote_time():
tweak_gravity(gravity_suspend_limit,peak_jump_gravity)
apply_gravity(delta)
var x_input:float = Input.get_axis(INPUT_WALK_LEFT, INPUT_WALK_RIGHT)
update_x_velocity(x_input, delta)
if (is_on_floor() or is_jump_buffered(delta)) and Input.is_action_just_pressed(INPUT_JUMP):
jump()
elif is_jumping() and Input.is_action_just_released(INPUT_JUMP):
cancel_jump()
move_and_slide()
_was_on_floor = (_on_floor and not is_on_floor())
#first frame in the air, not jumping
if was_on_floor() and not is_jumping():
coyote_time_started.emit()
# first frame on the ground (bug:repeats frames)
elif(is_on_floor() and not was_on_floor()):
pass
#print("ground frame")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment