- Home /
How to get the relative position of a Collider with OnControllerColliderHit()?
I want to make my character walking on walls with the tag "walkable". So if I hit a wall to the left the character should rotate clockwise around his z axis until the wall is below and then walk up the wall.
Is there a possibility to do this with the OnControllerColliderHit function? I can't find a way to get the appropriate relative position of the hit.collider. The problem mainly is, that the floor i'm walking on has also the tag "walkable"
I tried it with CollisionFlags but same problem there: Its not possible to get to know on which side (right or left) the collision happens.
Any suggestions?
Answer by Bunny83 · Mar 24, 2011 at 03:23 AM
First of all you should keep in mind that you can rotate GameObjects (or the Transform) in general, but a CharacterController will always be aligned along the y-axis. Everything will rotate but the capsule of the CharacterController will stay.
You have three possibilities:
- Use a rigidbody with a "normal" capsule collider instead of the CharacterController
- rotate the world around you
- Keep the CharacterController but make it a sphere at the bottom of your character. If you rotate your char you have to rotate around the controller center. So place the pivot there.
In almost all cases (1.) will be a lot easier ;)
Now to your "real" problem. If you use a rigidbody with capsule collider you have to use OnCollisionEnter instead of OnControllerColliderHit. OnCollisionEnter provides a Collision struct that looks different from the ControllerColliderHit. You can use one of the ContactPoints from Collision.contacts. The contact point have a face normal that can be used to determine the orientation of the wall surface.
If you want to keep the CharacterController you can't rely on the information "above", "below", ... that the controller provides, because below will always be negative y. ControllerColliderHit doesn't have multiple contact points but it provides also a single point and the face normal of the colliding object.
Answer by Mischa · Apr 07, 2011 at 02:52 PM
Thank's a lot Bunny83, your answer put me into the right direction. Finaly I got it working. Just in case any of you want to have a look at the code, here it is. If you have any suggestions on making it simpler, just tell me. I'm still new to unity.. :)
using UnityEngine; using System.Collections;
public class Walker : MonoBehaviour {
public float speed = 0.02f;
public float maxWorldRotationSpeed = 2f;
public float maxRotationSpeed = 10f;
private CharacterController controller;
private RaycastHit hit;
private Vector3 moveVector;
private GameObject geometry;
void Start () {
//get character controller and mesh for animation controls
controller = GetComponent<CharacterController>();
geometry = transform.Find("geometry").gameObject;
}
void FixedUpdate () {
//get all Inputs
Vector3 directionVector = new Vector3(Input.GetAxis("Horizontal"),0 , Input.GetAxis("Vertical"));
if(directionVector != Vector3.zero){
//we are walking
geometry.animation.CrossFade("walk");
//rotate the direction vector to the characters rotation
Quaternion inputTransformRotation = Quaternion.LookRotation(Vector3.forward, transform.up);
directionVector = inputTransformRotation * directionVector;
//add speed
moveVector = directionVector * speed;
//cast a ring around the character to get collision informations
//check the Ringcast function for more details
if(Ringcast(transform.position,transform.localScale.y/2+0.2f,16,out hit,Vector3.Cross(transform.up,directionVector),1<<10)){
if(hit.collider.tag == "walkable"){
//we can walk on the thing we hit
//calculate new up vector lerping to the walkable colliders normal, so the character walks head up
Vector3 newUp = ConstantSlerp(transform.up,hit.normal,maxWorldRotationSpeed);
if(Vector3.Angle(newUp,transform.up)==0f){
//character allready walks head up
controller.Move(moveVector);
}else{
//character needs to turn
//wee need to create a plane, to check our distance to the walkable collider. for walking up a wall we have to turn before we pass it, for walking down a wall we have to turn after we pass it.
Vector3 wallNormal;
//get the normal of the plane, so the normal points away from the character
if(Vector3.Angle(hit.normal,directionVector)<90){
wallNormal = hit.normal;
}else{
wallNormal = -hit.normal;
}
//create the distance plane. as soon as the distance on next move is 0, we have to turn
Plane wall = new Plane(wallNormal,hit.point+hit.normal*(transform.localScale.y/2+0.01f));
float distance = Mathf.Abs(wall.GetDistanceToPoint(transform.position));
if(distance-moveVector.magnitude<=0){
//move the character to the plane
if(wall.GetSide(transform.position)){
controller.Move(-wallNormal*distance);
}else{
controller.Move(wallNormal*distance);
}
//rotate the character to the new up vector
Vector3 forwd = Vector3.Cross(transform.right,newUp);
transform.rotation = Quaternion.LookRotation(forwd, newUp);
}else{
//move normal if the distance to the turning plane is too big
controller.Move(moveVector);
}
}
}
}
// Set rotation of the character to the move direction (same as in Platform Input controller)
if (directionVector.sqrMagnitude > 0.01) {
Vector3 newForward = ConstantSlerp(transform.forward,directionVector,maxRotationSpeed);
newForward = ProjectOntoPlane(newForward, transform.up);
transform.rotation = Quaternion.LookRotation(newForward, transform.up);
}
}else{
//the character doesnt move
geometry.animation.CrossFade("idle",0.1f);
}
}
bool Ringcast(Vector3 castCenter, float castRadius, int castSteps, out RaycastHit hit, Vector3 castAxis, int castLayerMask){
//Ringcast lets us cast a ring around any axis and get the first collider we hit
float castLength;
Vector3 rotationOffset = transform.position;
Vector3 castVector;
Vector3 castFrom;
Vector3 castTo;
//set up axis
castAxis = castAxis.normalized;
//set up rotation around axis
Quaternion stepRotation = Quaternion.AngleAxis(360/castSteps,castAxis);
//set up start point of the first raycast
castFrom = castCenter + Vector3.Cross(transform.up,castAxis)*castRadius;
//draw fancy stuff into the editor window ;)
Debug.DrawLine(castCenter,castFrom);
//rotate the first point to get the target point of the first raycast
castTo = stepRotation * (castFrom-rotationOffset)+rotationOffset;
castVector = castTo - castFrom;
castLength = castVector.magnitude;
//do as many raycasts as needed
for(int i=0;i<castSteps;i++){
if(Physics.Raycast(castFrom,castVector,out hit,castLength,~castLayerMask)){
//if theres any hit return true
return(true);
}
//draw more fancy stuff into the editor window!
Debug.DrawLine(castFrom,castTo);
//rotate again and set up for the next raycast
castFrom = castTo;
castTo = stepRotation * (castFrom-rotationOffset)+rotationOffset;
castVector = castTo - castFrom;
}
//if theres no hit set it to default and return false
hit = default(RaycastHit);
return(false);
}
Vector3 ProjectOntoPlane (Vector3 v, Vector3 normal) {
return v - Vector3.Project(v, normal);
}
Vector3 ConstantSlerp (Vector3 start, Vector3 to, float angle) {
float step = Mathf.Min(1, angle / Vector3.Angle(start, to));
return Vector3.Slerp(start, to, step);
}
float ConstantSlerp (float start, float to, float vel){
float step = Mathf.Min(1, vel / (to-start));
return Mathf.Lerp(start,to,step);
}
}
Your answer
Follow this Question
Related Questions
How to change the direction the player is facing when colliding with an object 2 Answers
CharacterController "breaks" collision, goes haywire 2 Answers
Game Object won't match rotation of new positions transform 1 Answer
Player Rotating after Collision... 0 Answers
Physics AddForce reduced when 3 objects are colliding 1 Answer