teaching machines

CS 491 Lecture 9 – HUD and Camera Tricks

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

TODO

Lab

Terrain and FPS

Quickly create a scene with a terrain and an FPSController. Don’t spend any time on this. Polish is for later.

Collectibles

We want to scatter some collectible items around our scene. For this prototype, let’s just add a cube, a cylinder, and maybe one more. Place them somewhere near your camera.

Collectible Empty

When players draw near one of our collectibles, let’s pop up a little dialog to see if the they’d like to take the object or leave it. To support this interaction, we need some colliders on our collectible objects. However, since they’ll all behave similarly, let’s factor this functionality out into an empty game object. Go ahead and create an empty.

Give it these components: Rigidbody (disable gravity), Constance Force (spinning around the y-axis), a Sphere Collider (set to be a trigger), and CollectibleController script that we will flesh out later. Drag the empty to the assets to make it a prefab.

Now drag the prefab back into the scene, once for each collectible item you made. Drag a collectible onto exactly one of the empties to make it the empty’s child. Remove any colliders from your collectible objects. They won’t need their own; they’ll use their parent empties.

Adjust the radius of the empty prefab’s Sphere Collider to be larger than the objects themselves. We want to make it so that as soon as we step in the vicinity of the object, we get a trigger.

Do your objects spin?

HUD

Now we’re ready to pop open the dialog. Add a Canvas game object. Make sure it’s set to screen space overlay. Add a Text child that says something like "You found something!". Add a Take button and a Leave button.

Disable the canvas by checking its box in the top-left of the Inspector. We don’t want it to appear until we actually have found something. As you continue to edit your game, you may want to re-enable it temporarily.

You may also find it helpful when working with the UI to hit the 2D/3D toggle at the top of the scene editor. In 2D mode, you can see the Canvas straight on. As in 3D, hitting F while the Canvas is selected will zoom and center the view around the selected object.

PlayerController

Add a PlayerController script to your FPSController. Give it a public GameObject for the canvas and drop in the canvas in the Inspector.

Add a function ShowUI that takes in a boolean isEnabled. When the popup is shown, we want to make the canvas active. The SetActive function helps with this.

Add an OnTriggerEnter(Collider collider) function. Call ShowUI to make the UI appear.

Playtest and run into one of your collectibles. Does the UI appear?

Mouse Control

While the UI is open, we want to stop the mouse from moving the point of view, and we want to show the otherwise hidden mouse cursor so that our players can actually move it to the Take or Leave buttons.

Add these lines to ShowUI to do just that:

GetComponent<UnityStandardAssets.Characters.FirstPerson.FirstPersonController>().enabled = !isEnabled;
Cursor.lockState = isEnabled ? CursorLockMode.None : CursorLockMode.Locked;
Cursor.visible = isEnabled;

The first line enables/disables the Mouse Look script on the first-person controller. The second line unlocks or locks the cursor. The third toggles its visibility.

Responding to Buttons

Let’s have our buttons do something. Add two public methods: OnTake and OnLeave. These must be public so that the outside world can invoke them. Add to their bodies a quick little Debug.Log/ print.

Back in Unity’s scene editor, click on one of the buttons. In the Inspector, go to the On Click widget and register your callback. You must select the object receiving the callback and method to be called. Do the same for the other button.

Do your clicks trigger the debug message?

Add a ShowUI call to both callbacks to deactivate the UI.

Taking

When an object is taken, let’s just destroy it. Later on we’ll do something more interesting. Add a public Acquire method to CollectibleController that destroys the GameObject owning the script:

Destroy(gameObject);

In OnTake, let’s call Acquire on the object we ran into. We’ll have to get at the CollectibleController component:

objectToTake.GetComponent<CollectibleController>().Acquire();

What is objectToTake? That’s the object whose trigger we ran into. We’ll need to hang on to that object via an instance variable in order for this method to access it.

Dimmed Background

Let’s add an aesthetic affect. When we encounter a collectible, let’s dim the background and highlight only the collectible. There are probably many ways to do this, but the one described here hits upon some ideas that may be useful in other contexts.

First, let’s dim the entire scene by overlaying an image at the back of our Canvas. Select the Canvas and add an Image object. Be careful of its relative order in the children of the Canvas. Objects are drawn from first to last, with the last object appearing atop the earlier ones. We want our Image to appear behind the buttons and text.

Though we added an Image, we’re not really going to use it to draw a picture. We just want to overlay a solid color across our scene. In the Inspector, set the Image’s color to a semi-transparent black.

