x


Basic movement. Walking on walls.

Hi I've been trying to figure this one out for a few days now and I just can't do it.

I'm trying to make a robot game where the player walks on the outside of space stations. What I'm trying to do is have the robot walk along a surface but also be able to detach and float in empty space. I've made games before using the character controllers but I've found them to be severely lacking in flexibility.

I've figured out how to make the character move in space with a rigidbody and applying local forces but I can't make it stick to a surface. I've tried with raycasts getting the normal and using quaternion FromToRotation(or whatever it's called) to align my robot but it just teleports to a weird angle.

So the question: How can I make my rigidbody with cube collider align to the surface of another object and then move around on the surface of that object?

more ▼

asked Aug 13 '11 at 12:30 AM

Xazper gravatar image

Xazper
31 1 1 3

Added as a comment rather than an answer because I'm not 100% on the principles at work, but a starting point might be to decide where the "center" of gravity is on your object, trace rays to the character (or vice-versa, trace rays from the character to the center of gravity), and make the gravity direction be dependent on the angle of the ray.

However, if your world has a lot of concave areas and you want to walk perfectly upright on every single straight surface, this method falls apart.

Aug 13 '11 at 12:36 AM Jason B

That was also a method I had considered. My first idea was to have the robots walk on the outside of huge cylindrical "generation ships" where on the outside magnetic clamps is necessary but on the inside there'd be normal gravity due to rotation. I suppose maybe I can get the surface normal and use that vector3 as gravity. I think I may just have to take a look at the character control scripts and recreate them with my own tweaks.

BTW: I forgot to mention I work in C#

Aug 13 '11 at 11:06 AM Xazper
(comments are locked)
10|3000 characters needed characters left

6 answers: sort voted first

The CharacterController doesn't work in this case because it needs Y to be the normal direction. A good way to do this is to use a rigidbody (uncheck Use Gravity), and apply a local gravity in the form of a constant force opposite to the character normal direction - the character normal must be updated using a raycast to the down side.
This script does this. I created an empty object, added a box collider and a rigidbody (useGravity = false), then childed my model to it. The terrain normal is constantly determined with the downside raycast, and smoothly updates myNormal (the character normal). myNormal is used to create the local gravity, so the character is always being attracted to the surface under its feet. In order to align the character to myNormal without loosing its current forward direction, a smart trick is used (thanks to -T- for that!): the current forward is found from the cross product between transform.right and myNormal, and the desired rotation is calculated with LookRotation(new forward, myNormal) - this returns a rotation that keeps the character looking forward and with its head pointing in myNormal direction.The weight force is applied at FixedUpdate to make it strictly constant. The character moves with WS using Translate, and can even jump to its vertical direction.
ADDED FEATURE: When jump is pressed, the character casts a forward ray; if the ray hits any wall in the jumpRange distance, the character jumps and rotates nicelly to land in this wall - much like someone with magnetic boots in the outer space.

EDITED: The original algorithm used had a big problem when the character was fully upside down: it started to flip back/forth at random points, driving us crazy. Thanks to a Boo script suggested by -T-, the character now keeps its forward direction under all circumstances, and call walk on the roof or on spherical planets like expected.

var moveSpeed: float = 6; // move speed
var turnSpeed: float = 90; // turning speed (degrees/second)
var lerpSpeed: float = 10; // smoothing speed
var gravity: float = 10; // gravity acceleration
var isGrounded: boolean;
var deltaGround: float = 0.2; // character is grounded up to this distance
var jumpSpeed: float = 10; // vertical jump initial speed
var jumpRange: float = 10; // range to detect target wall

private var surfaceNormal: Vector3; // current surface normal
private var myNormal: Vector3; // character normal
private var distGround: float; // distance from character position to ground
private var jumping = false; // flag "I'm jumping to wall"
private var vertSpeed: float = 0; // vertical jump current speed 

function Start(){
    myNormal = transform.up; // normal starts as character up direction 
    rigidbody.freezeRotation = true; // disable physics rotation
    // distance from transform.position to ground
    distGround = collider.bounds.extents.y - collider.center.y;  
}

function FixedUpdate(){
    // apply constant weight force according to character normal:
    rigidbody.AddForce(-gravity*rigidbody.mass*myNormal);
}

function Update(){
    // jump code - jump to wall or simple jump
    if (jumping) return;  // abort Update while jumping to a wall
    var ray: Ray;
    var hit: RaycastHit;
    if (Input.GetButtonDown("Jump")){ // jump pressed:
        ray = Ray(transform.position, transform.forward);
        if (Physics.Raycast(ray, hit, jumpRange)){ // wall ahead?
            JumpToWall(hit.point, hit.normal); // yes: jump to the wall
        }
        else if (isGrounded){ // no: if grounded, jump up
            rigidbody.velocity += jumpSpeed * myNormal;
        }                
    }
    
    // movement code - turn left/right with Horizontal axis:
    transform.Rotate(0, Input.GetAxis("Horizontal")*turnSpeed*Time.deltaTime, 0);
    // update surface normal and isGrounded:
    ray = Ray(transform.position, -myNormal); // cast ray downwards
    if (Physics.Raycast(ray, hit)){ // use it to update myNormal and isGrounded
        isGrounded = hit.distance <= distGround + deltaGround;
        surfaceNormal = hit.normal;
    }
    else {
        isGrounded = false;
        // assume usual ground normal to avoid "falling forever"
        surfaceNormal = Vector3.up; 
    }
    myNormal = Vector3.Lerp(myNormal, surfaceNormal, lerpSpeed*Time.deltaTime);
    // find forward direction with new myNormal:
    var myForward = Vector3.Cross(transform.right, myNormal);
    // align character to the new myNormal while keeping the forward direction:
    var targetRot = Quaternion.LookRotation(myForward, myNormal);
    transform.rotation = Quaternion.Lerp(transform.rotation, targetRot, lerpSpeed*Time.deltaTime);
    // move the character forth/back with Vertical axis:
    transform.Translate(0, 0, Input.GetAxis("Vertical")*moveSpeed*Time.deltaTime); 
}

