09
01

 

해당 글은 아래 유튜버분의 코드를 바탕으로 제가 향상 시킨 방법을 소개하는 글입니다.


https://github.com/onewheelstudio/Adventures-in-C-Sharp/tree/main/Stats%20and%20Upgrades

 

Adventures-in-C-Sharp/Stats and Upgrades at main · onewheelstudio/Adventures-in-C-Sharp

Tutorial Files from OWS C# Videos. Contribute to onewheelstudio/Adventures-in-C-Sharp development by creating an account on GitHub.

github.com

영상 링크 & 블로그 링크

더보기

 


#1 준비물

해당 스탯과 업그레이드는 SO(ScriptableObject)를 기반으로 데이터를 관리하기 때문에 이 스탯과 업그레이드를 SO로 생성한 뒤 인스펙터로 데이터를 지정해야합니다.

인스펙터로 데이터를 지정하기 위해선 Serialize하여 인스펙터에 뜨게 해야하는데 이걸 가능하게 해주는 에셋이 있습니다.
만약 Odin Inspector 에셋 이나 NaughtyAttributes 에셋이 있으면 인스펙터에서 SO 데이터를 수정할 수 있습니다.

 

두 에셋 모두 없으시면 아래 접은글에서 방법을 확인해보시면 됩니다.

그리고 이번 글에서는 ScriptableObject를 간단하게 SO라고 줄여서 설명하도록 하겠습니다.

 

┌에셋 없다면 대체 방법

더보기

기존 방법은 딕셔너리로 stats를 인스펙터에서 지정할 수 있습니다만 위에서 말한 에셋들 둘다 설치 못한다면 그냥 딕셔너리를 리스트화시키면됩니다. 아래는 기존 코드입니다.

 

아래는 리스트로 대체한 stat 리스트입니다.

 

보다시피 StatInfo 자료형인데 이것 또한 클래스로 만들어주시면됩니다.

 

 

그러면 인스펙터에 성공적으로 뜨게 될겁니다. 


#2 스탯 구현

이제 준비는 끝났으니 제대로 스탯을 구현해봅시다. 참고로 제공되는 코드중 더 아래에서 구현하게될 업그레이드도 포함되기 때문에 에러가 뜨더라도 글을 끝까지 따라할 때 까지 기다려주시기바랍니다.

 

우선 Stats.cs를 만들어 줍시다. 

using System.Collections.Generic;
using UnityEngine;
using System;
using Data;

//* 여기 오딘 인스펙터 에셋을 사용하였기 때문에 선언하였습니다. 대체법은 블로그에서 찾아주시기 바랍니다. 
using Sirenix.OdinInspector;
using ButtonAttribute = Sirenix.OdinInspector.ButtonAttribute;

namespace SO
{
    public class Stats<StatEnum> : SerializedScriptableObject where StatEnum : Enum
    {
        public Dictionary<StatEnum, float> instanceStats = new Dictionary<StatEnum, float>();
        public Dictionary<StatEnum, float> stats = new Dictionary<StatEnum, float>();
        private List<StatsUpgrade<StatEnum>> appliedUpgrades = new List<StatsUpgrade<StatEnum>>();

        public event Action<Stats<StatEnum>, StatsUpgrade<StatEnum>> upgradeApplied;

        public virtual float GetStat(StatEnum stat)
        {
            if (instanceStats.TryGetValue(stat, out var instanceValue))
                return GetUpgradedValue(stat, instanceValue);
            else if (stats.TryGetValue(stat, out float value))
                return GetUpgradedValue(stat, value);
            else
            {
                return 0;
            }
        }

        public int GetStatAsInt(StatEnum stat)
        {
            return (int)GetStat(stat);
        }

        public void UnlockUpgrade(StatsUpgrade<StatEnum> upgrade)
        {
            if (!appliedUpgrades.Contains(upgrade))
            {
                appliedUpgrades.Add(upgrade);
                upgradeApplied?.Invoke(this, upgrade);
            }
        }

