I am trying to create a character stats menu that uses keyboard/controller input to navigate between options instead of the mouse. I have not found any tutorials for this kind of UI navigation.

In this way, pressing "Up"/"Down" will highlight the closest button above/below the current selection, and pressing "Left"/"Right" will highlight the closest button to the left/right of the current selection.

This is how console menus, especially for popular JRPG's, worked. Two example images of what I am talking about:

In this top-level menu, only the 8 options in the upper right are selectable, although not all options are necessarily present when the game begins. The finger-pointer icon is used to highlight a button:

This is the Equip menu. Equipable items are pushed into this menu as they are collected and can be scrolled through with the cursor. If the user presses "left", the cursor will jump to whichever character's valid equipment slot is closest:

Could anyone explain (briefly or otherwise) how to make a menu like this?

WIP: The hand cursor does not move yet. It is supposed to snap to highlighted buttons.

Use Input.GetKeyDown(KeyCode.DownArrow) and Input.GetKeyDown(KeyCode.UpArrow) to be able to tell when the user is going up and down the list. Then have an int to keep track of which option the user is on. To visually show the user what option they are on you can do something such as changing the color of the text.

This script will allow you to use the up and down arrow on some text options. It will highlight the currently selected option by changing it's color to white. I left out the gamepad support for going up and down, however, if that is something you need then this should help you out: http://wiki.unity3d.com/index.php?title=Xbox360Controller

using UnityEngine;
using UnityEngine.UI;

public class menu : MonoBehaviour {

public Text option1;
public Text option2;
public Text option3;

private int numberOfOptions = 3;

private int selectedOption;

// Use this for initialization
void Start () {
selectedOption = 1;
option1.color = new Color32(255, 255, 255, 255);
option2.color = new Color32(0, 0, 0, 255);
option3.color = new Color32(0, 0, 0, 255);
}

// Update is called once per frame
void Update () {
if (Input.GetKeyDown(KeyCode.DownArrow) /*|| Controller input*/)
{ //Input telling it to go up or down.
selectedOption += 1;
{
selectedOption = 1;
}

option1.color = new Color32(0, 0, 0, 255); //Make sure all others will be black (or do any visual you want to use to indicate this)
option2.color = new Color32(0, 0, 0, 255);
option3.color = new Color32(0, 0, 0, 255);

switch (selectedOption) //Set the visual indicator for which option you are on.
{
case 1:
option1.color = new Color32(255, 255, 255, 255);
break;
case 2:
option2.color = new Color32(255, 255, 255, 255);
break;
case 3:
option3.color = new Color32(255, 255, 255, 255);
break;
}
}

if (Input.GetKeyDown(KeyCode.UpArrow) /*|| Controller input*/)
{ //Input telling it to go up or down.
selectedOption -= 1;
{
selectedOption = numberOfOptions;
}

option1.color = new Color32(0, 0, 0, 255); //Make sure all others will be black (or do any visual you want to use to indicate this)
option2.color = new Color32(0, 0, 0, 255);
option3.color = new Color32(0, 0, 0, 255);

switch (selectedOption) //Set the visual indicator for which option you are on.
{
case 1:
option1.color = new Color32(255, 255, 255, 255);
break;
case 2:
option2.color = new Color32(255, 255, 255, 255);
break;
case 3:
option3.color = new Color32(255, 255, 255, 255);
break;
}
}

if (Input.GetKeyDown(KeyCode.Return) ||  Input.GetKeyDown("joystick button 0")){
Debug.Log("Picked: " + selectedOption); //For testing as the switch statment does nothing right now.

switch (selectedOption) //Set the visual indicator for which option you are on.
{
case 1:
/*Do option one*/
break;
case 2:
/*Do option two*/
break;
case 3:
/*Do option two*/
break;
}
}
}
}


Be sure to attach your menu options to the script like so:

This script is not perfect and there is a lot you can do to optimize and clean it up but this should give you the idea of how this is done and will work fine.

With Pointer

Add public Image pointer; and link the pointer in the scene. You can update the position of this using pointer.transform.position = new Vector3(/*PUT X VALUE OF POINTER LOCATION HERE */, option1.transform.position.y); and replace the /*PUT X VALUE OF POINTER LOCATION HERE */ comment with the x position (as they are all lined with the same x).

If you need the x value to change (such as for the quit option) you can manually put it in for the option in the switch statement or you can replace where the comment is hard coded to get the start of the text/button.

