Print Friendly and PDF

유니티/게임 제작

[디펜스 게임 제작] 강화 시스템 변경

나는야 개발자 2025. 6. 25. 23:28
반응형

현재 강화시스템은 단일로 강화하면 모든 영웅들이 강화가 되도록 했습니다.

좀 더 전략적으로 강화 시키기 위해 개별로 강화 시킬 수 있도록 변경할 예정입니다.

 

- 이런 느낌으로 각 영웅들의 정보를 띄어주고 클릭하면 하단에 강화 버튼이 나오도록 할 예정입니다.

 

 

클래스 구조

1. 먼저는 UI_Hero 클래스에서 UI_Hero_Btn클래스를 초기화 해준다.

2. UI_HeroBtn을 클릭하면 UI_Status 클래스를 가진 오브젝트가 활성화가 된다.

3. 활성화된 UI_Status는 UI_Btn_Status_Upgrade클래스를 가진 버튼 오브젝트를 초기화 해준다.

4. 활성화된 UI_Btn_Status_Upgrade 버튼을 클릭하여 이벤트를 통해 Status를 StatsuUpgadeManager로 업그레이드 한다.

5. 업그레이드 순간 이벤트를 통해 업그레이드 한 영웅을 찾아 Player_Base에서 스테이터스를 더해준다.

 

 

각 영웅들 정보 UI 추가

- 유저가 장착한 영웅들의 정보를 보여주는 버튼 UI이며, 클릭 시 해당 영웅을 업그레이드 시킬 수 있다.

 

UI_Hero 클래스추가

using UnityEngine;

public class UI_Hero : MonoBehaviour
{
    [SerializeField] UI_Hero_Btn[] _herobtn;
    [SerializeField] UI_Status _uistatus;
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        PlayManager._play_ready_event += HeroButtonSetting;
    }

    void OnDisable()
    {
        PlayManager._play_ready_event -= HeroButtonSetting;
    }

    void HeroButtonSetting()
    {
        var maxcount = _herobtn.Length;
        var userheroidlist = GameManger._userdata._userherodata;
        for (int i = 0; i < maxcount; i++)
        {
            _herobtn[i].Init(userheroidlist[i]._heroid);
        }
    }
}

- GameManager을 통해 유저가 장착한 정보를 가져온 후 그 정보를 UI_Hero_Btn으로 넘겨 초기화 하도록 한다.

 

UI_Hero_Btn 클래스 추가

using System;
using Mono.Cecil.Cil;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class UI_Hero_Btn : MonoBehaviour
{
    [SerializeField] TextMeshProUGUI _name;
    [SerializeField] TextMeshProUGUI _status;
    [SerializeField] Image _icon;

    const string STATUSDATA = "- {0}\n- {1}%\n- {2}%";
    Player_Base _heroclass;
    public static event Action<int> _hero_click_event;

    void OnDisable()
    {
        StatusUpgradeManager._statusupgrade_event -= Init;
    }

    void OnEnable()
    {
        StatusUpgradeManager._statusupgrade_event += Init;
    }

    public void Init(int heroid)
    {
        //현재 필드에 있는 영웅들 리스트를 가져와 아이디 매칭 
        var herolist = PlayerSpawnManager.instance.GetHeroList();
        var heroclass = herolist.Find(x => x.GetID() == heroid);
        if (heroclass == null)
        {
            this.gameObject.SetActive(false);
            return;
        }

        _heroclass = heroclass;
        this.gameObject.SetActive(true);

        //영웅의 기본 데이터를 가져와 이름, 아이콘을 매칭하고 스테이터스는 필드에 있는 거에서 매칭하기
        var hero_origindata = PlayManager.instance.GetHeroData(_heroclass.GetID());
        _name.text = hero_origindata._name;
        _icon.sprite = hero_origindata._icon;

        var criticalper = _heroclass.GetStatus()._critical * 100;
        var criticaldamageper = _heroclass.GetStatus()._critical_damage * 100;
        _status.text = string.Format(STATUSDATA, _heroclass.GetStatus()._damge, criticalper.ToString("F1"), criticaldamageper.ToString("F1"));
    }

    public void Btn_Click()
    {
        UI_Status._heroclass = () => _heroclass;
        _hero_click_event?.Invoke(_heroclass.GetID());
    }
}

- 받아온 유저의 영웅 정보를 확인하여 이름, 아이콘, 스테이터스를 셋팅한다.

