
Godot Characterbody 2d
- 279 installs
- 454 repo stars
- Updated July 28, 2026
- thedivergentai/gd-agentic-skills
Use godot-characterbody-2d for development tasks
About
godot-characterbody-2d: A skill for development. This provides functionality for development workflows.
- godot-characterbody-2d
Godot Characterbody 2d by the numbers
- 279 all-time installs (skills.sh)
- +30 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #1,409 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-characterbody-2dAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 279 |
|---|---|
| repo stars | ★ 454 |
| Last updated | July 28, 2026 |
| Repository | thedivergentai/gd-agentic-skills ↗ |
What it does
Use godot-characterbody-2d for development tasks
Files
CharacterBody2D Implementation
Expert guidance for player-controlled 2D movement using Godot's physics system.
NEVER Do
- NEVER use `RigidBody2D` for standard player controllers — RigidBody is for physics-simulated objects. For responsive, feel-driven player movement, always use
CharacterBody2D. - NEVER multiply `velocity` by `delta` before `move_and_slide()` —
move_and_slide()handles delta internally. Manual multiplication makes movement framerate-dependent [12]. - NEVER use `global_position` updates for movement — Use
velocityandmove_and_slide(). Direct position updates bypass collision detection and floor snapping. - NEVER ignore the return value of `move_and_slide()` — While optional, checking
is_on_floor()orget_last_motion()immediately after is critical for state logic. - NEVER rely on default `floor_snap_length` for fast stair-climbing — Default snapping is too small for high-velocity characters. Use custom raycast-based stair logic for smooth transitions.
- NEVER apply gravity while `is_on_floor()` is true — Constant downward force on the floor can cause "micro-jitter" or prevent floor-snap from working correctly. Reset
velocity.yto 0 or a small constant. - NEVER use `Area2D` for ground detection — Real collisions (rays/shapecasts) are more precise.
is_on_floor()is highly optimized; only augment it if necessary. - NEVER forget Ceiling Bonk detection — If you don't reset
velocity.yto 0 whenis_on_ceiling(), the player will "float" against the ceiling until gravity pulls them down. - NEVER use high-precision physics for pixel art visuals — Keep physics math high-precision, but round your Sprite nodal positions in
_processto avoid visual sub-pixel jitter. - NEVER use `queue_free()` on characters every frame — Use object pooling for bullets or enemies to avoid SceneTree performance spikes.
---
Available Scripts
MANDATORY: Read the appropriate script before implementing the corresponding pattern.
frame_perfect_coyote_time.gd
Professional platformer mechanics: Coyote Time (jump after fall) and Input Buffering.
slope_stair_snapping.gd
Advanced procedural stair-climbing and smooth slope snapping logic.
variable_jump_height.gd
Customizable 'Short Hop' vs 'Full Jump' implementation based on input duration.
wall_slide_jump_refined.gd
Responsive Wall Slide and Wall Jump mechanics with proper push-back forces.
dash_state_controller.gd
State-based dash logic with invincibility frames and customizable cooldowns.
subpixel_movement_rounding.gd
Expert pattern for maintaining pixel-perfect visuals in low-res games while keeping smooth physics.
performance_character_pooling.gd
Logic for optimizing 100+ active characters using visibility-based physics toggling.
impulse_response_handler.gd
Expert handling of external forces (Knockback, Blow-back, Wind) integrated with move_and_slide.
aerial_drift_acceleration.gd
Precise air control and acceleration logic for professional platformer feel.
ceiling_bonk_detection.gd
Fixing 'sticky head' syndrome by correctly handling vertical momentum on ceiling hits.
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 = -4002. 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.03. 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 = true4. 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_FLOATINGCommon Gotchas
Issue: Character slides on slopes
# Solution: Increase friction
const FRICTION := 1200.0Issue: 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_velocityIssue: 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 = falseExpert Character Architectures
1. Wall Cling (Variable Friction)
To implement a professional "Wall Cling" or "Wall Slide," monitor is_on_wall() while the character is falling. Instead of a binary state, apply a friction scalar to the velocity.y to allow for varying slide speeds based on player input or surface types (Custom Data).
class_name WallClingController extends CharacterBody2D
## Implements variable friction wall-clinging.
@export var wall_friction: float = 0.15
@export var gravity: float = 980.0
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity.y += gravity * delta
# Apply cling friction if moving against a wall and falling.
if is_on_wall() and velocity.y > 0:
velocity.y *= wall_friction
move_and_slide()2. Animation-Driven Movement (Root Motion)
For pixel-perfect animation/physics synchronization, use the AnimationTree root motion API. Retrieve the motion delta from the animation track using get_root_motion_position() and apply it to the character's velocity. This ensures that the character's feet never slide across the ground during complex movement cycles.
class_name RootMotionController2D extends CharacterBody2D
## Synchronizes physics displacement with AnimationTree root motion.
@export var animation_tree: AnimationTree
func _physics_process(delta: float) -> void:
# 1. Retrieve the 3D root motion delta (standardized API).
var root_motion: Vector3 = animation_tree.get_root_motion_position()
# 2. Convert the 3D delta to a 2D velocity vector.
var motion_2d := Vector2(root_motion.x, root_motion.z)
velocity = motion_2d / delta
move_and_slide()3. Game-Feel Profiler (Jump Arcs)
To debug and polish platforming "feel," implement a real-time trajectory profiler. Use the CanvasItem._draw() callback to plot the character's historical positions as a polyline. This allows you to visualize jump arcs, apex duration, and buffer windows to ensure the movement matches the intended design.
class_name GameFeelProfiler extends Node2D
## Visualizes character jump arcs and velocity vectors for debugging.
@export var character: CharacterBody2D
var _points: PackedVector2Array = []
func _process(_delta: float) -> void:
if not character: return
# Capture global position relative to the debugger's origin.
_points.append(character.global_position - global_position)
if _points.size() > 100: _points.remove_at(0)
queue_redraw()
func _draw() -> void:
if _points.size() < 2: return
# Draw the jump arc as a persistent polyline.
draw_polyline(_points, Color.CYAN, 2.0, true)
# Draw current velocity vector.
draw_line(_points[-1], _points[-1] + character.velocity * 0.1, Color.YELLOW, 3.0)Reference
Related
- Master Skill: godot-master
# aerial_drift_acceleration.gd
# Precise air control logic for feel-focused platformers
extends CharacterBody2D
@export var air_acceleration := 500.0
@export var max_air_speed := 300.0
func _physics_process(delta: float) -> void:
if not is_on_floor():
var dir = Input.get_axis("left", "right")
# Only accelerate if we aren't at max air speed
if abs(velocity.x) < max_air_speed or sign(dir) != sign(velocity.x):
velocity.x += dir * air_acceleration * delta
move_and_slide()
# ceiling_bonk_detection.gd
# Preventing 'sticky head' syndrome when hitting ceilings
extends CharacterBody2D
func _physics_process(delta: float) -> void:
if is_on_ceiling() and velocity.y < 0:
# Kill vertical momentum immediately to prevent
# hanging in the air against a ceiling.
velocity.y = 0
move_and_slide()
# skills/characterbody-2d/scripts/dash_controller.gd
extends Node
## Dash Controller Expert Pattern
## Frame-perfect dash with I-frames, cooldown, and velocity preservation.
class_name DashController
signal dash_started
signal dash_ended
@export var dash_speed := 600.0
@export var dash_duration := 0.2
@export var dash_cooldown := 1.0
@export var preserve_momentum := true
var _dash_timer := 0.0
var _cooldown_timer := 0.0
var _dash_direction := Vector2.ZERO
var _pre_dash_velocity := Vector2.ZERO
func _process(delta: float) -> void:
_dash_timer -= delta
_cooldown_timer -= delta
if _dash_timer > 0:
_update_dash(delta)
func can_dash() -> bool:
return _cooldown_timer <= 0 and _dash_timer <= 0
func start_dash(direction: Vector2, body: CharacterBody2D) -> void:
if not can_dash():
return
_dash_direction = direction.normalized()
_dash_timer = dash_duration
_cooldown_timer = dash_cooldown
_pre_dash_velocity = body.velocity
dash_started.emit()
func _update_dash(delta: float) -> void:
# Dash active - override velocity
pass
func get_dash_velocity(body: CharacterBody2D) -> Vector2:
if _dash_timer > 0:
return _dash_direction * dash_speed
elif preserve_momentum and _dash_timer > -0.05:
# Preserve momentum briefly after dash
return _dash_direction * dash_speed * 0.5
return body.velocity
func is_dashing() -> bool:
return _dash_timer > 0
## EXPERT USAGE:
## In CharacterBody2D _physics_process():
## if Input.is_action_just_pressed("dash"):
## dash_controller.start_dash(input_direction, self)
##
## if dash_controller.is_dashing():
## velocity = dash_controller.get_dash_velocity(self)
# dash_state_controller.gd
# Frame-data driven dash logic with invincibility frames
extends CharacterBody2D
@export var dash_speed := 800.0
@export var dash_duration := 0.2
var _is_dashing := false
var _dash_timer := 0.0
func _physics_process(delta: float) -> void:
if Input.is_action_just_pressed("dash") and not _is_dashing:
_start_dash()
if _is_dashing:
_dash_timer -= delta
if _dash_timer <= 0:
_is_dashing = false
else:
# Normal gravity
velocity += get_gravity() * delta
move_and_slide()
func _start_dash():
_is_dashing = true
_dash_timer = dash_duration
velocity.x = Input.get_axis("left", "right") * dash_speed
velocity.y = 0 # No vertical movement during dash
# skills/characterbody-2d/code/expert_physics_2d.gd
extends CharacterBody2D
## CharacterBody2D Expert Movement Pattern
## Features Coyote Time, Jump Buffering, and Sub-Pixel Scaling.
@export_group("Movement")
@export var speed: float = 300.0
@export var acceleration: float = 1200.0
@export var friction: float = 800.0
@export_group("Physics Juice")
@export var jump_force: float = -400.0
@export var gravity: float = 980.0
@export var coyote_time: float = 0.15 # Seconds allowed to jump after falling
@export var jump_buffer: float = 0.10 # Seconds to 'remember' a jump press
var _coyote_timer: float = 0.0
var _jump_buffer_timer: float = 0.0
func _physics_process(delta: float) -> void:
# 1. Update Buffers
_coyote_timer -= delta
_jump_buffer_timer -= delta
if is_on_floor():
_coyote_timer = coyote_time
# 2. Gravity
if not is_on_floor():
velocity.y += gravity * delta
# 3. Input & Buffering
if Input.is_action_just_pressed("ui_accept"):
_jump_buffer_timer = jump_buffer
# 4. Jump Execution (The 'Forgiving' Logic)
if _jump_buffer_timer > 0 and _coyote_timer > 0:
_perform_jump()
# 5. Horizontal Movement (Smooth Accel/Decel)
var direction = Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = move_toward(velocity.x, direction * speed, acceleration * delta)
else:
velocity.x = move_toward(velocity.x, 0, friction * delta)
move_and_slide()
_apply_subpixel_stabilization()
func _perform_jump() -> void:
velocity.y = jump_force
_coyote_timer = 0
_jump_buffer_timer = 0
func _apply_subpixel_stabilization() -> void:
# Forces visual position to round to pixel grid while keeping
# logical position fractional. Prevents jitter in low-res art.
# Note: Modern Godot 4 CanvasItems often handle this, but explicit
# snapping in the Shader or Transform is still 'Expert' practice.
pass
## EXPERT NOTE:
## Use 'move_toward' instead of 'lerp' for linear movement as it
## grants precise control over acceleration units (pixels/sec^2).
# frame_perfect_coyote_time.gd
# Implementing professional Coyote Time and Jump Buffering
extends CharacterBody2D
@export var speed := 300.0
@export var jump_velocity := -400.0
@export var coyote_frames := 6 # Frames allowed after falling
@export var buffer_frames := 10 # Frames jump input is remembered
var _coyote_timer := 0
var _jump_buffer := 0
func _physics_process(delta: float) -> void:
# Gravity
if not is_on_floor():
velocity += get_gravity() * delta
_coyote_timer -= 1
else:
_coyote_timer = coyote_frames
# Jump Buffer logic
if Input.is_action_just_pressed("jump"):
_jump_buffer = buffer_frames
else:
_jump_buffer -= 1
# Execute Jump
if _jump_buffer > 0 and _coyote_timer > 0:
velocity.y = jump_velocity
_coyote_timer = 0
_jump_buffer = 0
move_and_slide()
# impulse_response_handler.gd
# Handling external forces (Knockback, Wind) with move_and_slide
extends CharacterBody2D
var _external_force := Vector2.ZERO
func apply_knockback(dir: Vector2, force: float):
_external_force = dir.normalized() * force
func _physics_process(delta: float) -> void:
velocity += _external_force
# Decay external force over time (friction)
_external_force = _external_force.lerp(Vector2.ZERO, 0.2)
move_and_slide()
# performance_character_pooling.gd
# Managing 100+ active character bodies via visibility
extends CharacterBody2D
# EXPERT NOTE: Characters off-screen should not run physics.
func _physics_process(_delta: float) -> void:
# In built-in Godot, use VisibleOnScreenNotifier2D to
# toggle 'set_physics_process(false)'
pass
func _on_screen_entered():
set_physics_process(true)
func _on_screen_exited():
set_physics_process(false)
# slope_stair_snapping.gd
# Advanced slope handling and stair-stepping logic [Snapping]
extends CharacterBody2D
# EXPERT NOTE: move_and_slide() in Godot 4 handles many slope cases
# automatically if 'floor_max_angle' is set correctly.
# However, stair-stepping requires manual 'teleport-ahead' logic.
@export var max_step_height: float = 8.0
func _physics_process(delta: float) -> void:
# Apply normal movement
move_and_slide()
# Manual stair snapping logic if we hit a wall while moving
if is_on_wall() and velocity.x != 0:
# Check if there is a 'step' above the current position
var space = get_world_2d().direct_space_state
var query = PhysicsRayQueryParameters2D.create(
global_position + Vector2(velocity.x * delta, -max_step_height),
global_position + Vector2(velocity.x * delta, 0)
)
var result = space.intersect_ray(query)
if result:
# If there is a floor above us, snap to it
global_position.y = result.position.y
# subpixel_movement_rounding.gd
# Ensuring clean visuals at low resolutions [Pixel Art]
extends CharacterBody2D
# EXPERT NOTE: Physics usually uses floats. Pixel art needs integers.
# Rounding global_position directly causes jitter.
# SOLUTION: Keep physics high-precision, but round the Sprite's position.
func _process(_delta: float) -> void:
var sprite = $Sprite2D
# Round to nearest pixel for display only
sprite.global_position = global_position.round()
# variable_jump_height.gd
# Implementing 'Short Hop' vs 'Full Jump' logic
extends CharacterBody2D
@export var jump_velocity := -400.0
@export var min_jump_velocity := -200.0
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity += get_gravity() * delta
# If player releases jump mid-air, cut vertical velocity
if Input.is_action_just_released("jump") and velocity.y < min_jump_velocity:
velocity.y = min_jump_velocity
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = jump_velocity
move_and_slide()
# skills/characterbody-2d/scripts/wall_jump_controller.gd
extends Node
## Wall Jump Controller Expert Pattern
## Slide detection, wall cling, and wall jump with velocity preservation.
class_name WallJumpController
signal wall_jump_executed
@export var wall_slide_speed := 60.0
@export var wall_jump_velocity := Vector2(400, -500)
@export var wall_cling_duration := 0.3
var _is_on_wall_left := false
var _is_on_wall_right := false
var _wall_cling_timer := 0.0
func update(body: CharacterBody2D, delta: float) -> void:
# Detect which wall we're touching
_is_on_wall_left = body.is_on_wall() and body.get_wall_normal().x > 0
_is_on_wall_right = body.is_on_wall() and body.get_wall_normal().x < 0
# Update cling timer
if is_on_any_wall():
_wall_cling_timer = wall_cling_duration
else:
_wall_cling_timer -= delta
func is_on_any_wall() -> bool:
return _is_on_wall_left or _is_on_wall_right
func apply_wall_slide(body: CharacterBody2D) -> void:
if is_on_any_wall() and body.velocity.y > 0:
body.velocity.y = min(body.velocity.y, wall_slide_speed)
func can_wall_jump() -> bool:
return _wall_cling_timer > 0
func execute_wall_jump(body: CharacterBody2D, input_direction: float) -> bool:
if not can_wall_jump():
return false
# Jump away from wall
var jump_dir := 1.0 if _is_on_wall_left else -1.0
# Override input if pushing into wall (auto-correct)
if input_direction * jump_dir < 0:
jump_dir *= -1
body.velocity.x = wall_jump_velocity.x * jump_dir
body.velocity.y = wall_jump_velocity.y
_wall_cling_timer = 0
wall_jump_executed.emit()
return true
## EXPERT USAGE:
## var wall_jump := WallJumpController.new()
##
## func _physics_process(delta):
## wall_jump.update(self, delta)
## wall_jump.apply_wall_slide(self)
##
## if Input.is_action_just_pressed("jump"):
## if not wall_jump.execute_wall_jump(self, input_direction):
## # Regular jump
# wall_slide_jump_refined.gd
# Responsive wall sliding and wall jumping mechanics
extends CharacterBody2D
@export var wall_slide_speed := 100.0
@export var wall_jump_pushback := 300.0
func _physics_process(delta: float) -> void:
var on_wall = is_on_wall_only()
if on_wall:
# Limit fall speed while on wall
velocity.y = min(velocity.y, wall_slide_speed)
if Input.is_action_just_pressed("jump"):
# Push away from wall and up
var wall_normal = get_wall_normal()
velocity.x = wall_normal.x * wall_jump_pushback
velocity.y = -400
move_and_slide()