controllerface / bvge

Personal game dev experiments
0 stars 0 forks source link

Improve Player Movement Characteristics #83

Closed controllerface closed 1 month ago

controllerface commented 1 month ago

I had hoped the CCD work I did would bear fruit in terms of making the visual a bit smoother, but since it didn't, I need to explore other options on this front.

The main think making jumping and some physics interactions difficult to get right, is the fact that I have the exact player model as the geometry bounds. I did not want to lose this so I avoided doing a more traditional bounding box. However, it will simplify things quite a bit if I make some concessions here, and I have an idea how to do it without a big ugly square for a hit box.

Having some success with adding "companion" hulls for the mouse and block cursor, I want to expand this concept to all entities, and then use them to track certain characteristics of the model. Most likely, I will compute the shapes to be used making them as small as possible without risking tunnelling. The standard block size of 32 should be plenty. With that in place, for multi-mesh entities (i.e. character models) I would generate a bounding block that is tied directly to the entity world position, so movement of the hull affects the entity position.

This would replace the cumulative process that occurs now, at least, for the general vertical position of the entity. This hinges on the entity base position essentially being the base or "feet" of the character. The hull will be offset just enough so that the y position of the entity is exactly along the bottom edge of the hull. This will effectively become the entity hitbox, solely for the vertical position of the character's feet.

I also started experimenting a few weeks ago with keeping a reference to the player's head mesh. Initially, I was planning to use it for more tinkering with lighting but I now think this could be useful to track, perhaps even as the root hull instead of the pelvis, and allow this hull to affect the entity location, the same as it does today. Then, for all other hulls, allow them to impart their movements, also the same as is currently done, but restrict the movement to only the horizontal direction. This will allow the hands and feet to continue to influence movement, but not interfere with gravity.

I could also look into simply allowing only hand and foot head meshes to affect the entity. Right now, because all meshes have an impact it could be part of the problem. At the very least, it could help prove if having fewer hulls affecting the entity has any impact.

controllerface commented 1 month ago

tried a quick and dirty test limiting entity position impact to only head, hands, and feet. The visual impact is pretty much identical, though i noticed a slightly higher instance of clipping into the floor that before. The foot meshes are pretty small, and so without the rest of the leg meshes contributing, it's just few less dice to roll to have to miss the collision. It still stops at the pelvis, and jumping pops the player out of the floor, but this is less than ideal for sure.

Re-evaluating my idea from before, and am leaning away from it, and back toward a single mesh, still roughly box shaped, but taller and with a height that follows the head hull, rather than using an entirely separate hull and trying to combine their effects. I realized as well that if I handle this correctly, I may be able to start working in rotation data using this hull.

Right now, any model with an armature can't rotate the way free hulls like blocks and shards do. This is a side effect of being animated, since those animations are absolute with respect to the model. If i wanted to allow rotating, I'd need to do it similarly to how I currently to the left/right facing flip. If I have a plain hit box that is a separate hull, associated with the model, but itself a "free-hull" I can potentially use it's rotation value for that.

I would need to pull back some old code from much earlier in the project, before I had animation. I pulled it out just because I didn't use it for a while, but there was a point in the integration kernel where for certain hulls I could detect a rotation away from 0 and pull the hull points back toward 0. This effectively created "self-righting" blocks. I had a magnitude as well that controlled how aggressive it was. Before I had decided to use skeletal animation I was going to use this to keep my character upright, because otherwise, you could spin around in all kinds of random direction, just like blocks do currently. If I can manage to make that work, it might be nice to try and have the character rotate on a slope for example.

So yeah, rather than be weird and try some zany multi-hull stuff, just going to make a variable sized bounding hull and see how it works.

controllerface commented 1 month ago

Spent a few days making some sketches and trying to just visually work out how everything will operate, and of course modified my ideas a few times 😆

After some tests with how rotation would work, I started to get less enthused about the single bounding box idea again. In particular, the rotation part of things just would not work. For one, the character would look really funny just tilted completely to whatever rotation angle the bounds were sitting at. But also, the bounding box would rotate in such a way that it would make certain generated world layouts more cumbersome to navigate due to the weird diagonal tilt. However, the idea of the rotation being mapped to the entity I do still think could have some potential (which I will get to in a sec.).

