Ahhhh, yes. Let’s talk about HUD/GUI elements, shall we? The first rule of HUD elements is WE DO NOT USE GUI ELEMENTS. Every other rule thereafter, see rule 1. One of the first things I set out to learn when diving into Unity for the first time was pixel perfect HUD placement, neatly tucked away in corners of the screen or wherever we would like it to be tucked away. This is incredibly easy to do with any GameObject and takes mere seconds to implement.
Let’s begin with the rundown of how this works:
- Find the camera
- Assign object’s transform to world space using screen size
That’s pretty much it. This isn’t rocket science. On our HUD GameObject we simply store a reference for the Camera. There’s 2 ways you can do this – first, make the reference public so you can drag the Camera component onto the HUD GameObject in the inspector or second (my preferred method) is to make it private and get the reference using GetComponent. My rule of thumb is that if it really does not need to be public, make it private. There’s no need for public everything just for the sake of making it available in the inspector or other classes, IMO. Private that bad boy:
PROTIP: You’ll notice I used an underscore before naming the reference. I do this with all Components in my classes. It’s how I know if something is a component or a variable at quick glance because we all know the hardest friggin’ part about coding is naming things, dammit.
Now, we need to reference that when the class component loads on that GameObject. We do this by looking for the main camera by TAG (we like tag, slightly faster than by object name according to multiple sources and more flexible). We can do this in the Awake() or Start() functions, either or will do (depending on the rest of your classes and how they need to execute, keep that in mind):
Sweet, we now have a reference to the camera. This is good. Be warned: if the camera does not already exist in the scene you will get an error, use common sense when creating references to objects NOT already in scene. Either wait for it to load before referencing or use a catch to make sure no errors are thrown.
We will now use this reference to the camera to set the HUD GameObject’s transform.position when the scene is launched or HUD object instantiated like so:
There it is. Notice what we are doing here – we are using Camera.ScreenToWorldPoint to dictate the placement of our HUD element. What this does is transform the current screen space into world space. We can get the height and width of the current screen simply by using Screen.height or Screen.width, then offset by X number of pixels we need using a Vector3.
In the example above, we are moving the HUD element 15 pixels to the right from the absolute left side of the screen (0) and 15 pixels downwards from the top from the absolute top of the screen (Screen.height). The Vector placement of pixels begins at the bottom left of the screen at (0, 0), so we know that (20, 20) will be 20 pixels up and in from the bottom left. Likewise, Screen.width-15 will yield 15 pixels from the right side of the screen. Nifty.
Now you’re going to ask: “cool, I did this, but when my camera moves, I lose my HUD!”. That’s because your camera moved. When your camera moves, we need to move the HUD, right? There’s a few ways you can go about this – the easiest method is to simply attach your HUD GameObject to your Camera GameObject and let it fly. The second method is to update the position of the HUD GameObject relative to your Camera AFTER you move your camera.
We can do this by creating a function to update the transform.position of the HUD GameObject and call that function from the Camera AFTER the camera is finished moving using Update() or LateUpdate() before the current frame is finished (depending on your call order). Simple, right? I’m a big fan of telling classes what to do and when vs having them listen to changes so I always prefer this method of direct control.
Now you can safely tuck your HUD elements into corners by exact pixels! Now that we are armed with this information we can use this to create an adjustable HUD for use with PC monitors and TVs with overscan. We can give users options to set their screen bounds so that the HUD elements will always look nicely placed even when the end user has overscan ON on their HDTVs. Good stuff, mates. Good stuff.