        private float GetUpgradedValue(StatEnum stat, float baseValue)
        {
            foreach (var upgrade in appliedUpgrades)
            {
                if (!upgrade.upgradeToApply.TryGetValue(stat, out float upgradeValue))
                    continue;

                if (upgrade.isPercentUpgrade)
                    baseValue *= (upgradeValue / 100f) + 1f;
                else
                    baseValue += upgradeValue;
            }

            return baseValue;
        }

        [Button]
        public void ResetAppliedUpgrades()
        {
            appliedUpgrades.Clear();
        }
    }
}

보다시피 Stats는 제네릭타입을 받아서 제네릭 타입은 Enum 값으로 한정하였습니다.

이렇게 하는 이유는 아래 Enum 데이터를 구현하고 설명드리겠습니다.

 

혹시 SerializedScriptableObject 를 상속할 수 없다면 위에서 설명드린 Odin Inspector 에셋을 설치해주셔야합니다.

List 대체 방식을 사용하셨다면 아래 사진과 같이 모든 Stats을 사용하는 코드마다 SerializeField 처리 해주시면됩니다.

 

 


#3 Enum 데이터 구현 

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

namespace Data
{
    //* 다양한 스탯들 모음
    public enum PlayerStat //* 플레이어 기본 스탯
    {
        Health, //* 체력
        AttackDamage, //* 공격력
        MovementSpeed, //* 이동속도
    }
    public enum PlayerPassiveStat //* 플레이어 영구 패시브 
    {
        Passive_Health, //* 체력
        Passive_AttackDamage, //* 공격력
        Passive_MovementSpeed, //* 이동속도
    }
}

코드에서 나오듯이 다양한 enum 데이터를 구현해보았습니다.

위에 Stats에서 우린 enum 데이터로 값을 제한하였습니다. 그 이유는 다양한 enum 데이터를 Stats에서 사용하면 Stats을 상속한 코드들은 분리된 데이터를 관리하고 외부에서 호출했을 때 Stats의 GetStat()나 GetUpgradedValue()와 같은 메서드(함수)를 보장하기 때문입니다.

 

 


#4 업그레이드 구현

 

이제부턴 조금 난이도가 있는 업그레이드를 구현할 차례입니다.

using Data;
using Sirenix.OdinInspector;
using UnityEngine;

namespace SO
{
    [CreateAssetMenu(menuName = "스탯/Upgrade SO")]
    public abstract class UpgradeSO : SerializedScriptableObject
    {
        public Sprite icon { get; private set; }
        
        public string upgradeName;
        public string description { get; private set; }

        public abstract bool DoUpgrade();
    }
}

우선 위와같은 추상 클래스를 만들어줍니다. 

 

Odin Serialization 설치하셨다면 읽으세요

더보기

만약 Odin Serialization를 설치하셨다면 아래 코드로 더 이쁘게 인스펙터를 꾸밀 수 있습니다.

 

using Data;
using Sirenix.OdinInspector;
using Sirenix.Serialization;
using UnityEngine;
using BoxGroupAttribute = Sirenix.OdinInspector.BoxGroupAttribute;
using ButtonAttribute = Sirenix.OdinInspector.ButtonAttribute;

namespace SO
{
    [CreateAssetMenu(menuName = "스탯/Upgrade SO")]
    public abstract class UpgradeSO : SerializedScriptableObject
    {
        [PropertyOrder(-2)]
        [BoxGroup("디테일")]
        [OdinSerialize]
        [PreviewField(75, ObjectFieldAlignment.Center)]
        [HideLabel]
        public Sprite icon { get; private set; }
        
        [BoxGroup("디테일")]
        [PropertyOrder(-1)]
        public string upgradeName;
        [BoxGroup("디테일")]
        [OdinSerialize]
        [LabelText("설명")]
        public string description { get; private set; }

        [Button(Name = "업그레이드하기")]
        public abstract bool DoUpgrade();
    }
}

 

 

 

이제 해당 추상 클래스를 상속해주는 StatsUpgrade.cs를 구현해보겠습니다.

using System;
using System.Collections.Generic;
using Data;
using UnityEngine;

namespace SO
{
    public class StatsUpgrade<StatEnum> : UpgradeSO where StatEnum : Enum
    {
        [Header("업그레이드가 적용한 스탯들")]
        [SerializeField]
        public List<Stats<StatEnum>> unitsToUpgrade = new List<Stats<StatEnum>>();
        public Dictionary<StatEnum, float> upgradeToApply = new Dictionary<StatEnum, float>();
        public bool isPercentUpgrade = false;

        public override bool DoUpgrade()
        {
            foreach (var unitToUpgrade in unitsToUpgrade)
            {
                foreach (var upgrade in upgradeToApply)
                {
                    unitToUpgrade.UnlockUpgrade(this);
                }
            }
            return true;
        }
    }
}

 

보다시피 해당 StatsUpgrade.cs 또한 StatEnum을 제네릭으로 Enum 타입으로 제한하였습니다.

위에서 했던 Stats 처럼 이걸 상속해서 원하는 Enum 타입으로 업그레이드를 구현하면 되는 것입니다.

 


#5 원하는 Enum 데이터를 제네릭으로 지정하여 스탯 & 업그레이드 구현

이제 거의 다 왔습니다. 원하는 Enum 데이터를 하나 고르시고 (저는 PlayerStat 골랐습니다)

PlayerStats.cs를 생성해서 제네릭 타입에 해당 Enum 타입(PlayerStat)을 지정하시면 됩니다. 

 

Enum 타입 지정한 Stats 구현

using System.Collections;
using System.Collections.Generic;
using Data;
using UnityEngine;

namespace SO
{
    [CreateAssetMenu(menuName = "스탯/Stats/PlayerStats")]
    public class PlayerStats : Stats<PlayerStat>
    {

    }
}

이렇게 작성한 뒤 인스펙터로 돌아가면 SO를 생성할 수 있게 됩니다.

Project (프로젝트) 탭에서 우클릭한 뒤 스탯 -> Stats -> Player Stats를 선택해주시면 해당 SO가 생성됩니다.

 

그리고 우리가 지정한 PlayerStats의 Enum 데이터 타입중 하나를 인스펙터에서 선택해서 해당 값을 담당하는 SO를 만들게 됩니다. 

 

이제 업그레이드도 똑같이 해보겠습니다.

PlayerStatsUpgrade.cs를 생성해서 제네릭 타입에 해당 Enum 타입(PlayerStat)을 지정하시면 됩니다. 

 

Enum 타입 지정한 StatsUpgrade 구현

using System.Collections.Generic;
using UnityEngine;
using Data;

namespace SO
{
    [CreateAssetMenu(menuName = "스탯/Upgrades/Player Stats Upgrade")]
    public class PlayerStatsUpgrade : StatsUpgrade<PlayerStat>
    {
    }
}

그러면 이제 인스펙터에서 SO를 생성하실 수 있습니다.

Project (프로젝트) 탭에서 우클릭한 뒤 스탯 -> Upgrades -> Player Stats Upgrade를 선택해주시면 해당 SO가 생성됩니다.

이런식으로 인스펙터에서 지정하면 해당 Enum 타입의 Stats를 업그레이드 해주는 SO가 완성되었습니다.

우리가 원하는 아까 만든 데이터 SO를 업그레이드 해주고 업그레이드 값을 50으로 지정 해주게 됩니다.

이 방식으로 우리가 원하는 업그레이드마다 업그레이드 SO를 만들어주고 업그레이드 될 대상 데이터 SO를 지정해주고 값을 지정해주면 됩니다.

 


#6 심화) 레벨에 기반한 값 업그레이드

해당 레벨업은 하나의 에셋이 더 필요합니다.

바로 UGS 라이브러리를 유니티로 구현하고 한글로 번역까지 된 한국인이 만든 UGS 에셋입니다.

https://shlifedev.gitbook.io/unitygooglesheets/getting-start/before-start  

 

다운로드 | UGS 개발문서

Download & Import

shlifedev.gitbook.io

 

 

위의 링크로 다운을 하셨다는 가정하에 진행을 이어가보겠습니다.

우선 UGS를 설정해줍시다.

https://shlifedev.gitbook.io/unitygooglesheets/getting-start/apps-script

https://shlifedev.gitbook.io/unitygooglesheets/getting-start/google-drive

https://shlifedev.gitbook.io/unitygooglesheets/how-to-use/spreeadhseet-rules

위의 세 개의 가이드를 따라 만들어주세요.

가이드 따라서 만드신 시트를 살짝 수정하겠습니다.

이런식으로 저희가 만들 Enum을 지정해서 시트에 미리 레벨에 따른 수치를 작성해줍시다.

예를 들어 위에서 저희가 만든 Enum중에 PlayerPassiveStat을 타겟으로 시트를 작성해봤습니다.

작성이 완료된 후에는 UGS 에셋의 Generate 버튼을 눌러서 데이터를 갱신해주세요.

(데이터가 제대로 갱신되지 않는다면 Assets -> UGS.Generated 폴더를 지우고 다시 눌러주세요)

 

 

이제 다시 코드로 돌아와서 위에서 만든 Stats을 상속한 LevelStats.cs를 만들겠습니다.

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

namespace SO
{
    public class LevelStats<StatEnum> : Stats<StatEnum> where StatEnum : Enum
    {
        private LevelUpgradeSO<StatEnum> curLevelUpgrade;
        public event Action<Stats<StatEnum>, LevelUpgradeSO<StatEnum>> levelUpgradeApplied;
        public override float GetStat(StatEnum stat)
        {
            if (instanceStats.TryGetValue(stat, out var instanceValue))
                return GetUpgradedValue(instanceValue);
            else if (stats.TryGetValue(stat, out float value))
                return GetUpgradedValue(value);
            else
            {
                return 0;
            }
        }
        private float GetUpgradedValue(float baseValue)
        {
            if(curLevelUpgrade == null) return baseValue;
            LevelUpgradeSO<StatEnum> upgrade = curLevelUpgrade;
            if (upgrade.isPercentUpgrade)
                baseValue *= (upgrade.GetUpgradeValueConvert() / 100f) + 1f;
            else
                baseValue = upgrade.GetUpgradeValueConvert();

            return baseValue;
        }

        public void SetLevelUpgrade(LevelUpgradeSO<StatEnum> levelUpgrade)
        {
            curLevelUpgrade = levelUpgrade;
            levelUpgradeApplied?.Invoke(this, levelUpgrade);
        }
    }
}

이걸 상속한 패시브 레벨 스탯들을 가지고 있을 SO를 생성하게 해주는 PassiveLevelStats.cs를 만들겠습니다. 

using System.Collections.Generic;
using Data;
using UnityEngine;

namespace SO
{
    [CreateAssetMenu(menuName = "스탯/Stats/LevelStats/Passive Level Stats")]
    public class PassiveLevelStats : LevelStats<PlayerPassiveStat>
    {
        
    }
}

그리고 SO를 바로 생성해줍시다.

 

그리고 해당 PassiveLevelStats.cs를 업그레이드할 때 필요한 레벨 데이터 UpgradeData.cs를 만들겠습니다.

namespace Data
{
    public class UpgradeData
    {
        public string upgradeName;
        public int level;
    }
}

 

이제 업그레이드해줄 LevelUpgradeSO.cs를 만들겠습니다.

using System;
using System.Collections;
using System.Collections.Generic;
using Data;
using Manager;
using UnityEngine;

namespace SO
{
    public abstract class LevelUpgradeSO<StatEnum> : UpgradeSO where StatEnum : Enum
    {
        [Header("업그레이드가 적용될 스탯들")]
        [SerializeField]
        public List<LevelStats<StatEnum>> unitsToUpgrade = new List<LevelStats<StatEnum>>();

