- Home /
What's a good way to keep a character connected to any surface -- with gravity still only going down?
I've been trying to find an elegant way to do this without much luck.
Imagine the character is running around on the inside of this surface, upside-down and the like, but never falling off.
The key thing is that despite not falling, gravity is still always pulling towards the world down, so this is not the Mario Galaxy effect. Rather, the character will slide down (world down) surfaces where you'd expect him to, except that it even happens when he's upside-down.
For example, imagine the character is upside-down on the east-facing underside wall of that large valley. He won't fall off, because that's the effect I'm after, but the gravity pulling towards the world down will still cause him to slide down the underside of that valley. Standing at the bottom point of that valley, upside-down, is however effortless.
Force cannot be used to keep the character pushed against the surface because of this:
"Disconnecting" from the surface will often mean not falling back towards it.
I have been working with an approach of duplicating the whole surface, leaving the duplicate in the same place and flipping its normals, and using the 2 surfaces as 2 mesh colliders. Then creating 2 sphere colliders, parented to a rigidbody, that are hooked around the surface. Maybe this is the best way to go, maybe it isn't. I find it requires transform.rotation to keep things in the right place, which is obviously not playing nice with the physics side of things.
Is there a better way known to go about this? Or is coding in my own physics the only thing for it?
Well I'd be going with the physics approach and apply just enough force to stay on the surface using the normal of the part hit.
I'm not sure what you have attached to this surface, so can't comment on how great this force would need to be when opposing gravity. Real things which cling oppose gravity (hence its hard for massy things to cling). It should be reasonably easy to calculate the necessary force each fixed step.
Edited the question in response. Basically, if the character is upside-down at an angle, the force is either going to push the character up the slope, which is bad, or pull the character down off the slope, which is bad.
Aren't you really just wanting to have no gravity on this particular object only when touching the surface and align it to the surface normal at a given distance?
Nope, the gravity must always be pulling the character down any surface, just not off it.
I was suggesting previously that you apply a force to combat gravity compared to the surface angle, that would not make it slide if it were calculated correctly, but is efectively the opposite of gravity combined with the vector to the surface, just that seems to be easier to fudge.
Answer by s_guy · Apr 24, 2013 at 08:16 AM
It really depends on the feel of gameplay you're after. If you really want stuff to stick to the track or play relative to the track itself and not gamespace, I wouldn't use Unity physics or a generalized physics solution at all. Use Unity's character controller, roll your own, or use a kinematic rigid body.
This way, you don't have to tune complex force interactions. Such a set up would likely be fragile. Instead, limit the movement direction explicitly and let your gravity calculations influence it.
Set a movement vector to Vector3.zero for each Update
Determine the current direction vector based on your character's position on the track and user input. Perhaps raycast to the surface under the character's collider (not necessarily "down") and look at the hitinfo's normal to work out effective character direction.
Add to your movement vector, the direction vector multiplied by speed related to input
Add to your movement vector, the the gravity factor; could just be addition of a constant negative value to the "vertical" axis of your movement vector
Multiply this movement vector by Time.deltaTime for that Update
Invoke your move, e.g. controller.Move(movementVector)
With this approach you can have:
global, unidirectional gravity
complete control of allowed character movement
characters perfectly "stuck" to the surface below them
Also see restricting movement to a curved path
Good luck!
I believe this would also require seeking the ground then transform.position'ing the character any time they passed a convex curve. Doable, but I think it involves some guesstimating raycasts to find the ground. I'm not sure of the best way to go about that.
I think that gravity factor is not just a constant, but it could be worked out. This is basically the core of your suggestion; work out the factor of gravity's acceleration along the current surface plane and subtract that from whatever the player's input plus inertia would produce.
The same with the movement vector. I wouldn't want to set it to zero unless the character had no inertia at all. So I would need to also recalculate that for the new surface angle.
You would need something to drop your character to the track or figure out how to start it flush with the track. I would probably use my own gravity solution and raycast from the "foot" of my controller to see if I've landed.
I'm suggesting that gravity is always "down" and you manually manage inertia across Update events, if at all. Gravity plays no part in keeping you stuck to the track; you simply don't allow movement except along the track. Gravity just makes going uphill "hard" and accelerates you downhill, making it "easy".
I would get a solution working with no inertia or gravity at first. Basically, your character would glide freely (and correctly) on the track with forward and backward input. Once you've got that, you can add complications.
Setting the movement to zero each frame is just a methodology for starting with a clean slate before deter$$anonymous$$ing how you're going to influence the controller each update. If you want to later have some frame-to-frame influence, have fun! (but I suggest starting without that.)
Regarding overshooting convex bends, here's an image describing that problem.
The blue text indicates the issue with no gravity; the red, with gravity, which I of course want.
What do you think? Allowing the discrepancy would mean allowing it for all convex bends, which I imagine could make motion around the outside of spheres be faster than it should. $$anonymous$$aybe not enough/too infrequent to notice.
All I can think of for this is casting some arbitrary number of rays out behind the character in frame 2, and using the hit of the ray that travelled the least distance.
Answer by harperrhett · Nov 02, 2020 at 02:23 AM
I know this is an older post but I'd like to share my coded solution anyways just in case. It essentially does what was suggested above. Also not sure if it's the exact solution as I'm not sure what is being asked about sliding and additional forces. The player used has a rigid body on it and has gravity turned off btw, but to get what you're looking for you would probably just keep it on. It just takes the normals of the surface below the player, rotates the player to align with them, and then applies gravity in that direction. I also used lerping to smooth out the transition between surfaces.
Using UnityEngine;
[RequireComponent (typeof (Rigidbody))]
public class GravityBody : MonoBehaviour {
// Initialize
private Rigidbody rb;
private Vector3 normal = Vector3.down;
private Vector3 targetDirection = Vector3.down;
private Quaternion targetRotation = new Quaternion(0.0f, 0.0f, 0.0f, 0.0f);
private const float GRAVITY = -10.0f;
private const float RAYDISTANCE = 15.0f;
private const float ROTATIONSPEED = 0.15f;
// When script initializes
private void Awake() {
rb = GetComponent<Rigidbody>();
}
// Physics update
private void FixedUpdate() {
// Set up ray to check the surface below player
RaycastHit temp_hit;
Ray temp_ray = new Ray(transform.position, -transform.up * RAYDISTANCE);
// Gets the normal of the surface below the character, and also creates a target direction for gravity
if (Physics.Raycast(temp_ray, out temp_hit)) {
normal = temp_hit.normal;
targetDirection = (transform.position - temp_hit.point).normalized;
}
// Finds desired rotation relative to surface normal
targetRotation = Quaternion.FromToRotation(transform.up, normal) * transform.rotation;
// Apply rotation and gravity
transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, ROTATIONSPEED);
rb.AddForce(targetDirection * GRAVITY);
}
}
Good luck!
Answer by Belangia · Apr 24, 2013 at 08:36 AM
I would use force for gravity, shoot a ray from the Models local bottom to the (LAND). I would then record that impact and use force to push towards the (LAND). Then I would have the model always look away from the direction of force so that the Model seems to always have its bottom side facing the (GROUND). I would use OnTriggerEnter() to check if force needs to shut off, if so also prevent the new translation on the transform so that you do not collide with the (LAND). Other than that I would use trial and error till you get the feel you want to have. Good Luck. Sorry I don't have Code to show for this but the logic is there.
Can't use force because it will be denying the pull of gravity the player should feel.
And we don't know where LAND is, except for your earlier suggestion of the last point at which contact was made... which is not something I'd want to align the character base to.
I think because of the force issue, s_guy is right. Apart from my "2 colliders" approach, I can't just say "ignore gravity but obey gravity" to Unity's physics.
Your answer
Follow this Question
Related Questions
Help with orbits 1 Answer
Falling object, change its angle 0 Answers
Affecting FPS controller/Character Motor physics with game object 1 Answer
Setting a RigidBody's velocity messes with my custom gravity, not sure how to proceed. 0 Answers
Gravity still being applied with unity CharacterController 0 Answers