Open idanarye opened 10 months ago
Hi any workaround? I think I have the same problem when I want to push back the character after getting hit
Not yet. I have direction for an idea how to implement this, but I don't have all the details ironed out and I want to create a demo environment first so that I can create it (which I plan to do after #59). Either way it wouldn't be a workaround - my idea can only be done as a proper feature.
I'll write the idea here (probably should have wrote it down long ago, but there's no time like the present):
Basically, I want to maintain a perceived velocity. Unlike the true velocity, which is read from physics backend, the perceived velocity will be updated by Tnua every frame based on:
As long as there are no external forces (other than the expected ones, aka gravity) the perceived velocity should be the same as the true velocity, so the third factor will not affect it and it'll behave the same as now. Once there is an external force - e.g. an impact from an attack - there will be a difference between the two. If the EMA was set with $\alpha = 1$, the perceived velocity will immediately match the true velocity - so again no issue. But if it's smaller than 1 - it'll take time, and during that time the character will not be able to react as strongly to the external force (because it would not have perceived that its velocity was changed - or at least won't perceive it in full force) which should produce the desired effect.
Still need to decide if this should be controlled by the basis or by the controller directly.
Okay, I have a demo level for testing this: https://idanarye.github.io/bevy-tnua/demos/platformer_3d-avian?level=Pushback
Now to work on the actual feature...
So... I have it in the feature/pushover
branch. But I don't really like how it turned out:
https://github.com/user-attachments/assets/014dc10d-e53d-4e35-b059-5b8996bb6ba4
My main issues:
Res<Time>
the physics backend uses (it did work better when I used the fixed schedule), maybe it's a rounding problem (it did work better with f64) or maybe I just don't understand various things that happen in the physics simulation. I'm not sure how to fix this.
I'll keep that branch for now, but I'm going to try and think a different solution for this problem.
The idea is to maintain a "boundary" in the velocity space. When the character gets pushed and due to that has a velocity of - let's say - 20 units west, then the boundary is at -20.0 * Vector3::X
and a direction of Dir3::NEG_X
. Once there is a boundary, the rules are:
Vector3::ZERO
(on the axis of it's direction) it gets removed (or just gets ignored?)The big question here is - how to create the boundary in the first place? Maybe something similar to the pushover mechanism but without the EMA? Instead, I'll always "predict" the new boundary location (maybe do it in the basis itself? TnuaBuiltinWalk
does calculate a running_velocity
) and if they are too far away create a boundary? The direction of the difference can be used to define the direction of the boundary.
I'd also like to somehow automatically "dissolve" boundaries over time.
If the character runs in the same direction it was pushed the boundary will not get pushed back - or at least it won't get pushed back all the way to zero. Say the character's speed is 10 and it gets pushed to a speed of 20. It'll slow down (with the boundary pushing acceleration, which is lower than its regular acceleration) from 20 to 10 because of the boundary, and then continue to run west at that speed. As long as it runs, the speed will not fall below 10 - which means that the boundary will not be pushed below 10.
That would mean that if the character keeps running for a while, the barrier will still be there when it finally brakes. We don't want that - if the character keeps running, it should eventually (a word which sounds like a lot of time, but in practice it'll usually be less than 10 seconds - maybe even less than 5) become a normal run that uses the normal brake acceleration.
I can think of two possible solutions:
I kind of like the third option, but it would mean the barrier will have to be act differently than just acceleration limiter (or maybe the acceleration limit should slowly progress toward the regular acceleration?). Also, if I do that, I probably should do it with a linear dissolution?
Another option - detect when the barrier is not being pushed, and set a (relatively short) timer on that.
Instead of removing the barrier when it gets to Vector3::ZERO
, I'm thinking maybe it'd be better to remove it when it gets pushed after the original predicted velocity? That way I could deal with negative barriers - e.g. the characgter runs, gets pushed but not enough to completely negate its velocity, and continues to run. As long as it continues the run, it'll never get the boundary to zero - because it pushes it down (pushing is always "down" - as in the opposite direction of the boundary) and it's already in the negative.
It's true that with the timer it'll eventually stop pushing, but I don't think it'd feel right to trust the timer on this.
On the other hand, if the character gets pushed west while running east and continues to run. It makes no sense if it has to push the barrier all the way back to its original position - that would mean that after passing the zero it'll have to keep pushing the barrier, causing a slower acceleration that if it'd start from rest. So maybe if the actual and predicted velocities are on the different sides of the zero, I'll terminate the barrier once it passes the zero?
I'm not sure which option is better. But then again - Tnua's philosophy is that every game should be able to configure how it feel, so maybe I should make an enum
for choosing between these options?
I feel sorry I can’t help you much with it since I don’t know the domain. Anyway I think that letting the developer decide is better. Anyway have you considered nullifying the moving velocity while being hit? Then restore it after a timer? It probably wouldn’t allow some use cases but it could be an option
Not sure I like how it turned out:
https://github.com/user-attachments/assets/df49aa15-9b36-44ee-8543-24be86093a21
Maybe if I get the boundary's strength to fade over time? (or over velocity recovered?)
I've added a diminishing factor. At first it was linear by percentage of "boundary penetration" (actually - how much velocity has been recovered) which didn't feel very good, but when I made it a power function it became much better:
https://github.com/user-attachments/assets/8a7d696e-d09c-496d-a9d1-0b15aaf0ef7e
I still don't think it's good enough though. I'm not sure if it's because I haven't tweaked enough with the numbers, because I didn't add a dedicated animation, or because something in my math is lacking.
Tweaking with the numbers improved quite a lot. Changing Pushover: Barrier Strenght Diminishing
from 0.2 to 2.0 (to get the barrier strength diminishing function to generally match with this suggestion I got on Reddit greatly improved things:
https://github.com/user-attachments/assets/b0f6ca27-0b3b-46f5-b909-c4e0d98d2607
When I increase the bullet strength the change becomes more apparent:
https://github.com/user-attachments/assets/95c44ca6-5850-44c5-ae05-b5e2b42fe28f
I'm trying a logarithmic function:
https://github.com/user-attachments/assets/af08d753-0f4f-4036-9e11-d1c491a230c4
I'm not sure if I like it more or less...
I'll think I go with the logarithmic function
Or maybe not. I'm getting weird results because using $ln$ moves the number out of the $0..1$ range...
I'll stick with the old function.
And of course I wouldn't think to try it in the regular level before merging it to main
...
https://github.com/user-attachments/assets/da754754-c383-44b4-8e78-cff28da70233
In retrospect, I should have seen it coming. Hitting a wall, after all, is also an impulse...
I'll need to figure out how to distinguish between these impulses. I may end up having to make the user code tell the controller about the impulse instead of detecting it automatically...
Since user code will have to manually trigger it anyway (otherwise I can't differentiate between "hitting an obstacle" and "getting hit by an attack"), I'm thinking of maybe just making a TnuaBuiltinKnockback
action.
This does mean I'll have to devise a way to prioritize actions so that it won't get overwritten by a user action in the same frame. We don't want to create a glitch that allows cancelling a knockback with a perfectly timed crouch...
TnuaBuiltinKnockback
is the way to go. I've started a branch to work on it.
If the character is pushed, Tnua will apply the maximum acceleration it can to reset its velocity. If we want the controls to be snappy, the acceleration is usually pretty high - which means that Tnua can easily negate these external forces.
I need to figure out a way to let the character be manipulated by such forces, without compromising on having a high acceleration for regular movement.