4 분 소요

Intro

이번포스팅에서는 내 캐릭터의 정보를 볼 수 있는 캐릭터 정보창을 만들어보았습니다.

image

GUI는 위와같이 설계했습니다.

  • EquipmentUI : 마우스 이벤트와 장비슬롯들을 관리하는 클래스
  • EquipmentSlotUI : 개별 장비슬롯을 관리하는 클래스
  • StatTextUI : 플레이어 데이터를 읽어와 텍스트를 표시하는 클래스

GUI 만들기

캐릭터 정보창의 GUI는 아래와같이 만들었습니다.

image

  • UI를 마우스 드래그로 움직일수 있도록 Header Area에 MovableHeaderUI 클래스를 재사용했습니다.

플레이어 데이터 추가

플레이어 데이터에는 현재 공격력과 방어력이 없습니다.

따라서 플레이어 데이터에 두가지 데이터를 추가했습니다.

// PlayerDataDTO.cs
[System.Serializable]
public class PlayerDataDTO
{
    public List<StatusDTO> Status;

    [System.Serializable]
    // 스탯 정보
    public class StatusDTO
    {
        public float maxHp;             // 최대체력
        public float curHp;             // 현재체력
        public float maxMp;             // 최대마나
        public float curMp;             // 현재마나
        public float speed;             // 이동속도
        public float rotateSpeed;       // 회전속도
        public float damage;            // 기본 공격력
        public float armor;             // 기본 방어력
    }
}
// PlayerData.cs
public class PlayerData
{
    [SerializeField] private float maxHp;
    [SerializeField] private float curHp;
    [SerializeField] private float maxMp;
    [SerializeField] private float curMp;
    [SerializeField] private float speed;
    [SerializeField] private float rotateSpeed;
    [SerializeField] private float damage;
    [SerializeField] private float armor;


    public float MaxHp => maxHp;
    public float CurHp => curHp;
    public float MaxMp => maxMp;
    public float CurMp => curMp;
    public float Speed => speed;
    public float RotateSpeed => rotateSpeed;
    public float Damage => damage;
    public float Armor => armor;

    // 생성자 - Status 초기화
    public PlayerData(PlayerDataDTO.StatusDTO dto)
    {
        this.maxHp = dto.maxHp;
        this.curHp = dto.curHp;
        this.maxMp = dto.maxMp;
        this.curMp = dto.curMp;
        this.speed = dto.speed;
        this.rotateSpeed = dto.rotateSpeed;
        this.damage = dto.damage;
        this.armor = dto.armor;
    }
}
// PlayerData.json
{
    "Status": [
        {
            "maxHp": 1000.0,
            "curHp": 1000.0,
            "maxMp": 1000.0,
            "curMp": 1000.0,
            "speed": 3.0,
            "rotateSpeed": 10.0,
	        "damage": 5.0,
	        "armor": 0
        }
    ]
}

StatTextArea

StatTextArea 클래스는 DataManager를 통해 플레이어의 데이터를 받아와서 텍스트로 표시합니다.

// StatTextArea.cs
public class StatTextUI : MonoBehaviour
{
    [Header("Connected Texts")]
    [SerializeField] private Text DamageText;
    [SerializeField] private Text HpText;
    [SerializeField] private Text SpeedText;
    [SerializeField] private Text ArmorText;

    private void Update()
    {
        DamageText.text = string.Format("{0}", Mathf.FloorToInt(DataManager.Instance.GetPlayerData().Damage));
        HpText.text = string.Format("{0}", Mathf.FloorToInt(DataManager.Instance.GetPlayerData().CurHp));
        SpeedText.text = string.Format("{0}%", Mathf.RoundToInt(DataManager.Instance.GetPlayerData().Speed + 100));
        ArmorText.text = string.Format("{0}", Mathf.FloorToInt(DataManager.Instance.GetPlayerData().Armor));
    }
}

에디터의 인스펙터창에서 각 텍스트 오브젝트를 연결시켜주어야합니다.

EquipmentSlotUI

EquipmentSlotUI 클래스는 인벤토리에서와같이 장비슬롯에 마우스가 올라갈 때 강조효과가 발생하도록 했습니다.

대부분의 코드를 재사용했습니다.

public class EquipmentSlotUI : MonoBehaviour
{
    [SerializeField] private Image iconImage;           // 아이템 아이콘 이미지
    [SerializeField] private Image highlightImage;      // 하이라이트 이미지
    
    #region ** Fields **
    private EquipmentUI equipmentUI;

    private GameObject highlightGo;
    private GameObject iconGo;

    private RectTransform highlightRect;

    private float maxHighlightAlpha = 0.5f;             // 하이라이트 이미지 최대 알파값
    private float currentHighlightAlpha = 0f;           // 현재 하이라이트 이미지 알파값
    private float highlightFadeDuration = 0.2f;         // 하이라이트 소요시간
    #endregion

    private void Awake()
    {
        Init();
    }

    private void Init()
    {
        equipmentUI = GetComponentInParent<EquipmentUI>();

        highlightGo = highlightImage.gameObject;
        highlightImage.raycastTarget = false;
        highlightRect = highlightImage.rectTransform;

        highlightGo.SetActive(false);
    }

