# Wallcrawling

I’m trying to have a rigidbody character that can latch to objects and walk around on the surface. I modified code I found on UnityAnswers and it gives the results I want and expect most of the time, but on certain polygons, when you get to the edge of that polygon the character suddenly flips around 180 degrees (around his Y axis), effectively trapping you “inside” that polygon.

``````def FixedUpdate():
ray as Ray
hit as RaycastHit
ray = Ray(transform.position, transform.up * -1)

if Physics.Raycast(ray, hit):
isGrounded = hit.distance <= grabDistance
surfaceNormal = hit.normal
else:
isGrounded = false

if isGrounded and Input.GetButton("Down"):
rigidbody.drag = 2

myNormal = Vector3.Lerp(myNormal, surfaceNormal, turnSpeed)
rot = Quaternion.FromToRotation(Vector3.up, myNormal)

# SEE BELOW
turn = Input.GetAxis("Mouse X") * mouseSpeed
turnAngle = (turnAngle + turn) % 360
rot *= Quaternion.Euler(0,turnAngle,0)

transform.rotation = rot

# Movement
transform.Translate(Input.GetAxis('Strafe') * crawlSpeed, 0,Input.GetAxis("Thrust") * crawlSpeed)

# Gravity
``````

You can see what this does here: http://dl.dropbox.com/u/2188428/BattleRoom/WebPlayer/WebPlayer.html

Press pace to thrust yourself forward, hold shift to “grab” a surface beneath you, then WASD to move around. Most of the time it works pretty much how I want, but in certain places I get strange behaviour. I figure it must be in the part marked SEE BELOW. If I understand what’s going on right, every frame the character is going to try to rotate around to exactly match the surface normal beneath him, so the Y angle he should be facing is stored in a separate variable that gets reapplied after the lerp. It must be having trouble with cases where a new surface normal is found? Another possibility is it’s a problem with the mesh normals, but I’ve tried both imported normals and calculated normals and they act the same. Also tried changing split tangents, smoothing angle, and a convex collider, but none of those options seem related to the issue.

It seems to be like it might be more elegant to somehow rotate the character around only his local X and Z axes, so that his local Y isn’t being touched and I can just use transform.Rotate() to turn left and right.

Or, if that idea isn’t elegant either, is there an accepted method for doing this? I can only think of four games that have done this, AvP, Tremulous, Prey, and Natural Selection.

I should mention, it’s not really needed for the character to remain non-kinematic while wallcrawling. My initial idea was that he was going to need to turn into something like an FPSController while grounded, and physics controlled while airborne. I just haven’t had an issues with leaving him as an active physics body yet.

Oh yeah, freaky space world.

So I got to tinkering with this and mind you what I came up with is probably not exactly what you want, or maybe it is exactly what you want. The first thing I did was to look deep into how you got a few things, First was the up vector coming from the surface normal. And getting an an up vector from the player. This was different from other methods I have done, but was fairly interesting and taught me a bit about how to get a forward vector from a normal.

So as any good space, weightless thing is, I immediately stopped all angular rotation… lol (its the devil’s spawn)

I then used two basic variables, Are we capable of being on the ground? And are we close enough that if we were on the ground, can we jump? Since the two distances are actually different. Another thing I am checking is to see if the current surface normal is way different then the last one. This way I can just simply say, “You walked off an edge, your flying again”

OK, now there are two basic states, On the ground, and not on the ground. If you are on it, you move one way, not on it, you move another way. You use the left shift to swap between the two. (remember, you have to be feet first towards the ground before it will put you there.)

Now, trickery. I use two look at commands to get myself straight up from a surface normal. The first one, I just look at my current forward vector and use the surface normal as up, the second, I use a cross of my local X direction and the surface normal with the surface normal as up. This gets my player perfectly in line with the normal and maintains direction. Unfortunately, it snaps right now, as it doesnt look very good if I lerp the rotation. I will figure this out before long. (I am thinking that if I do all my actions on the surface normal, then adjust the rotation in the LateUpdate, I will get what I want)

The movement controls are easy, mouse needs to look as normal while we are on the ground, forward should be forward, left and right strafe and the space bar needs to jump. Just like we were on the ground in a normal fps.

Next is the air movement. The first thing I do is lerp the camera’s rotation to the person’s. since we can look up while on the ground, we are using the Y movement in the air from the camera to control the x rotation of the person. Because of this, we need the camera to look where the character is facing.

Movement controls in the air, are forward, left, right and backward as on the ground, the Q and E keys rotate the character. The Space is up, and the X is down. And the mouse looks as normal.

OK, I tidied it all up into a web player:

http://www.lod3dx.net/Unity/SpaceFPS.html

I added in much the same as what you had, but I also added a ball to make it really weird.

Here is the code as I presented it. It is the only script in the scene.

``````var moveSpeed = 20.0;
var lookSpeed = 10.0;
var landDistance = 2.0;
var jumpDistance = 1.1;
private
var onGround = false;
private
var lastNormal = Vector3.zero;

function Start() {
if (!rigidbody)
rigidbody.drag = 2;
rigidbody.useGravity = false;
Camera.main.transform.position = transform.position + transform.up;
Camera.main.transform.parent = transform;
}

function FixedUpdate() {
var ray: Ray = Ray(transform.position, -transform.up);
var hit: RaycastHit;
var surfaceNormal = Vector3.zero;
var jumpGround = false;
var landGround = false;

if (Physics.Raycast(ray, hit)) {
jumpGround = hit.distance <= jumpDistance;
landGround = hit.distance <= landDistance;
surfaceNormal = hit.normal;
if (Vector3.Dot(surfaceNormal, lastNormal) < 0.8)
onGround = false;
lastNormal = surfaceNormal;
} else {
onGround = false;
lastNormal = Vector3.zero;
}

if (onGround) {
if (Input.GetKeyDown(KeyCode.LeftShift))
onGround = false;
} else {
if (landGround  Input.GetKeyDown(KeyCode.LeftShift))
onGround = true;
}
rigidbody.angularVelocity = Vector3.zero;

if (onGround) {

var rotation = transform.rotation;
transform.LookAt(transform.position + transform.forward, surfaceNormal);
var cross = Vector3.Cross(transform.right, surfaceNormal);
transform.LookAt(transform.position + cross, surfaceNormal);
//transform.rotation=Quaternion.Lerp(rotation, transform.rotation, 2.0 * Time.deltaTime);

var moveModifier = moveSpeed;
if (!jumpGround) moveModifier *= 0.5;
var move = Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")).normalized * moveModifier;
if (jumpGround  Input.GetButtonDown("Jump")) move.y = moveSpeed * 20;
transform.Rotate(0, Input.GetAxis("Mouse X") * lookSpeed, 0);
var x = Camera.main.transform.localEulerAngles.x;
if (x > 180) x = x - 360;
x -= Input.GetAxis("Mouse Y") * lookSpeed;
x = Mathf.Clamp(x, -60, 60);
Camera.main.transform.localEulerAngles.x = x;
} else {
Camera.main.transform.rotation = Quaternion.Lerp(Camera.main.transform.rotation, transform.rotation, 0.004);
move = Vector3(
Input.GetAxis("Horizontal"),
(Input.GetKey(KeyCode.Space) ? 1.0 : 0.0) + (Input.GetKey(KeyCode.X) ? -1.0 : 0.0),
Input.GetAxis("Vertical")).normalized * moveSpeed * 0.5;