Player menu, Log #12 (Unity, C#)
24.02.2018

    As in every RPG there need to be an inventory, attributes and other informations about character. And I need somehow to acces my inventory, usually, after hitting Tab or I keys, games show directly inventory or other kind of menu. I am going to have inventory and attributes windows separated. For this i want to have ring type mini menu, and each sector is going to be button. Left for inventory, top for attributes and right..well I haven't decide yet. The bottom will be empty.

    Basis of UI (User Interface) in Unity is Canvas, without Canvas you cant create UI elements. I add Canvas (UI>Canvas) to my scene and rename it to "UI". Now I created root object, resp. container for most of my user interface. For keeping objects in order, my player menu will be individual panel. I add new Panel (UI>Panel) and rename it to PlayerMenu. Main component of every UI elemnt is RectTransform. Two important elements of every RectTransform are Anchor and Pivot. Now I wont explain what these two do, there is manual on Unity website, and plenty of videos on YouTubte.

    So, my PlayerMenu is gonna be dynamically changing its position based on character (head) position, therefore I dont have to worry about anchors and set them all to 0. Last thing I need to change is alpha variable of my Image component of PlayerMenu panel to 0, so the panel is transparent. Base element of my menu is gonna be one third sector of my ring. I created this shape in graphical editor and saved as png into my assets (Assets>UI>UI Textures). For UI is neccessary to set png image in Texture Type as "Sprite (2D and UI)". Under my PlayerMenu panel I add new button (UI>Button), rename it to Inventory_Button and in component Image as Source Image I simply drag my png sprite. Then I change color to shade of yellow, because this panel will be only used as border (graphically), check Preserve Aspect and finally change size to 370x210. Instead of child object Text I add new image (UI>Image), rename it to Background and use same sprite only change padding (Top, Bottom, Left and Right in RectTransform) to 2px, change color into dark grey and check box RaycastTarget. Then I add one more image as icon. I set its size to 120x120 and drag into Source Image inventory icon I downloaded at game-icons.net. I set icon at the center of button, set position Y to 10px and I keep RectTransform stretched with 2px padding. Whole button I set on the left center of the panel. Then I create next buttons the same way. As last thing I need scale the panel down til it looks as desired. Here is what it looks like...

 

Player menu

 

    I need this menu to appear only when I hold Tab key. So I create new script called PlayerUIMenuManager a drag it on panel PlayerMenu. Because there is gonna be only one menu in game I am gonna use singelton pattern. I will use it mainly when accessing the static instance. Now I need position where I want menu to appear, so I create variable GameObject headPosition and in inspector I drag the object on which position I want menu to appear. This object will be new created EmptyObject renamend to HeadPivot and moved at position I need. Then I create method called Repos() which will move my menu at psition of my HeadPivot. Since the HeadPivot works with 3D coords but UI with 2D, I need convert world coordinates in screen coordinates. So I am gonna use method WorldToScreenPoint(Vector 3). There are two options how to set position of my menu. I can either set localPosition of RectTransform or anchoredPosition. The difference is that localPosition is relative position related to parental element, but anchoredPosition is pivot position against anchors (so I could say it is absolute position). I wasn't much satisfied with anchoredPosition behaving (due to some reason menu wasn't horizontally centered) so I used localPosition instead. Only I needed to substract the Y position of parental element. I move menu out of screen (canvas) everytime the Tab key is not held. In method MoveOut() I set localPosition of menu to negative of double value of Y parental element. I also want my menu to smoothly appear and disappear. For this I am using Unity  coroutines, so I create IEnumerator functions FadeIn() and FadeOut(). In function Repos() firstly I call FadeIn() by invoking StartCoroutine("FadeIn") then I set position of menu. For disappearing I call FadeOut() in method MoveOut(). Last thing is needed to menu smoothly fade in or out. It is variable alpha in component CanvasGroup. In script I change this alpha from 0 to 1 or opposite in steps via for. Whole script is below.

 

public class PlayerUIMenuManager : MonoBehaviour
{

    public static PlayerUIMenuManager Instance { get; private set; }
    public GameObject headPosition;

    // Awake for Singelton
    void Awake()
    {
        if (Instance == null)
            //if not, set instance to this
            Instance = this;
        //If instance already exists and it's not this:
        else if (Instance != this)
            //Then destroy this. This enforces our singleton pattern, meaning there can only ever be one instance of a GameManager.
            Destroy(gameObject);
    }

    public void Repos()
    {
        StartCoroutine("FadeIn");
        Vector3 pos = Camera.main.WorldToScreenPoint(headPosition.transform.position);
        GetComponent<RectTransform>().localPosition = new Vector3(0, pos.y - transform.parent.GetComponent<RectTransform>().localPosition.y, 0);
    }

    IEnumerator FadeIn()
    {
        for (float i = 0; i <= 1.2; i += 0.1f)
        {
            GetComponent<CanvasGroup>().alpha = i;
            yield return new WaitForSecondsRealtime(0.01f);
        }
    }

    IEnumerator FadeOut()
    {
        for (float i = 1; i >= -0.08; i -= 0.1f)
        {
            GetComponent<CanvasGroup>().alpha = i;
            yield return new WaitForSeconds(0.01f);
        }
        GetComponent<RectTransform>().localPosition = new Vector3(0, -transform.parent.GetComponent<RectTransform>().localPosition.y * 2, 0);
    }

    public void MoveOut()
    {
        StartCoroutine("FadeOut");
    }
}

 

    Now I need to my menu work with Tab key. I create new script PlayerUIMenu and drag it on my UI canvas. Inside this class I will keep only Update() method. Inside this method I create two conditions, one for holding the Tab where I call Repos() on singleton instance of PlayerUIMenuManager.Instance, second for releasing it where I call MoveOut(). I am not using usual access to input via Key.Tab, but new key defined in Edit>Project Settings>Input as "Player UI Menu", which I call in methods GetButton() and GetButtonUp().

 

public class PlayerUIMenu : MonoBehaviour
{
    void Update()
    {
        if (Input.GetButton("Player UI Menu"))
        {
            PlayerUIMenuManager.Instance.Repos();
        }
        if(Input.GetButtonUp("Player UI Menu"))
        {
            if (PlayerUIMenuManager.Instance != null)
            {
                PlayerUIMenuManager.Instance.MoveOut();
            }
        }
    }
}

 

    Menu is now fading in and out after holding and releasing Tab key. Last thing I want is, highlighting buttons when I move mouse pointer over them and showing its text. By highlighting I mean moving the button a little on side (or up for top button). This could be easily achieved by choosing "Animation" Transition in Button component. After choosing animation transition, button Auto Generate Animation appear at the bottom of component in inspector. So I click at this button and save my new generated Animation controller into my assets (UI>Animations). To create animation I turn on Animation window (Window>Animation), choose button I want to animate (in scene hierarchy) and then choose state - Highlighted in Animation window. Now I click on red circle (key frames recording) and set my button exactly how I want it to be as highlighted. So move it to the left by 7px (at 45 on X) and change icon color to shade of yellow, same as border. Turn of recording and return to Normal state, then set default values to the button. Do the same for other two buttons.

    For the text I add UI text (UI>Text) under PlayerMenu pane, rename it to PlayerMenu_Text and set size to 160x30. To change text for each button I have to add component EventTrigger to each button. To each component I add two Events, one for pointer enter, second for pointer exit. The text oject I drag on events and then set text I want to show there. The same I do for rest. Menu is done.