July 29, 2014
Press Releases
July 29, 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

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);
{
leftHitInfo = overrideLeftHitInfo;
}
}
//if moving right
else
{
RaycastHit overrideRightHitInfo;
Ray overrideRightRay = new Ray(transform.position + horizontalRayProperties.originOffsetY * selfTransform.up, -selfTransform.right);
{
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

Activision — Seattle, Washington, United States
[07.29.14]

Software Engineer - Activision Seattle
Treyarch / Activision — Santa Monica, California, United States
[07.29.14]

Senior Environment Concept Artist - Treyarch (temporary)
Vicarious Visions / Activision — Albany, New York, United States
[07.29.14]

Software Engineer-Vicarious Visions
Sony Online Entertainment, San Diego — San Diego, California, United States
[07.28.14]

UI Programmer, PlanetSide 2

 Alexandre stroukoff
 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
 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
 Love the design of these guys, they're super cute when they walk.

 Marvin Papin
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
 thanks for bringing this to our attention! We should be using rigidbody.MovePosition() and rigidbody.MoveRotation() because we are using physics

 Federico Fasce
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.

 Yes we are, for that reason exactly.

 Craig Jensen
 This game looks amazing.

 Cristian Dinca
 Great stuff, Adam, thx for sharing!

 Alexandre Daze-Hill
 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
 does this built to work in 2D project or 3D?

 Jason Keefer
 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.

 none Comment: