[Unity] UI Tool Kit - Custom Menu (2)

suhan0304·2024년 7월 9일
0

유니티 - UI Tool Kit

목록 보기
8/8
post-thumbnail

시작하기에 앞서 글씨체를 바꿔줬다.


Select Logic Update

Control_Button을 먼저 좀 정리해주자. 두 이벤트의 스타일 추가, 삭제를 모두 Toggle로 바꿔준 다음 델리게이트를 호출하도록 바꿔줬다. 이러면 다른 스크립트도 델리게이트에 메소드를 붙여서 실행할 수 있어진다.

Control_Button.cs

using System;
using System.Collections.Generic;
using UnityEngine.Rendering;
using UnityEngine.UIElements;


public class Control_Button : VisualElement
{
    internal new class UxmlFactory : UxmlFactory<Control_Button, UxmlTraits> { }

    internal new class UxmlTraits : VisualElement.UxmlTraits {
        private readonly UxmlIntAttributeDescription m_CardNum
            = new UxmlIntAttributeDescription { name = "card_Number", defaultValue = 4 };
        private readonly UxmlStringAttributeDescription m_ButtonLabel
            = new UxmlStringAttributeDescription { name = "button_Label", defaultValue = "Menu Button" };

        public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
        {
            get {yield break;}
        }

        public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
        {
            base.Init(ve, bag, cc);
            ((Control_Button)ve).CardNumber = m_CardNum.GetValueFromBag(bag, cc);
            ((Control_Button)ve).ButtonLabel = m_ButtonLabel.GetValueFromBag(bag, cc);
        }
    }

    public int CardNumber { get; set; }
    public string ButtonLabel {
        get { return _label.text; }
        set{ _label.text = value; }
    }


    private VisualElement _fill;
    private VisualElement _icon;
    private Label _label;

    public event Action<Control_Button> OnHover;
    
    public event Action<Control_Button, int> OnSelect;

    public Control_Button() 
    {
        // 버튼 클릭할 떄
        _fill = new VisualElement();
        Add(_fill);

        // 버튼 아이콘
        _icon = new VisualElement();
        Add(_icon);

        // 버튼 레이블
        _label = new Label();
        Add(_label);

        this.name = "Background";
        _fill.name = "Fill";
        _label.name = "Label";
        _icon.name = "Icon";

        // Preventing Clicks
        _fill.pickingMode = PickingMode.Ignore;
        _label.pickingMode = PickingMode.Ignore;
        _icon.pickingMode = PickingMode.Ignore;

        // Load and apply the stylesheet
        this.AddToClassList("background--normal");
        _fill.AddToClassList("fill--normal");
        _icon.AddToClassList("icon--normal");
        _label.AddToClassList("label--normal");

        // On Mouse Enter event
        this.RegisterCallback<MouseEnterEvent>(OnMouseEventControlButton);
        this.RegisterCallback<MouseLeaveEvent>(OnMouseEventControlButton);

        // On Mouse Click Event
        this.RegisterCallback<ClickEvent>(OnMouseEventControlButton);
    }   
    public void OnMouseEventControlButton(EventBase evt)
    {
        // Mouse Enter Control Button
        if (evt.eventTypeId == MouseEnterEvent.TypeId() || evt.eventTypeId == MouseLeaveEvent.TypeId()) {
            ToggleHoverStyle(this);
            OnHover?.Invoke(this);
        }

        // Mouse Click Control Button
        if (evt.eventTypeId == ClickEvent.TypeId()) {
            ToggleSelectStyle(this, CardNumber);
            OnSelect?.Invoke(this, CardNumber);
        }
    }

    // Hover Style Toggle Method
    public void ToggleHoverStyle(Control_Button m_button)
    {
        m_button.ToggleInClassList("background--hover");
        m_button._icon.ToggleInClassList("icon--hover");
        m_button._label.ToggleInClassList("label--hover");
    }

    // Select Style Toggle Method
    public void ToggleSelectStyle(Control_Button m_button, int m_cardNum) {
        m_button._fill.ToggleInClassList("fill--select");
        m_button._icon.ToggleInClassList("icon--select");
        m_button._label.ToggleInClassList("label--select");
    }
}

이제 하나를 클릭하면 다른 버튼의 Select가 풀리도록 구현해보자. Control_Button 내부에서 다른 버튼을 접근하는 것 보다는 관리자를 하나 새로 만들어서 관리하도록 하는게 좋을 것 같다.

Control_Button_Manager.cs

using System.Collections;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.UIElements;

public class Control_Button_Manager : MonoBehaviour
{
    [SerializeField] private UIDocument UI_Doc;
    [SerializeField] private List<Control_Button> controlButtons;

    [SerializeField] private Control_Button selectedButton;

    // Odin - Tap Define 
    [TabGroup("Tap","Debug",SdfIconType.CodeSlash, TextColor="red")]

    [TabGroup("Tap","Debug")]
    [Button("Init Control Button List")]
    void Init() {
        var root = UI_Doc.rootVisualElement;

        // Clear Control Button List
        if (controlButtons != null && controlButtons.Count > 0)
            controlButtons.Clear();

        // Load Control Button in UI_Doc
        controlButtons = new List<Control_Button>();
        var buttons = root.Query<Control_Button>().ToList();
        foreach (var button in buttons) {
            button.OnSelect += OnSelectControlButton;
            controlButtons.Add(button);
        }
        Debug.Log($"[Control Button Manager] {controlButtons.Count} Buttons Initialized");
    }

    void OnSelectControlButton(Control_Button currentButton, int cardNumber) {
        Debug.Log($"[Control Button Manager] {currentButton.GetLabelText()} Button Select");

        if (selectedButton == currentButton) {
            selectedButton = null;
            return;
        }
        
        if (selectedButton != null) 
            selectedButton.ToggleSelectStyle(selectedButton, cardNumber); // Untoggle SelectedButton
            
        selectedButton = currentButton; // change selected Button
    }

#if UNITY_EDITOR
    [TabGroup("Tap","Debug")]
    [Button("Check Selected Control Button")]
    void CheckSelectedControlButton() {
        if (selectedButton != null)
            Debug.Log($"[Control Button Manager] {selectedButton.GetLabelText()} Button Selected Now!");
        else 
            Debug.Log($"[Control Button Manager] No buttons are selected.");
    }
#endif
}

실행한 후에 Init Cutron Button List 버튼을 누르면 이제 로직이 잘 작동한다. (나중에 Start나 OnEnable로 수정해주면 된다.)

이제 SelectStyle이 잘 적용된다.


Card Update

이제 버튼을 클릭하면 선택 가능한 카드가 옆으로 나오는 부분을 구현해보자. 일단 먼저 UXML 만들어보자.

그 다음 Control Card 스크립트를 작성해보자.

Control_Card.cs

using System;
using System.Collections.Generic;
using UnityEngine.UIElements;

public class Control_Card : VisualElement
{
    internal new class UxmlFactory : UxmlFactory<Control_Card, UxmlTraits> { }

    internal new class UxmlTraits : VisualElement.UxmlTraits {
        private readonly UxmlStringAttributeDescription m_CardLabel 
            = new UxmlStringAttributeDescription { name = "card_Label", defaultValue = "" };
        private readonly UxmlStringAttributeDescription m_ButtonLabel
            = new UxmlStringAttributeDescription { name = "button_Label", defaultValue = "Menu Button" };

        public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription 
        {
            get { yield break;}
        }

        public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
        {
            base.Init(ve, bag, cc);
            ((Control_Card)ve).CardLabel = m_CardLabel.GetValueFromBag(bag, cc);
            ((Control_Card)ve).ButtonLabel = m_ButtonLabel.GetValueFromBag(bag, cc);
        }
    }
    public string CardLabel { 
        get { return _card_Label.text; }
        set{ _card_Label.text = value; }
    }
    public string ButtonLabel {
        get { return _button_label.text; }
        set{ _button_label.text = value; }
    }

    public int CardNumber;

    private VisualElement _card_BackGround;
    private VisualElement _card_Image;
    private Label _card_Label;
    
    private VisualElement _card_Button;
    private VisualElement _button_icon;
    private Label _button_label;

    public event Action<Control_Card> OnHover;
    public event Action<Control_Card, int> OnSelect;

    public Control_Card() 
    {
        // 카드 뒷배경
        _card_BackGround = new VisualElement { name = "Card_Background" };
        Add(_card_BackGround);

        // 카드 이미지
        _card_Image = new VisualElement { name = "Card_Image" };
        _card_BackGround.Add(_card_Image);

        // 카드 레이블
        _card_Label = new Label { name = "Card_Label" };
        _card_BackGround.Add(_card_Label);

        // 카드 버튼
        _card_Button = new VisualElement { name = "Card_Button" };
        Add(_card_Button);

        // 버튼 아이콘
        _button_icon = new VisualElement { name = "Button_Icon" };
        _card_Button.Add(_button_icon);

        // 버튼 레이블
        _button_label = new Label { name = "Button_Label" };
        _card_Button.Add(_button_label);

        // Preventing Clicks
        _card_BackGround.pickingMode = PickingMode.Ignore;
        _card_Image.pickingMode = PickingMode.Ignore;
        _card_Label.pickingMode = PickingMode.Ignore;
        _card_Button.pickingMode = PickingMode.Ignore;
        _button_icon.pickingMode = PickingMode.Ignore;
        _button_label.pickingMode = PickingMode.Ignore;

        // Load and apply the stylesheet
        this.AddToClassList("control__card--normal");
        _card_BackGround.AddToClassList("card__background--normal");
        _card_Image.AddToClassList("card__image--normal");
        _card_Label.AddToClassList("card__label--normal");
        _card_Button.AddToClassList("card__button--normal");
        _button_icon.AddToClassList("button__icon--normal");
        _button_label.AddToClassList("button__label--normal");


        // On Mouse Enter event
        this.RegisterCallback<MouseEnterEvent>(OnMouseEventControlButton);
        this.RegisterCallback<MouseLeaveEvent>(OnMouseEventControlButton);

        // On Mouse Click Event
        this.RegisterCallback<ClickEvent>(OnMouseEventControlButton);
    }
    
    public void OnMouseEventControlButton(EventBase evt)
    {
        // Mouse Enter Control Button
        if (evt.eventTypeId == MouseEnterEvent.TypeId() || evt.eventTypeId == MouseLeaveEvent.TypeId()) {
            ToggleHoverStyle(this);
            OnHover?.Invoke(this);
        }

        // Mouse Click Control Button
        if (evt.eventTypeId == ClickEvent.TypeId()) {
            ToggleSelectStyle(this, CardNumber);
            OnSelect?.Invoke(this, CardNumber);
        }
    }

    // Hover Style Toggle Method
    public void ToggleHoverStyle(Control_Card m_card)
    {
    }

    // Select Style Toggle Method
    public void ToggleSelectStyle(Control_Card m_card, int m_cardNum) {
    }
}

이제 Hover 애니메이션 넣어보자. 먼저 Hover 스타일 클래스를 하나 만들어주고 스크립트를 업데이트한다.

// Hover Style Toggle Method
public void ToggleHoverStyle(Control_Card m_card)
{
    _card_Image.AddToClassList("card__image--hover");
    _card_Button.AddToClassList("card__button--hover");
    _button_icon.AddToClassList("button__icon--hover");
    _button_label.AddToClassList("button__label--hover");
}

카드 메뉴를 관리할 UI Manager로 합쳐서 작성해보자.

UI_Manager.cs

using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.UIElements;

public class UI_Manager : MonoBehaviour
{
    [SerializeField] private UIDocument UI_Doc;
    [SerializeField] private List<Control_Button> controlButtons;
    [SerializeField] private List<Control_Card> controlCards;

    [SerializeField] private Control_Button selectedButton;
    [SerializeField, TabGroup("Tap","Control Buttons")] private int seletedTabNumber = 0;

    // Odin - Tap Define 
    [TabGroup("Tap","Control Buttons",SdfIconType.CodeSlash, TextColor="red")]

    void Start() {
        //InitButtons();
    }


    [TabGroup("Tap","Control Buttons")]
    [Button("Init Control Button List"), GUIColor(0,1,0)]
    void InitButtons() {
        var root = UI_Doc.rootVisualElement;

        // Clear Control Button List
        if (controlButtons != null && controlButtons.Count > 0)
            controlButtons.Clear();

        // Load Control Button in UI_Doc
        controlButtons = new List<Control_Button>();
        var buttons = root.Query<Control_Button>().ToList();
        int _tabNumber = 1;
        foreach (var button in buttons) {
            button.OnSelect += OnSelectControlButton;
            button.TabNumber = _tabNumber++;
            controlButtons.Add(button);
        }
        Debug.Log($"[UI Manager] {controlButtons.Count} Buttons Initialized");
    }

    [TabGroup("Tap","Control Cards")]
    [Button("Init Control Button List"), GUIColor(0,1,0)]
    void InitCards() {
        var root = UI_Doc.rootVisualElement;

        // Clear Control Card List
        if (controlCards != null && controlCards.Count > 0)
            controlCards.Clear();
        
        // Load Control Card in UI_Doc
        controlCards = new List<Control_Card>();
        var cards = root.Query<Control_Card>().ToList();
        int cardIndex = 1;
        foreach (var card in cards) {
            card.AddToClassList($"card-{cardIndex++}");
            controlCards.Add(card);
        }
        Debug.Log($"[UI Manager] {controlCards.Count} Cards Initialized");
    }