- 클릭히 UI_Status에 현재 영웅의 클래스를 구독하여 보내어 사용할 수 있도록 한다.

 

UI_Status 클래스 추가

using System;
using UnityEngine;

public class UI_Status : MonoBehaviour
{
    [SerializeField] UI_Btn_Status_Upgrade[] _btnstatusupgrades;
    public static Func<Player_Base> _heroclass;
    public static event Action _status_disable_event;

    void OnEnable()
    {
        var maxcount = _btnstatusupgrades.Length;

        for (int i = 0; i < maxcount; i++)
        {
            _btnstatusupgrades[i].Init(_heroclass.Invoke());
        }
    }

    void OnDisable()
    {
        _status_disable_event?.Invoke();
    }
}

- _btnStatusupgrades에는 각 강화 버튼들이 존재하며, _heroclass를 이용해 클릭한 영웅의 정보를 받아와 각 버튼들을 셋팅해준다.

 

UI_Btn_Status_Upgrade 클래스 추가

using System;
using TMPro;
using UnityEngine;

public class UI_Btn_Status_Upgrade : MonoBehaviour
{
    [SerializeField] ESTATUSUPGRADE _estatusupgradekind;

    [SerializeField] TextMeshProUGUI _level;

    [SerializeField] TextMeshProUGUI _statusvalue;

    public static event Action<int, ESTATUSUPGRADE> _statusupgrade_event;
    const float ACTIVEDEALYTIME = 0.2f;//꾸욱 누를때 딜레이
    const float POINTUPDEALYTIME = 1f;//첫 딜레이
    Player_Base _heroclass;
    bool _ispointdown;
    float _delaytime;

    void Update()
    {
        if (_ispointdown == false)
        {
            return;
        }

        _delaytime -= Time.deltaTime;

        if (_delaytime > 0)
        {
            return;
        }

        _delaytime = ACTIVEDEALYTIME;
        ActiveUpgarde();
    }

    public void Init(Player_Base heroclass)
    {
        this._heroclass = heroclass;
        LevelAndValueSetting();
    }

    void LevelAndValueSetting()
    {
        var heroStatus = _heroclass.GetStatus();
        var upgradeLevel = StatusUpgradeManager.instance.GetStatusUpgrade(_heroclass.GetID(), _estatusupgradekind);

        float statusValue = 0f;
        string statusText = "";

        // _estatusupgradekind에 따라 해당하는 status 값 가져오기
        switch (_estatusupgradekind)
        {
            case ESTATUSUPGRADE.ATTACKPER:
                statusValue = heroStatus._damge;
                statusText = _estatusupgradekind + ": " + statusValue.ToString("F1");
                break;
            case ESTATUSUPGRADE.CRITICALPER:
                statusValue = heroStatus._critical * 100; // 퍼센트로 표시
                statusText = _estatusupgradekind + ": " + statusValue.ToString("F1") + "%";
                break;
            case ESTATUSUPGRADE.CRITICALDAMAGE:
                statusValue = heroStatus._critical_damage * 100; // 퍼센트로 표시
                statusText = _estatusupgradekind + ": " + statusValue.ToString("F1") + "%";
                break;
            case ESTATUSUPGRADE.PROTECTMAXHPPER:
                statusValue = heroStatus._hp;
                statusText = _estatusupgradekind + ": " + statusValue.ToString("F1");
                break;
            case ESTATUSUPGRADE.PROTECTARMOR:
                statusValue = heroStatus._armor;
                statusText = _estatusupgradekind + ": " + statusValue.ToString("F1");
                break;
        }

        _statusvalue.text = statusText;
        _level.text = "Lv. " + upgradeLevel.level.ToString();
    }

    public void Btn_EvetnTrigger_PointDown()
    {
        _delaytime = POINTUPDEALYTIME;
        _ispointdown = true;
        ActiveUpgarde();
    }

    public void Btn_EventTrigger_PointUp()
    {
        _ispointdown = false;
    }

    void ActiveUpgarde()
    {
        _statusupgrade_event?.Invoke(_heroclass.GetID(), _estatusupgradekind);
        LevelAndValueSetting();
    }
}

- 받아온 영웅 정보를 이용해 현재 성장 레벨과, 스테이터스 값 등을 셋팅한다.

- 클릭 시 _statusupgrade_event에 구독된 곳으로 값을 넘겨 업그레이드를 진행 후 레벨과 값을 재셋팅하도록 한다.

