First frame is on_ground, second is start_jump. Next state of on_ground is start_jump and next state of start_jump is in_air.
console.log in getNextState will log the state transition.
You are describing what getNextState returns, not controller as part of a library.
in_air \ wants to jump - is just workaround the fact that player is “Supported” for 2 frames instead of 1 - initial frame before moving controller.
I am trying to figure out why we have to do that - is there something I am not aware of (within library itself) - why it is still “supported” on 2nd frame? Should it be like this or Is it a bug?
This is very unintuitive.
I was trying to make my own controller, so I was using logic provided by engine \ library - supportedState.
Player jumps - I check supportedState - if it’s SUPPORTED - I let player jump and apply jump impulse.
Next logical step would be to check after that frame when player lands, but supportedState still returns state as still SUPPORTED. (which initially made me think I am doing something wrong, so I went into PG example and it behaves the same there)
So in order for it to work we need our own wrapper \ our own ground state detection.
I do believe this should not be like that, but…maybe there is a reason for it and I just don’t understand something?
frame 0
State is on_ground, user presses space, wantJump = true
frame 1
physics tick
State is on_ground, state START_JUMP, up vector added to desiredLinearVelocity
For frames 0 or 1, velocity has not changer, nor position so support is still on.
Basically log happens before any change and physics tick so I’m not surprised by the support state here.
Let me know what I’m missing.
Yea, you are right, on this PG we are setting velocity only after physics observable.
I might setup better example at some point based on what I’m doing in my actual project thing, I have same result when setting velocity on the same frame \ update cycle.
I get all data - input velocity , impulse velocity , gravity, then add everything together - pass it to controller and get same thing - player considered ‘UNSUPPORTED’ only on 3rd frame (impulse applied and velocity set on 1st) unless I do some messy workarounds (which I did)
Take care of before/after physics ticks. I think you can get a clear picture with console.log.
If there is a sync issue or latency in the controller, it will be easier to track with complete sequence.
I had no idea about order in which everything happens in babylon.
Physics
Render
(I think?)
On top of that - support provides information based on previous physics step?
So it is not like a real time thing, it is basically always one step behind - if I check onBeforePhysicsObservable - it is going to give me information based on previous frame physics, if I check onAfterPhysicsObservable - it is going to give me information based on current frame, but physics already did it’s thing, so for next physics step that information would be, again - 1 frame behind?
Is that correct? My initial thought was it’s like “request real time ground state” of some sort, which seems wrong.
I think it’s a bit of a mix. HP_World_ShapeProximityWithCollector will perform proximity testing and will fill the collector when it’s called. But physics bodies state (position/velocity) has been update the frame before.
Yeah, this is expected, due to the way the character is written. For posterity, here’s an explanation of what’s happening. First of all, important to note that the character is completely decoupled from the physics step. The playground is using onAfterPhysicsObservable(), but the character could be updated without ever ticking the physics world. There’s three important functions for updating the character; two of which are in the BABYLON.CharacterController, which handle the geometry and one function in the playground, which handles the “game logic” parts to determine what kinds of actions (walking, jumping, double-jumping, flying, etc.) which are specific to the game.
CharacterController.checkSupport()
Examines contacts to determine support state
Playground getDesiredVelocity()
Updates character state, return a desired velocity for this frame
CharacterController.integrate()
Query+save new contacts based on where the character currently is as well as where they want to move in the future (the desired velocity.) Then, we modify the desired velocity to avoid penetration with all those contacts. Finally, we update the character position based on that new velocity.
Suppose we start standing still on the ground, with no user inputs.
checkSupport() sees a single contact, pointing up
getDesiredVelocity() returns 0
integrate() updates contacts (unchanged), velocity is zero, character position stays the same
Then, user presses “jump” button
checkSupport() sees a single contact, pointing up
getDesiredVelocity() changes state to START_JUMP, wants a large upwards velocity
integrate() updates contacts; we’re still in contact with the ground, but this doesn’t affect the desired velocity because it’s pointing the same direction; character moves upwards a little
Next frame, we’re in START_JUMP
checkSupport() sees the single contact from last frame, sees the character as supported
getDesiredVelocity() changes state to IN_AIR, uses gravity to decrease last frame’s large upwards velocity
integrate() updates contacts; no longer in contact with the ground; character continues moving upwards
Next frame, we’re in IN_AIR
checkSupport() no longer sees any contacts, returns UNSUPPORTED
So yes, this does result in a one-frame latency as checkSupport() sees the set of contacts which we calculated at the start of the previous frame’s integrate(), before the character’s position was updated. While it would be possible to update the set of contacts at the beginning of checkSupport(), we chose not to for performance reasons - generating those contacts requires a bunch of geometry processing.