Self-Built UMG Navigation (Algorithmic blueprinting)

Okay, so long story short, I’m trying to develop my game, and it requires individual umg navigation in a splitscreen environment. The user focus wasn’t really working (even though it was only being called by either the controller with a variable of the UMG currently active, or by the UMG calling “get owning player”, already knowing the controller for it), so I decided to make my own system. Currently, it’s simple because this is being used on the lobby controller for the “Start Screen/Menu”, but it’ll get a tiny more complex when I integrate it into the controller for the game character, though I have an idea in mind. So the idea is this:

Make a Blueprint Interface with all the possible commands you would need. Not only for the controller buttons, but also for dynamic functions you would need, such as button highlights, and highlights for boxes, drop-down menus, button selections, etc.

The buttons are going to require to be able to be “artificially” pressed. So when we press the key, we send a message interface to the default button of the active menu. Active menu has the variable saved, as does the controller. If you press I to open the inventory, just set the focus to the inventory’s default menu button (usually slot 0, unless you want to save it in the widget the last one that was focused on, will save from having to scroll up if you were scrolled down before). So whenever you press any button, it sends it to the button. In cases where it’s a slider UMG, you want to be able to accept left and right scrolls, as well as those menu options (which I made for myself) based on an array which kind of work similar to the drop-down, except it scrolls left and right. Example:
Race: << Human >>
So you have the Category, left arrow (mouse friendly), array item, right arrow.
Anything the “button” uses, you define it, and anything it doesn’t, you send through the interface to its parent (which is saved to an exposed, editable variable). You also need the “On Click” event to call a custom event, which calls to an event dispatcher. The “confirm” effect function in the interface calls that same event, so it’s working with both the mouseclick, and gamepad’s face bottom button. The parent gives it all the data it needs (in my case, the name of the text, hover/selected effect are invisible by default), and assigns it’s event dispatcher to the effect it will have defined. This is the replacement of having the button’s click defined in the parent where it would normally go. Boom.

How I get it to work, now, is by calling a function from the interface of the parent to properly direct the navigation input. I made a structure where I individually assign the widget for up, down, left, right, next, and previous at the construction of the parent widget. I thought about artificially making a structure with a widget array as the only variable to be the “rows”, and then make an array of that structure to the be “columns”. So if I pressed up, it would reduce the index of StructArray by 1, if it becomes below 0, the index would be set to the last index of the array. Vice versa with pressing down, and increasing the index by 1, setting it to 0 if the index is greater than the last index. The sub-index would work the same, except increase with a “right” command and decrease with the “left” command. It would also require me so set the sub-index of the Widget array inside the structure to be the last index of the widget array should the current index of the widget array inside the structure be smaller than the current sub-index. BUT that concerned me in terms of greater potential for error. So upon construction of the parent arrays, apart from defining the required variables manually, would also require for me to construct the structures for navigation. So I decided to make an algorithm for a function that does the checking of input received, and what it does there based the navigation input passed on to the parent from the “button” via the blueprint function interface. Into the function I take in the stored navigation structure of the current widget, as well as the array containing all of the navigation structures inside the parent blueprint, and an enumeration of the navigation direction. I didn’t make the enumeration variable for that since there was one already made. Anyways, here is that last function.

My reason for all of this?

  1. Partially I wanted to share it to figure out if the Array[Structure[Widget Array]] was a bad idea or if it seemed like I had it under control based on how I mentioned above. Would it be better to define the double array in a display of “MakeArray” put into “MakeArray”'s, or keep it as I displayed?
  2. Kind of to show the chaos that’s in my head because I feel that most people probably would have manually defined the last function. Admittedly, I’m quite proud of it. But my intent is not to show off even slightly, as I intend to show people an algorithmic way of setting up functions. Finding patterns and building upon them as a result. They are all over the place, and custom structures as well as the custom enumerations are there to help you.

The only problem I see, currently, I will need set up a double IsValid function with a bool return in the final picture, replacing the exec function, and I’ll essentially be checking both the validity of the return value of the first “select” in the far left, as well as the validity of the “Focused Widget” since it will need to pass both just to be secure in terms of errors.

This way if a side direction is sent, if there isn’t a side-widget involved, it will not follow up. After the function I want to check if the output return widget is valid. If it isn’t, then nothing is done. If it is, I replace the “focused widget” variable and that’d be it.

When Parent UMG is constructed, define all the “buttons”, bind the artificial “button click” event dispatchers, and set up the navigation arrays. When a key is pressed, it fires the corresponding interface message to the focused “button”. If it does something to the button, it reacts accordingly, if not, it sends the command to its parent who handles the navigation. Essentially, with this method, even if I “confirm” a button press, I could still add a check for the navigation at the end of its particular function for it to move “next”.

Thats why I would love to get some more details about Slate and do it all in code :smiley:

I’m going to end up doing it the way I theorized above with the structured array of widget arrays. Each index of the “structure” will be a group. Most likely I’ll use it so that the individual structure will count as a column item, and the array within it as the row. I don’t see the flaw with left/right being -/+ 1 to the sub-index respectively, and the up/down being the -/+ 1 to the structure index, clamping the range of the sub-index between 0 and last index of the sub-index on change of the index value. I think I thought of all the necessary safeguards, and it’s a lot less work. The method displayed above with the long function is a great way to do it, albeit it much more labor intensive, but it allows for less error. I will probably just have to define the use of the structure_array[widget_array[Widget, subIndex], index] for each parent widget individually.

For example, for my inventory system I can simply use the length of each row and index number of focused item in this way:
row length = (W)
left/right: -/+ 1 to sub-index (X)
up/down: -/+ 1 to index (Y)
master index (Z) = place in the inventory array, as dictated by formula (Y*W)+X

For my personal preferences, when X is changed, we make sure the change is >= 0, and < W. If it falls below 0, we set X to W-1. If X => W, we set X to 0. Whenever either X OR Y change, we recalculate Z, and clamp the value of Z between 0 and inventory_last_index. If it’s true, nothing else will be done. If it is false, then we find the limit for X by doing this:

A = W*Y;
X = inventory_last_index-A;

Let’s fill this all in.

inventory length = 46
inventory_last_index = 45;
Z = 41;
W = 6;
Y = 6;
X = 5;
Z = (WY)+X; //(66)+5 = 41
//Down is pressed
Z = (WY)+X; //(67)+5 = 47
if(Z > inventory_last_index) //or if you prefer if(Z >= inventory.length())
A = (WY); // 67=42;
X = inventory_last_index - A; // 45-42=3; Since X was changed, we recalculate Z
Z = (WY)+X; //(67)+3 = 45, the last index