- 요즘 게임엔 클릭으로 업그레이드 하는 것도 있지만 클릭 후 꾸욱 누르면 업그레이드가 되기 때문에 EventTrigger를 이용하여 꾸욱 누르면 업그레이드 되도록 만듦

 

StatusUpgradeManager 클래스 수정

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

public class StatusUpgradeManager : MonoBehaviour
{
    public static StatusUpgradeManager instance;

    Dictionary<int, Dictionary<ESTATUSUPGRADE, int>> _statusupgrade = new Dictionary<int, Dictionary<ESTATUSUPGRADE, int>>();
    public static event Action<int> _statusupgrade_event;

    [SerializeField]
    List<float> _maxlevelvalue = new List<float>()
    {
        0,
        1000,//공격력 퍼센트
        1,   //크리티컬 퍼센트
        1,   //크리티컬 데미지 퍼센트
        1000,//보호 오브젝트 최대 HP 퍼센트
        100  //보호 오브젝트 최대 방어력(상수)
    };


    const int MAXCOINVALUE = 100000;//최대 강화 총 비용
    const int MAXLEVEL = 100;       //최대 레벨 

    void Awake()
    {
        instance = this;
    }

    void Start()
    {
        _statusupgrade.Clear();
    }

    void OnEnable()
    {
        UI_Btn_Status_Upgrade._statusupgrade_event += StatusLvUpgrade;
    }

    void OnDisable()
    {
        UI_Btn_Status_Upgrade._statusupgrade_event -= StatusLvUpgrade;
    }


    public (int level, float values) GetStatusUpgrade(int heroid, ESTATUSUPGRADE estatusupgrade)
    {
        if (_statusupgrade.ContainsKey(heroid) == false)
        {
            _statusupgrade.Add(heroid, new Dictionary<ESTATUSUPGRADE, int>());
        }
        if (_statusupgrade[heroid].ContainsKey(estatusupgrade) == false)
        {
            _statusupgrade[heroid].Add(estatusupgrade, 0);
        }

        var curlevel = _statusupgrade[heroid][estatusupgrade];

        if (curlevel <= 0)
        {
            return (0, 0);
        }

        //최대와 현재 레벨과 최대 상승값을 이용해 일정한 값이 오르도록 수식작성
        float percentPerLevel = _maxlevelvalue[(int)estatusupgrade] / (MAXLEVEL - 1);
        return (curlevel, percentPerLevel * (curlevel - 1));
    }

    public (int level, float values) GetStatusBeforeUpgrade(int heroid, ESTATUSUPGRADE estatusupgrade)
    {
        if (_statusupgrade.ContainsKey(heroid) == false)
        {
            _statusupgrade.Add(heroid, new Dictionary<ESTATUSUPGRADE, int>());
        }
        if (_statusupgrade[heroid].ContainsKey(estatusupgrade) == false)
        {
            _statusupgrade[heroid].Add(estatusupgrade, 0);
        }

        var curlevel = _statusupgrade[heroid][estatusupgrade];
        curlevel--;

        if (curlevel <= 0)
        {
            return (0, 0);
        }

        //최대와 현재 레벨과 최대 상승값을 이용해 일정한 값이 오르도록 수식작성
        float percentPerLevel = _maxlevelvalue[(int)estatusupgrade] / (MAXLEVEL - 1);
        return (curlevel, percentPerLevel * (curlevel - 1));
    }

    void StatusLvUpgrade(int heroid, ESTATUSUPGRADE estatusupgrade)
    {
        if (_statusupgrade.ContainsKey(heroid) == false)
        {
            _statusupgrade.Add(heroid, new Dictionary<ESTATUSUPGRADE, int>());
        }
        if (_statusupgrade[heroid].ContainsKey(estatusupgrade) == false)
        {
            _statusupgrade[heroid].Add(estatusupgrade, 0);
        }

        var nextlevel = _statusupgrade[heroid][estatusupgrade] + 1;
        if (UpgradeCointSetting(nextlevel) == false)
        {
            //TODO: 돈 없다는 팝업 띄우기
            return;
        }
        _statusupgrade[heroid][estatusupgrade]++;
        _statusupgrade_event?.Invoke(heroid);
    }

    //TODO: 추후 데이터 테이블을 이용해서 비용 할 수 있도록 개선 필요
    bool UpgradeCointSetting(int nextlevel)
    {
        return true;
        var nextlevelcoinvalue = MAXCOINVALUE / MAXLEVEL;
        var currentupgradecoinvalue = nextlevelcoinvalue * nextlevel;

        //TODO: 유저 돈 가져와서 처리할 것 
        var usercoin = 0;
        if (currentupgradecoinvalue > usercoin)
        {
            return false;
        }

        usercoin -= currentupgradecoinvalue;
        return true;
    }

