- Home /
make player character stick to the level mesh and adjust rotation perpendicular to the point of touch
Hi i am making a demo game and i am stuck very badly at a point
i need to make player character stick to the level mesh and adjust rotation perpendicular to the point of touch
basically i need the player character to move on the surface of the mesh like a lizard in 2d space
i think its got something to do with ray cast... please do help
Just be aware it is exceptionally, incredibly, difficult to do this - both conceptually and in practice.
Note that if you have a human being making this decision, there are many places where it's hard to say which way the guy should be pointing.
To get you started,
http://docs.unity3d.com/Documentation/ScriptReference/Transform.LookAt.html
play around with the "worldUp" argument of LookAt. That will help you stand him upright, whatever upright is.
Next you ask "So what 'upright' direction do I use?"
You will get that from your collision (not so much a raycast)
From the collision, find the contact point with the mesh. Then UNity does all the work:
http://docs.unity3d.com/Documentation/ScriptReference/ContactPoint-normal.html
The "normal" from the mesh is, to some extent, what you are looking for.
It can take literally months to code this well (whole $$anonymous$$ms are employed full time on this sort of thing on huge games) and the solution in each case depends on the specific situation, there's no really general situation.)
(the ray casting thing you mention works in certain case, more like "reasonably flat" paddocks such as in a race game.
Answer by zico_mahilary · Oct 03, 2013 at 02:45 AM
sorry no luck.... but a friend told me about this...
this is pretty much what i need and its free ... asset store is awesome !!!
Answer by Marsupilami · Oct 11, 2013 at 02:28 PM
This can be achieved <-- using a single spherecast and applying your own gravity.
RaycastHit hit;
if (Physics.SphereCast(transform.position, 0.5f, -transform.up, out hit, 5))
{
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(Vector3.Cross(transform.right, hit.normal), hit.normal), Time.deltaTime * 5.0f);
}
rigidbody.AddForce(-transform.up * Time.deltaTime * gravitySpeed);
You pretty much need 3 spherecast to handle angles > 90°. See this video here $$anonymous$$agic Boots v2.0
float distForward = $$anonymous$$athf.Infinity;
RaycastHit hitForward;
if (Physics.SphereCast(transform.position, 0.25f, -transform.up + transform.forward, out hitForward, 5))
{
distForward = hitForward.distance;
}
float distDown = $$anonymous$$athf.Infinity;
RaycastHit hitDown;
if (Physics.SphereCast(transform.position, 0.25f, -transform.up, out hitDown, 5))
{
distDown = hitDown.distance;
}
float distBack = $$anonymous$$athf.Infinity;
RaycastHit hitBack;
if (Physics.SphereCast(transform.position, 0.25f, -transform.up + -transform.forward, out hitBack, 5))
{
distBack = hitBack.distance;
}
if (distForward < distDown && distForward < distBack)
{
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(Vector3.Cross(transform.right, hitForward.normal), hitForward.normal), Time.deltaTime * 5.0f);
}
else if (distDown < distForward && distDown < distBack)
{
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(Vector3.Cross(transform.right, hitDown.normal), hitDown.normal), Time.deltaTime * 5.0f);
}
else if (distBack < distForward && distBack < distDown)
{
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(Vector3.Cross(transform.right, hitBack.normal), hitBack.normal), Time.deltaTime * 5.0f);
}
rigidbody.AddForce(-transform.up * Time.deltaTime * gravitySpeed);
It's been almost 7yrs from this answer.
This piece of code saved days of my life -->
"Quaternion.LookRotation(Vector3.Cross(transform.right, hitDown.normal)".
I almost searched every bit of the internet but none of them (including unity's docs which is recommending to use "Quaternion.FromToRotation(Vector3.up, transform.forward);") worked as it's supposed to be.
man Quaternions are true evil :/
I know this is old but THHHHHX ! JUST wrote what i needed !!!
Hey, I know it's been like 7 years since this was posted but was hoping someone would be able to help me. I'm wanting to accomplish this in my game, but the character is a constantly rotating ball so this script doesn't seem to work with is. Is there any way to modify this script so that it works based on world coordinates ins$$anonymous$$d of the circles local rotation? Thanks, Red
Answer by Hoeloe · Sep 30, 2013 at 09:10 AM
`Raycast` is a physics procedure, which essentially traces a line out from a source in a specified direction, and determines what it is touching. You can write a Raycast like this:
RaycastHit h;
if(Physics.Raycast(sourceVector, directionVector, out h, distance))
{
//Do something
}
This looks reasonably complex, but I'll walk you through it. The first line creates a temporary object that you can use to store a bunch of information about the intersection point, if you need it (this is pointless if you just want to know whether or not it will hit something, but that's not what you want). Next, we have an if statement. The method Physics.Raycast
will return true
if it hits something, and false
if it doesn't. This is useful, because it allows you to run code only when a collision occurs (when you can be sure of having collision information). To run this method, you need to define the start point, the direction of the ray, and the distance it will travel. You also need to tell it where to store the collision information, which is the argument I have written as out h
. The out
here is very important, as it labels the argument as being one that will change as a result of the method call.
So, how can we apply this to your problem? First of all, you need to decide the source, direction and distance of your Raycast. A good starting point for the source will be your player's position, so transform.position
works nicely for that argument. Secondly, you need a direction. Now, since you're rotating your player, you can't just use Vector3.down
, because that will give incorrect results when your player rotates more than a few degrees. Instead, you want to use the local down vector of your player, to detect what is at the player's feet. Transform objects don't have a transform.down
vector, but since down is always in the opposite direction from up, you can just use -transform.up
, and that deals with the direction. Next you need to decide on the distance from your player to detect ground. This is entirely up to you, but keep in mind that longer Raycasts are more expensive, computationally, while one that is too short may not work correctly (because it will stop before it detects the ground).
Now we know how to describe the Raycast, we need to deal with the result. Fortunately, we have all the information stored in the variable h
. The first thing to do is find out what the Raycast has detected. To do this, you can set a "tag" on the colliders of the objects you want the player to collide against. You can then add to the end of your if statement:
if(Physics.Raycast(sourceVector, directionVector, out h, distance) && h.collider.tag == "Tag")
Where Tag is the name of the tag your object has set. This ensures the following code will only react to the objects you want it to. Now, the two things you want to do are to move the player to the collision position, and to rotate the player away from the ground. You can do this in numerous ways, but the key part is that you can get exactly the information you need from the collision information. The two properties you need to read from this are h.point
and h.normal
. The first of this gives the world space coordinates of the collision point (which will be a point on the surface of your collider), the second gives the world space direction of the "normal" vector to the collider surface. This is a vector pointing directly away from the surface.
Using this information, you can set the position of your player relative to the hit.point
. That will move your player to the collision point. The next part, the rotation, is a little more awkward. Perhaps the simplest thing to do is to just set the transform's up vector, like this: transform.up = h.normal;
. This will work, but may end up with your player facing in strange directions on other axes. What you actually want to use is Quaternion.LookRotation
. This allows you to get a rotation, given the desired forward vector of the object. We obvious can't apply h.normal directly, but we can calculate the desired forward vector, by taking the existing forward vector, and excluding any components in the direction of the collision normal. This will give up a forward vector similar to the current one, but at a right-angle to the new normal vector. Fortunately, this is very easy, as the Vector3 structure has a method built into it called Vector3.Exclude
. You can use this method like this: Vector3.Exclude(h.normal, transform.forward)
, and this will give you your desired forward vector. So now all we need to do is apply that to Quaternion.LookRotation
, which is quite easy:
transform.rotation = Quaternion.LookRotation(Vector3.Exclude(h.normal, transform.forward), h.normal);
This should align your player to the ground, and rotate them accordingly.
Please note that I haven't personally tested this code, and it will have bugs in it. I posted this here not to give you the code, but hopefully to explain the methodology behind solving a problem such as this, and hopefully you'll be able to understand and solve the problem with this information.
a very generous answer, but you should never recommend quaternions to beginners man, I reckon. you'd just use "look at" which is the same, and it makes sense ("the second argument is how the character is standing up!")
i've found the strategy of casting down from your feet won't work on extremely irregular surfaces like his "question mark" example.
as a rough beginning, he'd have to do a collision with something the shape of the whole body. (I mean any part of the body - maybe the head - could be the "thing nearest" the strange planet shape.)
it is incredibly hard to do this well. to begin with, I'm sure you agree any non-hello-world algorithm would be stateful. in practice i have often fudged it with taking "small moves from where you are" (shooting for them) and see what happens. for example if it is a reasonably flat surface you can just get away with exa$$anonymous$$ing in a simple flat circle around, and reorient your investigation circle each time.
in practice with what he's doing. i think it's much better to avoid the issue and do it nonphysically. so create at dev time a big set of possible points you can be at, and the standing vector for each one. and just move around those points.
another huge question mark in this algorithm is whether you can "arrive anywhere" or whether it only deals with movements from a current position.
You also would have to add, inevitably, many many simplifications and exceptions. eg, "at this weird seam, the fact is you are simply only allowed to stand either here or here" and so on.
it would actually be a worthwhile package or even basic function in unity, "stick character controller to bizarre mesh in reasonable manner". I guess in like 10 yrs we'll expect game engines to completely include such stuff.
one more thought: "First of all, you need to decide the source, direction and distance of your Raycast." just as you say. Any time I've done any work like this, it's involved intelligence of knowing the "center" of the object, arm, blob or whatever. this often comes down to writing f***ing centerline algorithms if the object is utterly dynamic, or he should just build a centerline object in $$anonymous$$aya. then you "merely" have closest point on some lines math to take care of.
this must be another list as we're discussing real program$$anonymous$$g :)
I don't see why beginners should avoid Quaternions, as long as they pay attention to what they are and are careful with them.
Casting down with a ray in that way does have its issues (namely it can cause the player to "slide" around, and you'll encounter odd angles from small bumps in the mesh), but you can solve some of these by using more than one Raycast and averaging the results. Lerping between positions and rotations also helps.
Your method is perfectly valid, and will probably give smoother results, but it is infeasible for large areas, as you'd need to define millions upon millions of points. For this one question mark, it's not really an issue, but for anything much larger, it becomes more difficult.
As for knowing the centre of your object, it does depend on how dynamic the player is, and how you handle animation. I try to keep the centre point reasonably consistent when creating characters, which allows methods like this to work well without additional maths.
"I don't see why beginners should avoid Quaternions" you are literally too smart to see why :)
"Casting down with a ray in that way does have its issues (namely it can cause the player to "slide" around, and you'll encounter odd angles from small bumps in the mesh)"
no, it just won't work at all, almost ever!! (unless I misunderstand the problem) quite simply, it will only coincidentally work if there happens to be some mesh "down" from the player's current position/orientation.
I'm assu$$anonymous$$g you can just plop the character in any orientation anywhere near the mesh, and the algorithm must work. (obviously if he's not touching the object, it's meaningless.)
regarding doing it nonphysically, "define millions upon millions of points" yeah you'd choose say "10 million" or something and use those. i'd just use a cheap solution like start with all the mesh triangle centers and have some perl smooth from there you know. (ie, all this an dev time.) alternately you could use some incredible cloud-surface-whatever package to do the work, or, also conceivably you could do the same type of thing with a low number of points (let's say, same as the mesh) and smooth the movement at runtime - or something. the fact is though for an arcade-feel you'd defitely go with a non physical, map, solution - I reckon. so it's "clickey" you know? (cue mario music)
final para -- I meant center of the "large object". if you wanna shoot for it, you need to find some meaningful "towards the object" to shoot for, which is a staggeringly difficult A.I. problem that has never been solved very well for general blobby stuff, but any 4 year old (I believe that's the usual stated cutoff) can do instantly of course! fuckin' AI eh? civilisation's stumbling block.
I think people just need to be careful, and make an effort to read documentation (though admittedly, Unity's docs aren't very helpful in most places).
Ah, I see what you mean now. Yes, this method does assume that you're always facing roughly towards the ground. A way you could make it work, though, is by approximating gravitational attraction when the Ray doesn't find anything - that is, pull and rotate the player towards the centre of the object. That way, the ray will necessarily find something eventually.
Yes, that would work. Your method will depend on whether you want the player to only exist at discrete points, or anywhere along the surface of the mesh, though in the latter case you can just interpolate between points.
Yeah, I see what you mean now. That is a much more difficult problem.
thanks a lot guys .... im working on it right now .... i hope i can get it done .... Cheers!
Answer by Ronyplay · Apr 06, 2018 at 12:26 PM
Marsupilami's script does work.
But just make sure to turn up the rotation speed where it says *5f following transform.rotation to something like 500 (crazy example, but works.) Or else it may seem like the script doesn't work right.
Also thanks Marsupilami.
Your answer
Follow this Question
Related Questions
RaycastHit always returns 0 1 Answer
Elevator to load next scene 2 Answers
Raycast is not working 1 Answer
Trigger and raycast error? 2 Answers