Character that Climbs Everything Including On Ceiling

We have been collectively banging our heads against this particular problem for a month or two now. We get results that are almost there but nothing is working perfectly.

The goal is to get our character to scramble over and around any surface at any angle in the level, much like a spider is able to do. This includes over uneven surfaces and around protruding cliff edges.

The main problem is keeping the guy grounded:

We can’t seem to keep the rigidbody glued to the ground–addForce isn’t controlled enough for ground hugging. CharacterControllers are awesome for ground hugging, but unfortunatly world Y up seems to be very important to it–any time we rotate our CharacterController, it wigs out. So we sigh and go back trying to get the rigid body to work.

Our main approach with the rigid body character is to use raycasting to detect ground/cliff normal’s angle and orient our character to the angle. We then apply fake gravity on the local y axis to keep him on the surface, and lerp between any change in angle as our character moves over terrain.

The main problem we’ve encountered is that a rigidbody moved by addForce is very prone to leaving the ground because our character likes to ramp off of bumps. This leads to grounded state issues and a character with weird floating behavior when on a cliff face. We’ve attempted to add more local downward force when he leaves the ground to push him back to the surface, the results have been uneven motion and a bit of a jitter and he still leaves the ground a little bit, which means its not really accomplishing the goal anyway.

We have also tried the Locomotion System which has a planet walking demo where the character(rigid body) walks on all angles. For performance reasons(we are launching on iOS), and for the sake of simplicity, we’d prefer not to go this route. At any rate, the character flys off the planet when you round any sharp corners because of the local y down gravity they use to keep it stuck to the surface.

We’ve experimented with translate to move our character about, but the rigid body tended to ignore the terrain and go through the mesh. I’ve heard movePosition has been used, but it sounds like it would have the same collider ignoring problems of translate. Perhaps by directly manipulating velocity we could gain a bit more control, but regardless none of those solutions alone would solve our need to hug a bumpy ground–please correct me if I’m wrong there.

Perhaps there is a way of using velocity to move forward while using ray casts to translate my character to the surface, kind of like a match target?

I wish unity had a stick-to-any-surface-like-glue-while-moving-forward function :).

Any suggestions would be greatly appreciated. Thanks.

Well as luck would have it we found the answer. By directly controlling the rigid body velocity to move our character and using a downward local y velocity to keep him on the surface we oriented him to our character sticks like glue. This worked much better than addForce.

Modified this slightly. Controls velocity.

We got the character orienting code from another forum thread that I can’t find anymore. We put it in our series of ray casts to orient character to normal face should raycast hit.

private Vector3 curNormal = Vector3.zero;
private Vector3 usedNormal = Vector3.zero;
private Quaternion tiltToNormal;
public float turnSpeed = 2f;
public float MoveSpeed = 20f

moveDirection = transform.TransformDirection (moveDirection);		

           //rotates character
            rotationAmount = Input.GetAxis ("Horizontal") * turnSpeed * Time.deltaTime;
		transform.RotateAround (transform.up, rotationAmount);
		
		//set up a variable to manipulate velocity directly. 
		Vector3 velocityAllAxis = transform.rigidbody.velocity;
		velocityAllAxis.x = moveDirection.x;
		velocityAllAxis.z = moveDirection.z;
	        velocityAllAxis.y = moveDirection.y;
		transform.rigidbody.velocity = velocityAllAxis;	

//now for detection and orientation.

		RaycastHit outhit;
		if (Physics.Raycast (transform.position, transform.forward, out outhit, 3f)) {
        
			Debug.DrawRay (transform.position, transform.forward, Color.blue, 2f);
		
			usedNormal = outhit.normal;
			curNormal = Vector3.Lerp (curNormal, usedNormal, 6.0f * Time.deltaTime);
			tiltToNormal = Quaternion.FromToRotation (transform.up, curNormal) * transform.rotation;
			transform.rotation = tiltToNormal;
		
		} else { 
			if (Physics.Raycast (transform.position, -transform.up, out outhit, 3f)) {
			
				Debug.DrawRay (transform.position, -transform.up, Color.green, 2f);
				usedNormal = outhit.normal;
				curNormal = Vector3.Lerp (curNormal, usedNormal, 6.0f * Time.deltaTime);
				tiltToNormal = Quaternion.FromToRotation (transform.up, curNormal) * transform.rotation;
				transform.rotation = tiltToNormal;
			
			} else {
 
				if (Physics.Raycast (transform.position + (-transform.up), -transform.forward + new Vector3 (0, .3f, 0), out outhit, 3f)) {
	
	
					Debug.DrawRay (transform.position + (-transform.up), -transform.forward + new Vector3 (0, .3f, 0), Color.green, 2f);
					usedNormal = outhit.normal;
					curNormal = Vector3.Lerp (curNormal, usedNormal, 6.0f * Time.deltaTime);
					tiltToNormal = Quaternion.FromToRotation (transform.up, curNormal) * transform.rotation;
					transform.rotation = tiltToNormal;
	
				} else {
		
					curNormal = Vector3.Lerp (curNormal, Vector3.up, 6.0f * Time.deltaTime);
					tiltToNormal = Quaternion.FromToRotation (transform.up, curNormal) * transform.rotation;
					transform.rotation = tiltToNormal;
		
				}
 
 
			}

		}