Does the whole scene dim behind the UI when you encounter a collectible?

Render Texture

Dimming the whole scene is not quite what we want. We want the collectible to be highlighted. Unfortunately, it can be a little difficult to get a non-UI Game Object to appear in front of a screen space overlay. We won’t try. Instead, we will render a quick shot of the collectible to a texture and then paste the flat texture into the UI.

Our first step is to create what’s called a Render Texture. This is essentially an allocation of pixels on the graphics card that will get filled in during runtime. In the Project assets pane, click Create / Render Texture. Make its size have the same aspect ratio as your game window.

Second Camera

How do we fill this texture in with an image of our collectible at runtime? Well, we need to tell a camera that instead of automatically showing its view in the main game window, it should render to this Render Texture instead. Since the first camera—a child of our FPSController—is already busy with the overall scene, we need a second camera that’s just like the first. Add a camera and make it a child of the FirstPersonCharacter, which in turn is a child of FPSController. Reset its transform so it aligns exactly with its parent camera.

Change its Target Texture property to the Render Texture that you just made.

Raw Image

Let’s paste our Render Texture onto our UI. Select the Canvas and this time add a Raw Image. (Images show Sprites, but Raw Images show Textures. We have a texture.) Place it so it renders in front of the dimming background image.

We want the texture to fill the screen so that it matches up with game world captured by the normal camera. Click on the anchor widget in the Inspector, hold down Option-Shift or Alt-Shift, and click on the bottom-right icon to force the image to stretch both vertically and horizontally.

Playtest. Does the background appear dimmed while the collectible is rendered normally?

Rendering Only Collectibles

Probably not. Our second camera will re-render the entire scene by default. We only want it to capture the collectible. First off, let’s put all the collectibles in their own Collectible layer. Click on your Collectible prefab, add a layer in the Layer dropdown in the top-right of the Inspector, and assign the layer. Creating and assigning are two separate steps. Don’t miss the assigning step.

Find the child collectibles in the game scene and put them in this layer as well.

Select your render-to-texture camera. We only want to it to render collectibles. So click on its Culling Mask. Set it to Nothing to wipe clear its list of layers that it renders. Then re-add the Collectibles layer.

Playtest. How does it look now?

Disabling the Background

Hopefully, the second camera shows only the collectibles, but it still renders a skybox. Change the Clear Flags of the camera to change how a camera resets a scene that its about to draw into. Change the default Skybox to Solid Color. Set the color to completely transparent anything. This will give the camera a transparent background, preventing it from obscuring the rest of the scene.

How do things look now?

Inventory HUD

We’ve got a dialog UI that appears when we encounter a collectible. Now let’s add a heads-up display to always show our inventory. We’ll create an arrangement of our collectible items in the view of another camera that renders atop the regular camera.

Make a third camera. It doesn’t need to be a child of anything. Add an Inventory layer and set the camera’s Culling Mask so that it only shows Inventory items. Make it Orthographic instead of Perspective. We don’t need accurate depth in a HUD. Set its Clear Flags to Depth Only so that the previously rendered game world is preserved but the HUD elements don’t fight with the objects from there for sorting order. Change the camera’s depth to something larger than the default 0. Camera will be rendered from least to greatest depth.

Duplicate your collectibles (only the meshes, not the empty parents) and add them as children of the camera. Arrange them in a line at the top or bottom of your scene to represent an inventory display. Put them on the Inventory layer.

Shadowed Inventory

Let’s shadow out the items in the inventory HUD until we’ve collected them. Create a new Material asset. Change its Shader to Unlit/Color and select black as its color. Apply this material to the inventory HUD items.

When we pick up the item in the game world, we want to restore a normal shaded material to the object in the inventory HUD. First, create a standard shaded material. Then, open your CollectibleController script and two publics: a GameObject for the corresponding inventory item and a shaded Material you wish to apply to the inventory item. Using the Inspector, drop in the appropriate objects.

In Acquire, apply the material to the corresponding inventory item’s MeshRenderer:

inventoryItem.GetComponent<MeshRenderer>().material = shadedMaterial;

 

Does the inventory item light up when you collect it?

Lighting

The lighting in our inventory HUD might appear a little strange. Our objects there aren’t really part of the regular game world, yet they are shaded by the world’s light. It’d be better to have a separate light for just the inventory.

Add one as a child of inventory HUD camera and set its Culling Mask to only shade the Inventory layer.

Likewise, visit the world light and uncheck the Inventory layer from its Culling Mask.