Skip to content

Instantly share code, notes, and snippets.

@nothke
Created September 1, 2021 22:22
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nothke/b3020e4373c995711a96e0258e6bf990 to your computer and use it in GitHub Desktop.
Save nothke/b3020e4373c995711a96e0258e6bf990 to your computer and use it in GitHub Desktop.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Nothke.SimpleMenu;
using TMPro;
public class ExampleMenu : Menu
{
int intTest = 10;
protected override void Create()
{
// A simple option with confirm
root.Add(new Option("Confirm Example")
{
OnConfirmed = () => Debug.Log("Confirmed!")
});
// An option that changes when left or right is pressed
root.Add(new Option() // We don't need a label since we'll override it in OnRevealed
{
OnValueChanged = (o, i) => intTest += i,
OnRevealed = (o) => o.label = "Int: " + intTest,
});
// Adds a submenu with 2 options
root.Add(new Option("Submenu")
{
options = new List<Option>()
{
new Option("Suboption 1") { OnConfirmed = () => Debug.Log("Foo!") },
new Option("Suboption 2") { OnConfirmed = () => Debug.Log("Bar!") },
},
});
root.Add(new Option("Quit")
{
OnConfirmed = () => Application.Quit()
});
}
protected override void PlaceTextOnCreation(TMP_Text text, int index)
{
text.rectTransform.anchoredPosition = Vector2.down * index * 50;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
using TMPro;
namespace Nothke.SimpleMenu
{
public enum OptionType { Confirm, Submenu, Suboptions };
public class Option
{
public string label;
public List<Option> options;
public Option parent;
public int index;
public Action OnConfirmed;
public Action OnSelected;
public Action OnDeselected;
public Action<Option> OnRevealed;
public Action<Option, int> OnValueChanged;
public int Count => options == null ? 0 : options.Count;
public bool IsSubmenu => options != null;
public Option() { }
public Option(string title)
{
this.label = title;
}
public void Add(Option option)
{
if (options == null)
options = new List<Option>();
option.parent = this;
options.Add(option);
}
public void SelectChild(int i)
{
index = i % Count;
if (index < 0)
index = Count - 1;
options[index].Select();
}
public void Deselect()
{
if (OnDeselected != null)
OnDeselected();
}
public void ChangeValue(int by)
{
if (OnValueChanged != null)
OnValueChanged(this, by);
}
public void Select()
{
if (OnDeselected != null)
OnDeselected();
}
public void Reveal()
{
if (OnRevealed != null)
OnRevealed(this);
}
public void ConfirmChild()
{
if (options[index].OnConfirmed != null)
options[index].OnConfirmed();
}
}
public abstract class Menu : MonoBehaviour
{
public RectTransform rootTransform;
public TMP_Text textPrefab;
public Option root;
Option current;
[NonSerialized] public List<TMP_Text> texts;
private void Start()
{
root = new Option("") { options = new List<Option>() };
current = root;
Create();
GenerateUI();
Repaint();
}
protected virtual void GenerateUI()
{
texts = new List<TMP_Text>();
for (int i = 0; i < 10; i++)
{
var tGO = Instantiate(textPrefab.gameObject);
tGO.transform.SetParent(rootTransform);
var t = tGO.GetComponent<TMP_Text>();
PlaceTextOnCreation(t, i);
texts.Add(t);
tGO.SetActive(false);
}
}
protected virtual void PlaceTextOnCreation(TMP_Text text, int index)
{
var pos = text.rectTransform.anchoredPosition;
pos.y = -50 * index;
text.rectTransform.anchoredPosition = pos;
}
protected abstract void Create();
public void ReturnToRoot()
{
current = root;
Repaint();
}
public void Show()
{
rootTransform.gameObject.SetActive(true);
}
public void Hide()
{
rootTransform.gameObject.SetActive(false);
}
public virtual void Repaint()
{
for (int i = texts.Count - 1; i >= 0; i--)
{
OnTextDeselected(texts[i]);
texts[i].gameObject.SetActive(false);
}
int len = current.Count;
for (int i = 0; i < len; i++)
{
texts[i].gameObject.SetActive(true);
current.options[i].Reveal();
texts[i].text = current.options[i].label;
if (i == current.index)
OnTextSelected(texts[i]);
}
}
private void Update()
{
GetInput(
out bool down, out bool up,
out bool left, out bool right,
out bool confirm, out bool cancel);
if (down)
current.SelectChild(current.index + 1);
else if (up)
current.SelectChild(current.index - 1);
var suboption = current.options[current.index];
if (left || right)
{
suboption.ChangeValue(right ? 1 : -1);
}
if (confirm)
{
current.ConfirmChild();
if (suboption.options != null)
current = suboption;
Repaint();
}
if (cancel)
{
if (current.parent != null)
current = current.parent;
}
if (down || up || left || right || confirm || cancel)
Repaint();
}
protected virtual void GetInput(
out bool down, out bool up,
out bool left, out bool right,
out bool confirm, out bool cancel)
{
left = Input.GetKeyDown(KeyCode.A);
right = Input.GetKeyDown(KeyCode.D);
down = Input.GetKeyDown(KeyCode.S);
up = Input.GetKeyDown(KeyCode.W);
confirm = Input.GetKeyDown(KeyCode.Space) || Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter);
cancel = Input.GetKeyDown(KeyCode.Escape) || Input.GetKeyDown(KeyCode.Backspace);
}
public virtual void OnTextSelected(TMP_Text text)
{
text.transform.localScale = Vector3.one * 1.2f;
text.color = Color.yellow;
}
public virtual void OnTextDeselected(TMP_Text text)
{
text.transform.localScale = Vector3.one;
text.color = Color.white;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment