플레이어의 스탯을 관리하기위에 구현하는 스탯 찍기 시스템은 게임 진행과 함께 플레이어의 동기 부여와 캐릭터 발전을 장려해줌으로써 게임의 재미와 오래 즐길 수 있는 장점을 줄 수 있습니다. 지난 글에 이어 이번 글에서는 플레이어의 스탯을 관리하는 시스템을 구현하고 이 내용을 정리하였습니다.

 

💬 목차

1. [유니티] 스탯 시스템(1) - 디자인

[📌현재 글]  2. [유니티] 스탯 시스템(2) - 플레이어 스탯관리

 

 

💬 서론

  • 이 글은 게임을 대부분 완성한 상태에서 정리하고자 쓰는 글이기에 구현하는 클래스의 핵심 이외의 다른 클래스의 기능들이 일부 포함되어 있습니다.
  • 이 기능은 [직전 글]에서부터 이어지는 글입니다. 

 

✅ 구현

  • 플레이어의 레벨업을 통해 받은 스탯 포인트를 기반으로 플레이어의 기본스탯을 향상시킬 수 있는 기능을 구현합니다.

 

· StatManager

/// <summary>
    /// 플레이어의 스탯 포인트를 관리하는 매니저
    /// </summary>
    public class StatManager : Singleton<StatManager>
    {
        public static bool IsStatDialogEnable { private set; get; } = false;

        [Header("플레이어의 스탯")]
        [SerializeField] private PlayerController mPlayerController;

        // 레벨 영역
        [Header("레벨, 스탯포인트")]
        [SerializeField] private TextMeshProUGUI mLvLabel; // 레벨 레이블
        [SerializeField] private TextMeshProUGUI mStatPtLabel; // 스탯포인트 레이블
        [HideInInspector] public int CurrentStatPoint { private set; get; } = 1; // 현재 스탯 포인트 (1로 시작하도록 의도)

        [Space(30)]
        [Header("스탯(현재)")]
        [SerializeField] private TextMeshProUGUI mHpCurrentLabel; // 현재 체력 레이블
        [SerializeField] private TextMeshProUGUI mMpCurrentLabel; // 현재 마나 레이블
        [SerializeField] private TextMeshProUGUI mAttackCurrentLabel; // 현재 공격 레이블
        [SerializeField] private TextMeshProUGUI mSpeedCurrentLabel; // 현재 속도 레이블
        [SerializeField] private TextMeshProUGUI mDefenseCurrentLabel; // 현재 방어력 레이블

        [Space(30)]
        [Header("스탯(최대)")]
        [SerializeField] private TextMeshProUGUI mHpMaxLabel; // 최대 체력 레이블
        [SerializeField] private TextMeshProUGUI mMpMaxLabel; // 최대 마나 레이블

        [Space(30)]
        [Header("스탯 증가 버튼들")]
        [SerializeField] private GameObject[] mStatButtons; // 스탯을 찍는 버튼들

        [Space(30)]
        [Header("토글할 스탯 UI 오브젝트")]
        [SerializeField] private GameObject mStatUiGo;

        private void Awake()
        {
            // 초기화시 전역 활성화상태 해제
            StatManager.IsStatDialogEnable = false;
        }

        private void Update()
        {
            if (Input.GetKeyDown(KeyManager.Instance.GetKeyCode("Stat")))
            {
                this.ToggleStatDialog();
            }
        }

        public void LevelUp()
        {
            mPlayerController.StatData.UpgradeBaseStat(StatType.LEVEL); // 레벨 1 증가
            CurrentStatPoint += 2; // 스탯포인트 2 증가

            ParticleController particle = ParticlePool.Instance.GetFromPool(PARTICLE_TYPE.LEVEL_UP);
            particle.transform.SetParent(mPlayerController.transform);
            particle.transform.localPosition = Vector3.up;

            SoundManager.Instance.PlaySound2D("Levelup" + SoundManager.Range(1, 2));

            UpdateStatTexts();
        }

        /// <summary>
        /// 세이브파일로부터 로드된경우 스탯 포인트를 설정
        /// </summary>
        public void LoadFromData(PlayerGameData playerGameData)
        {
            this.CurrentStatPoint = playerGameData.statPoint;
        }

        #region 스탯 창 열고닫기

        /// <summary>
        /// 스탯 다이얼로그 창을 토글합니다.
        /// </summary>
        public void ToggleStatDialog()
        {
            if (mStatUiGo.activeSelf)
                TryCloseStatDialog();
            else
                TryOpenStatDialog();
        }

        private void TryOpenStatDialog()
        {
            UtilityManager.UnlockCursor();
            mStatUiGo.SetActive(true);
            IsStatDialogEnable = true;
            transform.SetAsLastSibling();

            UpdateStatTexts();
        }

        private void TryCloseStatDialog()
        {
            IsStatDialogEnable = false;
            UtilityManager.TryLockCursor();
            mStatUiGo.SetActive(false);
        }

        #endregion

        #region 스탯 창 업데이트

        public void UpdateStatTexts()
        {
            foreach (GameObject go in mStatButtons)
                go.SetActive(CurrentStatPoint > 0);


            mLvLabel.text = mPlayerController.StatData.level.ToString();
            mStatPtLabel.text = CurrentStatPoint.ToString();

            UpdateCurrentHpStat();
            UpdateCurrentMpStat();

            mHpMaxLabel.text = $"/{mPlayerController.StatData.hpMax.ToString()}";
            mMpMaxLabel.text = $"/{mPlayerController.StatData.mpMax.ToString()}";

            mAttackCurrentLabel.text = $"{mPlayerController.StatData.baseAttack} <size=9>+{mPlayerController.StatData.AttackCurrent - mPlayerController.StatData.baseAttack}";
            mSpeedCurrentLabel.text = $"{mPlayerController.StatData.baseMovementSpeed} <size=9>+{mPlayerController.StatData.MovementSpeedCurrent - mPlayerController.StatData.baseMovementSpeed}";
            mDefenseCurrentLabel.text = $"{mPlayerController.StatData.baseDefense} <size=9>+{mPlayerController.StatData.DefenseCurrent - mPlayerController.StatData.baseDefense}";
        }

        public void UpdateCurrentHpStat()
        {
            mHpCurrentLabel.text = mPlayerController.StatData.HpCurrent.ToString();
        }

        public void UpdateCurrentMpStat()
        {
            mMpCurrentLabel.text = mPlayerController.StatData.HpCurrent.ToString();
        }

        #endregion

        #region Ui 입력

        public void BTN_UpgradeStat(int statIndex)
        {
            // 스탯 포인트 1 감소
            --CurrentStatPoint; 

            // 스탯 증가
            mPlayerController.StatData.UpgradeBaseStat((StatType)statIndex);

            // 스탯 Ui 갱신
            UpdateStatTexts();
        }

        #endregion
    }

 

