"Faux Gravity/Physics" Sillyness...

Hi,

I’m trying to use the current faux gravity methods from:
http://forum.unity3d.com/threads/8873-Faux-Gravity-making-my-brain-spin…-Help!
http://forum.unity3d.com/threads/33987-Character-align-to-surface-normal

It works, to a degree. The problems I’m having are:

  1. Wanting to use a rigidbody as the player, rather than character controller

Kind of works, but stops a little halfway below the planet

  1. Allowing the player to jump onto obstacles, such as platforms etc - without being completely restrained to only the sphere, which it currently is with those scripts.

The best working examples of this would be from Ratchet Clank 2 (I believe it was 2 - going commando / locked and loaded) which had a moon planet that you could run around and still jump on other platforms as well as “jump grids”.

The way “1” works is by using the following script from page 2 of this thread:
http://forum.unity3d.com/threads/33987-Character-align-to-surface-normal

    var rotationSpeed = 120.0;
    var translationSpeed = 10.0;
    var height = 7.2;          //height from ground level
    private var centre : Transform;            //transform for planet
    private var radius : float;               //calculated radius from collider
    var planet : SphereCollider ;         //collider for planet
     
     
    function Start ()
     
    {
          //consider scale applied to planet transform (assuming uniform, just pick one)
          radius = planet.radius * planet.transform.localScale.y;
          centre = planet.transform;
          //starting position at north pole
          transform.position = centre.position + Vector3(0,radius+height,0);
    }
     
    function Update ()
     
    {
          //translate based on input      
          //var inputMag  = Input.GetAxis("Vertical")*translationSpeed*Time.deltaTime;
          //transform.position += transform.forward * inputMag;
          
          //snap position to radius + height (could also use raycasts)
          targetPosition = transform.position - centre.position;
          var ratio = (radius + height) / targetPosition.magnitude;
          targetPosition.Scale(Vector3(ratio, ratio, ratio) );
          transform.position = targetPosition + centre.position;
          //calculate planet surface normal                      
          surfaceNormal = transform.position - centre.position;
         // surfaceNormal.Normalize();
          
          //GameObject's heading
          //var headingDeltaAngle = Input.GetAxis("Horizontal") * Time.deltaTime * rotationSpeed;
          //headingDelta = Quaternion.AngleAxis(headingDeltaAngle, transform.up);
          
          //align with surface normal
          //transform.rotation = Quaternion.FromToRotation( transform.up, surfaceNormal) * transform.rotation;
          
          //apply heading rotation
          //transform.rotation = headingDelta * transform.rotation;
       }

I am obviously not using it’s rotational or movement properties.

Instead, I then have a character with the following movement code a rigidbody w/ gravity enabled:

function FixedUpdate() {
 if (Input.GetKey ("w")) { 
   rigidbody.AddForce(forward * speed * Time.deltaTime, ForceMode.VelocityChange); 
   } 
	
if (Input.GetKey("s")) { 
   rigidbody.AddForce(forward * -speed * Time.deltaTime, ForceMode.VelocityChange); 
   } 
    
if (Input.GetKey ("d")) { 
	rigidbody.AddForce(right * speed * Time.deltaTime, ForceMode.VelocityChange); 
	} 

if (Input.GetKey ("a")) { 
	rigidbody.AddForce(right * -speed * Time.deltaTime, ForceMode.VelocityChange); 
	}
}

function Update() {
   var hit: RaycastHit; // use raycast to check if character grounded (3.5m tolerance)
    var castPos = Vector3(transform.position.x,transform.position.y-.25,transform.position.z);
    if (Physics.Raycast (castPos, -transform.up, hit)) {
        transform.rotation = Quaternion.FromToRotation (Vector3.up, hit.normal);
    }
}

I figured I might be able to make an array of obstacles you could jump onto and move on - but then won’t that stuff up the gravitational pull?

Any insight onto the right path?

The simplest way is by applying a force on all objects within the planets radius towards it’s center.

This works well for big planets, but do not work well for smaller ones, because it will cause the player/objects to slide in certain situations. The solution for this is to split your gravitation into smaller segments, which all pull in a certain direction, as seen in the picture below

