- CharacterBody2D Implementation
- Expert guidance for player-controlled 2D movement using Godot's physics system.
- NEVER Do
- NEVER multiply velocity by delta when using
- move_and_slide()
- —
- move_and_slide()
- already accounts for delta time. Multiplying causes slow, frame-dependent movement.
- NEVER forget to check
- is_on_floor()
- before jump
- — Allows mid-air jumps without double-jump mechanic.
- NEVER use
- velocity.x = direction * SPEED
- without friction
- — Character slides infinitely without friction in else branch. Use
- move_toward(velocity.x, 0, FRICTION * delta)
- .
- NEVER set
- velocity.y
- to exact value when falling
- — Overwrites gravity accumulation. Use
- velocity.y += gravity * delta
- instead of
- velocity.y = gravity
- .
- NEVER use floor_snap_length > 16px
- — Large snap values cause "sticking" to slopes and walls.
- Available Scripts
- MANDATORY
-
- Read the appropriate script before implementing the corresponding pattern.
- expert_physics_2d.gd
- Complete platformer movement with coyote time, jump buffering, smooth acceleration/friction, and sub-pixel stabilization. Uses move_toward for precise control.
- dash_controller.gd
- Frame-perfect dash with I-frames, cooldown, and momentum preservation.
- wall_jump_controller.gd
- Wall slide, cling, and directional wall jump with auto-correction.
- Do First
- Read expert_physics_2d.gd for platformer foundation before adding dash/wall-jump. When to Use CharacterBody2D Use CharacterBody2D For: Player characters (platformer, top-down, side-scroller) NPCs with custom movement logic Enemies with non-physics-based movement Use RigidBody2D For: Physics-driven objects (rolling boulders, vehicles) Objects affected by forces and impulses Platformer Movement Pattern Basic Platformer Controller extends CharacterBody2D const SPEED := 300.0 const JUMP_VELOCITY := - 400.0
Get the gravity from the project settings
var gravity : int = ProjectSettings . get_setting ( "physics/2d/default_gravity" ) func _physics_process ( delta : float ) -> void :
Apply gravity
if not is_on_floor ( ) : velocity . y += gravity * delta
Handle jump
if Input . is_action_just_pressed ( "jump" ) and is_on_floor ( ) : velocity . y = JUMP_VELOCITY
Get input direction
var direction := Input . get_axis ( "move_left" , "move_right" )
Apply movement
if direction : velocity . x = direction * SPEED else : velocity . x = move_toward ( velocity . x , 0 , SPEED ) move_and_slide ( ) Advanced Platformer with Coyote Time & Jump Buffer extends CharacterBody2D const SPEED := 300.0 const JUMP_VELOCITY := - 400.0 const ACCELERATION := 1500.0 const FRICTION := 1200.0 const AIR_RESISTANCE := 200.0
Coyote time: grace period after leaving platform
const COYOTE_TIME := 0.1 var coyote_timer := 0.0
Jump buffering: remember jump input slightly before landing
const JUMP_BUFFER_TIME := 0.1 var jump_buffer_timer := 0.0 var gravity : int = ProjectSettings . get_setting ( "physics/2d/default_gravity" ) func _physics_process ( delta : float ) -> void :
Gravity
if not is_on_floor ( ) : velocity . y += gravity * delta coyote_timer -= delta else : coyote_timer = COYOTE_TIME
Jump buffering
if Input . is_action_just_pressed ( "jump" ) : jump_buffer_timer = JUMP_BUFFER_TIME else : jump_buffer_timer -= delta
Jump (with coyote time and buffer)
if jump_buffer_timer
0 and coyote_timer
0 : velocity . y = JUMP_VELOCITY jump_buffer_timer = 0 coyote_timer = 0
Variable jump height
if Input . is_action_just_released ( "jump" ) and velocity . y < 0 : velocity . y *= 0.5
Movement with acceleration/friction
var direction := Input . get_axis ( "move_left" , "move_right" ) if direction : velocity . x = move_toward ( velocity . x , direction * SPEED , ACCELERATION * delta ) else : var friction_value := FRICTION if is_on_floor ( ) else AIR_RESISTANCE velocity . x = move_toward ( velocity . x , 0 , friction_value * delta ) move_and_slide ( ) Top-Down Movement Pattern 8-Directional Top-Down extends CharacterBody2D const SPEED := 200.0 const ACCELERATION := 1500.0 const FRICTION := 1000.0 func _physics_process ( delta : float ) -> void :
Get input direction (normalized for diagonal movement)
var input_vector := Input . get_vector ( "move_left" , "move_right" , "move_up" , "move_down" ) if input_vector != Vector2 . ZERO :
Accelerate toward target velocity
velocity
velocity . move_toward ( input_vector * SPEED , ACCELERATION * delta ) else :
Apply friction
velocity
velocity . move_toward ( Vector2 . ZERO , FRICTION * delta ) move_and_slide ( ) Top-Down with Rotation (Tank Controls) extends CharacterBody2D const SPEED := 200.0 const ROTATION_SPEED := 3.0 func _physics_process ( delta : float ) -> void :
Rotation
var rotate_direction := Input . get_axis ( "rotate_left" , "rotate_right" ) rotation += rotate_direction * ROTATION_SPEED * delta
Forward/backward movement
var move_direction := Input . get_axis ( "move_backward" , "move_forward" ) velocity = transform . x * move_direction * SPEED move_and_slide ( ) Collision Handling Detecting Floor/Walls/Ceiling func _physics_process ( delta : float ) -> void : move_and_slide ( ) if is_on_floor ( ) : print ( "Standing on ground" ) if is_on_wall ( ) : print ( "Touching wall" ) if is_on_ceiling ( ) : print ( "Hitting ceiling" ) Get Collision Information func _physics_process ( delta : float ) -> void : move_and_slide ( )
Process each collision
for i in get_slide_collision_count ( ) : var collision := get_slide_collision ( i ) print ( "Collided with: " , collision . get_collider ( ) . name ) print ( "Collision normal: " , collision . get_normal ( ) )
Example: bounce off walls
if collision . get_collider ( ) . is_in_group ( "bouncy" ) : velocity = velocity . bounce ( collision . get_normal ( ) ) One-Way Platforms extends CharacterBody2D func _physics_process ( delta : float ) -> void :
Allow falling through platforms by pressing down
if Input . is_action_pressed ( "move_down" ) and is_on_floor ( ) : position . y += 1
Move slightly down to pass through
velocity . y += gravity * delta move_and_slide ( ) Movement States with State Machine extends CharacterBody2D enum State { IDLE , RUNNING , JUMPING , FALLING , DASHING } var current_state := State . IDLE var dash_velocity := Vector2 . ZERO const DASH_SPEED := 600.0 const DASH_DURATION := 0.2 var dash_timer := 0.0 func _physics_process ( delta : float ) -> void : match current_state : State . IDLE : _state_idle ( delta ) State . RUNNING : _state_running ( delta ) State . JUMPING : _state_jumping ( delta ) State . FALLING : _state_falling ( delta ) State . DASHING : _state_dashing ( delta ) func _state_idle ( delta : float ) -> void : velocity . x = move_toward ( velocity . x , 0 , FRICTION * delta ) if Input . is_action_pressed ( "move_left" ) or Input . is_action_pressed ( "move_right" ) : current_state = State . RUNNING elif Input . is_action_just_pressed ( "jump" ) : current_state = State . JUMPING move_and_slide ( ) func _state_dashing ( delta : float ) -> void : dash_timer -= delta velocity = dash_velocity if dash_timer <= 0 : current_state = State . IDLE move_and_slide ( ) Best Practices 1. Use Constants for Tuning
✅ Good - easy to tweak
const SPEED := 300.0 const JUMP_VELOCITY := - 400.0
❌ Bad - magic numbers
velocity . x = 300 velocity . y = - 400 2. Use @export for Designer Control @ export var speed : float = 300.0 @ export var jump_velocity : float = - 400.0 @ export_range ( 0 , 2000 ) var acceleration : float = 1500.0 3. Separate Movement from Animation func _physics_process ( delta : float ) -> void : _handle_movement ( delta ) _handle_animation ( ) move_and_slide ( ) func _handle_movement ( delta : float ) -> void :
Movement logic only
pass func _handle_animation ( ) -> void :
Animation state changes only
if velocity . x
0 : $AnimatedSprite2D . flip_h = false elif velocity . x < 0 : $AnimatedSprite2D . flip_h = true 4. Use Floor Detection Parameters func _ready ( ) -> void :
Set floor parameters
floor_max_angle
deg_to_rad ( 45 )
Max slope angle
floor_snap_length
8.0
Distance to snap to floor
motion_mode
MOTION_MODE_GROUNDED
Vs MOTION_MODE_FLOATING
- Common Gotchas
- Issue
- Character slides on slopes
Solution: Increase friction
- const
- FRICTION
- :=
- 1200.0
- Issue
- Character stutters on moving platforms
Solution: Enable platform snap
func _physics_process ( delta : float ) -> void : move_and_slide ( )
Snap to platform velocity
- if
- is_on_floor
- (
- )
- :
- var
- floor_velocity
- :=
- get_platform_velocity
- (
- )
- velocity
- +=
- floor_velocity
- Issue
- Double jump exploit
Solution: Track if jump was used
var can_jump := true func _physics_process ( delta : float ) -> void : if is_on_floor ( ) : can_jump = true if Input . is_action_just_pressed ( "jump" ) and can_jump : velocity . y = JUMP_VELOCITY can_jump = false Reference Godot Docs: CharacterBody2D Godot Docs: Using CharacterBody2D Related Master Skill: godot-master