I'm wondering if people could give me some ideas on how to make a smoother planetary gravity? See, I just aline the player to the normal of a collider, but obviously when the player moves over to a new face it jerks to that position which isn't very good. I'm wondering if people could give me some advice on methods to smooth it out. This is my test code with no smoothing: Code (csharp): using UnityEngine; using System.Collections; public class MoveScript : MonoBehaviour { public float speed; public float sensitivityX; public float jumpForce; public float smoothFactor; public float gravity; public LayerMask mask; private bool isGrounded; private Vector3 _movement; private float vForce; void Update () { Ray ray = new Ray(transform.position,transform.TransformDirection(Vector3.down)); RaycastHit hit; if(Physics.SphereCast(ray,0.2f,out hit,mask)) { if(Vector3.Distance(hit.point, transform.position) < 1f) { isGrounded = true; } else { isGrounded = false; } transform.rotation = Quaternion.LookRotation( Vector3.Cross( transform.right, hit.normal), hit.normal); } vForce = gravity; if(Input.GetButtonDown("Jump")) { vForce = jumpForce; } if(isGrounded){ _movement = new Vector3(Input.GetAxis("Horizontal") * speed, 0, Input.GetAxis("Vertical") * speed); Debug.Log("Hello"); } else { _movement = new Vector3(0,vForce,0); } rigidbody.velocity = transform.TransformDirection(_movement); //rigidbody.AddRelativeForce(0,-9.81f,0); transform.Rotate(0, Input.GetAxis("Mouse X") * sensitivityX, 0); } } I can easily smooth the rotation by lerping or slerping the rotation, like so Code (csharp): transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation( Vector3.Cross( transform.right, hit.normal), hit.normal), Time.deltaTime * smoothFactor); But that isn't good as the character should be properly aligned all the time. I think the best way to do this would be to take the surrounding faces normals and get a sort of gradient from them to the current face then change the characters rotation based on this? Its hard to explain but maybe this visualisation will make sense: The green line is the normal of the face the character is currently on, the yellow lines are the normals of the surrounding faces, the black lines are the faces and the red lines are the proposed smoothing of the faces. But this process seems complex and I can't think of how to do it. I'm wondering if anyone has any suggestions on how to ether do my process or any suggestions on other ways to smooth the movement? Thanks
In what crazy universe are planets all round?!?! But seriously l use the normal of the surface as my planets might sometimes be strange shapes, like a torus for example.
You said you want it to be smooth but then you said that they must be properly aligned at all times. Applying some kind of smoothing is insinuating that there will be at least some time where the player is not completely aligned with the surface normal.
My wording was wrong, I mean I don't want the player to move over time, using lerp the player rotates smoothly but their is a delay because of how it works. I need a solution with no delay. It doesn't need to be aligned to the normal it just needs to be alined to something.
Spherical planets should always be aligned to the center of that planet. If you have odd shapes like the torus suggested then you will want the normal from the raycast hit. I noticed that you are using a SphereCast to get your hit points. SphereCasts have a tendency to throw odd normals depending on the angle that they hit things. If you are going to use then use a RayCast, this always gives you the normal from whatever it hit. This, unfortunately is probably also not right. Having a center of gravity is the best way to go. If not, then you will want to create a world gravity collider that you throw raycats on. Here is a good example: You want your world to be a torus, but then you also want things on your world, like different object or things to walk on, slopes, hills and such. If you used that collider as your "gravity" then you would always get odd directions, however, if you have a generic torus that is the foundation (a mesh that is the lowest point on the game geometry) You RayCast that, that gives you your "up" direction and then you let physics do the rest. If you are simply building a planet, always use the center of that planet for your up direction.
Thanks for the reply. I'm already going to have mesh colliders as my planets and then have other stuff on them(as to not have the characteralined to the side of a mountain for example) but none of my planets are round, and even if they were their would be things like loops and such that work with the gravity. Also I'm pretty sure there isn't an issue with perfectly round planets as they would use a sphere collider which is perfectly round.
How are you using lerp? There should be no delay... And there's plenty of tutes and docs around telling people to use it incorrectly in a way that introduces delay, so it's worth checking.
Thanks for replying. I'm currently using it just like: Code (csharp): transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation( Vector3.Cross( transform.right, hit.normal), hit.normal), Time.deltaTime * smoothFactor); the smooth factor is just a float, mine is 1 but i've done higher like 5. By delay I mean that once the player stops moving the character still continues to rotate until it is aligned correctly with the surface normal, but this is no good as once the player stops moving they need to be at a position that makes sense. Perhaps an analogy to the lerp problem would one of Newton's laws that states "A body at rest wants to stay at rest" as thats pretty much what the lerp is doing ATM.
I was playing around with lerp some more and found a not ideal solution, by Slerping it like this: Code (csharp): transform.rotation = Quaternion.Slerp( transform.rotation, Quaternion.LookRotation( Vector3.Cross( transform.right, hit.normal), hit.normal), Time.deltaTime *(smoothFactor * Mathf.Abs(Input.GetAxis("Vertical") + Input.GetAxis("Horizontal"))) ); I can get a somewhat ok result, as it isn't moving when the player is stopped, still not the best though as now my player is sometimes rotated a little off.
TIme.deltaTime is almost always the wrong value to stick in a lerp function, because it stops it from being an actual lerp and turns it into a MoveTowards with some kind of... logarithmic function? Anyway, I've explained why this ain't right (for intended use cases) at least twice in detail in the past. Search for me + lerp and you should find it with a little digging.
I did the digging for you. This talks about positions, but the same applies to rotations: http://forum.unity3d.com/threads/19...()-problem-!?p=1353160&viewfull=1#post1353160 What you probably want to do is transition between the normals of different faces with weight based on distance from each, or something like that, using the lerp function to find the interpolated result. Note that you'll probably want to do something nicer to handle where 3 (or more) faces meet, though. It can still be based on a lerp, but needs to work on more samples.
Yes, thats how I was thinking would be the best way to do it too, but it seems pretty hard, I'd need to grab the vertex positions of the hit triangle and then check against all the triangles in the mesh and compare their positions to find the neighbouring triangles(I think), something that sounds like it would be expensive, then get the normals of them and find the right rotation. TBH it sounds to time consuming and hard to write. And with the Lerp thing, so "t" isn't time at all but a decimal representation of where between the two values should be?
Yep. It's the "interpolation factor", a value between 0 and 1, where 0 represents the "from" value exactly and 1 the "to" value. I don't think this needs to be too hard. You just need to have a weighted average between the nearest set of normals, changing the weighting based on distance. Lerp would do fine it if you only need had worry about two at a time.
You can use barycentric coordinates to interpolate values along the face of a triangle. Actually there's an example in the docs that does exactly that, i.e. interpolate normals: RaycastHit.barycentricCoordinate
Cool, thanks for that info. By set of normals you mean the normals of the surrounding faces yeah? I'm thinking maybe I could raycast down around the player as well then take the normals that are from the faces. Sorry to sound ignorant, but what is this? I saw it when I was doing the alignment before but the demo just seems to output the normal with no smoothing or anything? When I search BarycentricCoordinate it seems like its just to find a position on a triangle?
All you need to know is this: If you know the barycentric coordinates of a point on a triangle, then you can use that to calculate the position of that point in space as a weighted sum of the positions of the triangle's vertices. So for triangle with vertices at v1, v2, and v3, and a point p with barycentric coordinates (bc1, bc2, bc3), the position of p in space is: p = v1 * bc1 + v2 * bc2 + v3 * bc3 But you can also you the barycentric coordinates to calculate the average of any value at v1, v2, and v3 at point p. So for normals n1, n2, n3 at v1, v2, v3 respectively, the normal at p would be: Np = Nv1 * bc1 + Nv2 * bc2 + N3 * bc3 You can even use it to calculate the average color, weightmap, etc. And the example actually does calculate the normal as an average of the normals at the triangle's verts. The closer you get to v1, the more the normal will match the normal at v1, and the closer your approach the center of the triangle, the more the normal will match the average of all three normals at the points of the triangle. So it will be smooth
Cool, it turns out I was getting the same thing because I had hard edges on my object(Atleast I think that was it) now it looks promising. I'll be back with some code later tonight
It works absolutely awesome! Code (csharp): using UnityEngine; using System.Collections; public class AlignToNormal : MonoBehaviour { public float groundedDistance = 1f; public LayerMask mask; [HideInInspector] public bool isGrounded = false; private MeshCollider oldCollider; private Vector3[] normals; private int[] triangles; void Update () { Ray ray = new Ray(transform.position,transform.TransformDirection(Vector3.down)); RaycastHit hit; if (!Physics.Raycast(ray,out hit,mask)) return; if(Vector3.Distance(hit.point,transform.position) < groundedDistance) { isGrounded = true; } else { isGrounded = false; } MeshCollider meshCollider = hit.collider as MeshCollider; //if (meshCollider == null || meshCollider.sharedMesh == null) // return; Mesh mesh = meshCollider.sharedMesh; if(meshCollider != oldCollider){ normals = mesh.normals; triangles = mesh.triangles; oldCollider = meshCollider; } Vector3 n0 = normals[triangles[hit.triangleIndex * 3 + 0]]; Vector3 n1 = normals[triangles[hit.triangleIndex * 3 + 1]]; Vector3 n2 = normals[triangles[hit.triangleIndex * 3 + 2]]; Vector3 baryCenter = hit.barycentricCoordinate; Vector3 interpolatedNormal = n0 * baryCenter.x + n1 * baryCenter.y + n2 * baryCenter.z; interpolatedNormal = interpolatedNormal.normalized; Transform hitTransform = hit.collider.transform; interpolatedNormal = hitTransform.TransformDirection(interpolatedNormal); transform.rotation = Quaternion.LookRotation( Vector3.Cross( transform.right, interpolatedNormal), interpolatedNormal); Debug.DrawRay(hit.point, interpolatedNormal); } } The only issue is that when I move to a new face their is a jerk, I'm not sure why that happens.
Its just happening on a normal mesh? Like all one part, smooth and all that. Heres a web player of the issue and this is a link to a package containing all the scripts and stuff used.
Just incase someone runs into this at some point, the issue was the movement script not the gravity script. Because the character was being pulled down, but the collider still had polygons so it bumped when it moved over the edges(At Least thats what I think happened).
Interestingly, though you have a working solution now, I thought you may like to know that your solution is not unlike that of Super Mario Galaxy, which is explained here: http://www.gamasutra.com/view/feature/3593/games_demystified_super_mario_.php
That webplayer was extremely dizzying! And i am used to seeing bumping like that. if it's a problem, try using interpolation on the rigidbody. And another question: mind if i use this in my own game? are you going to release an asset on this? If so, that would be great!
I won't release it on the asset store but feel free to use it I'll post a more recent one in a a few moments(IIRC the error is fixed in the new one)
https://dl.dropboxusercontent.com/u/52409681/PlanetaryGravity.unitypackage You can download it there, its more or less a full project so I'd advise opening it in a empty scene and taking the bits you want, enjoy.
Thanks! And I am not a fan of the asset store, so dropbox will do! Now let's see if it is possible to make an anti-gravity fighting game... or a racer! BTW, if you were wondering about this late reply, it was thanks to the fact that firefox looks amazing, and all my bookmarks are in chrome...