    // 하이라이트 이미지를 상/하단으로 표시
    public void SetHighlightOnTop(bool value)
    {
        if (value)
            highlightRect.SetAsLastSibling();
        else
            highlightRect.SetAsFirstSibling();
    }

    // 슬롯 하이라이트 표시 및 해제
    public void Highlight(bool show)
    {
        if (show)
            StartCoroutine(nameof(HighlightFadeIn));
        else
            StartCoroutine(nameof(HighlightFadeOut));
    }

    #region ** Coroutines **
    // 하이라이트 Fade-in
    private IEnumerator HighlightFadeIn()
    {
        // 실행중인 fade-out 멈추기
        StopCoroutine(nameof(HighlightFadeOut));

        // 하이라이트 이미지 활성화
        highlightGo.SetActive(true);

        float timer = maxHighlightAlpha / highlightFadeDuration;

        // 하이라이트 이미지 알파값을 서서히 증가시키기
        for (; currentHighlightAlpha <= maxHighlightAlpha; currentHighlightAlpha += timer * Time.deltaTime)
        {
            highlightImage.color = new Color(
                     highlightImage.color.r,
                     highlightImage.color.g,
                     highlightImage.color.b,
                     currentHighlightAlpha
                );

            yield return null;
        }
    }

    // 하이라이트 Fade-out
    private IEnumerator HighlightFadeOut()
    {
        StopCoroutine(nameof(HighlightFadeIn));

        float timer = maxHighlightAlpha / highlightFadeDuration;

        // 하이라이트 이미지 알파값을 서서히 감소시키기
        for (; currentHighlightAlpha >= 0f; currentHighlightAlpha -= timer * Time.deltaTime)
        {
            highlightImage.color = new Color(
                     highlightImage.color.r,
                     highlightImage.color.g,
                     highlightImage.color.b,
                     currentHighlightAlpha
                );

            yield return null;
        }

        highlightGo.SetActive(false);
    }
    #endregion
}

EquipmentUI

EquipmentUI 클래스는 마우스 이벤트를 구현하고, 각 장비슬롯들을 저장할 리스트를 가지고있습니다.

public class EquipmentUI : MonoBehaviour
{
    [Tooltip("캐릭터 장비 슬롯")]
    public List<EquipmentSlotUI> slotUIList = new List<EquipmentSlotUI>();

    #region ** Fields **
    private GraphicRaycaster gr;
    private PointerEventData ped;
    private List<RaycastResult> rrList;

    private EquipmentSlotUI pointerOverSlot;            // 현재 마우스 포인터가 위치한 곳의 슬롯
    private EquipmentSlotUI beginDragSlot;              // 마우스 드래그를 시작한 슬롯
    private Transform beginDragIconTransform;           // 마우스 드래그를 시작한 슬롯의 위치
    #endregion

    #region ** 유니티 이벤트 함수 **
    private void Awake()
    {
        Init();
    }

    private void Update()
    {
        ped.position = Input.mousePosition;

        OnPointerEnterAndExit();
    }
    #endregion

    #region ** Private Methods **
    // 초기화
    private void Init()
    {
        TryGetComponent(out gr);
        if (gr == null)
            gr = gameObject.AddComponent<GraphicRaycaster>();

        ped = new PointerEventData(EventSystem.current);
        rrList = new List<RaycastResult>(10);
    }
    #endregion

    #region ** 마우스 이벤트 함수들 **
    // 마우스 커서가 UI 위에 있는지 여부
    private bool IsOverUI() => EventSystem.current.IsPointerOverGameObject();

    // 레이캐스팅한 첫 UI요소의 컴포넌트를 가져오기
    private T RaycastAndgetFirstComponent<T>() where T : Component
    {
        // RaycastResult 초기화
        rrList.Clear();

        // 현재 마우스 위치에서 감지된 UI요소 저장
        gr.Raycast(ped, rrList);

        // 없으면
        if (rrList.Count == 0)
            return null;

        // 첫번째 UI의 컴포넌트 반환
        return rrList[0].gameObject.GetComponent<T>();
    }

    // 마우스 올라갈때 나갈때 처리
    private void OnPointerEnterAndExit()
    {
        // 이전 프레임 슬롯
        var prevSlot = pointerOverSlot;

        // 현재 프레임 슬롯
        var curSlot = pointerOverSlot = RaycastAndgetFirstComponent<EquipmentSlotUI>();

        // 마우스 올라갈 때
        if(prevSlot == null)
        {
            if(curSlot != null)
            {
                OnCurrentEnter();
            }
        }
        // 마우스 나갈 때
        else
        {
            if (curSlot == null)
            {
                OnPrevExit();
            }
            // 다른 슬롯으로 커서 옮길때
            else if (prevSlot != curSlot)
            {
                OnPrevExit();
                OnCurrentEnter();
            }
        }

        void OnCurrentEnter()
        {
            curSlot.Highlight(true);
        }

        void OnPrevExit()
        {
            prevSlot.Highlight(false);
        }
    }

    #endregion
}

테스트

image

댓글남기기