There are many effects that can be applied to both 2D and 3D menus. Some of them can be small and subtle, such as a glow effect when the player moves the mouse cursor on a menu item. But while these effects are typically a nice touch for creating more dynamic interactions, they are usually complementary to 2D menus. 3D menus provide us with the ability to add another layer of movement along another dimension. As such, we can have the entire menu perform a range of different movements, such as rotation and tilting, both on its own and via user interaction. Since we are able to utilize the z axis, we are able to have elements projected in a different way. For instance, we are able to have the elements placed at various locations along the z axis. When we rotate items that are farther away, they rotate at a slower rate than those that are closer (to the camera). This is known as the parallax effect. This recipe will touch on some basic movements, such as moving and rotating the 3D UI element. These movements could be for the entire menu, by making it rotate according to the mouse's position.
If your menu does not have a root, it is good practice to always have one. This is for keeping the contents of your menu in an ordered structure and applying modifications to all the elements in an easier way within your scripts. In order to create it on an existing menu, right-click on the Hierarchy Panel and then select Create Empty. Finally, rename it to MenuRoot. Use Rect Tool to modify the size of MenuRoot until it includes all the UI elements that belong to the menu. Now, select all the elements and drag them onto MenuRoot. We do this because it allows us to parent them to our root.
using UnityEngine.UI;
statement at the beginning of the script.public
variable to set in the Inspector that stores the range of degrees to which the UI element can turn on the x axis and the y axis. Therefore, we can use Vector2
and set some arbitrary starting values:public Vector2 range = new Vector2(10f, 6f);
public
variable for it. Again, set its starting value arbitrarily:public float speed = 5f;
Vector2
:private Vector2 tiltRotation = Vector2.zero;
Update()
function. Since the mouse can move all over the screen, we have to, in some way, clamp its value between -1
and 1
so that we can also distinguish which side of the screen it is on. This is done in order to represent a percentage of how far the mouse is from the center of the screen. Let's start by calculating the two coordinate halves of the screen:float halfWidth = Screen.width / 2f; float halfHeight = Screen.height / 2f;
float x = Mathf.Clamp((Input.mousePosition.x - halfWidth) / halfWidth, -1f, 1f); float y = Mathf.Clamp((Input.mousePosition.y - halfHeight) / halfHeight, -1f, 1f);
x
and y
that we have calculated in the previous step, we have to start from the rotation that the UI element had in the last frame and make it rotate a little towards the rotation that it should have at the end. Therefore, to achieve this, we need to linearly interpolate. While doing this, we can pass the time from the last frame as the control parameter. In fact, if we assign this value to tiltRotation
, we can start from this frame and move on to the next one. Furthermore, if we multiply deltaTime
with our speed stored in speed
, we can control how smooth the rotation will be:tiltRotation = Vector2.Lerp(tiltRotation, new Vector2(x, y), Time.deltaTime * speed);
Euler
angles in Quaternion
, we can make the assignment:transform.localRotation = originalRotation * Quaternion.Euler(-tiltRotation.y * range.y, tiltRotation.x * range.x, 0f);
It is possible to slightly change the script to make it more customizable by designers. This is the aim of the following sections that will teach us how to do this.
Some menus can have an initial rotation that determines where they start with the tilt effect. In order to do this, we need to store the initial rotation in a private
variable, like this:
private Quaternion originalRotation;
Then, we have to initialize it in the Start()
function with this line:
originalRotation = transform.localRotation;
Finally, in the last line of our Update()
function, we just multiply the new rotation with the original one:
transform.localRotation = originalRotation * Quaternion.Euler(-tiltRotation.y * range.y, tiltRotation.x * range.x, 0f);
Now, every time the tilt effect is applied, it will start from the initial rotation of the UI element that we have set.
In some instances, we might want to provide an easier way for designers to tweak the smoothness of the rotation instead of the velocity. In this case, we can replace the speed
variable with this one:
public float smoothnessFactor = 0.2f;
Then, we use smoothnessFactor
in the code in this way:
tiltRotation = Vector2.Lerp(tiltRotation, new Vector2(x, y), Time.deltaTime * (1f/ smoothnessFactor);
In some games, both the axes are inverted, and in others, only one axis is. Since we have scripted our tilt effect to include a range vector that can have negative values, we can achieve inversion by changing the sign to the components of the range vector. Of course, it is possible to have only one negative value to invert a specific axis. Furthermore, to simplify designers' lives, we can keep the values for the vector positive. Thus, we just need to change the signs of the vector components when they are utilized in the script. This means negative to positive and vice versa. In particular, we have to change the following line in this way:
transform.localRotation = originalRotation * Quaternion.Euler(-tiltRotation.y * -range.y, tiltRotation.x * -range.x, 0f);
If we want to rotate our UI element using an asymmetric tilt effect, it could be a bit tricky, because it is a little more complicated than other concepts that we have previously covered. An asymmetric tilt effect means that when the mouse goes from one side of the screen to another, the range of rotation changes. Therefore, we need another range vector for the symmetric part:
public Vector2 symmetricRange = new Vector2(7f, 4f);
Now, when the script is running, we have to use one vector or the other depending on where the mouse is. Hence, we have to use an if
statement by checking this and applying one range vector or the other when we rotate the UI element:
if(tiltRotation.y > 0 && tiltRotation.x > 0) transform.localRotation = originalRotation * Quaternion.Euler(-tiltRotation.y * range.y, tiltRotation.x * range.x, 0f); if(tiltRotation.y < 0 && tiltRotation.x > 0) transform.localRotation = originalRotation * Quaternion.Euler(-tiltRotation.y * symmetricRange.y, tiltRotation.x * range.x, 0f); if(tiltRotation.y > 0 && tiltRotation.x < 0) transform.localRotation = originalRotation * Quaternion.Euler(-tiltRotation.y * range.y, tiltRotation.x * symmetricRange.x, 0f); if(tiltRotation.y < 0 && tiltRotation.x < 0) transform.localRotation = originalRotation * Quaternion.Euler(-tiltRotation.y * symmetricRange.y, tiltRotation.x * symmetricRange.x, 0f);
In some cases, we don't want the area in which the tilt effect takes place to be extended along all of the screen size. In such cases, we should replace it with an arbitrary Rect
. In this case, when we calculate the x
and y
values, we have to use the size of Rect
, as follows:
float x = Mathf.Clamp((Input.mousePosition.x - halfRectWidth) / halfRectWidth, -1f, 1f); float y = Mathf.Clamp((Input.mousePosition.y - halfRectHeight) / halfRectHeight, -1f, 1f);
If Rect
is not centered in the middle of the screen, remember to add the offset of the position of Rect
.
3.15.186.79