public static bool IsStatDialogEnable { private set; get; } = false;
  • 현재 스탯 다이얼로그 창이 활성화 되어있는지 확인하기위해 사용합니다.

 

[Header("플레이어의 스탯")]
[SerializeField] private PlayerController mPlayerController;
  • 스탯 매니저는 플레이어의 레벨업으로 기본 스탯을 강화하기위해 디자인 되었습니다.
  • 플레이어 컨트롤러의 스탯데이터를 레퍼런스하기위해 플레이어를 직접 가져옵니다.

 

// 레벨 영역
[Header("레벨, 스탯포인트")]
[SerializeField] private TextMeshProUGUI mLvLabel; // 레벨 레이블
[SerializeField] private TextMeshProUGUI mStatPtLabel; // 스탯포인트 레이블
[HideInInspector] public int CurrentStatPoint { private set; get; } = 1; // 현재 스탯 포인트 (1로 시작하도록 의도)
  • 플레이어의 현재 레벨을 보여주고, 스탯포인트를 보여주는 라벨을 위치시킵니다.
  • 아래 그림의 빨간색 영역이 이에 해당됩니다.

 

[Space(30)]
[Header("스탯(현재)")]
[SerializeField] private TextMeshProUGUI mHpCurrentLabel; // 현재 체력 레이블
[SerializeField] private TextMeshProUGUI mMpCurrentLabel; // 현재 마나 레이블
[SerializeField] private TextMeshProUGUI mAttackCurrentLabel; // 현재 공격 레이블
[SerializeField] private TextMeshProUGUI mSpeedCurrentLabel; // 현재 속도 레이블
[SerializeField] private TextMeshProUGUI mDefenseCurrentLabel; // 현재 방어력 레이블
  • 현재 스탯 상태를 보여주는 레이블들을 가져옵니다.
  • 아래 그림의 빨간색 영역이 이에 해당됩니다.

 

 