I am back to considering a multi-hull approach, and unlike my original thought of doing it in a similar way to the current mouse and block cursors, I would instead learn toward the hulls being included within the entity of the character model itself. This was one thing that originally made me hesitant before, because in reality that uses multiple entities, and relies on them being in a predictable place in the global memory buffers. If they are included with the main entity, and just have some special logic to handle, it removes that complexity while at the same time making them possible to add to any entity, not just the player.

So now, I am back to the idea of having a square for the feet of the player, and a circle hull that is pinned to the head model. However, in between these, I would place a triangle, with the two base vertices offset from the entity location, just as the square's center would be. Unlike the square, in this case I would lock the rotation, preventing it from happening to the triangle, so the base always starts at about the same spot, which in the current character model would be right about at knee height. The 3rd point of the triangle would then also be pinned to the head hull center point, making it change shape with the movement of the head mesh. This would provide a simple, 3 hull "bounding group" which would then contribute their positional data to the entity.

And about the rotation idea, re-thinking it a bit, I believe that instead of using that rotation information to rotate the entity, instead it would be better to apply it only to the foot models. The effect should be to make it so, when standing on a slope, the foot models would be rotated such that they would align with the slope of the floor being stood on. Now, this may not be a perfect effect in cases where there's a lot of variation on the ground, it would probably look a little funny, but no worse that the current visual where the toe is basically always straight, no matter what surface is stood upon.

So yeah, modified game plan, I think this will be a good starting point, working with the basic shapes and then trying to do the rotation thing perhaps as a stretch goal. Hoping to get started on this tonight.

controllerface commented 1 month ago

Did a little support work for this, when I started experimenting adding a circle hull to the entity, I realized the debug renderer for circles actually uses entities and not hulls directly. Fixed that and then did some quick tests with just adding a circle to the entity with no other logic. Was glad to see nothing went completely bonkers, though movement with just a random wandering circle attached is certainly weird 😆

I now have to work out a good way to "pin" hulls to other ones using their centroid. For circles, I have in the past used the model transform ID slot to carry the ID of another hull, not for pinning exactly but for a range lookup. This is how the mouse cursor works. However, I think it would be a good idea to build a proper way to do this. While I could just slap another buffer on and call it "pin link" or something, I should take some time to consider more robust solutions.

This first thing I am thinking about related to this kind of thing is the "bonds" idea I sketched out a while back. The concept there was that I would want to start adding some form of clumping/clustering behavior to blocks and water. When I consider it for a bit though, that will be something that will tie entities together, and here I need to tie hulls within an entity together.

Something else I could look into is actually using points already present in the model's hulls, and creating the other hulls using those points, which would effectively pin them together. The problem there though is I really am wanting a specific point (in the circle case) pinned to the center of a hull, not one of its points.

Thinking a bit further, I wonder if I actually need the "head circle" when I have the head itself and can just use that hull as-is. In my sketched, I just kind of had the 3 geometric shapes square feet, triangle body, and circle head as bounding geometry. But the head honestly is fine as is, and now I don't need to worry about sizing a circle around weird shaped heads on other characters.

So with that off the table, I need to do the foot box, and body triangle. I have the hull debug renderer on now and am just looking at how the hulls are positioned relative to each other to get ideas. I don't think I could attempt sharing points for this kind of geometry, as the edges would need to be updated... though, I could also make an edge flag that just instructed the constraint solver to ignore that edge.

Ok, so if I make a triangle above the foot area, using the block cursor as a guide for what the foot box would look like, the two base points would be somewhere around the shin area. Though, I don't know how well that would work as the legs moved back and forth in a walk cycle. I am pretty sure if there's only 3 points, mathematically there's no way to make the shape become concave, so the collision logic should work... but looking at how the character moves around, that triangle is going to get into some weird shapes.. yeah i don't think point sharing is going to work for this.