987570--36508--$gravity.png

The white lines, shows the segment borders, the white circle is the gravitation field. The green lines show the direction of the gravity. There are 12 segements, 30° for each segment. So within that 30° the gravitation is showing in the same direction, which is not like real gravity which always points towards the center.

But is necessary to have a stable gravity on small objects. If your planet is significantly bigger, you may want to add more sectors (my script above calculates the sectors dynamically depending on what you set in the inspector)

edit:
I see in your first script that you are trying to move the object by changing positions. That do not works with rigidbodies. A rigdbody must be moved via physics, if you move it via script, you will get issues like bouncing. Because moving via script ignores the colliders and on next physic calculation the engine sees that the objects are intersecting and will bounce it off

Alternatively, you can use a long raycast and pull based on surface normal and ignore deviations over 45 deg. This prevents creep if you’ve got a varied terrain on the target world.

var hit: RaycastHit;
    
var castPos = Vector3(transform.position.x, transform.position.y, transform.position.z);

if (Physics.Raycast (castPos, -transform.up * 200, hit)) {
Debug.DrawRay (castPos, -transform.up * 200, Color.green);
	rigidbody.AddForce(hit.normal * -gravitypull);
}

if (Physics.Raycast (castPos, transform.up * 200, hit)) {
Debug.DrawRay (castPos, transform.up * 200, Color.red);
	rigidbody.AddForce(hit.normal * -gravitypull);
}

I’m using the raycast method, trying to pull the ridigbody player towards the hit.normal. It does work, however it seems to be fighting against gravity once it goes underneath the planet and continuously slides up/down along the planet surface - unable to be controlled by player input.

Is there a way to normalize the player gravity based on the main camera?

For example, no matter where the player is, from the sky down (or top of the camera downwards in this instance) physics.gravity is always applied onto the player?

Currently have this going movement:

   var cameraTransform = Camera.main.transform; 
   var forward = cameraTransform.TransformDirection(Vector3.forward); 
   forward.y = 0; 
   forward = forward.normalized; 
   var right = Vector3(forward.z, 0, -forward.x);
Physics.gravity = cameraTransform.TransformDirection(Vector3.up * speed);

Didn’t really help >.<

Also looking at example of:
http://forum.unity3d.com/threads/39618-My-first-sucessful-script!-(Planet-Gravity...)

rigidbody.velocity = ((-transform.position + planet.transform.position) * gravitypull)/ (Mathf.Abs(-transform.position.x + planet.transform.position.x) + Mathf.Abs(-transform.position.y + planet.transform.position.y) + Mathf.Abs(-transform.position.z + planet.transform.position.z)) ;

Has that gravitational pull - but doesn’t fix the gravity direction, which means the rigidbody player remains at a particular rotation and just “gravitationally” slides across the bottom of the planet.

I’d like it to react like any normal flat floor or terrain, the gravitational pull I really don’t need. The player is to travel across the planet, able to jump and go onto other objects and so forth. If gravitational pull is required, I’d have to balance that well with jumping and whatnot.

Seems a little complex for rigidbodies needing physics on…

function FixedUpdate() {
var hit: RaycastHit;
var castPos = Vector3(transform.position.x, transform.position.y, transform.position.z);

if (Physics.Raycast (castPos, -transform.up * 200, hit)) {
Debug.DrawRay (castPos, -transform.up * 200, Color.green);
	Physics.gravity = hit.normal.up * 500;
}

if (Physics.Raycast (castPos, transform.up * 200, hit)) {
Debug.DrawRay (castPos, transform.up * 200, Color.red);
	Physics.gravity = hit.normal.up * 500;
}

rigidbody.velocity = ((-transform.position + planet.transform.position) * gravitypull)/ (Mathf.Abs(-transform.position.x + planet.transform.position.x) + Mathf.Abs(-transform.position.y + planet.transform.position.y) + Mathf.Abs(-transform.position.z + planet.transform.position.z)) ;
}