function JumpToWall(point: Vector3, normal: Vector3){
    // jump to wall 
    jumping = true; // signal it's jumping to wall
    rigidbody.isKinematic = true; // disable physics while jumping
    var orgPos = transform.position;
    var orgRot = transform.rotation;
    var dstPos = point + normal * (distGround + 0.5); // will jump to 0.5 above wall
    var myForward = Vector3.Cross(transform.right, normal);
    var dstRot = Quaternion.LookRotation(myForward, normal);
    for (var t: float = 0.0; t < 1.0; ){
        t += Time.deltaTime;
        transform.position = Vector3.Lerp(orgPos, dstPos, t);
        transform.rotation = Quaternion.Slerp(orgRot, dstRot, t);
        yield; // return here next frame
    }
    myNormal = normal; // update myNormal
    rigidbody.isKinematic = false; // enable physics
    jumping = false; // jumping to wall finished
}
more ▼

answered Aug 14 '11 at 01:00 AM

aldonaletto gravatar image

aldonaletto
41.2k 16 42 195

Wow this looks amazing. I can probably translate it into C# myself, that'll also give me a chance to go through it and properly understand how and why it works.

I just tested it and it is perfect! Thank you so much. Now I can finally start creating gameplay and get on with the game.

Aug 14 '11 at 04:49 PM Xazper

Glad to know it helped! The worst things to convert to C# are the coroutine (JumpToWall must be declared as IEnumerator and called with StartCoroutine) and the collider.size and collider.center - you must use GetComponent to get the BoxCollider in a variable in order to use the size and center properties.

Aug 14 '11 at 05:06 PM aldonaletto

I'm using a modified form of this, lacking the JumpToWall stuff, and I notice sometimes, on certain polygons, the character will suddenly rotate 180 degress around its Y axis when trying to walk to a new polygon, effectively trapping you "in" that polygon.

I don't think I understand the turnAngle business as well as I need to, would you mind explaining it more?

Aug 31 '11 at 05:43 AM -T-

Look at this, this gives me exactly the behaviour I want.

public turnSpeed = 0.05 # Factor for the rotation lerp
public grabDistance = 1.0 # How far away you can grab
public gravity = 10.0 # How much force is applied to keep the character stuck to the surface

private isGrounded as bool
private myForward as Vector3
private surfaceNormal as Vector3

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

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

	if isGrounded and Input.GetButton("Down"):
		myForward = Vector3.Cross(transform.right, surfaceNormal)
		targetRotation = Quaternion.LookRotation(myForward, surfaceNormal)
		transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, turnSpeed)
		
		rigidbody.AddForce(-gravity * surfaceNormal)
		
		# Here, simply use AddForce() or Translate() to move around, and Rotate() to turn
Aug 31 '11 at 09:40 PM -T-

Thanks, -T-, this suggestion was really great! My script was adapted to use its main idea, and now works perfectly!

Sep 01 '11 at 01:27 PM aldonaletto
(comments are locked)
10|3000 characters needed characters left

i know one of my friends who made a spider game literally rotated the whole level, making the walls turn into floors. not sure if its the best way, but it looked good

more ▼

answered Aug 15 '11 at 03:41 PM

handsomePATT gravatar image

handsomePATT
16 2 3 5

Don't repost the same comment, please.

Aug 31 '11 at 09:42 PM Illogical-Ironic
(comments are locked)
10|3000 characters needed characters left

There's another question that addresses the same issue here. I posted an answer to that one (two, actually, but one just points back here).

more ▼

answered Aug 09 '12 at 08:32 PM

jpivarski gravatar image

jpivarski
30 1 1 2

(comments are locked)
10|3000 characters needed characters left

how come when i add packages i get a bunch of errors in my game and i cant play until i remove the addon i added the locomotive demmo for my controller and i didnt even apply it yet and i get a bunch of errors but the demo works fine seperatly and the locomotive addon is good for the gravity thing if you can get it to work

more ▼

answered Jan 14 '12 at 09:42 PM

kidrockkenny gravatar image

kidrockkenny
1 6 11 12

(comments are locked)
10|3000 characters needed characters left

The demo of my game here: Download. I used this script

more ▼

answered Oct 07 '12 at 08:57 PM

Matheusfig gravatar image

Matheusfig
0 2

(comments are locked)
10|3000 characters needed characters left
Your answer
toggle preview:

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this question

By Email:

Once you sign in you will be able to subscribe for any updates here

By RSS:

Answers

Answers and Comments

Topics:

x1781
x1362
x668
x436
x122

asked: Aug 13 '11 at 12:30 AM

Seen: 6683 times

Last Updated: Feb 13 at 07:55 AM