Going to get dinner and do some more sketches...

controllerface commented 1 month ago

Ok, taking a step back I want to spell out what i really want to achieve here. The goal is better movement and more precise jumping. When I think about the challenge right now with how both of these things it function, a big part of the problem comes down tot he multi-mesh nature of the entity.

All of this led me to the conclusion I needed to do more "traditional" hit boxes, so there's just a single object that "is" the character, and I came up with these different schemes to do that.. and each time I keep finding things I don't like about it. Extra hulls, extra logic... and special cases everywhere. I need to back up a sec...

Maybe there's another approach I can take to make the current setup just work a bit better instead.

let's break this down a bit... jumping and moving. jumping first:

jumping

The main issue with jumping is that it's pretty finnicky due to how the "can jump" detection works. basically, if a "foot" hull gets any anti-grav adjustment, it counts as hitting a floor. There's obvious issues here that happen when the player's foot touches a wall, for just a sec you go into a landing animation, which pulls your foot back away and you fall again, and the cycle continues until you move away from the wall or actually hit the ground.

Perhaps a better use of time would be to change this logic to be more purpose built for detecting jumping. I guess that means I need to make some check against the slope of the edge the foot is touching, and only consider something a floor if the slope is less than some angle. Not sure exactly how to go about it just yet, but that would be a much better check. In addition, I should add some small amount of leeway when considering a foot "off the ground", so you have to be off the ground at least a few frames before it considers you unable to jump. This should fix the problem where the small period between the jump start, the recoil animation, and the jump action itself can all result in the can_jump flag becoming false. This leads to inconsistent jumps a lot of times.

movement

Overall, basic movement is not so bad, both on land and in water, you can move petty easily, though more complex terrain can be a bit of a pain, and when running on a flat surface, the friction force that applies to the model ends up being significantly reduced due to the compound movement of the constituent hulls. All this leads to the player sometimes sliding after coming to a stop, even when the friction of the surface causes blocks or circles to correctly slow down. In addition to these issues, when landing from a jump, I find that even if you are moving pretty fast, you just kind of halt instead of being able to continue running immediately upon landing.

For the friction issue, I did experiment a bit with just setting a crazy high friction amount and I did find the character slowed down more, which is good and validates the assumption that the main cause of the problem is the cumulative effect of the other hulls, which don't touch the ground and therefore aren't given any friction force. But this of course made other objects way too "sticky" and was tricky to tune. Perhaps I could try some type of modifier on friction to account for the number of hulls in an entity. So the player which i think is something like 20 meshes or so, would get a 20x multiplier on all friction applied. This might be enough to counteract things.

The problem with landing and running after a jump is a bit more nebulous. I did try giving the player just a crazy amount of speed to see what would happen and when you have a really high velocity, the issue doesn't seem to occur. It could be that the slowdown force is just not high enough to be noticeable when moving that fast, not sure though. This will need a bit of digging to get more info.

Gonna try and tackle some of these first before worrying about extra hulls.

controllerface commented 1 month ago

After looking at the issue for a bit, I decided to try adding a hull like before, except this time I will make it just a line, which is a hull type I added not long ago for testing. What i will do with this hull, since it is just and edge, is create a new type of edge using a flag, and just add a new buffer to carry some extra data. The new edge logic will, instead of keeping a distance between the points, move the first point to the centroid of a specific hull, and then extend the next point downward by the length defined in the edge data.

I will attach these hulls to both of the foot hulls of the player model, and they should be kind of like little "spikes". Instead of getting a reaction of any kind, these hulls will simply act as downward-facing "probes" for the feet. I will have to add some special logic for these to the collision check which instead of doing the normal reaction process, will instead detect if the slope of the colliding edge is within the target range. If so, it will set a flag indicating as much. This will become the new can_jump sensor.

Once that's in place, I will see how I can go about adding the timing leeway, and other stuff mentioned above.

controllerface commented 1 month ago