This half works - I need to be able to continuously change the gravity towards the hit.normal or based on the sphere radius collider component.

What I don’t understand is, how am I able to detect the angle of the hit.normal? If the hit.normal is underneath the planet, it needs to reverse the default gravity direction from down to up. Which would mean, the normal would need detect it’s position within the current object and make a local or world xyz check as to whether or not it’s up, down, left or right?

Not quite sure…

That’s simple, you use Vector3.Dot.

You just insert two normalized directions (not points, that’s important) vectors and the result will be between -1 and 1. It’s important that the input directions are normalized, because only then the values will be between -1 and 1.

If the return value is exactly 1, the both directions are showing to each other, i.e —> <----
If the return value is 0, they are perpendicular, i.e —> ^
If the return value is -1, they are exactly pointing in the opposite way, i.e <— —>

But I have my doubts that this will bring you the desired effect. Did you tried what I described above in my first post?

If you place a null at the center of the world and have it always look at the player it provides the axis for gravity locally down the nulls z axis which would be the characters or objects y axis. Worked for me quite nicely in the same sort of scenario. No expensive raycasts either.

HTH

I am new to Unity 3D. In fact, this is my first post!

I have been messing around with point gravity, and it seems to work fine for me (see attached videos).
Some of the suggestions from other people seem to be very complicated. There are probably problems I have not thought of since I have not extensively tested it yet. (EG: I have not tried to put a walking character on it yet, not tried to accurately bounce something off it, etc)

  1. Make an empty GameObject, call it “Gravity”.

  2. Create a script and add it to the empty GameObject. This is the script that “does” all the gravity for whatever planet you attach the gameObject to.

  3. Add a sphereCollider in the script. Set the collider to Trigger = Yes. Set the radius to something much greater than the planet radius. I scripted mine to = “planet radius * 10”.
    This collider is the gravity’s “pull radius” - Anything inside it with a RigidBody will be pulled in, anything outside does not apply.

  4. The main part that effects gravity “almost every tick” (as quoted from the scripting ref) is using the collider event OnTriggerStay.

gravitySphere = gameObject.AddComponent ("SphereCollider") as SphereCollider; //You will need this if you are adding the gravity radius in the script, which I recommend.

void OnTriggerStay (Collider other)
{
    Vector3 fallDirection = ... //This is the direction things needs to fall. Use trigonometry to make this point towards the center of the planet (or away from the planet, if you want to create anti-gravity!!!!111!!11)

    float fallSpeed = ... //This is how fast in this instance the object is being pulled. Think about gravity being stronger on the Earth's surface, and weaker in space. Make fallSpeed smaller further away and larger closer. Again, fairly simple trigonometry.

    other.attachedRigidbody.AddForce (fallDirection.normalized * fallSpeed);
}

Other parts of the script will need to reference the “gameObject.transform.parent.transform.position” (The attached planet’s position), and there are other little bits of calculation and private variables involved, depending on how fancy you want to make it. (Mine auto-calculates the force of gravity based on the average x-y-z radius of the sphere).

  1. Make the Gravity object a prefab and add it to every planet you have. If you set it up correctly, it should work on any planet you attach it to. Note: If the planet has a RigidBody, it must be Kinetic, otherwise WHEN ROCKS HIT IT WILL FLY AWAY FROM SUN INTO SPACE ON HO HELP.

It’s not perfect, but it seems to work fine for me, as you can see in the 2 videos in the attached .zip.
In the second video, the Gravity prefab was added to the Earth and the Moon. The moon is not pulled to Earth by the script because I made another sprint for it that just does a boring old circular orbit by "THEMOON.transform.position = “cos(x),0,sin(x)”. Yeah, cos and sin are easier for me than all the fancy Vector3 functions. :stuck_out_tongue:
Erm… What I mean is that any effect the Earth is having on the moon is cancelled by the “orbit transform” script.

Good luck! Start simple and gradually add more features to it as you go.

1616934--98614--$ROCK_HIT_EARTH_ALSO_MOON.png

1616934–98607–$ROCK_HIT_PALNET_CALL_POILICE.zip (1.39 MB)