This is a post-mortem walkthrough of my process for creating the above menu effect. As per the brief outlining my Unity test, I have attempted to match the behaviour of the following Image’s UI in Unity.
I started by analyzing the different visual and behavioural elements pertaining to the reference, listed as follows:
- Scrolls up / down.
- Loops / wraps around.
- Has an ascending and descending order of index, demonstrated by number on the side of the holo-watch.
- Fades as it gets further from the current selected, also rotates, and tweens in z-pos.
- Can be activated and de-activated, seemingly with the push of a button.
- Seems to scroll on button-press, not responding to holding the button down.
- Has 2 sprites, one for selected, and one for Deselected.
- It gives the illusion of menu items disappearing behind the wrist watch.
- Each Menu item has a corresponding sub menu.
- The HoloWatch is WorldSpace.
- You can’t tell from this image, but you can from it’s relevant video that the sub-menus appear to be in ScreenSpaceCamera.
I have never actually done anything in UI that has been more than some standard buttons and implementing health gauges / timers. So I had to put a fair bit of thought in to what exactly I needed to do for this task. And also how much time I needed to spend. Unfortunately some research was required. I watched about 4 hours of videos at double-speed trying to see what approaches other people had taken to similar things, there was nothing I could find for this specific type of menu in unity, which is usually my first hope, that there would be.
After some research I gathered some basic ideas for what I wanted:
- It needs to have several options in a single menu, the list of menu items should be easily customizable. A designer should be able to add / remove / reposition menu items easily and without anything bad happening.
- The behaviour should be easily modifiable in terms of the behavioural elements I’ve managed to identify: Alpha, ZPos, and XRotation tweening.
- The menu needs to be able to move it’s menu elements up and down when we scroll, there are plenty of ways to do this, some of which are less desirable than others.
I made the following assumptions to begin with:
- Using the latest GUI system, each menu item would be it’s own GameObject.
- Out-of-the-box, simple things are very easy, We have components: Scrollrect, VerticalLayoutGroup, LayoutElement, and Buttons. These look like they have what I need to complete this task.
I ran in to a few problems though. It was one thing to assume I would be able to use certain out-of-the-box components to achieve what I wanted, but in practice, I realized some things.
- Buttons provide mouse-over behaviour, something that was not apparent in the reference. I would either have to write my own InputModule for the EventSystem (actually I found there were a few on-line that people had already shared) which did not sound good to me, because I’m not aware what system this GUI will be integrated to once I’m done building it. I don’t want to create custom GUI InputModules that could clash with something, wherever this UI ends up. So I decided to simply do away with buttons, and rather have panels or images that I manipulate based off of a currentSelectionIndex. I am of the belief this could have been an early mistake, but to my knowledge, I didn’t have another solution and I was wasting time trying to research and find one.
- VerticalLayoutGroup was awesome and was side-stepping an annoying process I was glad not to have to do, however as I had decided for now that I would scroll things over time by moving from a current position to another position, and eventually from the top of menu down to the bottom of menu, it made sense to me that all my YPositional movement would be applied per menu item child. However when it came to needing to get my current YPosition, it seems VerticalLayout group overrides the RectTransform properties and whenever I wanted to get YPosition of my child menu items, it would return zero, even though the inspector was telling me they had an actual yposition under rectTransform. I realized I need to either do-away with verticalLayoutGroup and create my own little system for arranging everything in a vertical grid, or manipulate the YPosition of parent panel. I went with the latter. Looking at the first option, It just sounded messy to me at the time, especially as a big advantage of the verticalLayoutGroup was the instant visual feedback for designers. I’m also not very familiar with Editor scripts, if I was, I’d have been less hesitant to work out my own GridLayout that updates inside the editor. I didn’t regret my choice here.
- ScrollRect was able to achieve the scrolling that I wanted, and there were resources on-line for getting it to snap to positions, however it didn’t loop, and did not take keyboard / button input, the documentation wasn’t being much help with this, so I pondered on ignoring ScrollRect and doing this on my own.
- LayoutElement was a wonderful partner to VerticalLayoutGroup and I realized with an epiphany that I would actually be able to use the Preferred / Min height of this component, added to the Spacing property of VerticalLayoutGroup in order to get the value I need to scroll my menu.
By this point I was beginning to worry that I was missing a big picture of how to properly use the latest GUI stuff in Unity to achieve my task, there are probably components and editor / animation methods I’m not thinking about. I decided to prototype something with some quick / dirty code and then see what I can do to bring it back into the realm of Unity uGUI.
So, by using some simple maths I had achieved basically what I wanted, except for the looping around part, and I had a feeling that adding rotation would be a simple matter of recycling the ZPosition tweening logic. I began this with the initial goal of bringing my psuedo code logic close to uGUI methods, but this was feeling a lot more comfortable. uGUI elements work well with state machines and I identified animation as an option for achieving alpha, zPos and rotation without touching script. But as I wanted to create an adjustable system for the GUI that could easily be edited in the inspector, hard animation was just going to make things trickier for me at some point, and if I was to tween throughout animation based on certain percentage values, I feared I would actually just be finding an equally long workaround for it in my script, as if I was to simply do it via maths in the first place. I’m a fan of finding algorithms to do things in place of animation because once the logic is there, it can usually work for multiple scenarios whereas hard-animation is usually quite limiting. In any case, I took a step back and looked at what I had.
I had a single class that was iterating over a bunch of GUI items, I’m fine with that, but I was having a hard time working out what values would apply to each one, so it was time to make a separate class that I would then apply to each menu Item, and from my main class, I would get and set parameters pertaining to each individual menu item in my List.
Things instantly got easier as they should have, and I quickly used the new set-up to get back to where I was prior to the introduction of this second class. I still didn’t have an idea for how to do the “looping” or wrap around. I was trying to do it by manipulating the children to be above and below the menu once certain conditions were met. This sucked and did nothing for maintaining my List. So my option seemed to be: jump through some hoops, probably unnecessarily, and try to provide a new index for menu items that have made their transition above and below the menu. Nope, I hated the thought of even taking one step down this path.
I realized that updating the hierarchy in unity editor under a VerticalLayoutGroup would automatically rearrange all my elements visually, So actually I made this into the basis for the path I wanted to take. The result right now was teleporting menu items, or at least it looked that way as I shuffled my menu siblings. I needed to compensate for it somehow. certainly I couldn’t do this on the children themselves as VerticalLayoutGroup overriding the RectTransform was something I didn’t feel comfortable messing with, it made more sense to me once again to only manipulate the parent panel’s Yposition by teleporting it by my already-calculated “scrollAmount” (defined by menuItem height + verticalLayout spacing). Indeed, this worked. I now had one last thing to implement: “rotation”.
I thought for sure it would be a simple matter of taking my ZPos offset function and recycling it without the absolute value maintaining that menu items higher or lower than the currentSelectionIndex would be able to rotate in positive and negative values instead of keeping the same value. I was partly right, but there was this issue where invisible menu items, upon returning into the visibleIndex range would rotate from the wrong direction once their order in the hierarchy flipped from first to last or vise-versa. The problem was: at one point maybe somewhere near the end of the list i had a value of say -180, and upon switching from top-to-bottom or vise-versa, it would realize it needs not -180 rotation, but a positive value. As I was using MoveTowards, it was not taking the shortest route, it was instead trying to go all the way from a positive value, over to it’s negative counterpart, or vise-versa. I basically needed to observe what happens to it when it goes out of index range and receives an opposite place in the hierarchy and make sure I had a specific angle of rotation in a particular case, depending on if my menu was scrolling up, or scrolling down. I got pretty confused here and it was one point where I really needed to ask a friend of mine why my way of handling it was not working. We worked through the issue for over two hours, partly hindered by mysterious “Array Out of Index” errors that arose when we attempted to fix this simple rotation issue, but we got through, it turned out to be a simple issue, of course.
A major obstacle throughout the duration of the exercise was time, for certain. As I was developing this outside of work hours over an extended period of time, I frequently would have to come back to code which I hadn’t finished. It’s nothing new to me and I usually leave little debug logs to remind myself of what I was working on or any clues I had uncovered for a difficult problem.
Now I just needed to work out if I was going to do any transitions or sub-menus, I figured more to the point, rather than actually putting time to a fully functional sub menu, my limited time may be better spent simply demonstrating that each menu item is used as it should be, and actually provides an index that we can use correctly, and allow us to open whatever sub-menu we choose with some simple set-up in the inspector. I used animation to transition between the Main Menu and Sub Menus, as this was not risky business, unlike when I considered animation for the overall tweening effect of my main menu. Something to note is that the sub-menus in this particular Unity Project are on a ScreenSpaceCamera Canvas, while the holoWatch menu itself is on a WorldSpace canvas, this results from an observation of the reference. They are placeholder sub menus, merely there to insinuate that you can load whatever menu you please as long as it has my Menu component on it, and it helps if it is animated.
While actually connecting this main menu up to work with the sub menus was very easy, it’s worth mentioning just how easy it is to add the sub menus.
This method is common from what I could tell with a small bit of research, but as long as a menu has 2 animations: a default hidden state, and an entry state, they can easily be set-up in the animator editor to transition based on the value of a simple isOpen bool. You create the panels with these states and animations set-up, and now you simply plug it in to the MenuItem component’s connectedMenu property.
I suppose that is the full story. I definitely learned a couple things from it.
Thank you for your time in reading 🙂