godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
88.31k stars 20k forks source link

`is_on_floor` delayed by a frame while already touching floor by `safe_margin` #90718

Open aniezurawski opened 4 months ago

aniezurawski commented 4 months ago

Tested versions

v4.2.1.stable.official.b09f793f5

System information

Godot v4.2.1.stable - Ubuntu 22.04.4 LTS 22.04 - X11 - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 4080 () - AMD Ryzen 7 7800X3D 8-Core Processor (16 Threads)

Issue description

If CharacterBody2D is already touching the floor by safe_margin after move_and_slide() is called, collision won't be reported until next call to move_and_slide().

It leads to very counterintuitive behavior. Imagine you give player a dash ability but it can be done only while not on floor. Dash sets vertical speed and gravity to 0. With a frame-perfect input player can dash while actually already touching the floor. is_on_floor() returns false so dash is possible but in the next frame magically is_on_floor() reports true even though there was no gravity nor vertical velocity so there is no reason to suddenly collide with the floor.

Steps to reproduce

Create some StaticBody2D and above it place a CharacterBody2D with following script:

extends CharacterBody2D

var touched = false

func _ready():
    velocity.y = 8

func _physics_process(delta):
    if touched:
        return

    var diff = velocity * delta
    var expected_pos = position + diff

    prints("BEFORE:", "floor:", is_on_floor(), "position:", position, "expected_pos:", expected_pos, "velocity:", velocity)
    touched = move_and_slide()
    prints(touched)
    prints("AFTER:", "floor:", is_on_floor(), "position:", position, "expected_pos:", expected_pos, "velocity:", velocity)

At the very end of output you can see something like:

BEFORE: floor: false position: (400, 169.8665) expected_pos: (400, 169.9998) velocity: (0, 8)
false
AFTER: floor: false position: (400, 169.9998) expected_pos: (400, 169.9998) velocity: (0, 8)
BEFORE: floor: false position: (400, 169.9998) expected_pos: (400, 170.1332) velocity: (0, 8)
true
AFTER: floor: true position: (400, 169.9998) expected_pos: (400, 170.1332) velocity: (0, 0)

The one before last move_and_slide() call moves the body so it is already closer than safe_margin to StaticBody but the collision was not reported until the very last call to move_and_slide(). Last call does not move the body but actually reports the collision.

Minimal reproduction project (MRP)

example.zip

wyattbiker commented 4 months ago

Maybe related with #88752