Bit of progress today. I have the new edge pin flag working and can attach "sensor" edges to the player's feet. These sensors are hulls made of a single edge, and I added some logic so in the edge renderer, they brighten when colliding. This is how I know they don't quite work right, and I can't say I'm completely surprised, because I never tested the collision logic with simple line hulls.

I did try padding out the bounding box a bit, I remembered from the CCD experiment that this was needed to ensure edges would collide using that separate logic, and I could see with the debug bounds renderer it did generate the correct bounding box. However, the inconsistency was still there.

So the next task is to grab that edge collision logic and pull it into the mix with the other collision functions. I will skip line/circle collisions for now, since I don't want the player to be able to jump off of water anyway, and right now that's the only thing circle hulls are used for. Instead of the current SAT approach, I can drastically simplify the logic to just testing the sensor hull's edge against each edge of the polygon using the orientation based approach I used for the CDD stuff. I kept the code in the kernel's folder since I figured I'd use it later, and I guess now is later.

While I still have work to do on this, I can already see the potential for more sensors in other places, like for hands. This might be a way to implement ledge grabs, or even possibly some kind of mountain climbing mechanic. But yeah, gotta make them work for this use case first.

controllerface commented 1 month ago

After some debugging, i found that actually the line collisions work perfectly, I had mistakenly set the wrong buffer in the kernel argument slot for hull flags when building the kernel object. Fixing this and looking int he debug renderer, the sensor hulls now work exactly as I wanted, colliding with the ground beneath the player's feet. The circle collision works correctly too, so even though i don't need it right now will leave it, but problem just comment it out.

Next order of business will be figuring out what the edge slope is, and only setting the sensor flag when it's within some range. I may still want to make a separate collision check for this, since it will be a lot more readable and also more efficient for sensors. If I can avoid the overhead of an SAT algorithm and replace that with some pretty basic math, that bodes well for when I want to add a lot more sensors to things.

controllerface commented 1 month ago

Jumping already is looking better, I fixed the inconsistency by making sure to check if the player was jumping before doing the y-velocity clamp. I also did some adjustments to the can_jump flag, which helped get rid of the weird snapping when the player foot touches a wall during descent. This actually was one of the things I was going to use the angle check for, so I may not even need to add that, but will do more testing tomorrow and see if that holds.

controllerface commented 1 month ago

More testing and observation today, I have made sure to try jumping around in as many scenarios as possible and the player still consistently jumps, so that's good.

I still am able to get the player inside geometry, and I have a small test I can make with just "stairs" in font of a wall, and using that I'm able wind up with my feet in the floor just by hopping around a lot. Before, I was pretty sure the issue here was happening because of tunnelling, and that may in fact be a contributing factor, but I am pretty sure the animation is what is doing in. There's some animations that kind of need to happen fast to be responsive, but this results in the player's limbs being "teleported" into the ground by the animation.

So I am back again to probably needing a more generic hitbox, at least for static geometry. I still really don't want to do this, but I can see why it is the norm, navigating terrain is just more straight forward for a box with no animations. I am determined to keep the more accurate collisions for non-static objects though.

I can't remember now if I already made this observation, but if I do use a more basic collider and lock it to only static geometry, it would make it impossible to jump off of piles of dynamic objects. I am not sure what the visual would look like, but if say, there was a bounding box for the feet doing the can_jump and hit_floor checks, jumping into a pile of broken block pieces would still stop you from overlapping them by way of the collision reaction. This might be a little janky, like you could end up in one of the falling animations or something. I may have to actually allow the feet to work in tandem with the collider, so either could trigger a "landed" state.

So if I do this, I guess I need to add a flag for multi-mesh hulls that signifies they should not collide with static geometry. I also will need some other form of constraint that pins the collision box to the entity, with some offset to ensure the bottom edge is on the entity position exactly. Then I need a constraint that pins a hull's center to another hull's center, to add a static bounds for the head. Lastly, I will need some shape that spans between those two shapes, to cover the core mass of the character. that should cover enough of the character body to ensure the player can't walk through geometry.

controllerface commented 1 month ago

Added a little logic in the constraint solver to allow for pinning to an entity the same as a hull, which worked well. I decided to try this before adding a full block, mainly for testing purposes, but I am surprisingly finding even just the sensor edge is helpful.

