From dc354076867b314ba5b2380eccfc8acdc8c6ee83 Mon Sep 17 00:00:00 2001 From: icetea1110 Date: Tue, 7 Jul 2026 14:49:00 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20Warrior=20=EC=98=81=EC=97=AD=20?= =?UTF-8?q?=ED=8C=A8=EC=8B=9C=EB=B8=8C=20=EC=8A=A4=ED=82=AC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Prefabs/PlayerCharacter/Archer.prefab | 20 ++ .../Prefabs/PlayerCharacter/Warrior.prefab | 20 ++ .../Prefabs/PlayerCharacter/Wizard.prefab | 20 ++ .../Attack/SingleTargetAttackBehaviour.cs | 1 + .../Assets/Scripts/Battle/Entity.cs | 21 ++- .../Assets/Scripts/Battle/WaveController.cs | 4 + .../Core/SkillTree/ActiveSkillContext.cs | 27 +++ .../Core/SkillTree/ActiveSkillContext.cs.meta | 2 + .../Scripts/Core/SkillTree/ActiveSkillData.cs | 34 +++- .../Core/SkillTree/ActiveSkillExecutor.cs | 178 ++++++++++++++++++ .../SkillTree/ActiveSkillExecutor.cs.meta | 2 + .../SkillTree/IActiveSkillCooldownModifier.cs | 5 + .../IActiveSkillCooldownModifier.cs.meta | 2 + .../Core/SkillTree/PassiveSkillData.cs | 6 + .../Core/SkillTree/PassiveSkillRuntime.cs | 23 +++ .../SkillTree/PassiveSkillRuntime.cs.meta | 2 + .../Core/SkillTree/SkillTreeComponent.cs | 45 ++++- .../SkillTree/Skills.meta} | 6 +- .../Core/SkillTree/Skills/ActiveSkills.meta | 8 + .../ActiveSkills/StatBuffActiveSkill.cs | 24 +++ .../ActiveSkills/StatBuffActiveSkill.cs.meta | 2 + .../Core/SkillTree/Skills/PassiveSkills.meta | 8 + .../CooldownReductionPassiveSkill.cs | 15 ++ .../CooldownReductionPassiveSkill.cs.meta | 2 + .../KillStackAttackPassiveSkill.cs | 92 +++++++++ .../KillStackAttackPassiveSkill.cs.meta | 2 + .../LowHealthDamageHealPassiveSkill.cs | 56 ++++++ .../LowHealthDamageHealPassiveSkill.cs.meta | 2 + .../LowHealthRecoveryPassiveSkill.cs | 74 ++++++++ .../LowHealthRecoveryPassiveSkill.cs.meta | 2 + .../PassiveSkills/StatModifierPassiveSkill.cs | 16 ++ .../StatModifierPassiveSkill.cs.meta | 2 + .../SO/SkillData/Warrior/BasicSkill.asset | 19 -- .../SO/StatData/Player/ArcherStat.asset | 2 +- .../SO/StatData/Player/WarriorStat.asset | 2 +- .../SO/StatData/Player/WizardStat.asset | 2 +- .../UI/InDungeon/ActiveSkillButtonUI.cs | 133 +++++++++++++ .../UI/InDungeon/ActiveSkillButtonUI.cs.meta | 2 + 38 files changed, 850 insertions(+), 33 deletions(-) create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillContext.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillContext.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillExecutor.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillExecutor.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/IActiveSkillCooldownModifier.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/IActiveSkillCooldownModifier.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillRuntime.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillRuntime.cs.meta rename Fantasy-Grower/Assets/Scripts/{SO/SkillData/Warrior/BasicSkill.asset.meta => Core/SkillTree/Skills.meta} (52%) create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/ActiveSkills.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/ActiveSkills/StatBuffActiveSkill.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/ActiveSkills/StatBuffActiveSkill.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/CooldownReductionPassiveSkill.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/CooldownReductionPassiveSkill.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/KillStackAttackPassiveSkill.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/KillStackAttackPassiveSkill.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/LowHealthDamageHealPassiveSkill.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/LowHealthDamageHealPassiveSkill.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/LowHealthRecoveryPassiveSkill.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/LowHealthRecoveryPassiveSkill.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/StatModifierPassiveSkill.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/StatModifierPassiveSkill.cs.meta delete mode 100644 Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior/BasicSkill.asset create mode 100644 Fantasy-Grower/Assets/Scripts/UI/InDungeon/ActiveSkillButtonUI.cs create mode 100644 Fantasy-Grower/Assets/Scripts/UI/InDungeon/ActiveSkillButtonUI.cs.meta diff --git a/Fantasy-Grower/Assets/Prefabs/PlayerCharacter/Archer.prefab b/Fantasy-Grower/Assets/Prefabs/PlayerCharacter/Archer.prefab index 7ec73424..c4cf0ef3 100644 --- a/Fantasy-Grower/Assets/Prefabs/PlayerCharacter/Archer.prefab +++ b/Fantasy-Grower/Assets/Prefabs/PlayerCharacter/Archer.prefab @@ -3268,6 +3268,7 @@ GameObject: - component: {fileID: -934695906495670747} - component: {fileID: 4682663325673948411} - component: {fileID: 713001000000000003} + - component: {fileID: 713002000000000003} m_Layer: 0 m_Name: Archer m_TagString: Untagged @@ -6336,6 +6337,25 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: targets: {fileID: 430098703366088485} +--- !u!114 &713002000000000003 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5857381939795342657} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0733f8d1a3f642ca9bcfa8813c6cb14b, type: 3} + m_Name: + m_EditorClassIdentifier: + enableKeyboardInput: 1 + slotKeys: + - 49 + - 50 + - 51 + - 52 + - 53 --- !u!114 &6034931146787275272 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Fantasy-Grower/Assets/Prefabs/PlayerCharacter/Warrior.prefab b/Fantasy-Grower/Assets/Prefabs/PlayerCharacter/Warrior.prefab index b2ac5d97..c078eb7b 100644 --- a/Fantasy-Grower/Assets/Prefabs/PlayerCharacter/Warrior.prefab +++ b/Fantasy-Grower/Assets/Prefabs/PlayerCharacter/Warrior.prefab @@ -3670,6 +3670,7 @@ GameObject: - component: {fileID: 9091531221583279582} - component: {fileID: 8937113908660665095} - component: {fileID: 713001000000000002} + - component: {fileID: 713002000000000002} m_Layer: 0 m_Name: Warrior m_TagString: Untagged @@ -6748,6 +6749,25 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: targets: {fileID: 5115161450785208119} +--- !u!114 &713002000000000002 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6499316856751531827} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0733f8d1a3f642ca9bcfa8813c6cb14b, type: 3} + m_Name: + m_EditorClassIdentifier: + enableKeyboardInput: 1 + slotKeys: + - 49 + - 50 + - 51 + - 52 + - 53 --- !u!114 &6107641911005882464 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Fantasy-Grower/Assets/Prefabs/PlayerCharacter/Wizard.prefab b/Fantasy-Grower/Assets/Prefabs/PlayerCharacter/Wizard.prefab index 22e9f85c..0228975b 100644 --- a/Fantasy-Grower/Assets/Prefabs/PlayerCharacter/Wizard.prefab +++ b/Fantasy-Grower/Assets/Prefabs/PlayerCharacter/Wizard.prefab @@ -3173,6 +3173,7 @@ GameObject: - component: {fileID: 4753827104680141411} - component: {fileID: 4530378413672165486} - component: {fileID: 713001000000000004} + - component: {fileID: 713002000000000004} m_Layer: 0 m_Name: Wizard m_TagString: Untagged @@ -6319,6 +6320,25 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: targets: {fileID: 6694508944490675349} +--- !u!114 &713002000000000004 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6063808467322970300} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0733f8d1a3f642ca9bcfa8813c6cb14b, type: 3} + m_Name: + m_EditorClassIdentifier: + enableKeyboardInput: 1 + slotKeys: + - 49 + - 50 + - 51 + - 52 + - 53 --- !u!114 &4616874832124039420 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Attack/SingleTargetAttackBehaviour.cs b/Fantasy-Grower/Assets/Scripts/Battle/Attack/SingleTargetAttackBehaviour.cs index f8354b76..d2e61a01 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Attack/SingleTargetAttackBehaviour.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Attack/SingleTargetAttackBehaviour.cs @@ -31,6 +31,7 @@ public override bool TryAttack(Entity attacker) attacker.CriticalPercentage ); target.TakeDamage(damage); + attacker.NotifyDamageDealt(target, damage); return true; } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs b/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs index ace7d86d..6ebf7de2 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs @@ -36,8 +36,11 @@ public abstract class Entity : MonoBehaviour /// 실제 피해로 HP가 감소했을 때 피해 직전과 직후의 HP를 순서대로 전달한다. public event Action OnDamageTaken; + /// 다른 Entity에게 피해를 입혔을 때 대상과 실제 피해량을 전달한다. + public event Action OnDamageDealt; + /// Update문이 호출될 때 발화된다. - protected event Action OnUpdated; + public event Action OnUpdated; protected virtual void Awake() { @@ -150,6 +153,22 @@ public virtual void TakeDamage(float damage) Death(); } + public void Heal(float amount) + { + if (amount <= 0f || Hp <= 0f) + return; + + Hp = Mathf.Min(MaxHp, Hp + amount); + } + + public void NotifyDamageDealt(Entity target, float damage) + { + if (target == null || damage <= 0f) + return; + + OnDamageDealt?.Invoke(target, damage); + } + /// /// 새로운 스탯 보정값을 추가하고 해당 효과만 갱신하거나 제거할 수 있는 고유 Handle을 반환합니다. /// 같은 스킬이 여러 번 사용되어도 호출마다 다른 Handle이 발급되므로 각각 독립적으로 중첩됩니다. diff --git a/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs b/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs index 28f49567..97ac36c3 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs @@ -12,6 +12,9 @@ public class WaveController : MonoBehaviour /// 현재 웨이브의 모든 적이 사망했을 때 발화된다. public static event Action OnAllEnemiesDead; + /// 현재 웨이브에 등록된 적 하나가 사망했을 때 발화된다. + public static event Action OnEnemyDiedGlobal; + private int _aliveCount; private readonly HashSet _activeEnemies = new(); @@ -99,6 +102,7 @@ private void OnEnemyDied(Entity entity) entity.OnDied -= OnEnemyDied; _activeEnemies.Remove(enemy); _aliveCount = Mathf.Max(0, _aliveCount - 1); + OnEnemyDiedGlobal?.Invoke(enemy); if (_aliveCount == 0) OnAllEnemiesDead?.Invoke(); diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillContext.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillContext.cs new file mode 100644 index 00000000..c05584dc --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillContext.cs @@ -0,0 +1,27 @@ +/// +/// 액티브 스킬이 실행될 때 필요한 런타임 정보를 전달합니다. +/// ScriptableObject인 스킬 데이터가 Entity 핸들 같은 런타임 상태를 직접 보관하지 않도록 분리합니다. +/// +public readonly struct ActiveSkillContext +{ + public ActiveSkillContext( + ActiveSkillExecutor executor, + SkillTreeComponent skillTreeComponent, + Entity caster, + ActiveSkillData skill, + int slotIndex + ) + { + Executor = executor; + SkillTreeComponent = skillTreeComponent; + Caster = caster; + Skill = skill; + SlotIndex = slotIndex; + } + + public ActiveSkillExecutor Executor { get; } + public SkillTreeComponent SkillTreeComponent { get; } + public Entity Caster { get; } + public ActiveSkillData Skill { get; } + public int SlotIndex { get; } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillContext.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillContext.cs.meta new file mode 100644 index 00000000..636aa8d5 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillContext.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a0bbc9ade6e947329282fb1ee34afbbe diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillData.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillData.cs index dc8827f0..6e2ee953 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillData.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillData.cs @@ -1,7 +1,33 @@ -using UnityEngine; +using UnityEngine; /// -/// 액티브 스킬 데이터의 추상 기반 클래스. -/// UseSkill()을 구현하여 스킬별 발동 로직을 정의한다. +/// 액티브 스킬 데이터의 추상 기반 클래스입니다. +/// 실제 런타임 효과는 사용자, 슬롯, 실행기 정보를 담은 ActiveSkillContext를 통해 실행합니다. /// -public abstract class ActiveSkillData : SkillData { } +public abstract class ActiveSkillData : SkillData +{ + /// + /// 기존 테스트/에디터 호출 호환용 메서드입니다. + /// 실제 전투에서는 TryUseSkill(ActiveSkillContext)를 사용합니다. + /// + public override void UseSkill() { } + + public bool TryUseSkill(ActiveSkillContext context) + { + if (!CanUseSkill(context)) + return false; + + UseSkill(context); + return true; + } + + protected virtual bool CanUseSkill(ActiveSkillContext context) + { + return context.Caster != null && context.Executor != null; + } + + protected virtual void UseSkill(ActiveSkillContext context) + { + UseSkill(); + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillExecutor.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillExecutor.cs new file mode 100644 index 00000000..16d82c09 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillExecutor.cs @@ -0,0 +1,178 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +/// +/// 플레이어에게 장착된 액티브 스킬을 실행하고 쿨다운과 일시적 스탯 보정을 관리합니다. +/// +[DisallowMultipleComponent] +[RequireComponent(typeof(Player))] +[RequireComponent(typeof(SkillTreeComponent))] +public sealed class ActiveSkillExecutor : MonoBehaviour +{ + [Header("입력(컴퓨터 환경 전용)")] + [SerializeField] + private bool enableKeyboardInput = true; + + [SerializeField] + private KeyCode[] slotKeys = + { + KeyCode.Alpha1, + KeyCode.Alpha2, + KeyCode.Alpha3, + KeyCode.Alpha4, + KeyCode.Alpha5, + }; + + private readonly List temporaryModifierHandles = new(); + + private Player player; + private SkillTreeComponent skillTreeComponent; + private float[] cooldownEndTimes; + + private void Awake() + { + player = GetComponent(); + skillTreeComponent = GetComponent(); + } + + private void Start() + { + EnsureCooldownBuffer(); + } + + private void Update() + { + if (!enableKeyboardInput) + return; + + int slotCount = Mathf.Min(slotKeys.Length, GetActiveSlotCount()); + for (int i = 0; i < slotCount; i++) + { + if (Input.GetKeyDown(slotKeys[i])) + TryUseSkill(i); + } + } + + private void OnDisable() + { + ClearTemporaryModifiers(); + } + + public bool TryUseSkill(int slotIndex) + { + ActiveSkillData skill = GetEquippedSkill(slotIndex); + if (skill == null) + return false; + + EnsureCooldownBuffer(); + if (slotIndex < 0 || slotIndex >= cooldownEndTimes.Length) + return false; + + if (GetCooldownRemaining(slotIndex) > 0f) + return false; + + var context = new ActiveSkillContext(this, skillTreeComponent, player, skill, slotIndex); + + if (!skill.TryUseSkill(context)) + return false; + + float cooldown = GetModifiedCooldown(skill); + if (cooldown > 0f) + cooldownEndTimes[slotIndex] = Time.time + cooldown; + + return true; + } + + public float GetCooldownRemaining(int slotIndex) + { + if (cooldownEndTimes == null || slotIndex < 0 || slotIndex >= cooldownEndTimes.Length) + return 0f; + + return Mathf.Max(0f, cooldownEndTimes[slotIndex] - Time.time); + } + + public ActiveSkillData GetEquippedSkill(int slotIndex) + { + if (skillTreeComponent == null) + return null; + + return skillTreeComponent.GetEquippedActive(slotIndex); + } + + public float GetCooldownRatio(int slotIndex) + { + ActiveSkillData skill = GetEquippedSkill(slotIndex); + float cooldown = GetModifiedCooldown(skill); + if (skill == null || cooldown <= 0f) + return 0f; + + return Mathf.Clamp01(GetCooldownRemaining(slotIndex) / cooldown); + } + + private float GetModifiedCooldown(ActiveSkillData skill) + { + if (skill == null || skill.Cooldown <= 0f) + return 0f; + + float multiplier = + skillTreeComponent != null ? skillTreeComponent.GetActiveSkillCooldownMultiplier() : 1f; + return skill.Cooldown * multiplier; + } + + public EntityStatModifierHandle ApplyTemporaryModifier( + EntityStatModifier modifier, + float duration + ) + { + EntityStatModifierHandle handle = player.ApplyStatModifier(modifier); + temporaryModifierHandles.Add(handle); + + if (duration > 0f) + StartCoroutine(RemoveTemporaryModifierAfterDelay(handle, duration)); + + return handle; + } + + private IEnumerator RemoveTemporaryModifierAfterDelay( + EntityStatModifierHandle handle, + float duration + ) + { + yield return new WaitForSeconds(duration); + RemoveTemporaryModifier(handle); + } + + private void RemoveTemporaryModifier(EntityStatModifierHandle handle) + { + if (!handle.IsValid) + return; + + player.RemoveStatModifier(handle); + temporaryModifierHandles.Remove(handle); + } + + private void ClearTemporaryModifiers() + { + StopAllCoroutines(); + + foreach (EntityStatModifierHandle handle in temporaryModifierHandles) + player.RemoveStatModifier(handle); + + temporaryModifierHandles.Clear(); + } + + private void EnsureCooldownBuffer() + { + int slotCount = GetActiveSlotCount(); + if (cooldownEndTimes != null && cooldownEndTimes.Length == slotCount) + return; + + cooldownEndTimes = new float[slotCount]; + } + + private int GetActiveSlotCount() + { + return skillTreeComponent.GetEquippedActives()?.Count ?? 0; + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillExecutor.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillExecutor.cs.meta new file mode 100644 index 00000000..27c76544 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillExecutor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0733f8d1a3f642ca9bcfa8813c6cb14b diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/IActiveSkillCooldownModifier.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/IActiveSkillCooldownModifier.cs new file mode 100644 index 00000000..45fa0326 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/IActiveSkillCooldownModifier.cs @@ -0,0 +1,5 @@ +public interface IActiveSkillCooldownModifier +{ + /// 0.2 = 액티브 스킬 쿨다운 20% 감소. + float CooldownReductionRate { get; } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/IActiveSkillCooldownModifier.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/IActiveSkillCooldownModifier.cs.meta new file mode 100644 index 00000000..da20270c --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/IActiveSkillCooldownModifier.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 05559a5f4b4d4deeb747249cd4eaf123 diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillData.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillData.cs index caddb82f..cad37fb4 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillData.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillData.cs @@ -14,4 +14,10 @@ public override void UseSkill() { } /// SkillTreeComponent.RecalculatePassives()에서 일괄 호출된다. /// public abstract void ApplyPassive(ref EntityStatModifier modifier); + + /// + /// 장착 중에만 유지되어야 하는 런타임 패시브 효과를 생성한다. + /// 단순 스탯 패시브는 null을 반환하면 된다. + /// + public virtual PassiveSkillRuntime CreateRuntime(PassiveSkillRuntimeContext context) => null; } diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillRuntime.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillRuntime.cs new file mode 100644 index 00000000..54f1b9ca --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillRuntime.cs @@ -0,0 +1,23 @@ +public readonly struct PassiveSkillRuntimeContext +{ + public PassiveSkillRuntimeContext(SkillTreeComponent skillTreeComponent, Entity owner) + { + SkillTreeComponent = skillTreeComponent; + Owner = owner; + } + + public SkillTreeComponent SkillTreeComponent { get; } + public Entity Owner { get; } +} + +public abstract class PassiveSkillRuntime +{ + protected PassiveSkillRuntime(PassiveSkillRuntimeContext context) + { + Context = context; + } + + protected PassiveSkillRuntimeContext Context { get; } + + public abstract void Dispose(); +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillRuntime.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillRuntime.cs.meta new file mode 100644 index 00000000..ff7f0658 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillRuntime.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 054ebed1684b47539fa2f1ce5469bc95 diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs index de44de99..22d9fb80 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs @@ -23,6 +23,7 @@ public class SkillTreeComponent : MonoBehaviour private List equippedActives; private List equippedPassives; private readonly List passiveModifierHandles = new(); + private readonly List passiveRuntimes = new(); private ISkillTreeStrategy strategy; private Entity entity; @@ -63,6 +64,11 @@ private void Awake() RecalculatePassives(); } + private void OnDestroy() + { + ClearPassiveEffects(); + } + /// /// 트리 데이터를 결정한다. GameManager가 존재하고 선택된 직업의 트리가 /// 등록되어 있으면 그것을 사용하고, 아니면 직렬화된 treeData를 유지한다. @@ -116,9 +122,7 @@ public bool TryUnlockNode(SkillNodeData node) /// private void RecalculatePassives() { - foreach (EntityStatModifierHandle handle in passiveModifierHandles) - entity.RemoveStatModifier(handle); - passiveModifierHandles.Clear(); + ClearPassiveEffects(); foreach (PassiveSkillData passive in equippedPassives) { @@ -128,7 +132,42 @@ private void RecalculatePassives() EntityStatModifier modifier = EntityStatModifier.Zero; passive.ApplyPassive(ref modifier); passiveModifierHandles.Add(entity.ApplyStatModifier(modifier)); + + PassiveSkillRuntime runtime = passive.CreateRuntime( + new PassiveSkillRuntimeContext(this, entity) + ); + if (runtime != null) + passiveRuntimes.Add(runtime); + } + } + + public float GetActiveSkillCooldownMultiplier() + { + if (equippedPassives == null) + return 1f; + + float reduction = 0f; + foreach (PassiveSkillData passive in equippedPassives) + { + if (passive is IActiveSkillCooldownModifier cooldownModifier) + reduction += cooldownModifier.CooldownReductionRate; } + + return Mathf.Max(0f, 1f - reduction); + } + + private void ClearPassiveEffects() + { + foreach (PassiveSkillRuntime runtime in passiveRuntimes) + runtime.Dispose(); + passiveRuntimes.Clear(); + + foreach (EntityStatModifierHandle handle in passiveModifierHandles) + { + if (entity != null) + entity.RemoveStatModifier(handle); + } + passiveModifierHandles.Clear(); } // ─── 액티브 스킬 장착 ───────────────────────────────────────── diff --git a/Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior/BasicSkill.asset.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills.meta similarity index 52% rename from Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior/BasicSkill.asset.meta rename to Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills.meta index 6ab5c5f9..5a39a40e 100644 --- a/Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior/BasicSkill.asset.meta +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills.meta @@ -1,8 +1,8 @@ fileFormatVersion: 2 -guid: da12e4dcc00447545828f224e06be591 -NativeFormatImporter: +guid: 4e2142a09f936014cb9b17c83161d2fd +folderAsset: yes +DefaultImporter: externalObjects: {} - mainObjectFileID: 11400000 userData: assetBundleName: assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/ActiveSkills.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/ActiveSkills.meta new file mode 100644 index 00000000..7702eb0c --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/ActiveSkills.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0eb34d056c057584b94709ffaa926bc9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/ActiveSkills/StatBuffActiveSkill.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/ActiveSkills/StatBuffActiveSkill.cs new file mode 100644 index 00000000..26082c06 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/ActiveSkills/StatBuffActiveSkill.cs @@ -0,0 +1,24 @@ +using UnityEngine; + +[CreateAssetMenu( + fileName = "StatBuffActiveSkill", + menuName = "ScriptableObjects/SkillTree/Active/Stat Buff" +)] +public sealed class StatBuffActiveSkill : ActiveSkillData +{ + [SerializeField] + private EntityStatModifier modifier; + + [SerializeField, Min(0f)] + private float duration = 5f; + + protected override bool CanUseSkill(ActiveSkillContext context) + { + return base.CanUseSkill(context) && duration > 0f; + } + + protected override void UseSkill(ActiveSkillContext context) + { + context.Executor.ApplyTemporaryModifier(modifier, duration); + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/ActiveSkills/StatBuffActiveSkill.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/ActiveSkills/StatBuffActiveSkill.cs.meta new file mode 100644 index 00000000..658f49d3 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/ActiveSkills/StatBuffActiveSkill.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 040e06596a3f4feb9fc3b889fdf7d6cc diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills.meta new file mode 100644 index 00000000..ca69710f --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 34d01395847d39844aab4c4434d499a3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/CooldownReductionPassiveSkill.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/CooldownReductionPassiveSkill.cs new file mode 100644 index 00000000..13d2d943 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/CooldownReductionPassiveSkill.cs @@ -0,0 +1,15 @@ +using UnityEngine; + +[CreateAssetMenu( + fileName = "CooldownReductionPassiveSkill", + menuName = "ScriptableObjects/SkillTree/Passive/Cooldown Reduction" +)] +public sealed class CooldownReductionPassiveSkill : PassiveSkillData, IActiveSkillCooldownModifier +{ + [SerializeField, Range(0f, 1f)] + private float cooldownReductionRate; + + public float CooldownReductionRate => cooldownReductionRate; + + public override void ApplyPassive(ref EntityStatModifier modifier) { } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/CooldownReductionPassiveSkill.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/CooldownReductionPassiveSkill.cs.meta new file mode 100644 index 00000000..cfa7b59c --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/CooldownReductionPassiveSkill.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0aff9f200441458aa7efb6ef8fd87091 diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/KillStackAttackPassiveSkill.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/KillStackAttackPassiveSkill.cs new file mode 100644 index 00000000..076cc6bb --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/KillStackAttackPassiveSkill.cs @@ -0,0 +1,92 @@ +using UnityEngine; + +[CreateAssetMenu( + fileName = "KillStackAttackPassiveSkill", + menuName = "ScriptableObjects/SkillTree/Passive/Kill Stack Attack" +)] +public sealed class KillStackAttackPassiveSkill : PassiveSkillData +{ + [SerializeField] + private EntityStatModifier modifierPerStack; + + [SerializeField, Min(0)] + private int maxStack; + + public override void ApplyPassive(ref EntityStatModifier modifier) { } + + public override PassiveSkillRuntime CreateRuntime(PassiveSkillRuntimeContext context) + { + return new Runtime(context, modifierPerStack, maxStack); + } + + private sealed class Runtime : PassiveSkillRuntime + { + private readonly EntityStatModifier modifierPerStack; + private readonly int maxStack; + private EntityStatModifierHandle modifierHandle; + private int currentStack; + + public Runtime( + PassiveSkillRuntimeContext context, + EntityStatModifier modifierPerStack, + int maxStack + ) + : base(context) + { + this.modifierPerStack = modifierPerStack; + this.maxStack = maxStack; + WaveController.OnEnemyDiedGlobal += HandleEnemyDied; + } + + public override void Dispose() + { + WaveController.OnEnemyDiedGlobal -= HandleEnemyDied; + + if (Context.Owner != null && modifierHandle.IsValid) + Context.Owner.RemoveStatModifier(modifierHandle); + + modifierHandle = default; + } + + private void HandleEnemyDied(Enemy _) + { + if (Context.Owner == null || Context.Owner.Hp <= 0f) + return; + + if (maxStack > 0 && currentStack >= maxStack) + return; + + currentStack++; + EntityStatModifier modifier = ScaleModifier(modifierPerStack, currentStack); + + if (modifierHandle.IsValid) + { + if (!Context.Owner.UpdateStatModifier(modifierHandle, modifier)) + modifierHandle = Context.Owner.ApplyStatModifier(modifier); + } + else + { + modifierHandle = Context.Owner.ApplyStatModifier(modifier); + } + } + + private static EntityStatModifier ScaleModifier(EntityStatModifier modifier, int stack) + { + return new EntityStatModifier + { + BonusHp = modifier.BonusHp * stack, + BonusHpRecovery = modifier.BonusHpRecovery * stack, + BonusDamageReduction = modifier.BonusDamageReduction * stack, + BonusAttackPower = modifier.BonusAttackPower * stack, + BonusAttackSpeed = modifier.BonusAttackSpeed * stack, + BonusCriticalPercentage = modifier.BonusCriticalPercentage * stack, + BonusHpRate = modifier.BonusHpRate * stack, + BonusHpRecoveryRate = modifier.BonusHpRecoveryRate * stack, + BonusDamageReductionRate = modifier.BonusDamageReductionRate * stack, + BonusAttackPowerRate = modifier.BonusAttackPowerRate * stack, + BonusAttackSpeedRate = modifier.BonusAttackSpeedRate * stack, + BonusCriticalPercentageRate = modifier.BonusCriticalPercentageRate * stack, + }; + } + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/KillStackAttackPassiveSkill.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/KillStackAttackPassiveSkill.cs.meta new file mode 100644 index 00000000..4e6aedb6 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/KillStackAttackPassiveSkill.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 049d4e3d2f4a48dabfd4dc22a15854be diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/LowHealthDamageHealPassiveSkill.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/LowHealthDamageHealPassiveSkill.cs new file mode 100644 index 00000000..351f775c --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/LowHealthDamageHealPassiveSkill.cs @@ -0,0 +1,56 @@ +using UnityEngine; + +[CreateAssetMenu( + fileName = "LowHealthDamageHealPassiveSkill", + menuName = "ScriptableObjects/SkillTree/Passive/Low Health Damage Heal" +)] +public sealed class LowHealthDamageHealPassiveSkill : PassiveSkillData +{ + [SerializeField, Range(0f, 1f)] + private float healthThreshold = 0.5f; + + [SerializeField, Range(0f, 1f)] + private float damageHealRate; + + public override void ApplyPassive(ref EntityStatModifier modifier) { } + + public override PassiveSkillRuntime CreateRuntime(PassiveSkillRuntimeContext context) + { + return new Runtime(context, healthThreshold, damageHealRate); + } + + private sealed class Runtime : PassiveSkillRuntime + { + private readonly float healthThreshold; + private readonly float damageHealRate; + + public Runtime( + PassiveSkillRuntimeContext context, + float healthThreshold, + float damageHealRate + ) + : base(context) + { + this.healthThreshold = healthThreshold; + this.damageHealRate = damageHealRate; + Context.Owner.OnDamageDealt += HandleDamageDealt; + } + + public override void Dispose() + { + if (Context.Owner != null) + Context.Owner.OnDamageDealt -= HandleDamageDealt; + } + + private void HandleDamageDealt(Entity _, float damage) + { + if (Context.Owner == null || Context.Owner.MaxHp <= 0f) + return; + + if (Context.Owner.Hp / Context.Owner.MaxHp > healthThreshold) + return; + + Context.Owner.Heal(damage * damageHealRate); + } + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/LowHealthDamageHealPassiveSkill.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/LowHealthDamageHealPassiveSkill.cs.meta new file mode 100644 index 00000000..07a3a3d8 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/LowHealthDamageHealPassiveSkill.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: be420bc1b73d40d2a8cf9843d088fbcf diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/LowHealthRecoveryPassiveSkill.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/LowHealthRecoveryPassiveSkill.cs new file mode 100644 index 00000000..e8dd5d70 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/LowHealthRecoveryPassiveSkill.cs @@ -0,0 +1,74 @@ +using UnityEngine; + +[CreateAssetMenu( + fileName = "LowHealthRecoveryPassiveSkill", + menuName = "ScriptableObjects/SkillTree/Passive/Low Health Recovery" +)] +public sealed class LowHealthRecoveryPassiveSkill : PassiveSkillData +{ + [SerializeField, Range(0f, 1f)] + private float healthThreshold = 0.5f; + + [SerializeField] + private EntityStatModifier recoveryModifier; + + public override void ApplyPassive(ref EntityStatModifier modifier) { } + + public override PassiveSkillRuntime CreateRuntime(PassiveSkillRuntimeContext context) + { + return new Runtime(context, healthThreshold, recoveryModifier); + } + + private sealed class Runtime : PassiveSkillRuntime + { + private readonly float healthThreshold; + private readonly EntityStatModifier recoveryModifier; + private EntityStatModifierHandle modifierHandle; + private bool isApplied; + + public Runtime( + PassiveSkillRuntimeContext context, + float healthThreshold, + EntityStatModifier recoveryModifier + ) + : base(context) + { + this.healthThreshold = healthThreshold; + this.recoveryModifier = recoveryModifier; + Context.Owner.OnUpdated += Refresh; + Refresh(); + } + + public override void Dispose() + { + if (Context.Owner != null) + Context.Owner.OnUpdated -= Refresh; + + if (Context.Owner != null && modifierHandle.IsValid) + Context.Owner.RemoveStatModifier(modifierHandle); + + modifierHandle = default; + } + + private void Refresh() + { + if (Context.Owner == null || Context.Owner.MaxHp <= 0f) + return; + + bool shouldApply = Context.Owner.Hp / Context.Owner.MaxHp <= healthThreshold; + if (shouldApply == isApplied) + return; + + isApplied = shouldApply; + if (isApplied) + { + modifierHandle = Context.Owner.ApplyStatModifier(recoveryModifier); + } + else if (modifierHandle.IsValid) + { + Context.Owner.RemoveStatModifier(modifierHandle); + modifierHandle = default; + } + } + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/LowHealthRecoveryPassiveSkill.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/LowHealthRecoveryPassiveSkill.cs.meta new file mode 100644 index 00000000..44de13c3 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/LowHealthRecoveryPassiveSkill.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e7bf5697f6dd45a38c1e56c160fa8cf9 diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/StatModifierPassiveSkill.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/StatModifierPassiveSkill.cs new file mode 100644 index 00000000..0ac597c6 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/StatModifierPassiveSkill.cs @@ -0,0 +1,16 @@ +using UnityEngine; + +[CreateAssetMenu( + fileName = "StatModifierPassiveSkill", + menuName = "ScriptableObjects/SkillTree/Passive/Stat Modifier" +)] +public sealed class StatModifierPassiveSkill : PassiveSkillData +{ + [SerializeField] + private EntityStatModifier modifier; + + public override void ApplyPassive(ref EntityStatModifier targetModifier) + { + targetModifier += modifier; + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/StatModifierPassiveSkill.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/StatModifierPassiveSkill.cs.meta new file mode 100644 index 00000000..0d9aaa2b --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Skills/PassiveSkills/StatModifierPassiveSkill.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b7ec831eb812409db2fe187c6081ead2 diff --git a/Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior/BasicSkill.asset b/Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior/BasicSkill.asset deleted file mode 100644 index a9adaba5..00000000 --- a/Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior/BasicSkill.asset +++ /dev/null @@ -1,19 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 0c074805eb6fdc84eb032f40ad30f1e5, type: 3} - m_Name: BasicSkill - m_EditorClassIdentifier: - SkillName: - SkillIcon: {fileID: 0} - SkillDescription: - Cooldown: 0 - Damage: 0 diff --git a/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/ArcherStat.asset b/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/ArcherStat.asset index 82b19cbd..ecf19912 100644 --- a/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/ArcherStat.asset +++ b/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/ArcherStat.asset @@ -13,7 +13,7 @@ MonoBehaviour: m_Name: ArcherStat m_EditorClassIdentifier: Hp: 30 - HpRecovery: 15 + HpRecovery: 5 DamageReduction: 0.15 AttackPower: 8 AttackSpeed: 2.3 diff --git a/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WarriorStat.asset b/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WarriorStat.asset index 2f60a720..162a51c6 100644 --- a/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WarriorStat.asset +++ b/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WarriorStat.asset @@ -13,7 +13,7 @@ MonoBehaviour: m_Name: WarriorStat m_EditorClassIdentifier: Hp: 50 - HpRecovery: 20 + HpRecovery: 7 DamageReduction: 0.25 AttackPower: 5 AttackSpeed: 1.5 diff --git a/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WizardStat.asset b/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WizardStat.asset index 2c28d844..50fa8f44 100644 --- a/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WizardStat.asset +++ b/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WizardStat.asset @@ -13,7 +13,7 @@ MonoBehaviour: m_Name: WizardStat m_EditorClassIdentifier: Hp: 20 - HpRecovery: 18 + HpRecovery: 6 DamageReduction: 0.08 AttackPower: 10 AttackSpeed: 1 diff --git a/Fantasy-Grower/Assets/Scripts/UI/InDungeon/ActiveSkillButtonUI.cs b/Fantasy-Grower/Assets/Scripts/UI/InDungeon/ActiveSkillButtonUI.cs new file mode 100644 index 00000000..bc7ea37e --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/UI/InDungeon/ActiveSkillButtonUI.cs @@ -0,0 +1,133 @@ +using TMPro; +using UnityEngine; +using UnityEngine.UI; + +/// +/// 모바일 던전 UI에서 액티브 스킬 슬롯 하나를 실행하는 버튼입니다. +/// +[DisallowMultipleComponent] +[RequireComponent(typeof(Button))] +public sealed class ActiveSkillButtonUI : MonoBehaviour +{ + [SerializeField, Min(0)] + private int slotIndex; + + [Header("표시")] + [SerializeField] + private Image iconImage; + + [SerializeField] + private Image cooldownFillImage; + + [SerializeField] + private TMP_Text cooldownText; + + [SerializeField] + private GameObject emptySlotObject; + + private static ActiveSkillExecutor cachedExecutor; + + private Button button; + private ActiveSkillData currentSkill; + + private void Awake() + { + button = GetComponent