Hold an Object on Click Until Clicked Again Unity
A few months back, I played effectually with (nevertheless over again) rebuilding a half-finished Metroidvania-style game I've played around with off and on over the years.
One of my goals in this experiment was to use base Unity functionality as much as possible, replacing 2d Toolkit and custom systems equally much as possible. One of the prime candidates for such a rewrite was the menu system.
If it's not immediately obvious from the screenshot to the right, that's substantially a vertical carte du jour with items that accept horizontal behaviors. When "Equipment" is selected, y'all tin cull an detail to use or equip on the horizontal centrality. When a volume slider is selected, you tin can adjust the value with the horizontal axis.
Importantly, the game's intended to exist played with a gamepad, so I didn't want the presence or absenteeism of mouse or touch input to affect this beliefs.
Unity UI navigation is pretty smart, and got me virtually of the way there. If you lot've focused on mouse/bear on interface when building your UI, good news: if it's a filigree-ish format, it probably works the way you'd look. That's thank you to…
The Navigation Property
Selectable objects similar buttons have a "Navigation" holding. By default, this is set to Automated, which again, does the right affair in most cases.
Unity uses each object's position to decide which object will be selected when navigating up, down, left, or right. This is basically how controller navigation works: you lot're building a large chain of objects, which can connect to up to 4 other objects. Helpfully, there'south a "Visualize" button which overlays yellow arrows indicating these connectors.
Aside from Automatic, the other Navigation options are:
- None: Turns off navigation entirely.
- Vertical and Horizontal: Works similar Automatic, but limits navigation to horizontal or vertical connections.
- Explicit: As shown higher up, allows you to explicitly set the objects that are selected on up, downward, left, and right navigation.
Explicit requires a bit of micromanagement, so it's best to use it only if one of the other options doesn't give you what you want.
Information technology'due south likewise (as we'll encounter) something you can manipulate in scripts, which is helpful for generating menus on-the-fly or swapping behaviors. The Selectable class (the basis for several Unity UI components, and something you can inherit from for your ain components) defines SetNavigationType, SetUp, SetDown, SetLeft, and SetRight methods for changing navigation backdrop.
Selectable Interfaces
Another bit of Unity UI magic I took advantage of was the collection of selectable interfaces, specifically ISelectHandler, IDeselectHandler, and ICancelHandler.
Each of these interfaces has i method. If your MonoBehaviour implements one of these interfaces, Unity will pass certain UI-related events to it:
- ISelectHandler fires OnSelect if a Selectable object attached to the aforementioned game object receives focus.
- IDeselectHandler fires OnDeselect if a Selectable object attached to the same game object loses focus.
- ICancelHandler fires OnCancel if a Selectable object fastened to the same game object is selected, and the user presses the "Cancel" push.
All of these methods take a single "consequence data" parameter, which may be a different subclass depending on where it's used.
Depending on what you're doing, you may need to call its Employ() method to get correct beliefs. This signals to Unity that you're handling the consequence in your code, rather than relying on Unity's default behavior.
Rewiring Navigation Properties
Our main stupid UI trick is rewiring navigation on the fly to build "submenus."
My Equipment submenu is implemented every bit a Submenu "container" form which contains multiple SubmenuItem objects. The Submenu itself isn't Selectable; it'south simply a manager for the items which are defined in the component.
The Submenu also knows a few other things, such equally which items are "previous" and "side by side" for the submenu, and what the currently selected detail is. For example, my submenu above knows that it's the first item in the vertical listing and is followed by the Sound slider. It works a footling like the onetime PlayStation system menu, except rotated π/2 radians.
(I should mention that my equipment submenu is an piece of cake instance. Since information technology'southward a Metroidvania, I know all of the objects a player might ever have, and I've difficult-coded them into the menu game objects.)
The lawmaking's a little complex (partially considering I tried to abstract it plenty to exist reusable), so if you want to follow along, it'due south at https://bitbucket.org/snippets/dylanwolf/5ex9Ea.
When the Submenu loads, I prep all of the submenu items by setting their navigation blazon to Explicit (using the SetNavigationType method of Selectable). Then, I iterate through each submenu items every bit follows:
- I fix the explicit down navigation properties on the subitem to signal to the Sound slider (using the SetDown method of Selectable). This means that no matter which item is selected, pressing down will select the Audio slider. (Information technology'southward too a good example of how Unity navigation isn't necessarily ane-to-one.)
- I set the explicit left navigation holding (SetLeft method) on the subitem to point to the previous item in the listing (if at that place was i), and modify the explicit right navigation property (SetRight method) on that detail to signal back.
- Once this initialization runs, I tin can navigate within the submenu as well as out of the submenu, merely I tin can't navigate into the submenu.
To do this, I implement ISelectHandler and IDeselectHandler on the submenu item. On pick, a submenu detail does a couple of things:
- Updates the SelectedItem proprety of the Submenu object
- Updates the navigation of the "Previous" and "Next" objects to signal to the selected detail (in this case, calling the SetUp property of the Sound slider to navigate to the selected equipment icon).
On deselection, the submenu item changes the push button image to a "grayed out" inactive image. This way, the player tin run into which item is selected, simply knows the focus is elsewhere in the menu.
Redirecting navigation
I didn't actually demand to write any code to become the volume sliders to work right with controller navigation. Setting only the slider'due south up and down navigation properties leaves left and right available to adjust the value when information technology's selected.
However, I don't desire the player to have to click the slider to select information technology; I'd like to allow the player to click anywhere in the "Audio" or "Music" block.
To exercise this, I created a simple redirector course. Information technology's a Selectable that uses the None navigation blazon, so it tin can't be selected with a controller. All the same, if the player clicks it, OnPointerDown is fired, which sends the focus to the slider:
public form SliderContainer : Selectable { Slider slider; protected override void Start() { slider = GetComponentInChildren<Slider>(); this.ClearAllNavigation(); this.transition = Transition.None; base.Start(); } public override void OnPointerDown(PointerEventData eventData) { slider.Select(); } }
I apply this to the parent UI element that contains both the "Sound" or "Music" characterization, the pick frame, andone slider. When anything in that area is clicked, it passes the selection down to the slider.
Preventing Deselection
This is another trouble that wouldn't happen with a controller, only becomes an issue if a mouse or touchscreen is involved.
Unity's default beliefs is to clear selection when the player clicks outside of a Selectable object. Usually this is expected (it'south not fundamentally different from how most Windows applications conduct), merely in a controller-only game, recovering from a stray click could feel jarring.
Information technology'southward not hard to forestall this by adding the post-obit component, which remembers the last selected object and forces the Event system to reset it if information technology ever becomes null.
public class PreventDeselectionGroup : MonoBehaviour { EventSystem evt; private void Start() { evt = EventSystem.current; } GameObject sel; private void Update() { if (evt.currentSelectedGameObject != nil && evt.currentSelectedGameObject != sel) sel = evt.currentSelectedGameObject; else if (sel != null && evt.currentSelectedGameObject == null) evt.SetSelectedGameObject(sel); } }
Considering I'1000 deactivating the entire "in-game bill of fare" object tree when the menu closes and gameplay resumes, I don't accept to add whatever actress logic. When the menu is airtight, the script isn't running; when it is, no other Selectable UI elements should exist active.
Closing the Menu on Cancel
While the code to manage menu navigation and activation uses built-in Unity UI functionality, showing and hiding them menu relies on custom code. The game engine knows to cease play and prove the card when game land changes to "in game bill of fare" fashion; information technology also knows to hibernate the carte du jour and resume play when game country changes to "playing."
I can create a component that uses ICancelHandler to observe when the user presses the button designated every bit "Cancel" (Esc on the keyboard, or Start on a gamepad):
public form InGameMenuClose : MonoBehaviour, ICancelHandler { public void OnCancel(BaseEventData eventData) { Debug.Log("CANCEL"); GameEngine.Current.ChangeState(GameEngine.GameState.Playing); eventData.Employ(); } }
This tells Unity to call my game engine code and change the electric current mode; it also tells Unity to terminate whatsoever additional processing information technology might practise in response to a "cancel" bulletin.
Unfortunately, I don't think there's a global cancel handler; OnCancel is just called on the selected object when Esc is pressed. To work as expected, every object in my carte with a Selectable component has to implement this handler.
There's a number of means we could solve this, merely I chose to simply make my "InGameMenu" component add the close component to every child chemical element on initialization. (Over again, my menu'due south hard-coded, so no new menu objects volition be created after initialization.)
void InitMenu() { if (!hasInit) { InventoryIcons = GetComponentsInChildren<InventoryIcon>(); EquipmentItems = EquipmentMenu.Items.OfType<EquipmentItem>().ToArray(); foreach (var obj in GetComponentsInChildren<Selectable>().Select(x => x.gameObject)) obj.AddComponent<InGameMenuClose>(); hasInit = true; } }
Source: https://www.gamedeveloper.com/design/stupid-unity-ui-navigation-tricks
0 Response to "Hold an Object on Click Until Clicked Again Unity"
Postar um comentário