During the development of Mechropolis, our level designer came to me requesting the ability to limit the area in which the Stationary Thrower robot could turn to keep it’s arms from clipping into walls and to give the level designer more control when creating challenges.

The robot in the image above is the Stationary Thrower. It turns toward the player and throws her into the air when she steps onto it’s plate, kind of like a robotic jump pad.

Limiting the angles allowed for the robot was not a problem, but smoothly turning toward it’s target angle while avoiding the restricted segment proved to be a bit more of a challenge than I had anticipated. I had previously used Unity’s built in `Quaternion.RotateTowards`

function for this, but that function always does the shortest rotation possible which would cause the robot to rotate through obstacles if that was the shortest route.

I decided to approach the problem by manually rotating the robot towards it target on a plane with support for my own angular limits. I’ve tried keeping things as general as possible and while my use for this is limited to the xz-plane, this approach should work on any plane.

The parameters I use to control the angular limits are a “limit direction angle” and a “limit amount angle”. I decided to use these parameters to simplify the implementation, but more user friendly parameters could be used without too much pain.

So basically, what we need to know is how much to rotate by and if the rotation goes through the restricted segment and should therefore go the long way around.

Before calculating the rotation angle, we need to force the target orientation to stay outside of the restricted segment. To do this, we start by calculating the limit direction, a normalized vector pointing along the middle of the limited area. Then we get the angle between this vector and the target direction vector using `Vector3.Angle`

. If the angle is less than half the angle of the restricted segment, we clamp it.

Vector3 limit_direction; Vector3 limit_normal; Vector3 target_direction; float target_angle; float limit_angle = 180.0f; float limit_amount = 120.0f; //Get a normalized vector pointing toward the target, projected on the rotational plane target_direction = lookTarget.position - transform.position; target_direction -= Vector3.Project(target_direction, transform.up); target_direction.Normalize(); //Calculate limit direction and normal limit_direction = (transform.rotation * Quaternion.AngleAxis(limit_angle, Vector3.up)) * Vector3.forward; limit_normal = Vector3.Cross(limit_direction, transform.up); //Get the angle to the target relative to the limit direction target_angle = Vector3.Angle(limit_direction, target_direction); //Clamp the angle if it is inside the limited segment if (target_angle < limit_amount * 0.5f) target_angle = limit_amount * 0.5f; if (Vector3.Dot(limit_normal, target_direction) > 0.0f) target_angle = -target_angle;

We then take the angle between the target direction and the current direction. By dotting the desired direction with the right vector in the current orientation, we find if the rotation is clockwise or counter clockwise.

Now remains only to find if the robot should rotate directly toward the target or if it should take the long way around. The rules for this are as follows:

If the desired direction and current direction are on opposite sides of the limit vector AND if their normalized average is pointing toward the limit vector, rotate the long way around. Or in less confusing words: “if the start and the goal are on opposite sides of the restricted segment and the rotation would go toward the restricted segment, thus eventually passing it”.

//Update the target direction target_direction = transform.rotation * Quaternion.AngleAxis(target_angle + limit_angle, Vector3.up) * Vector3.forward; //Get the angle to the target relative to the tower's current direction target_angle = Vector3.Angle(target_direction, m_tower.forward); //m_tower is the rotating top part of the robot //Determine the turn direction if (Vector3.Dot(m_tower.right, target_direction) < 0.0f) target_angle = -target_angle; Vector3 average_direction = ((target_direction + m_tower.forward) * 0.5f); if (limit_amount > 0.0f) { //If tower forward and the target direction are on opposite sides of the limit direction and the average is pointing toward the limit direction if (Vector3.Dot(limit_normal, target_direction) * Vector3.Dot(limit_normal, m_tower.forward) < 0.0f && Vector3.Dot(limit_direction, average_direction) > 0.0f) { //Add or remove one revolution to go the long way around if (target_angle > 0.0f) target_angle = target_angle - 360.0f; else target_angle = target_angle + 360.0f; } } //Do the rotation m_tower.rotation = m_tower.rotation * Quaternion.AngleAxis(Mathf.Clamp(target_angle * 5.0f, -720.0f, 720.0f) * GlobalSingleton.deltaTime, Vector3.up);

I hope that someone may find this useful! If you know of a simpler approach for this, please let me know!