    /// <summary>
    /// 업그레이드 차이값을 계산하여 반환 (새로운 값 - 이전 값)
    /// </summary>
    public St_Status GetStatusUpgradeDifference(int heroid)
    {
        var heroobejct = PlayerSpawnManager.instance.GetHeroList().Find(x => x.GetID() == heroid);
        var baseStatus = heroobejct._so_npc._status;

        var differenceStatus = new St_Status();

        // 이전과 현재 업그레이드 값 가져오기
        var beforeAttack = GetStatusBeforeUpgrade(heroid, ESTATUSUPGRADE.ATTACKPER);
        var currentAttack = GetStatusUpgrade(heroid, ESTATUSUPGRADE.ATTACKPER);

        var beforeCritical = GetStatusBeforeUpgrade(heroid, ESTATUSUPGRADE.CRITICALPER);
        var currentCritical = GetStatusUpgrade(heroid, ESTATUSUPGRADE.CRITICALPER);

        var beforeCriticalDamage = GetStatusBeforeUpgrade(heroid, ESTATUSUPGRADE.CRITICALDAMAGE);
        var currentCriticalDamage = GetStatusUpgrade(heroid, ESTATUSUPGRADE.CRITICALDAMAGE);

        // 공격력 차이 계산
        int beforeAttackValue = beforeAttack.values > 0 ?
            Mathf.FloorToInt(baseStatus._damge * (beforeAttack.values / 100f)) : 0;
        int currentAttackValue = currentAttack.values > 0 ?
            Mathf.FloorToInt(baseStatus._damge * (currentAttack.values / 100f)) : 0;
        differenceStatus._damge = currentAttackValue - beforeAttackValue;

        // 크리티컬 확률 차이 계산
        float beforeCriticalValue = beforeCritical.values > 0 ? (beforeCritical.values / 100f) : 0;
        float currentCriticalValue = currentCritical.values > 0 ? (currentCritical.values / 100f) : 0;
        differenceStatus._critical = currentCriticalValue - beforeCriticalValue;

        // 크리티컬 데미지 차이 계산
        float beforeCriticalDamageValue = beforeCriticalDamage.values > 0 ? (beforeCriticalDamage.values / 100f) : 0;
        float currentCriticalDamageValue = currentCriticalDamage.values > 0 ? (currentCriticalDamage.values / 100f) : 0;
        differenceStatus._critical_damage = currentCriticalDamageValue - beforeCriticalDamageValue;

        return differenceStatus;
    }

    /// <summary>
    /// 현재 업그레이드 값을 St_Status로 반환
    /// </summary>
    public St_Status GetStatusUpgradeAsStatus(int heroid)
    {
        var heroobejct = PlayerSpawnManager.instance.GetHeroList().Find(x => x.GetID() == heroid);
        var baseStatus = heroobejct._so_npc._status;

        var upgradeStatus = new St_Status();

        // 각 업그레이드 타입별로 처리
        var attackUpgrade = GetStatusUpgrade(heroid, ESTATUSUPGRADE.ATTACKPER);
        var criticalUpgrade = GetStatusUpgrade(heroid, ESTATUSUPGRADE.CRITICALPER);
        var criticalDamageUpgrade = GetStatusUpgrade(heroid, ESTATUSUPGRADE.CRITICALDAMAGE);

        // 공격력: 기본값에 퍼센트 적용
        if (attackUpgrade.values > 0)
        {
            upgradeStatus._damge = Mathf.FloorToInt(baseStatus._damge * (attackUpgrade.values / 100f));
        }

        // 크리티컬 확률: 퍼센트 값 그대로 적용
        if (criticalUpgrade.values > 0)
        {
            upgradeStatus._critical = criticalUpgrade.values / 100f;
        }

        // 크리티컬 데미지: 퍼센트 값 그대로 적용
        if (criticalDamageUpgrade.values > 0)
        {
            upgradeStatus._critical_damage = criticalDamageUpgrade.values / 100f;
        }

        return upgradeStatus;
    }