    void OnSelectControlButton(Control_Button currentButton, int tabNumber) {

        if (selectedButton == currentButton) {
            Debug.Log($"[UI Manager] Control Button Unselect.");
            seletedTabNumber = 0;
            selectedButton = null;
            return;
        }
        
        if (selectedButton != null) 
            selectedButton.ToggleSelectStyle(selectedButton, selectedButton.TabNumber); // Untoggle SelectedButton

        // change selected Button
        selectedButton = currentButton; 
        seletedTabNumber = currentButton.TabNumber;
        Debug.Log($"[UI Manager][{tabNumber}] {currentButton.GetLabelText()} Button Select");
    }

#if UNITY_EDITOR
    [TabGroup("Tap","Control Buttons")]
    [Button("Check Selected Control Button")]
    void CheckSelectedControlButton() {
        if (selectedButton != null)
            Debug.Log($"[Control Button Manager][{selectedButton.TabNumber}] {selectedButton.GetLabelText()} Button Selected Now!");
        else 
            Debug.Log($"[Control Button Manager] No buttons are selected.");
    }
#endif
}

이제 인스펙터로 Control Buttons를 초기화해주면 자동으로 Card-1,2,3,4 스타일이 추가되면서 카드가 숨겨진다.


이제 Card를 Show, Hide하는 함수를 하나 만들어준다.

[TabGroup("Tap","Control Cards")]
[Button("Toggle Show/Hide Card Menus")]
void ToggleShowHideCards() {
    for (int i = 0; i < controlCards.Count; i++) {
        controlCards[i].ToggleInClassList($"card-{i + 1}--out");
    }
}


이제 탭 메뉴에 따라 카드 이미지와 라벨을 업데이트하는 것만 추가해보자

CardData.cs

using System;
using UnityEngine;

[Serializable]
public class CardData {
    public Texture2D card_image;
    public string card_label_text;
}

TabData.cs

using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(fileName = "Tab_Data", menuName = "Tab_Data", order = 0)]
public class TabData : ScriptableObject
{
    [SerializeField] 
    public List<CardData> tabData = new List<CardData>();
}

이제 탭 별로 카드 데이터들을 등록해준다.

동일하게 5개의 탭 데이터를 만들어준다.

이제 카드 데이터들을 탭 번호에 따라 업데이트 해주는 스크립트를 작성해주자.

[TabGroup("Tap","Control Cards")]
[Button("Cards Update")]
private UniTask<int> CardsUpdate(int tabNumber) {
    List<CardData> selectedTabData = tabDatas[tabNumber].tabData;
    for (int i = 0; i < selectedTabData.Count; i++) {
        controlCards[i].set_card_Image(selectedTabData[i].card_image);
        controlCards[i].set_card_Label(selectedTabData[i].card_label_text);
    }
    return UniTask.FromResult(selectedTabData.Count);;
}

이때 selectedTabData의 Count를 반환해주는 이유는 가지고 있는 카드 데이터 수의 카드만 Show 해줘야하기 때문에 Count를 넘겨준다.

UniTask로 해준 이유 : Card Update가 완료될때까지 기다리고(await) 카드를 Show해주기 위해서 UniTask로 작성했다.

async void OnSelectControlButton(Control_Button currentButton, int currentTabNumber) {
    
    if (selectedTabNumber != -1) {
        HideCards();
        await UniTask.Delay(TimeSpan.FromSeconds(0.2f));

        if (selectedTabNumber == currentTabNumber) {
            Debug.Log($"[UI Manager][{selectedTabNumber} = {currentTabNumber}] Unselect");
            selectedButton = null;
            selectedTabNumber = -1;
            return;
        }
        selectedButton.ToggleSelectStyle(selectedButton, selectedTabNumber);
        Debug.Log($"[UI Manager][{selectedTabNumber} → {currentTabNumber}] Button Select");
    }
    // change selected Button
    selectedButton = currentButton; 
    selectedTabNumber = currentButton.TabNumber;
    
    // Cards Update (using Tab Number)
    int countCards = await CardsUpdate(currentTabNumber);

    // Show Updated Cards
    ShowCards(countCards);
}

이제 TabData를 탭 메뉴 개수만큼 만들어준 후에 인스펙터로 초기화해준다.

아래와 같은 결과를 얻을 수 있다!

아래는 배틀필드 2042의 하단 UI 이미지이다. 거의 유사한 느낌으로 구현을 완료했다!


profile
Be Honest, Be Harder, Be Stronger

0개의 댓글