[Space(30)]
[Header("스탯(최대)")]
[SerializeField] private TextMeshProUGUI mHpMaxLabel; // 최대 체력 레이블
[SerializeField] private TextMeshProUGUI mMpMaxLabel; // 최대 마나 레이블
  • 최대 체력과 마나를 보여주는 레이블을 가져옵니다.
  • 아래 그림의 빨간색 영역이 이에 해당됩니다.

 

 

[Space(30)]
[Header("스탯 증가 버튼들")]
[SerializeField] private GameObject[] mStatButtons; // 스탯을 찍는 버튼들
  • 스탯을 찍기 위한 버튼들을 가져옵니다.
  • 스탯포인트가 있을때 스탯을 찍을 수 있도록 버튼들을 노출시키고, 스탯포인트가 없어 스탯을 찍을 수 없으면 버튼을 보여주지 않게 하기위해 오브젝트를 레퍼런스합니다.

 

[Space(30)]
[Header("토글할 스탯 UI 오브젝트")]
[SerializeField] private GameObject mStatUiGo;
  • 스탯창 자체를 토글하기위해 오브젝트를 레퍼런스합니다.

 

private void Update()
{
    if (Input.GetKeyDown(KeyManager.Instance.GetKeyCode("Stat")))
    {
        this.ToggleStatDialog();
    }
}
  • "Stat" 키를 눌러 스탯창을 열고 닫을 수 있습니다.
  • 이 기능은 별도로 구현한 KeyManager(키 설정 시스템)을 이용하며, 이것을 사용하지 않고 다른 키를 직접 확인하여 사용할 수 있습니다.
  • 키 설정 매니저에 대한 내용은 [여기] 에서 확인할 수 있습니다.

 

public void LevelUp()
  • 플레이어가 어떤 경우에의해 레벨업을 하게되면 호출됩니다.
  • CurrentStatPoint += 2; 로 스탯포인트가 2 증가되게 설정하였습니다.
  • 부수적인 기능으로 파티클 및 사운드를 재생하게 하였습니다.

 

/// <summary>
/// 세이브파일로부터 로드된경우 스탯 포인트를 설정
/// </summary>
public void LoadFromData(PlayerGameData playerGameData)
{
    this.CurrentStatPoint = playerGameData.statPoint;
}
  • 세이브 & 로드 기능을 이용한 함수이며 플레이어의 현재 남은 스탯포인트를 가져옵니다.

 

public void UpdateStatTexts()
  • 어떠한 경우에 현재 스탯창의 레이블이 갱신되어야 할 경우 호출됩니다.
  • 매 프레임별로 업데이트를 하지 않고 특정한 조건 (창을 열을때, 피해를 받을때, 회복을 할 때, 아이템 착용 등)에만 호출하게 하였습니다.

 

public void BTN_UpgradeStat(int statIndex)
{
    // 스탯 포인트 1 감소
    --CurrentStatPoint; 

    // 스탯 증가
    mPlayerController.StatData.UpgradeBaseStat((StatType)statIndex);

    // 스탯 Ui 갱신
    UpdateStatTexts();
}
  • 플레이어가 스탯포인트를 소모하여 기본 스탯을 증가시키도록 합니다.
  • Ui에 의해 호출되는 함수이며, 플레이어의 기본 스탯을 증가시킵니다.
  • StatData 디자인은 [여기]에서 확인할 수 있습니다.

 

· Ui 디자인

  • 아래의 이미지와 같이 Ui 컴포넌트들을 설정해줍니다.
  • SerializeField가 많아 복잡해 보이지만, 같은 패턴이 반복되어 크게 어렵지 않습니다.

 

  • 버튼에 BTN_UpgradeStat 함수를 등록합니다.
  • 매개변수는 enum의 번호로 설정합니다. 예) HP는 1번, MP는 2번..

 

✅ 사용

  • [유니티] 레벨, 경험치 시스템에서 플레이어가 레벨업을 하면 LevelUp() 함수를 호출하여 레벨업 효과 및 스탯포인트를 받게 합니다.
  • "Stat"키를 눌러 다이얼로그 창을 활성화하거나, 비활성화합니다.
  • 플레이어가 스탯찍기 버튼을 눌러 스탯포인트를 소모하면 버튼의 인덱스 번호에 맞게 BaseStat이 갱신됩니다.

 

 

bonnate