XR플밍 - 12. UnityEngine3D Reactive 프로그래밍 - 기업협약 프로젝트 27일차 (9/23)

이형원·2025년 9월 23일
0

XR플밍

목록 보기
204/215

1. 금일 한 업무 정리

  • 회복 증강 기능의 구현 및 시작 시 반영 적용
  • 캐릭터 스테이터스 증강 유닛 반영 구현
  • 성장 스텟 데이터 테이블만 우선 구현

2. 문제의 발생과 해결 과정

캐릭터에 증강 스텟을 반영하기 위해서 어떻게 해야 할지 고민하다가, UnitStatusController로 스텟 반영이 필요한 것을 이벤트를 이용해서 연결하는 방식으로 변경했다.

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem.XR;

public class AugmentManager : InGameSingleton<AugmentManager>
{
    #region Stat
    [Header("Status")]
    public Stat<int> MaxHealth;
    public Stat<float> AttackSpeed;

    [Header("Damage")]
    public Stat<int> PhysicalDamage;
    public Stat<int> MagicDamage;

    [Header("Defense")]
    public Stat<int> PhysicalDefense;
    public Stat<int> MagicDefense;

    [Header("CritRate")]
    public Stat<int> CritChance;

    [Header("Currency")]
    public Stat<float> GoldBonus;

    [Header("CurrentAugment")]
    public AUGData currentAugment;
    // 테스트용
    [SerializeField] private AUGData AUGData;

    // 캐릭터 스테이터스 적용
    private Dictionary<UnitBase, HashSet<string>> _appliedStatusAugments = new();

    private Dictionary<AUGData, HashSet<string>> _appliedCurrencyAugments = new();
    #endregion

    private void Start()
    {
        SelectAugment(AUGData);
    }

    public void SelectAugment(AUGData data)
    {
        currentAugment = data;
    }

    #region Unit Status Augment

    /// <summary>
    /// 유닛 스테이터스 증강 추가
    /// </summary>
    /// <param name="unit"></param>
    public void ApplyAugment(UnitBase unit)
    {
        if (currentAugment == null || !IsAugmentTarget(unit) || currentAugment.EffectType != EffectType.Buff_Debuff) return;

        if (!_appliedStatusAugments.ContainsKey(unit))
            _appliedStatusAugments[unit] = new HashSet<string>();

        if (_appliedStatusAugments[unit].Contains(currentAugment.AUGID))
        {
            Debug.LogWarning($"[AugmentManager] {unit.name} 에 {currentAugment.AUGID} 는 이미 적용됨.");
            return;
        }

        _appliedStatusAugments[unit].Add(currentAugment.AUGID);
        currentAugment.ApplyBuffEffect(unit);
    }

    /// <summary>
    /// 유닛 스테이터스 증강 해제
    /// </summary>
    /// <param name="unit"></param>
    public void ReleaseAugment(UnitBase unit)
    {
        if (currentAugment == null) return;

        if (_appliedStatusAugments.TryGetValue(unit, out var augments))
        {
            if (augments.Contains(currentAugment.AUGID))
            {
                currentAugment.RemoveBuffEffect(unit);
                augments.Remove(currentAugment.AUGID);
                if (augments.Count == 0)
                    _appliedStatusAugments.Remove(unit);

            }
            else
            {
                Debug.LogWarning($"[AugmentManager] {unit.name} 에 {currentAugment.AUGID} 는 적용되지 않아 해제할 수 없음.");
            }
        }
    }

    /// <summary>
    /// 유닛 스테이터스 증강 적용 여부 판정
    /// </summary>
    /// <param name="unit"></param>
    /// <returns></returns>
    public bool IsAugmentTarget(UnitBase unit)
    {
        if (currentAugment.TargetType == EffectTargetType.Ally) return true;
        else if (currentAugment.TargetType == EffectTargetType.SameClassType && unit.Status.Data.ClassSynergy == currentAugment.Class)
        {
            Debug.Log("클래스가 같음");
            return true;
        }
        // 리더일 경우 추가 필요

        return false;
    }

    #endregion

    #region Unit Increase

    public void ApplyHealAugment(UnitBase unit)
    {
        if (currentAugment == null || !IsAugmentTarget(unit) || currentAugment.EffectType != EffectType.Increase) return;

        if (!_appliedStatusAugments.ContainsKey(unit))
            _appliedStatusAugments[unit] = new HashSet<string>();

        if (_appliedStatusAugments[unit].Contains(currentAugment.AUGID))
        {
            Debug.LogWarning($"[AugmentManager] {unit.name} 에 {currentAugment.AUGID} 는 이미 적용됨.");
            return;
        }

        _appliedStatusAugments[unit].Add(currentAugment.AUGID);
        currentAugment.ApplyIncreaseEffect(unit);
    }

    public void ReleaseHealAugment(UnitBase unit)
    {
        if (currentAugment == null) return;

        if (_appliedStatusAugments.TryGetValue(unit, out var augments))
        {
            if (augments.Contains(currentAugment.AUGID))
            {
                augments.Remove(currentAugment.AUGID);

                if (augments.Count == 0)
                    _appliedStatusAugments.Remove(unit);
            }
            else
            {
                Debug.LogWarning($"[AugmentManager] {unit.name} 에 {currentAugment.AUGID} 는 적용되지 않아 해제할 수 없음.");
            }
        }
    }