        public bool isPercentUpgrade = false;

        protected int _curLevel = 0;
        protected int _maxLevel = 0;
        protected string _levelValue = "1";
        protected string _levelCost = "1";
        public void Initialize(int maxLevel, string cost, string levelValue)
        {
            _maxLevel = maxLevel;
            _levelCost = cost;
            _levelValue = levelValue;
            SetUpgrade();
        }
        public virtual bool IsMaxLevel()
        {
            return _curLevel >= GetMaxLevel();
        }
        public virtual int GetMaxLevel()
        {
            return _maxLevel;
        }
        public virtual string GetUpgradeCost()
        {
            return _levelCost;
        }

        public virtual string GetUpgradeValue()
        {
            return _levelValue;
        }
        public virtual float GetUpgradeValueConvert()
        {
            float output;
            if (float.TryParse(_levelValue, out output))
            {
                return output;
            }
            else
            {
                Debug.Log("숫자가 아닙니다.");
            }
            return output;
        }
        public void SetUpgrade()
        {
            foreach (var unitToUpgrade in unitsToUpgrade)
            {
                unitToUpgrade.SetLevelUpgrade(this);
            }
        }
        public override bool DoUpgrade()
        {
            return TryLevelUpgrade();
        }

        private bool TryLevelUpgrade()
        {
            GetUpgradeLevel();
            if(_curLevel == 0 || IsMaxLevel()) return false;
            _curLevel++;
            SetUpgradeLevel();
            return true;
        }
        
        //* 여기부턴 저장 코드를 담고 있기 때문에 레벨 관리는 자율로 구현하시기 바랍니다.
        public virtual int GetUpgradeLevel()
        {
            UpgradeData data = GetUpgradeDataByName(upgradeName);
            if(data != null)
                _curLevel = data.level;
            return _curLevel;
        }
        public virtual void SetUpgradeLevel()
        {
            UpgradeData data = GetUpgradeDataByName(upgradeName);
            if(data != null)
            {
                data.level = _curLevel;
                Global.UserDataManager.SetUpgradeData(data);
            }
        }
        public UpgradeData GetUpgradeDataByName(string upgradeName)
        {
            UpgradeData upgradeData = Global.UserDataManager.GetUpgradeData(upgradeName);

            return upgradeData;
        }
    }
}

지금까지 해온것처럼 이걸 상속해서 Enum을 지정해주시면 됩니다.

저희가 위에서 시트로 만든 PlayerPassiveStat을 타겟으로 지정해봅시다.

이걸 지정한 업그레이드 SO PlayerPassiveLevelUpgradeSO.cs를 만들어보겠습니다.

using System.Collections.Generic;
using UnityEngine;
using Data;
using Manager;

namespace SO
{
    [CreateAssetMenu(menuName = "스탯/Upgrades/Level/Player Passive Level Upgrade")]
    public class PlayerPassiveLevelUpgradeSO : LevelUpgradeSO<PlayerPassiveStat>
    {
        public virtual void Initialize(){
            GetMaxLevel();
            GetUpgradeCost();
            GetUpgradeValue();
            SetUpgrade();
        }
        public override int GetMaxLevel()
        {
            return _maxLevel;
        }
        public override string GetUpgradeCost()
        {
            return _levelCost;
        }
        public override string GetUpgradeValue()
        {
            return _levelValue;
        }

        public override int GetUpgradeLevel()
        {
            UpgradeData data = GetUpgradeDataByName(upgradeName);
            if(data != null)
                _curLevel = data.level;
            return _curLevel;
        }
        public override void SetUpgradeLevel()
        {
            UpgradeData data = GetUpgradeDataByName(upgradeName);
            if(data != null)
            {
                data.level = _curLevel;
                Global.UserDataManager.SetUpgradeData(data);
            }
        }
    }
}

이걸 이제 저희가 만든 시트 한페이지한페이지마다 개별의 데이터를 불러오는 스크립트를 만듭시다.

