Open JoeCreates opened 8 years ago
To clarify, there is a frame between when two shapes are no longer touching and when an END callback gets sent out.
For the case of standing on the ground: there is still an arbiter and contact registered for the collision even after the objects are separated, but arbiter.totalImpulse()
is a zero vector.
Nailing it down a bit more, in space.step()
, broadphase does its collision check before the velocity and position iterations are run. So a shape's AABB during the collision check is one frame behind where it's at at the end of the step()
(once the velocity has actually been accounted for).
So then all the callbacks occur a frame later than when they "actually" should happen. This is visually ok for BEGIN
because of the penetration step hiding the delay, but it's noticeable for ONGOING
and END
.
Unfortunately, that's simply how things work, collisions are done at the start of a physics step, so that is when all callbacks are determined too.
I know it's being pedantic ;p but the docs 'do' use the words 'interaction' when talking about the callbacks. BEGIN is when an interaction has 'began' on a particular step, not when the shapes intersect. END is when an interaction 'ended' on a particular step, which is when they are determined to have seperated (at the start of the next step).
The only way to not have this one frame delay, would be to run the entire collision detection twice per frame. These sorts of single frame delays are pretty common in game-dev... eg, it's pretty common for entity systems to delay addition/removal of entities until the start of the following frame too.
@deltaluca I'm not sure if you understood the issue correctly. In case it wasn't clear, TWO steps have to happen before the callback results catch up with the body positions. If that was previously clear, then I'm pretty sure it doesn't have to work this way.
The issue of things "commonly" being delayed by a frame in game dev--at least in the case of positions--is often due to bad design and in every case I've come across avoidable. Queuing the addition or removal of entities from a system such that this happens all at once is a different issue, quite unlike this.
Regarding the wording in the docs, I don't see how the wording you mentioned is consistent with Nape's behaviour. It is not until a second frame of two bodies being apart that we actually have the callback. That wording suggests that the first frame of the bodies being apart we should have the callback.
As it stands, for any game using nape, the callbacks will be a frame out of sync with the actual state of the space. For games that are purely physics based that don't really need to track the interactions this won't often be an issue. For implementing platforming, however, it is a nightmare.
As it stands, for any game using nape, the callbacks will be a frame out of sync with the actual state of the space. For games that are purely physics based that don't really need to track the interactions this won't often be an issue. For implementing platforming, however, it is a nightmare.
The issue is that nape does not stand on it's own. Other libraries interact with it around step. Step might look like this:
function step() {
collisionsAndCallbacks();
physics();
}
And when looped we see: collisionsAndCallbacks() -> physics() -> collisionsAndCallbacks() -> physics() -> collisionsAndCallbacks() -> physics() -> ...
Of course we need collisions to be checked between each physics update.
But now when a game is using nape:
init() {
set up interactions to determine if player is on ground;
}
mainLoop() {
nape.step();
set player sprite to body position;
set player animation based on whether it is on ground;
}
The player is now at a position off the ground, but the callback hasn't been called, so the animation is incorrect. The problems this causes can be far worse than simply having incorrect animations, and I have run into such issues, too.
But why not have:
function step() {
physics();
collisionsAndCallbacks();
}
For all but the very first step the loop would look the same. If it is absolutely essential that collisions are checked first, then this could happen before the first call to step, or the physics could be ignored on the first step.
Any other code using nape would get a consistent reading from nape, then. When nape tells me my player's body's position is something off the ground, I will also have had the callback to say the interaction ended.
after a call to step() the user may remove objects, move objects around, change objects from static to kinematic, add new objects etc, so it does not make sense to do collisions at the end of the step.
(Not addressing the two frames out of sync yet, just responding to other parts right now).
You could choose to update physics at the end, rather than the beginning which would mean you'd be visualising the state in sync with callbacks.
But talking about 'most games', really most games (talking professional level) do not update physics in sync with the rest of the game as it's almost always done at a different fixed frame rate with visual states interpolated for smoothness etc.
Yes, but this is not purely a visual issue. There is the case where game logic is dependent on the state of the physics. Some of this logic might want to check the position of a body. In another part of the logic it might be most efficient to use a callback.
The problem with this is that Nape, updating all at once, is effectively presenting this information for two different steps: the previous (as in two steps ago) step for callbacks, and the current step (the previous time step was called) for the state of bodies.
"after a call to step() the user may remove objects, move objects around, change objects from static to kinematic, add new objects etc, so it does not make sense to do collisions at the end of the step. "
Why should this matter any more than it should matter that they can do this now anyway?
By two steps I assume you're talking about END events in the case of something like an elastic collision right?
eg:
bouncy ball falls ....
not colliding at start -> step() -> now overlapping colliding at start -> step() -> now bounced up into the air (BEGIN, first step() that interaction occured) not colliding at start -> step() -> (END, interaction occured previously, now not colliding)
?
In the case of something being on the ground, and an impulse applied to push it into the air it should be more like:
colliding... colliding at start -> impulse applied -> step() -> now in mid-air not colliding at start -> step() -> (END)
Yes, that's correct.
Like I've explained, it's not consistent for nape to try and do collision detection at the end of the frame, and thus this behaviour IS expected, and generally speaking unavoidable.
One thing you can of course do (would it introduce bad behaviour? who the fuck knows, but hey, doesn't hurt to try ;p) is to do a second step with a miniscule timestep ;p
personally, I've not hit these kind of issues before as I've never worked on something that was so sensitive to frame delays. like animations are usually blended so the visual difference between starting an animation a frame or two later is unnoticeable, and any AI behaviours have equally never been so sensitive, and things like allowing players to jump normally have some tolerance on being able to jump even if you've just left the ground etc, and players can't jump immediately on hitting the ground (giving chances for animations to blend etc).
But I agree it's not 'ideal' in all cases for this to happen.
I still don't understand what would be inconsistent about my suggestion. What is the practical difference between:
[end of step()] Nape moves things around
[user code] User moves things around
[start of step()] Nape updates collisions
[end of step()] Nape moves things around
[user code] User moves things around
[start of step()] Nape updates collisions
...
And
[start of step()] Nape moves things around
[end of step()] Nape updates collisions
[user code] User moves things around
[start of step()] Nape moves things around
[end of step()] Nape updates collisions
[user code] User moves things around
...
In one case, nape alters the system before the user, in the other it alters it after. In both cases these are delimited by collision checks.
In both cases there are physics applied to the system on any given step that the user didn't directly specify.
okay, Incorrect words :)
It's not inconsistent to have Nape do collisions at the end of the frame to issue callbacks more tightly in sync with the post-step states of objects.
But it means that nape may end up doing more collision checking than is required as between the end of the step (do collisions) and the start of the next step (use data from collisions) the state of the world is freely modifiable so nape would be potentially forced to re-do collisions again
I'm not suggesting collisions should be redone. What is the case in which redoing the collisions would be required?
I can think of a case where these is potentially an issue: a ball lands on the ground. The collision happens (end of step), the user moves it to somewhere there is no collisions, then at the start of step the ball's velocity changes due to the bounce.
In this case, the user would have accurate information with regard to the existence of the collision, but they would not yet have information about the ball's velocity in response to the collision. With the current implementation, the user has immediate access to information about the new velocity, but not access to the fact that the collision happened.
Is this the kind of issue?
simpler.
physics step() happens, a box lands on the ground, and collisions are done at the end (box is colliding with the ground body).
user removes the ground body.
physics step() happens... wait wut? box is colliding with the ground body, but it doesn't exist anymore.
...
or
physics step() happens, a box is falling, and collisions are done at the end (box not colliding with anything).
user adds a ground body intersecting the box.
physics step() happens... box isn't colliding with anything and falls through the ground... wut?
..
In the first example, presumably it is only the body that the box collided with that cares? I would expect said body to respond to the collision. That collision happened after all, and the information that it had happened would have been available to the user. If they can see that a collision has happened, removing one of the bodies involved shouldn't make it as if the collision hadn't happened.
The second example seems more like the one I gave, and I can see an issue with it. In the example I gave it's the velocity seeming delayed, whereas in this one it's effectively the position (i.e. you'd need to place the new body one step ahead for the desired behaviour).
I can't agree that having collisions happen based on information from a previous frame is a valid thing to do, it'd also mean recording a hell of a lot more extra state in every arbiter about all the properties of the two bodies so that it could reliably perform the collision 'as though the other body where still there' and 'as though both bodies involved where in the exact same state as when the collision was detected'
The collision would have already happened, and this would be reported to the user. It's the physical response to the collision that is delayed.
It seems that this update order pretty much determines if the reported existence of collisions is delayed, or if the impulse is delayed with respect to collisions.
For my use cases I would prefer the later collision checks because these problems would seldom manifest and would be easier to work around compared to those of having the collision callbacks delayed, but I have no grounds to argue that the order should be switched. :P
The only way I can think of of having everything seem in sync, then, would be, as you say, if you do the whole collision checking routine twice--once following when nape moves things, and once again following when the user moves things.
There would perhaps be ways of doing this without having a big impact on performance, for example I have some vague idea of keeping track of dirty regions in which something has moved, and perhaps the update order could be user specified, but such changes would be way more substantial than what I thought I was asking for, so I guess I'll close this issue.
Thanks a lot for your time, Luca.
I've changed my mind. I am reopening this because it is still an issue.
On reading this back I also seem to be missing an explanation of why BEGIN happens without a delay, but only END has the delay. Why is this the case?
As per the demo provided in the initial post:
When landing:
above ground
step(), BEGIN
on ground
When jumping:
on ground (jump)
step() ONGOING
above ground! (but arbiter still exists and no callback)
step(), END
above ground
Why can BEGIN be correct but END not?
Omg. Are you taking into account the penetration step of begin
, where it takes a frame for you to actually be on the ground as opposed to within it?
The way callbacks are handled has always been perfect for me, but I manually handle my space stepping and super updating. I remember being confident that FlxNapeSpace does it wrong after I tried it once.
The penetration step doesn't factor into this. I remember we talked about this before and you simply didn't have a use case for this, but I did convince you that there was an issue, right?
The order of FlxNapspace isn't the cause. You can reproduce this issue easily without it just with debug render. It's simply a matter of what the state of a body is when you are outside of step() not matching up to the ONGOING and END callbacks that have happened at this point.
Note that beginning to be on the ground has no issue! Can you please tell me why is starting it fine and finishing it delayed?
To frame this issue in a different way to highlight how this is a problem, consider a box landing on the ground, staying for 3 frames before moving off. You will have three steps() after which the body position is on the ground, but you will have FOUR steps() with ONGOING events and it isn't until a fifth step() that you get END.
All I remember is how passionate you were, the 0% chance Luca would change anything, and how everything is fine if you step, update, render, and debug draw in the intended order, which FlxNapeSpace can’t do since it’s a plugin iirc.
I have responded to it and you just keep ignoring me. It makes no difference. In my actual game I'm not even using flixel's nape stuff.
It doesn't matter where the step goes, and it has nothing to do with rendering because this is an issue even if you have no intention of ever actually rendering anything.
Before you contradict me again on this, could you please try modifying the demo above to not have the issue?
Here is a diagram to illustrate clearly.
There are TWO step()
s after which the ball is on the ground.
There are THREE step()
s after which ONGOING is called.
Flixel doesn;t come into this. You can assume we aren't even rendering the ball. This is just a reflection of it's position.
@deltaluca Could you possibly explain to me why BEGIN (and the first ONGOING) can be correct but not END (and extra final ONGOING)?
I'm trying to implement a platformer in haxeflixel with nape, and the idea was to use InteractionCallbacks to determine if the player is standing on the ground. Unfortunately it seems that the callbacks are always called a single frame after the interaction event actually happens.
For example, if the player lands on the ground, it stops, but the callback doesn't happen until a frame later. If the player jumps, same thing. I have no way to see with the callbacks that the player is no longer on the ground even though the bodies are separated.
This demo (using haxeflixel for the debug interface accessed with the '@' key) shows a simple test case illustrating the issue. Open the log panel with the icon at the top right which shows a message for each time one of the callbacks is called. Pause the game, hold up on your keyboard, then press the forward button to go one frame at a time. You will see the bodies separated or colliding always one frame ahead of the callback message.
Note that this works with regular traces, too. It's not that the log is delayed.