    /// <summary>
    /// 이전 업그레이드 값을 St_Status로 반환
    /// </summary>
    public St_Status GetStatusBeforeUpgradeAsStatus(int heroid)
    {
        // PlayManager에서 기본 헤로 데이터 가져오기
        var heroData = PlayManager.instance.GetHeroData(heroid);
        var heroObject = heroData._playerobject;
        var baseNPC = heroObject.GetComponent<BaseNPC>();
        var baseStatus = baseNPC._so_npc._status;

        var beforeUpgradeStatus = new St_Status();

        // 각 업그레이드 타입별로 처리
        var beforeAttack = GetStatusBeforeUpgrade(heroid, ESTATUSUPGRADE.ATTACKPER);
        var beforeCritical = GetStatusBeforeUpgrade(heroid, ESTATUSUPGRADE.CRITICALPER);
        var beforeCriticalDamage = GetStatusBeforeUpgrade(heroid, ESTATUSUPGRADE.CRITICALDAMAGE);

        // 공격력: 기본값에 퍼센트 적용
        if (beforeAttack.values > 0)
        {
            beforeUpgradeStatus._damge = Mathf.FloorToInt(baseStatus._damge * (beforeAttack.values / 100f));
        }

        // 크리티컬 확률: 퍼센트 값 그대로 적용
        if (beforeCritical.values > 0)
        {
            beforeUpgradeStatus._critical = beforeCritical.values / 100f;
        }

        // 크리티컬 데미지: 퍼센트 값 그대로 적용
        if (beforeCriticalDamage.values > 0)
        {
            beforeUpgradeStatus._critical_damage = beforeCriticalDamage.values / 100f;
        }

        return beforeUpgradeStatus;
    }
}
public enum ESTATUSUPGRADE
{
    NONE,
    ATTACKPER,
    CRITICALPER,
    CRITICALDAMAGE,
    PROTECTMAXHPPER,
    PROTECTARMOR
}

- _statusupgrade에다가 각 영웅별 강화 수치를 저장해두어 사용할 수 있도록 만들었다.

- GetStatusUpgrade는 현재 강화 수치를 가져오도록 하였고 GetStatusBeforeUpgrade가 있어야 현재와 이전 값을 이용해 계산할 수 있어 함수를 정의 하였다.

- UpgradeCointSetting는 비용 계산을 위한 함수며 추후엔 데이터 테이블을 따로 빼둘 계획이다.

- GetStatusUpgradeDifference를 이용해 Player_Base에다가 추가할 값을 지정하는 함수다.

 

업그레이드 된 수치 각 영웅에게 적용

using UnityEngine;

public class Player_Base : BaseNPC
{
    protected int _id;
    public int GetID() => _id;
    [SerializeField] AttackAreaController _attackareacontroller;

    public virtual void OnSpawn(Vector2 spawnpoint)
    {
        transform.position = spawnpoint;
        StatusUpgradeManager._statusupgrade_event += AddStatus;
        UI_Hero_Btn._hero_click_event += AttackAreaView;
        base.OnSpawn();
    }

    public virtual void IDSetting(int id)
    {
        _id = id;
    }

    void AddStatus(int id)
    {
        if (id != _id)
        {
            return;
        }

        // 1. 이전 업그레이드 값 제거
        var beforeUpgradeStatus = StatusUpgradeManager.instance.GetStatusBeforeUpgradeAsStatus(_id);
        base.RemoveStatus(beforeUpgradeStatus);

        // 2. 새로운 업그레이드 값 적용
        var newUpgradeStatus = StatusUpgradeManager.instance.GetStatusUpgradeAsStatus(_id);
        base.AddStatus(newUpgradeStatus);
    }

    void AttackAreaView(int heroid)
    {
        if (heroid != _id)
        {
            return;
        }

        _attackareacontroller.SetAttackAreaVisibility_Active();
    }
}

- StatusUpgradeManager에다가 AddStatus를 구독하여 업그레이드 할때마다 업그레이드 차이 값을 추가하도록 만듦

 

결과

- 강화가 잘 되는 것을 확인할 수 있습니다.

*일부로 보호 오브젝트 HP높이고 강화 비용 무료로 해둔 상태


1차 22:55 ~ 23:30 UI추가/UI_Hero/UI_Hero_Btn(추가)

2차 20:40 ~ 22:00 UI수정/UI_Hero/UI_Hero_Btn/PlayerSpawnManager/StatusUpgardeManager(수정)

3차 250628 19:00 ~ 20:50 UI_Hero/UI_Hero_Btn/PlayerSpawnManager/StatusUpgardeManager/Player_Base(수정)

반응형