    #endregion

    #region Currency Augment

    /// <summary>
    /// 재화 획득 증강 추가
    /// </summary>
    public void ApplyAugment()
    {
        if (currentAugment == null) return;

        if (!_appliedCurrencyAugments.ContainsKey(currentAugment))
            _appliedCurrencyAugments[currentAugment] = new HashSet<string>();

        if (_appliedCurrencyAugments[currentAugment].Contains(currentAugment.AUGID))
        {
            Debug.LogWarning($"[AugmentManager] {currentAugment.name} 에 {currentAugment.AUGID} 는 이미 적용됨.");
            return;
        }

        _appliedCurrencyAugments[currentAugment].Add(currentAugment.AUGID);
        for (int i = 0; i < currentAugment.StatTypes.Length; i++)
        {
            AddAugment(currentAugment.StatTypes[i], currentAugment.currentRate, currentAugment.Name);
        }
    }

    /// <summary>
    /// 재화 획득 증강 해제
    /// </summary>
    public void ReleaseAugment()
    {
        if (currentAugment == null) return;

        if (_appliedCurrencyAugments.TryGetValue(currentAugment, out var augments))
        {
            if (augments.Contains(currentAugment.AUGID))
            {
                for (int i = 0; i < currentAugment.StatTypes.Length; i++)
                {
                    RemoveAugment(currentAugment.StatTypes[i], currentAugment.Name);
                }
                augments.Remove(currentAugment.AUGID);

                if (augments.Count == 0)
                    _appliedCurrencyAugments.Remove(currentAugment);
            }
            else
            {
                Debug.LogWarning($"[AugmentManager] {currentAugment.name} 에 {currentAugment.AUGID} 는 적용되지 않아 해제할 수 없음.");
            }
        }
    }

    #endregion

    #region Augument Management
    public void AddAugment(StatType statType, float value, string source, UnitStatusController controller)
    {
        switch (statType)
        {
            case StatType.MaxHealth:
                MaxHealth.AddModifier((int)value, source);
                controller?.AddStat(statType, (int)value, source);
                break;
            case StatType.PhysicalDamage:
                PhysicalDamage.AddModifier((int)value, source);
                controller?.AddStat(statType, (int)value, source);
                break;
            case StatType.MagicDamage:
                MagicDamage.AddModifier((int)value, source);
                controller?.AddStat(statType, (int)value, source);
                break;
            case StatType.CritChance:
                CritChance.AddModifier((int)value, source);
                controller?.AddStat(statType, (int)value, source);
                break;
            case StatType.PhysicalDefense:
                PhysicalDefense.AddModifier((int)value, source);
                controller?.AddStat(statType, (int)value, source);
                break;
            case StatType.MagicDefense:
                MagicDefense.AddModifier((int)value, source);
                controller?.AddStat(statType, (int)value, source);
                break;
            case StatType.AttackSpeed:
                AttackSpeed.AddModifier(value, source);
                controller?.AddStat(statType, (int)value, source);
                break;
        }
    }

    public void AddAugment(StatType statType, float value, string source)
    {
        switch (statType)
        {
            case StatType.GoldBonus:
                GoldBonus.AddModifier(value, source);                
                break;
        }
    }

    public void RemoveAugment(StatType statType, string source, UnitStatusController controller)
    {
        switch (statType)
        {
            case StatType.MaxHealth:
                MaxHealth.RemoveModifier(source);
                controller?.RemoveStat(statType, source);
                break;
            case StatType.PhysicalDamage:
                PhysicalDamage.RemoveModifier(source);
                controller?.RemoveStat(statType, source);
                break;
            case StatType.MagicDamage:
                MagicDamage.RemoveModifier(source);
                controller?.RemoveStat(statType, source);
                break;
            case StatType.CritChance:
                CritChance.RemoveModifier(source);
                controller?.RemoveStat(statType, source);
                break;
            case StatType.PhysicalDefense:
                PhysicalDefense.RemoveModifier(source);
                controller?.RemoveStat(statType, source);
                break;
            case StatType.MagicDefense:
                MagicDefense.RemoveModifier(source);
                controller?.RemoveStat(statType, source);
                break;
            case StatType.AttackSpeed:
                AttackSpeed.RemoveModifier(source);
                controller?.RemoveStat(statType, source);
                break;
        }
    }

    public void RemoveAugment(StatType statType, string source)
    {
        switch (statType)
        {
            case StatType.GoldBonus:
                GoldBonus.RemoveModifier(source);
                break;
        }
    }
    #endregion
}
using UnityEngine;

[CreateAssetMenu(fileName = "AUGEffect", menuName = "Data/AUG/AUGEffect")]
public class AUGData : MetaData
{
    public string AUGID;
    public SubGrade Grade;

    public EffectTargetType TargetType;
    public ClassType Class;

    public TriggerType Trigger;
    public EffectType EffectType;
    public EffectTime ApplyTime;

    public StatType[] StatTypes;