Here is how it would look implemented into the above code.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class menu : MonoBehaviour {

public Image pointer;

public Text option1;
public Text option2;
public Text option3;

private int numberOfOptions = 3;

private int selectedOption;

// Use this for initialization
void Start () {
selectedOption = 1;
option1.color = new Color32(255, 255, 255, 255);
option2.color = new Color32(0, 0, 0, 255);
option3.color = new Color32(0, 0, 0, 255);

pointer.transform.position = new Vector3(/*PUT X VALUE OF POINTER LOCATION HERE */, option1.transform.position.y);
}

// Update is called once per frame
void Update () {
if (Input.GetKeyDown(KeyCode.DownArrow) /*|| Controller input*/)
{ //Input telling it to go up or down.
selectedOption += 1;
{
selectedOption = 1;
}

option1.color = new Color32(0, 0, 0, 255); //Make sure all others will be black (or do any visual you want to use to indicate this)
option2.color = new Color32(0, 0, 0, 255);
option3.color = new Color32(0, 0, 0, 255);

switch (selectedOption) //Set the visual indicator for which option you are on.
{
case 1:
option1.color = new Color32(255, 255, 255, 255);
pointer.transform.position = new Vector3(/*PUT X VALUE OF POINTER LOCATION HERE */, option1.transform.position.y);
break;
case 2:
option2.color = new Color32(255, 255, 255, 255);
pointer.transform.position = new Vector3(/*PUT X VALUE OF POINTER LOCATION HERE */, option2.transform.position.y);
break;
case 3:
option3.color = new Color32(255, 255, 255, 255);
pointer.transform.position = new Vector3(/*PUT X VALUE OF POINTER LOCATION HERE */, option3.transform.position.y);
break;
}
}

if (Input.GetKeyDown(KeyCode.UpArrow) /*|| Controller input*/)
{ //Input telling it to go up or down.
selectedOption -= 1;
{
selectedOption = numberOfOptions;
}

option1.color = new Color32(0, 0, 0, 255); //Make sure all others will be black (or do any visual you want to use to indicate this)
option2.color = new Color32(0, 0, 0, 255);
option3.color = new Color32(0, 0, 0, 255);

switch (selectedOption) //Set the visual indicator for which option you are on.
{
case 1:
option1.color = new Color32(255, 255, 255, 255);
pointer.transform.position = new Vector3(/*PUT X VALUE OF POINTER LOCATION HERE */, option1.transform.position.y);
break;
case 2:
option2.color = new Color32(255, 255, 255, 255);
pointer.transform.position = new Vector3(/*PUT X VALUE OF POINTER LOCATION HERE */, option2.transform.position.y);
break;
case 3:
option3.color = new Color32(255, 255, 255, 255);
pointer.transform.position = new Vector3(/*PUT X VALUE OF POINTER LOCATION HERE */, option3.transform.position.y);
break;
}
}

if (Input.GetKeyDown(KeyCode.Return) ||  Input.GetKeyDown("joystick button 0")){
Debug.Log("Picked: " + selectedOption); //For testing as the switch statment does nothing right now.

switch (selectedOption) //Set the visual indicator for which option you are on.
{
case 1:
/*Do option one*/
break;
case 2:
/*Do option two*/
break;
case 3:
/*Do option two*/
break;
}
}
}
}

• Thanks! this is very easy to follow. Do you think you could help me program a hand cursor to snap to the highlighted buttons? I already made the sprite and have a mockup menu I made earlier. All text/buttons are on the same "row" but have variable offset, as the original game seems to have. Could I share it with you? I have a link. – hatinacat2000 Jun 15 at 19:51
• So are you saying they are all at the same x but different y values? If so you can just have what I have above but where I do color changes instead change the y value of the Image to the y value of the text which you can get from option1.transform.position.y – DragonHeart000 Jun 15 at 22:53
• Excuse me, I misspoke: All lines on which buttons/text sit are exactly 32 pixels apart (like a text document), but spacing and length of these buttons/text varies. I want to make sure my pointer icon "points" to the highlighted button. I have a project file with just the UI in it I can show you. – hatinacat2000 Jun 15 at 22:56
• Are the options still flush with each other? If so you can just change the position by 32 every time. It would be easier if you had a picture of your game so I could see where the pointer goes. – DragonHeart000 Jun 15 at 23:05
• I updated the answer to show you some of how you would have a pointer update position when going through the options. – DragonHeart000 Jun 16 at 0:48