teaching machines

CS 491 Lecture 10 – Compass and Publishing

February 25, 2016 by . Filed under gamedev3, lectures, spring 2016.

TODO

Lab

Few of you finished the last lab, so start there. Get your leave/take buttons and inventory HUD working and then let’s add a wayfinding UI.

Compass Image

Many of you indicated that you wanted some sort of compass in your user interface. Let’s add one to the walk-and-collect game we started last time. We’ll use a simple arrow to point to the nearest collectible item. Feel free to use the following image or make your own.

An arrow that will point to the nearest collectible.

An arrow that will point to the nearest collectible.

The arrow is intentionally white—the Image component of a Game Object lets us filter the color at runtime, so we can turn that white into any other color we choose. It also has a transparent background.

Import your arrow into your project assets. Click on the asset and ensure that its alpha channel is recognized and that it is imported as a sprite (not a texture, which is the default in 3D).

HUD

Where do we put this image? The Canvas we used earlier only shows up when we’re near an object, but we want our compass to always show. Let’s add a second screen-space overlay Canvas that’s always active.

Add an Image to this Canvas that shows the arrow you imported. Use the anchor presets to place the image near an edge or corner, but pull it away from all edges a bit so it can spin without going offscreen.

Since we want to the arrow to spin around its center, make its Pivot is set to (0.5, 0.5).

Finding Nearest Collectible

As we move through the world, we want to programmatically locate the nearest collectible item and rotate the arrow in the direction the player must turn to find it. Let’s start by giving all collectibles in our scene the tag Collectible. (This is similar to but different from the Collectible layer we added last time. Layers are used for physics, cameras, and lights. Tags are used for more custom purposes.)

Inside the Update method for PlayerController, we can visit each Collectible item with a foreach loop:

foreach (GameObject collectible in GameObject.FindGameObjectsWithTag("Collectible")) {
  // ...
}

We want to identify which collectible is nearest. The following pseudocode pseudo-accomplishes this task:

nearestCollectible = null
nearestSquaredDistance = Mathf.Infinity

for each collectible
  toCollectible = collectible's position - player's position
  squaredDistance = toCollectible.sqrMagnitude
  if squaredDistance < nearestSquaredDistance
    nearestCollectible = collectible
    nearestSquaredDistance = squaredDistance
  end
end

Playtest. Does it work?

Determining Angle

If we found a nearest collectible, we now calculate the angle between the direction the player is facing and the difference vector:

toCollectible = nearestCollectible's position - player's position
toForward = transform.forward
angle = Vector3.Angle(toCollectible, toForward)

Rotating Compass

Let’s use this angle to rotate the arrow. Add a public GameObject for the arrow image and assign it via the Inspector. Then in Update, apply your rotation by reaching into the image’s RectTransform component and spinning the image around the z axis:

arrow.GetComponent<RectTransform>().eulerAngles = new Vector3(0, 0, angle);

Playtest. Does the arrow point where you want it to?

Negative Angles

You probably found that the arrow only kind of works. The problem is that Vector3.Angle only gives back an angle in [0, 180]. The effect of this is that we only know the “absolute degree distance” between the player and the collectible. We don’t know whether to turn left or right to get there.

One trick we can use to distinguish between a left turn and a right turn is to determine the direction of the bend between the two vectors we computed. The seemingly irrelevant method Vector3.Cross helps with this. This method takes two vectors and computes a third that’s perpendicular to both. In our case, our two vectors are mostly parallel with the ground. If our two vectors are ordered one way, crossing them will yield a vector that mostly points up. If they are ordered the other way, crossing will yield a vector that points down.

Before we rotate the arrow, let’s use this method to expand our angle into the full [-180, 180] range:

Vector3 crossProduct = Vector3.Cross(toCollectible, toForward);
if crossProduct points down
  negate angle
end

Does that fix things?

Publishing

Recall that the deadline for the First Person homework is next week. One of the requirements is that you submit your game to Itch.io. As a last step, let’s familiarize ourselves with this process.

Check out Publishing Builds in the Unity documentation. Target WebGL. Make a ZIP file out of the exported game directory, and upload it to your itch.io account. The site asks you the kind of project you are uploading. Choose HTML. Ensure that the game can be played fullscreen.