    public float[] Rate = new float[3];

    public float currentRate;

    /// <summary>
    /// 증강 등급 적용
    /// </summary>
    /// <param name="grade"></param>
    public void ApplyRate(SubGrade grade)
    {
        switch (grade)
        {
            case SubGrade.SILVER: currentRate = Rate[0]; break;
            case SubGrade.GOLD: currentRate = Rate[1]; break;
            case SubGrade.PRISM: currentRate = Rate[2]; break;
            default: currentRate = 0; break;
        }
    }

    #region Status Augment

    /// <summary>
    /// 캐릭터 스테이터스 증강 적용
    /// </summary>
    /// <param name="unit"></param>
    public void ApplyBuffEffect(UnitBase unit)
    {
        ApplyRate(Grade);
        for (int i = 0; i < StatTypes.Length; i++)
        {
            unit.Status.Data.UnitStats[0].AddAugments(StatTypes[i], currentRate, unit.Status.Data.Name, unit.StatusController);
        }
    }

    /// <summary>
    /// 캐릭터 스테이터스 증강 삭제
    /// </summary>
    /// <param name="unit"></param>
    public void RemoveBuffEffect(UnitBase unit)
    {
        for (int i = 0; i < StatTypes.Length; i++)
        {
            unit.Status.Data.UnitStats[0].RemoveAugments(StatTypes[i], unit.Status.Data.Name, unit.StatusController);
        }
    }

    #endregion

    #region Increase Augment

    /// <summary>
    /// 회복 증강의 회복 적용
    /// </summary>
    /// <param name="unit"></param>
    public void ApplyIncreaseEffect(UnitBase unit)
    {
        ApplyRate(Grade);
        if (StatTypes[0] == StatType.MaxHealth)
        {
            int increaseRate = (int)(unit.Status.Data.UnitStats[0].MaxHealth * (1 + currentRate / 100));
            unit.StatusController.IncreaseHealth(increaseRate);
        }
        else if (StatTypes[0] == StatType.MaxMana)
        {
            int increaseRate = (int)(unit.Status.Data.UnitStats[0].MaxMana * (1 + currentRate / 100));
            unit.StatusController.IncreaseMana(increaseRate);
        }
    }

    #endregion
}
using System;
using UnityEngine;

[System.Serializable]
public class UnitStats
{
    [Header("Status")]
    public int MaxHealth;
    public int MaxMana;
    public int ManaGain;
    public float AttackSpeed;
    public float MoveSpeed;

    [Header("Damage")]
    public int PhysicalDamage;
    public int MagicDamage;

    [Header("CritRate")]
    public int CritChance;

    [Header("Defense")]
    public int PhysicalDefense;
    public int MagicDefense;

    [Header("Range")]
    public float AttackRange;
    public int AttackCount;

    public void AddAugments(StatType status, float rate, string name, UnitStatusController controller)
    {
        StatEffectModifier modifier = CalculateAugment(status, rate);
        AugmentManager.Instance.AddAugment(modifier.StatType, modifier.Value, name, controller);
    }

    public StatEffectModifier CalculateAugment(StatType status, float rate)
    {
        StatEffectModifier modifier = new StatEffectModifier();
        modifier.StatType = status;
        switch (modifier.StatType)
        {
            case StatType.MaxHealth: modifier.Value = Mathf.RoundToInt(MaxHealth * (rate / 100)); break;
            case StatType.MaxMana: modifier.Value = Mathf.RoundToInt(MaxMana * (rate / 100)); break;
            case StatType.ManaGain: modifier.Value = Mathf.RoundToInt(ManaGain * (rate / 100)); break;
            case StatType.AttackSpeed: modifier.Value = AttackSpeed * (rate / 100); break;
            case StatType.MoveSpeed: modifier.Value = MoveSpeed * (rate / 100); break;
            case StatType.PhysicalDamage: modifier.Value = Mathf.RoundToInt(PhysicalDamage * (rate / 100)); break;
            case StatType.MagicDamage: modifier.Value = Mathf.RoundToInt(MagicDamage * (rate / 100)); break;
            case StatType.CritChance: modifier.Value = Mathf.RoundToInt(CritChance * (rate / 100)); break;
            case StatType.PhysicalDefense: modifier.Value = Mathf.RoundToInt(PhysicalDefense * (rate / 100)); break;
            case StatType.MagicDefense: modifier.Value = Mathf.RoundToInt(MagicDefense * (rate / 100)); break;
            case StatType.AttackRange: modifier.Value = AttackRange * (rate / 100); break;
            case StatType.AttackCount: modifier.Value = Mathf.RoundToInt(AttackCount * (rate / 100)); break;
            default: modifier.Value = 0; break;
        }

        return modifier;
    }

    public void RemoveAugments(StatType status, string name, UnitStatusController controller)
    {
        AugmentManager.Instance.RemoveAugment(status, name, controller);
    }
}

3. 개선점 및 과제

3.1 증강 시스템 세부 조건 반영

3.2 성장 시스템 스텟 반영

3.3 UI 폴리싱

3.4 리팩토링

profile
게임 만들러 코딩 공부중

0개의 댓글