bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
35.42k stars 3.5k forks source link

ball escapes arena in breakout example #1240

Closed tigregalis closed 3 years ago

tigregalis commented 3 years ago

Bevy version

master

Operating system & version

Windows 10 / web

What you did

Just let it run for a while.

What you expected to happen

Ball stays in the arena.

What actually happened

Ball escapes the arena.

Additional information

There are three scenarios where this can happen (for each direction). Example below given for ball travelling downward.

https://github.com/bevyengine/bevy/blob/master/crates/bevy_sprite/src/collide_aabb.rs#L32-L40

        // check to see if we hit on the top or bottom side
        let (y_collision, y_depth) = if a_min.y < b_min.y && a_max.y > b_min.y && a_max.y < b_max.y
        {
            (Some(Collision::Bottom), b_min.y - a_max.y)
        } else if a_min.y > b_min.y && a_min.y < b_max.y && a_max.y > b_max.y {
            (Some(Collision::Top), a_min.y - b_max.y)
        } else {
            (None, 0.0)
        };

image

I suspect that at lower frame rates (or fluctuating timesteps) + high ball velocity, the ball "teleports" greater distances between frames so this can happen more often.

There is also an additional scenario where the ball can collide with two walls at once, but because we break out of the collision detection loop when Collider::Solid = *collider, it bounces off one wall into the other (and then next frame you get one of the scenarios above).

The simplest fix to the example would be to make the wall thickness thicker (at least as thick as the ball), but it doesn't really address the collision detection (which I understand is just a temporary thing).

Some code (and screenshots) to see what collisions are happening: https://gist.github.com/tigregalis/fcf99c587134f382c16ab42edc64af1c

alice-i-cecile commented 3 years ago

Reproduced on Chrome as well :)

hymm commented 3 years ago

I tried 2 different ways of fixing this. I tested them both by running Valheim in the background and setting breakout to only use 1 logical core. On main the ball would escape, but both solutions stopped it from happening. I'm not sure which would be better. It depends on what this example is trying to demonstrate.

Using a Fixed Timestep In this branch I changed all the physics and movement to a fixed timestep. This required removing the dependencies on the Time resource as that doesn't run at a fixed timestep and so the paddle and ball are moved a constant distance each "step".

Using a different colliding calc for the walls In this branch I changed the reflect logic to run off an event and changed the wall collision detection to detect any out of bounds. Basically check to "infinity". This required adding a good chunk of collision logic.

So the first method adds a dependency on FixedTimestep and removes any dependency on Time. So if the example was supposed to show time dependent velocity collisions, this probably doesn't make sense and using FixedTimestep might not be considered idiomatic.

The second method added an event system and some custom collision logic. I think this ends up making the example a little more complex, which might be bad since I think the breakout example is supposed to look pretty simple.

alice-i-cecile commented 3 years ago

IMO the FixedTimeStep solution is better. Fixed time step physics are very common, for good reason, and I think simplicity should be prioritized. There's definitely space for a couple more example games in the repo itself to make sure we're not breaking anything (and to demonstrate patterns to newcomers); see #1248 for discussion.

@hymm can you open a PR with the FixedTimestep approach? <3 And please note that if it gets updated, we'll want to sync up the tutorial PR sitting in the website repo too.