첫번째 시트는 Health이기 때문에 Health 수치를 레벨에 맞게 불러오는 HealthPassiveLevelUpgradeSO.cs를 만들어봅시다.

using System.Collections;
using System.Collections.Generic;
using Passive;
using SO;
using UnityEngine;


namespace BunnyCafe.SO
{
    [CreateAssetMenu(fileName = "HealthPassiveLevelUpgrade", menuName = "스탯/Upgrades/Level Upgrade/패시브/체력")]
    public class HealthPassiveLevelUpgradeSO : PlayerPassiveLevelUpgradeSO
    {
        public override void Initialize()
        {
            GetMaxLevel();
            GetUpgradeCost();
            GetUpgradeValue();
            SetUpgrade();
        }
        public override int GetMaxLevel()
        {
            _maxLevel = GetMap()?.Count ?? 1;
            return _maxLevel;
        }
        public override string GetUpgradeCost()
        {
            var b = GetValue();
            if(b != null)
            {
                _levelCost = b?.goldCost ?? "1";
            }
            return _levelCost;
        }
        public override string GetUpgradeValue()
        {
            var b = GetValue();
            if(b != null)
            {
                _levelValue = b.value;
            }
            return _levelValue;
        }
        private Dictionary<int, Health> GetMap()
        {
            return Health.HealthMap;
        }

        private Health GetValue()
        {
            var Map = GetMap();
            if (Map != null && Map.TryGetValue(GetUpgradeLevel(), out Health value))
            {
                return value;
            }

            return null;
        }
    }
}

이렇게 PlayerPassive만을 레벨업 해주는 부분을 상속해서 Health 패시브의 데이터를 가져오게 되면 PlayerPassiveLevelUpgrade의 함수들을 호출해도 자식에 있던 함수들이 호출되어 값을 올바르게 호출하게 됩니다. 

 

이제 다시 인스펙터로 돌아가서 위에서 만든 SO를 만들어봅시다.

우리가 지정한 곳에 SO를 만들어줍시다.

사진처럼 업그레이드할 레벨 패시브 수치를 관리하는 SO까지 지정해주셨으면

이제 이 패시브 SO들을 관리해줄 매니저를 만듭시다.

 


#7 심화) 스탯 & 업그레이드 SO를 관리해줄 매니저 생성

매니저는 두 가지 가정이 필요합니다.

첫 번째는 데이터들을 유니티 에디터에서 불러와서 인스펙터로 미리 지정합니다.

두 번째는 인게임에서 스탯들을 불러올 때 해당 매니저를 통해 데이터를 찾아서 가져옵니다.

 

주의해야할건 코드에서 경로지정하는 부분은 여러분이 스탯과 업그레이드 SO를 만드신 폴더를 지정해주세요

 

우선 스탯 SO들을 불러올 수 있는 기능을 가진 ExtraBits.cs를 만들겠습니다.

내부 클래스 이름은 바꾸셔도 됩니다.

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

namespace Data
{
    public static class HelperFunctions
    {
        public static List<T> GetScriptableObjects<T>(string path) where T : ScriptableObject
        {
#if UNITY_EDITOR


            string[] guids = UnityEditor.AssetDatabase.FindAssets("t:" + typeof(T).ToString(), new[] { path });
            List<T> scriptableObjects = new List<T>();

            foreach (var guid in guids)
            {
                UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
                string assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
                scriptableObjects.Add(UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath, typeof(T)) as T);
            }

            return scriptableObjects;
#else
        return null;
#endif
        }
    }
}

다음으로 스탯들을 관리할 StatsManager.cs를 만들겠습니다.

using System.Collections.Generic;
using Data;
using SO;
using Sirenix.OdinInspector;
using UnityEngine;

namespace Manager
{
    public class StatsManager : MonoBehaviour
    {
        [SerializeField] private List<Stats<PlayerStat>> playerStatsList;
        [SerializeField] private List<Stats<PlayerPassiveStat>> playerPassiveStatsList;
        private const string playerStatsPath = "Assets/Project/Scripts/Data/Stats/NormalStats";
        private const string playerPassiveStatsPath = "Assets/Project/Scripts/Data/Stats/LevelStats";

