Join Michael House for an in-depth discussion in this video Positioning GUI items over game objects, part of Advanced Unity 3D Game Programming.
In this video, I'm going to teach you how to position GUI elements relative to game objects. This type of tagging is done throughout games. For example, during a tutorial, we might want to place a label next to the object a player is supposed to click. Or we might want to use this for indicating targets in a shooter game, or even to show a progress bar above a building to indicate how far along construction is. Additionally, you can use these in your own debugging to provide more information about the objects on-screen. Once again, we're going to aim for a system that's flexible and easy to use.
We want to be able to track game objects and draw whatever game element we like over them. In previous videos, I've shown you a strategy for using abstract classes and deriving different classes to extend functionality. In this video, I'm going to show you a strategy of using delegates. Using delegates has advantages, for example, not needing to modify the element class to add new functionality. However, there's also the disadvantage of moving the code for an element outside of the element. This can lead to code duplication, and means more work for other people, including future you, to add the functionality needed.
Let's take a look at the code. Our ObjectTagger is going to maintain a dictionary of game objects and their TaggedObject class. The TaggedObject is going to contain a GameObject target and an offset. The offset vector is an offset into the 3D world for where we want to place our label. We can use this, for example, to place labels above a object or next to it, instead of directly over it. ClampToScreen, when true, will keep the label on the screen at all times even when the object is not.
This is useful for finding objects outside the screen and knowing which way to turn the camera to look at them. The screenEdgePadding is how near the edge we want the label to go. Finally we have the GUIContentGenerator. This is a delegate class that will be called to generate the GUI content that will be rendered into the label over the tagged object. This contentGenerator is going to be used in the draw call here. It generates a GUIContent, next the draw call finds a position in the 3D world, and converts that to a screen position. We then determine the size of the rectangle we need to draw based on the GUIContent generated.
We then use GUIBox to draw a rectangle on screen at the location of the object using the content generated from our contentGenerator. We have other draw calls that allow us to pass in GUIContent directly, bypassing the need for a contentGenerator. Finally we have a draw call that takes two delegates, a GUIElementGetSize and a GUIElementDraw. These two delegates will determine the size we need to draw and then using this elementDrawer, to draw a rectangle at the position of the object.
This allows us to bypass the GUIBox and use whatever kind of element we want above the object. We also have a FancyTaggedObject. The primary difference with this tag type, is it draws a 2D line from the tagger to the TaggedObject. This would be useful if you wanted to mouse over an item in a table, and have a line drawn from that item to the game object in world. Let's take a look at an interface that implements this. Our ObjectTaggerTestInterface is going to create a new ObjectTagger and perform a SimpleAdd.
The SimpleAdd method simply goes through all the game objects in the scene tagged with Tagged, and adds them to the gameObject tagger. Now that we have objects tagged, we're going to go to our OnGUI and call tagger.Draw and pass in this delegate method, GenerateName. GenerateName is going to generate a new GUIContent using the taggedObject as source, and in this case it's going to take the taggedObject and produce the name. To produce a new GUIContent, which will be rendered in the label above the object.
Let's see what this looks like in game. Here we see our objects and their names rendered above them. The tags stay above the objects at all times. Let's take a look at another GUI content generator. The GenerateSimpleInfo is going to generate a name and a position. We can pass that in here instead of our GenerateName. We see that we're generating a name and a position. Labels aren't the only thing we can generate. Here we're going to generate a target. We want to use a different SimpleAdd method that passes in a zero vector so that the target is directly over our object instead of above it.
The target is a texture that we load from our resources directory. Let's see what that looks like. Here we see the target as being drawn over. But since we're using the GUIBox call, we have a box as well as our texture. This is the reason for the more advanced draw call which takes two delegates for getSize and elementDrawer. We have an example of this being used here. The getSize delegate is going to calculate the size given the style of our texture. The GUI element on the draw is going to draw a label containing the texture in it. This avoids using GUIBox, so we shouldn't see a box around our targets.
And there we have just targets around our objects. Finally, let's demo the FancyAdd. This will simply draw a line from our tagger to the tagged objects. Here we see our tagger object up there, with lines drawn from our tagger to our tagged objects. In this video, I've shown you how to position GUI elements relative to game objects. This is ideal for using the GUI to indicate objects in a 3D world. This system also demonstrated the use of delegates for extending the functionality of the class instead of abstract classes to generate new functionality. Likely the ideal balance is some mixture of both.
Using derived classes to provide the most common or challenging functionality, and allowing the ability to pass in delegates for performing custom actions. Remember when writing code that you might not be the only one using it. You might release it to others someday, and putting in the extra work now helps you in the future as well.
NOTE: This course requires Unity 4.5.5. The newer versions of Unity have done away with the GUI system used in this course, so the interfaces included for many of the scenes will not work with 4.6 and higher. You can download Unity 4.5.5 at http://unity3d.com/get-unity/download/archive.
- Enabling/disabling with scripts
- Translating, rotating, and scaling objects with scripts
- Working with mouse and keyboard input
- Creating custom GUI controls like progress bars
- Loading assets with scripts
- Saving games
- Creating prefabs dynamically
- Making remote procedure calls
- Synchronizing object transforms
- Finding slow code
- Optimizing data access
- Extending the editor
Skill Level Advanced
Q: In the Chapter 3 file EntityLoader.cs, I get an error: DirectoryNotFoundException: Directory 'Assets\' not found. Removing the backslash after 'Assets' fixes it, but then I run into a different error. How do I fix this?
Q: What version of Unity does this course cover?
A: This course requires Unity 4.5.5. The newer versions of Unity have done away with the GUI system used in this course, so the interfaces included for many of the scenes will not work with 4.6 and higher. You can download Unity 4.5.5 at http://unity3d.com/get-unity/download/archive.
Q: The game object in the first chapter's Mouse Input Raycasting sample doesn't appear to follow the mouse. What's wrong?
A: Please enable the Box Collider component (by activating the checkbox next to the component name) on the game object. This is not explicitly mentioned in the video, but it will ensure the raycasts collide with the game object.