Created
April 12, 2024 22:24
-
-
Save kennethrapp/354aaa5394a014228e38a3c9cd8063fc to your computer and use it in GitHub Desktop.
Godot 4 default player controller
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 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