Gamasutra: The Art & Business of Making Gamesspacer
View All     RSS
October 25, 2014
arrowPress Releases
October 25, 2014
PR Newswire
View All
View All     Submit Event

If you enjoy reading this site, you might also want to check out these UBM Tech sites:

Lovers in a Dangerous Spacetime DevLog #7: Learning How to Walk
by Adam Winkels on 02/20/14 03:52:00 pm   Expert Blogs   Featured Blogs

The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.



We've had ground-based enemies, which we call Walkers, in Lovers since way back in the days of the GDC 2013 build. Until recently these enemies have been tethered to spherical (well, circular) planets, so programming their movement was simply a matter of ensuring that their distance from the center of the planet was constant and their velocity was tangential to the vector from the enemy's position to the planet's center. However, as we continued to add new scenarios for players to experience we needed Walkers to be able to traverse more exotic terrain. Being the lazy developers that we are, our first attempt to implement a more robust walking algorithm was the simplest and most naive that we could come up with. Luckily for us, it worked out pretty well.



Our naivety lead us to use raycasts to check for a collider underneath a Walker. Shooting one ray from the center may work for some situations, but the Walker would look squirrely as it moves across significant changes in terrain. Instead we shoot two rays, one from each side of the Walker:

bool doubleRaycastDown(TerrainMovementRayProperties movementRay, float rayLength, out RaycastHit leftHitInfo, out RaycastHit rightHitInfo)
    Vector3 transformUp = transform.up;
    Vector3 transformRight = transform.right;
    Ray leftRay = new Ray(transform.position + movementRay.originOffsetY * transformUp + movementRay.distanceFromCenter * transformRight, -transformUp);
    Ray rightRay = new Ray(transform.position + movementRay.originOffsetY * transformUp - movementRay.distanceFromCenter * transformRight, -transformUp);

    return Physics.Raycast(leftRay, out leftHitInfo, rayLength, DefaultTerrainLayerMask) && Physics.Raycast(rightRay, out rightHitInfo, rayLength, DefaultTerrainLayerMask);

If both rays hit a collider with the proper layer, we use take the average of the position and normal vectors of the resulting RaycastHit objects and use them to position and orient the Walker, respectively. Easy as that!

void positionOnTerrain(RaycastHit leftHitInfo, RaycastHit rightHitInfo, float maxRotationDegrees, float positionOffsetY)
    Vector3 averageNormal = (leftHitInfo.normal + rightHitInfo.normal) / 2;
    Vector3 averagePoint = (leftHitInfo.point + rightHitInfo.point) / 2;

    Quaternion targetRotation = Quaternion.FromToRotation(Vector3.up, averageNormal);
    Quaternion finalRotation = Quaternion.RotateTowards(transform.rotation, targetRotation, maxRotationDegrees);
    transform.rotation = Quaternion.Euler(0, 0, finalRotation.eulerAngles.z);

    transform.position = averagePoint + transform.up * positionOffsetY;

Well, almost as easy as that. Allowing the Walker to rotate an arbitrary amount each frame leads to very jittery-looking motion as it traverses the bumps and crevices. To combat this, we limit the amount it is allowed to rotate each frame using Quaternion.RotateTowards. This tweak goes a long way to allowing for more lifelike movement.

The Walker also had a tendency to get stuck in or clip through certain types of concave geometry. To overcome this, we added a very short horizontal ray in the direction of motion that extends to about the edge of the character's body. If this ray collides with any piece of terrain, it will override the downward ray on that side of the Walker.

if(rigidbody.velocity.sqrMagnitude > 0)
    //if moving left
    if(MathUtilities.VectorSimilarity(rigidbody.velocity, selfTransform.right) > 0)
        RaycastHit overrideLeftHitInfo;
        Ray overrideLeftRay = new Ray(transform.position + horizontalRayProperties.originOffsetY * selfTransform.up, selfTransform.right);
        if(Physics.Raycast(overrideLeftRay, out overrideLeftHitInfo, horizontalRayProperties.attachedRayLength, DefaultTerrainLayerMask))
            leftHitInfo = overrideLeftHitInfo;
    //if moving right
        RaycastHit overrideRightHitInfo;
        Ray overrideRightRay = new Ray(transform.position + horizontalRayProperties.originOffsetY * selfTransform.up, -selfTransform.right);
        if(Physics.Raycast(overrideRightRay, out overrideRightHitInfo, horizontalRayProperties.attachedRayLength, DefaultTerrainLayerMask))
            rightHitInfo = overrideRightHitInfo;

Below is the system in motion. The yellow and red lines represent the rays being shot from the Walker, the green and magenta lines are the normals generated from those rays' collisions with terrain and the cyan line is the average of those normals, originating at the average terrain intersection point of the Walker rays. (The horizontal override ray is not shown.)


The asteroid the enemy is walking on is a compound collider consisting of sphere and capsule colliders, but this system works equally well on an arbitrary mesh collider.

Original post

// Adam Winkels (@winkels) is a co-founder of Asteroid Base.

Related Jobs

Red 5 Studios
Red 5 Studios — Orange County, California, United States

Graphics Programmer
Red 5 Studios
Red 5 Studios — Orange County, California, United States

Gameplay Programmer
Gearbox Software
Gearbox Software — Plano, Texas, United States

Server Programmer
Forio — San Francisco, California, United States

Web Application Developer Team Lead


Alexandre stroukoff
profile image
Love all those post on movement & collision on unity,

I'm seeing more & more article about using Raycasts with 2D movement for collision...It seems to bee really the best way to handle this

Igor Hatakeyama
profile image
Oh wow. This is amazing! I gotta find some time to study this, I really need to learn more about raycasting. It's a useful thing to know!
Thanks for the great post!

Matt Boudreaux
profile image
Love the design of these guys, they're super cute when they walk.

Marvin Papin
profile image
Yeah, really cool.

Plus, it's not been mentioned but that way, no need to calculate physics (no rigidbody) and since you do apply the position directly, no need to apply a pseudo-gravity.

Jamie Tucker
profile image
thanks for bringing this to our attention!
We should be using rigidbody.MovePosition() and rigidbody.MoveRotation() because we are using physics

Federico Fasce
profile image
Just a question: are you using kinematic rigidbodies for the walkers? I guess they still need a collider in order to be destroyed by the bullets.

Adam Winkels
profile image
Yes we are, for that reason exactly.

Craig Jensen
profile image
This game looks amazing.

Cristian Dinca
profile image
Great stuff, Adam, thx for sharing!

Alexandre Daze-Hill
profile image
Nice post! But I wonder, could just getting the hit point of the raycasts and determine a rotation by the angle between these 2 points work?

haim ifrah
profile image
does this built to work in 2D project or 3D?

Jason Keefer
profile image
This is a nice solution for terrains with changes in angles less than 90 degrees. It is pretty elegant and only relies on two Raycasts (and a third for optimization). However, when introduced to a terrain with 90 degree angles (such as the same walker walking around a perfect square or rectangle), the process becomes more challenging.