I decided to allow the reaction kernels to effect this entity sensor, so unlike the foot/hull sensors, collisions will update its location. This gave me two interesting bits of information:

  1. allowing the sensor to affect entity position made the player character's run and walk animations "bounce" less, which does look nicer. I even got a couple of run -> jump -> land -> continue run cycles that didn't halt the player's movement. However, this was unfortunately inconsistent. Just to experiment though, I extended the sensor downward, just like the foot sensors, and this made the good behavior happen more often, though it was still intermittent. This led me to the second test..
  2. I tried making it so only the entity sensor affected the entity position. If it collision worked exactly right, it should look the same as with all hulls contributing, since it would just move to the position that the sensor is in. This did not work as expected, and instead the player sunk into the floor a bit. This indicates the collision reactions aren't applying exactly the same as for other hulls. This could be caused by the edge pin process, which directly sets the current and previous position of the edge's points. It also may be the case that, even though the collision test works correctly for lines, the reaction logic may not work exactly right.

So, I have some data to chew on for a bit. If I can manage to reduce the problems to things that are solvable using just edges, that could prove to be really good option instead of building out full hulls. I may still find I need to do that, but I'm happy to explore this until I prove that I must, because edges are a lot simpler to work with. I may yet still write that optimized edge collision kernel, I can definitely see ways to make this a little more efficient even if that's not the root cause of the reaction issues.

controllerface commented 1 month ago

Added the separate collision function for just sensors, and for the moment, only the entity sensor since it's hard-coded to move the sensor hull in the positive Y direction. The visual effect is the same, which is great since this new function is significantly less complex that the SAT kernel. However, this does mean the movement issue I had when trying to use just the sensor is not related to the SAT check. While it is nice to have yet further confirmation that the SAT code should really work just fine with edge hull like this, it does mean the constraint logic or something else prevents it from working exactly as I'd hoped.

Thinking about it for a bit, it may be more sensible to enforce the entity pin separately from the edge solver for these sensors, and then modify the entity sensor constraint solver to only enforce the second point's position relative to the first. I realized that because I actually want to care about the reaction and movement of this sensor, unlike the foot sensors, updating the position during constraint solving will throw that off a bit. Instead, I should update the sensor's p1 location to match the entity immediately after the entity integration step (or maybe during it) and allow the constraint solver to work more like it does normally, just enforcing the p2 position at a fixed y distance directly above p1, rather than just enforce a distance.

Doing this, the sensor should get the correct collision reaction that can then be used to adjust the entity position, and all of this should occur between calls to the edge solver. As it currently stands, the edge solver is where the update happens, and I think this is wrong based on the expected use. When thinking this through originally I envisioned this sensor effectively being a "proxy" for the entity location, and this doesn't quite do that because the enforcement of the entity pin happens after all the upkeep/processing for physics and I need it to happen during processing.

controllerface commented 1 month ago

After fixing some bugs in the new sensor code, and then adding the kernel to enforce the pin separating from the constraint solver, it turned out not to work well at all. The good news though, is with the bugs fixed, the current approach actually works a little bit better now.

The issue was that I was using the hull position of the sensor as the collision point, when I actually wanted to use the first point of the edge. This is the point that is pinned to the entity location. Fixing that, the sensor contact is happening correctly, every time the bottom point of the sensor touches anything below the player.

I may experiment again later with enforcing the pin between integration and the AABB generation, as I do think it might be possible to use ONLY the sensor for the entity location. I don't have any grand plans for that, but it might be interesting to experiment with. As it stands though, I am still happy with the better run/walk response and the reduced chances of clipping through the floor when falling.

At this point, i think I will probably not switch over to using separate bounding geometry for the player, and continue to use the meshes for a cumulative adjustment, since I'm seeing better results with this "sensor based" approach. I will do a little more tinkering though, and see if I can figure out how to improve things a bit more. If I can get the run -> jump -> land -> continue run to work better that would be a real improvement, so I will focus on that next.