        #if UNITY_EDITOR
        [Button("SO 불러오기")]
        public void LoadAssets()
        {
            playerStatsList = HelperFunctions.GetScriptableObjects<Stats<PlayerStat>>(playerStatsPath);
            playerPassiveStatsList = HelperFunctions.GetScriptableObjects<Stats<PlayerPassiveStat>>(playerPassiveStatsPath);
        }
        #endif
        private void OnApplicationQuit()
        {
            foreach (var stat in playerStatsList)
            {
                stat.ResetAppliedUpgrades();
            }
            foreach (var stat in playerPassiveStatsList)
            {
                stat.ResetAppliedUpgrades();
            }
        }
    }
}

 

그 다음으로 스탯 업그레이드들을 관리할 StatsUpgradeManager.cs를 만들겠습니다.

using System;
using System.Collections;
using System.Collections.Generic;
using Data;
using SO;
using Sirenix.OdinInspector;
using UnityEngine;

namespace Manager
{
    public class StatsUpgradeManager : MonoBehaviour
    {        
        [SerializeField] private List<LevelUpgradeSO<PlayerStat>> playerStatsUpgradeList;
        [SerializeField] private List<LevelUpgradeSO<PlayerPassiveStat>> playerPassiveStatsUpgradeList;
        private const string playerStatsPath = "Assets/Project/Scripts/Data/Upgrade/PlayerStat";
        private const string playerPassiveStatsPath = "Assets/Project/Scripts/Data/Upgrade/LevelStatUpgrade";

        #if UNITY_EDITOR
        [Button("SO 불러오기")]
        public void LoadAssets()
        {
            playerStatsUpgradeList = HelperFunctions.GetScriptableObjects<LevelUpgradeSO<PlayerStat>>(playerStatsPath);
            playerPassiveStatsUpgradeList = HelperFunctions.GetScriptableObjects<LevelUpgradeSO<PlayerPassiveStat>>(playerPassiveStatsPath);
        }
        #endif
    }
}

 

이제 StatsManager.cs와 StatsUpgradeManager.cs 모두 인스펙터에서 SO 불러오기 버튼을 눌러보시면 여러분이 지정하신 경로의 SO를 불러오는 모습을 볼 수 있습니다.

 

 

 

이제 구조를 다시 설명하겠습니다.

가장 추상적인 Stats를 상속한 LevelStats를 Enum 지정하여 우리가 원하는 종류의 레벨업이 가능한 스탯을 SO로 만들었습니다.

해당 SO는 스탯 데이터를 가지고 있는것 뿐이고 

가장 추상적인 UpgradeSO를 상속한 LevelUpgradeSO를 위의 스탯 데이터를 지정하기 위해 똑같은 종류의 Enum를 지정합니다.

그리고 이건 위에서 봤던 PlayerPassiveLevelUpgradeSO.cs라는 예시입니다. 이걸 또 상속한 HealthPassiveLevelUpgradeSO.cs는 PlayerPassiveLevelUpgradeSO.cs 에서 GetUpgradeValue()를 할시 해당하는 시트의 값을 불러오는 더 디테일한 코드입니다.

 

이런식으로  HealthPassiveLevelUpgradeSO.cs를 SO로 만들어서 위의 스탯 SO를 지정하고 외부에선 더 상위인 PlayerPassiveLevelUpgradeSO()의 GetUpgradeValue() 호출하여 시트에서 우리가 정한 해당 스탯SO의 값을 알게되는겁니다.

 

이상 스탯&업그레이드를 여러 Enum 지정 가능할 수 있는 방법을 소개해드렸습니다.

레벨업 기능까지는 너무 복잡한거같아서 추후 리팩토링해봐도 될 것 같습니다.

긴글 읽어주셔서 감사합니다.

COMMENT