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.

10372-invertedhills0002.png

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:

10380-noforce.png

“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?

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.

  1. Set a movement vector to Vector3.zero for each Update
  2. 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.
  3. Add to your movement vector, the direction vector multiplied by speed related to input
  4. 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
  5. Multiply this movement vector by Time.deltaTime for that Update
  6. 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 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!

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.