diff --git a/.plastic/plastic.changes b/.plastic/plastic.changes
index 713db581..24469120 100644
Binary files a/.plastic/plastic.changes and b/.plastic/plastic.changes differ
diff --git a/.plastic/plastic.wktree b/.plastic/plastic.wktree
index 0e6f65a0..2b307fce 100644
Binary files a/.plastic/plastic.wktree and b/.plastic/plastic.wktree differ
diff --git a/Assets/0.SCENE/MainGame.unity b/Assets/0.SCENE/MainGame.unity
index cf7b9a93..927d4c9b 100644
--- a/Assets/0.SCENE/MainGame.unity
+++ b/Assets/0.SCENE/MainGame.unity
@@ -9116,7 +9116,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
- m_IsActive: 0
+ m_IsActive: 1
--- !u!114 &85056389
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -65956,7 +65956,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
- m_IsActive: 0
+ m_IsActive: 1
--- !u!114 &600231663
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -204277,7 +204277,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
- m_IsActive: 0
+ m_IsActive: 1
--- !u!114 &1927423249
MonoBehaviour:
m_ObjectHideFlags: 0
diff --git a/Assets/1.myPrefab/MyMonster/KamikazeMonster.prefab b/Assets/1.myPrefab/MyMonster/KamikazeMonster.prefab
index 06512364..37bbcf89 100644
--- a/Assets/1.myPrefab/MyMonster/KamikazeMonster.prefab
+++ b/Assets/1.myPrefab/MyMonster/KamikazeMonster.prefab
@@ -12,7 +12,7 @@ GameObject:
- component: {fileID: 5083921884124959952}
- component: {fileID: 8260729899295208043}
- component: {fileID: 3104871206233954959}
- - component: {fileID: 4219865273348368139}
+ - component: {fileID: 3591254528184074799}
m_Layer: 6
m_Name: MonsterHP_Canvas
m_TagString: Untagged
@@ -106,7 +106,7 @@ MonoBehaviour:
m_BlockingMask:
serializedVersion: 2
m_Bits: 4294967295
---- !u!114 &4219865273348368139
+--- !u!114 &3591254528184074799
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -115,10 +115,12 @@ MonoBehaviour:
m_GameObject: {fileID: 1878191124198308913}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 9eb5b341f7503494e9b0f88cc4b1c17d, type: 3}
+ m_Script: {fileID: 11500000, guid: 1ac18100e1f3bbd4d95b5aefec8b645e, type: 3}
m_Name:
m_EditorClassIdentifier:
- targetObject: {fileID: 0}
+ targetObject: {fileID: 6300812397144094342}
+ hpFilledImage: {fileID: 6625992182938873674}
+ hpText: {fileID: 8364788239652814931}
--- !u!1 &2092223000242526199
GameObject:
m_ObjectHideFlags: 0
@@ -913,14 +915,7 @@ PrefabInstance:
objectReference: {fileID: 9100000, guid: 6c71df38704934aa1940c7c9f7d5832c, type: 2}
- target: {fileID: 9500000, guid: 74236a73e891a4540a264993ac8d337b, type: 3}
propertyPath: m_WarningMessage
- value: "\nBinding warning: Some generic clip(s) animate transforms that are
- already bound by a Humanoid avatar. These transforms can only be changed
- by Humanoid clips.\n\tTransform 'jaw_2'\n\tTransform 'thigh_l'\n\tTransform
- 'jaw'\n\tTransform 'pelvis'\n\tTransform 'thigh_r'\n\tTransform 'index_03_l'\n\tTransform
- 'lowerarm_l'\n\tTransform 'pinky_03_l'\n\tTransform 'neck_01'\n\tTransform
- 'pinky_03_r'\n\tand more ...\n\tFrom animation clip 'spawn'\n\tFrom animation
- clip 'damage'\n\tFrom animation clip 'die'\n\tFrom animation clip 'idle'\n\tFrom
- animation clip 'run'\n\tFrom animation clip 'walk'"
+ value:
objectReference: {fileID: 0}
- target: {fileID: 9500000, guid: 74236a73e891a4540a264993ac8d337b, type: 3}
propertyPath: m_ApplyRootMotion
@@ -1006,14 +1001,14 @@ MonoBehaviour:
hitSound: {fileID: 0}
deathSound: {fileID: 0}
deathEffectPrefab: {fileID: 0}
- hitEffect: {fileID: 0}
+ hitEffect: {fileID: 19827894, guid: 9edc33f62cf3ae849badc4cc12fe5f8a, type: 3}
impactSpawnPoint: {fileID: 0}
explodeRange: 13.07
triggerRange: 1.5
fuseTime: 3
explosionDamage: 500
explosionEffectPrefab: {fileID: 0}
- fuseEffect: {fileID: 0}
+ fuseEffect: {fileID: 19827894, guid: 9edc33f62cf3ae849badc4cc12fe5f8a, type: 3}
fuseSound: {fileID: 0}
explosionSound: {fileID: 0}
runAnim: 'scavenger_run '
diff --git a/Assets/1.myPrefab/MyMonster/Monster Weapon/Throw Monster Weapon.prefab b/Assets/1.myPrefab/MyMonster/Monster Weapon/Throw Monster Weapon.prefab
index b19393e1..7c1d2ecb 100644
--- a/Assets/1.myPrefab/MyMonster/Monster Weapon/Throw Monster Weapon.prefab
+++ b/Assets/1.myPrefab/MyMonster/Monster Weapon/Throw Monster Weapon.prefab
@@ -99,7 +99,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
lifetime: 5
- hitEffectPrefab: {fileID: 0}
+ hitEffectPrefab: {fileID: 149626, guid: 2aca4c37fa723b4489a42288a1356056, type: 3}
destroyOnHit: 1
--- !u!135 &661439740501535806
SphereCollider:
diff --git a/Assets/1.myPrefab/MyMonster/RushMonster2.prefab b/Assets/1.myPrefab/MyMonster/RushMonster2.prefab
index 85772360..bacd86e6 100644
--- a/Assets/1.myPrefab/MyMonster/RushMonster2.prefab
+++ b/Assets/1.myPrefab/MyMonster/RushMonster2.prefab
@@ -424,7 +424,7 @@ GameObject:
- component: {fileID: 1900671093442362138}
- component: {fileID: 8616576226391706942}
- component: {fileID: 8938442300426676634}
- - component: {fileID: 4958734649322249725}
+ - component: {fileID: 5242184969474340357}
m_Layer: 6
m_Name: MonsterHP_Canvas
m_TagString: Untagged
@@ -518,7 +518,7 @@ MonoBehaviour:
m_BlockingMask:
serializedVersion: 2
m_Bits: 4294967295
---- !u!114 &4958734649322249725
+--- !u!114 &5242184969474340357
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -527,10 +527,12 @@ MonoBehaviour:
m_GameObject: {fileID: 8076719585448199079}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 9eb5b341f7503494e9b0f88cc4b1c17d, type: 3}
+ m_Script: {fileID: 11500000, guid: 1ac18100e1f3bbd4d95b5aefec8b645e, type: 3}
m_Name:
m_EditorClassIdentifier:
- targetObject: {fileID: 0}
+ targetObject: {fileID: 7037573943750301576}
+ hpFilledImage: {fileID: 5890577574158001365}
+ hpText: {fileID: 3501248421892344481}
--- !u!1001 &7037573943750398246
PrefabInstance:
m_ObjectHideFlags: 0
@@ -829,12 +831,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 9500000, guid: 3c2d7f13adf114e5e917e1f1ec0dfe43, type: 3}
propertyPath: m_WarningMessage
- value: "\nBinding warning: Some generic clip(s) animate transforms that are
- already bound by a Humanoid avatar. These transforms can only be changed
- by Humanoid clips.\n\tTransform 'jaw_1'\n\tTransform 'pelvis'\n\tTransform
- 'index_03_l'\n\tTransform 'lowerarm_l'\n\tTransform 'neck_01'\n\tTransform
- 'ring_02_l'\n\tTransform 'hand_l'\n\tTransform 'index_03_r'\n\tTransform
- 'jaw_1'\n\tTransform 'ring_03_r'\n\tand more ...\n\tFrom animation clip 'spawn'"
+ value:
objectReference: {fileID: 0}
- target: {fileID: 9500000, guid: 3c2d7f13adf114e5e917e1f1ec0dfe43, type: 3}
propertyPath: m_ApplyRootMotion
@@ -889,6 +886,7 @@ MonoBehaviour:
Monster_Idle: Run-idle
Monster_GetDamage: Run-gethit
Monster_Die: Run-die
+ hitRecoverFallback: 1
attackRestDuration: 1.5
detectionRange: 10
showDebugGizmos: 1
@@ -905,6 +903,7 @@ MonoBehaviour:
chargeAnim: Run-run
prepareAnim: Run-wait
walkAnim: Run-walk
+ showChargeDebugLog: 1
--- !u!54 &8964470652111950350
Rigidbody:
m_ObjectHideFlags: 0
diff --git a/Assets/1.myPrefab/MyMonster/SwordMonster.prefab b/Assets/1.myPrefab/MyMonster/SwordMonster.prefab
index a89795d5..5f5e0690 100644
--- a/Assets/1.myPrefab/MyMonster/SwordMonster.prefab
+++ b/Assets/1.myPrefab/MyMonster/SwordMonster.prefab
@@ -1282,7 +1282,7 @@ GameObject:
- component: {fileID: 8545402927441503032}
- component: {fileID: 6695977629727991124}
- component: {fileID: 2866660601975138627}
- - component: {fileID: 2532056432622564957}
+ - component: {fileID: 229555066434838198}
m_Layer: 6
m_Name: MonsterHP_Canvas
m_TagString: Untagged
@@ -1376,7 +1376,7 @@ MonoBehaviour:
m_BlockingMask:
serializedVersion: 2
m_Bits: 4294967295
---- !u!114 &2532056432622564957
+--- !u!114 &229555066434838198
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -1385,10 +1385,12 @@ MonoBehaviour:
m_GameObject: {fileID: 3192165992615934428}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 9eb5b341f7503494e9b0f88cc4b1c17d, type: 3}
+ m_Script: {fileID: 11500000, guid: 1ac18100e1f3bbd4d95b5aefec8b645e, type: 3}
m_Name:
m_EditorClassIdentifier:
- targetObject: {fileID: 0}
+ targetObject: {fileID: 5674935864780053661}
+ hpFilledImage: {fileID: 197665650599347112}
+ hpText: {fileID: 6986518538193087157}
--- !u!1 &3511228176158128295
GameObject:
m_ObjectHideFlags: 0
@@ -1880,6 +1882,37 @@ Transform:
- {fileID: 6599583630465836384}
m_Father: {fileID: 6176598586534571958}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &5291722251081738114
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 351339871207031370}
+ m_Layer: 6
+ m_Name: EffectPos
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &351339871207031370
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 5291722251081738114}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: -0.013, y: 0.923, z: 0.728}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 8892907556271350198}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &5441857239707455192
GameObject:
m_ObjectHideFlags: 0
@@ -1951,6 +1984,7 @@ Transform:
- {fileID: 3942939034497495350}
- {fileID: 6080021456397241365}
- {fileID: 8585626370423984187}
+ - {fileID: 351339871207031370}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!95 &186042788264501781
@@ -1998,12 +2032,12 @@ MonoBehaviour:
hitRecoverFallback: 1
attackRestDuration: 1.5
detectionRange: 10
- showDebugGizmos: 0
+ showDebugGizmos: 1
hitSound: {fileID: 0}
deathSound: {fileID: 0}
deathEffectPrefab: {fileID: 0}
- hitEffect: {fileID: 0}
- impactSpawnPoint: {fileID: 0}
+ hitEffect: {fileID: 19820634, guid: 2d051abfebeda054eac2b6a15edf6d4e, type: 3}
+ impactSpawnPoint: {fileID: 351339871207031370}
attackRange: 2
attackDelay: 1.5
dropItemPrefabs:
diff --git a/Assets/1.myPrefab/MyMonster/ThrowMonster.prefab b/Assets/1.myPrefab/MyMonster/ThrowMonster.prefab
index 2d725065..59e6bfdd 100644
--- a/Assets/1.myPrefab/MyMonster/ThrowMonster.prefab
+++ b/Assets/1.myPrefab/MyMonster/ThrowMonster.prefab
@@ -1503,8 +1503,7 @@ Animator:
'index_03_l'\n\tTransform 'lowerarm_l'\n\tTransform 'pinky_03_l'\n\tTransform
'neck_01'\n\tTransform 'pinky_03_r'\n\tTransform 'clavicle_l'\n\tTransform 'ring_02_l'\n\tand
more ...\n\tFrom animation clip 'spawn'\n\tFrom animation clip 'idle'\n\tFrom
- animation clip 'walk'\n\tFrom animation clip 'Monster_GetDamage'\n\tFrom animation
- clip 'Monster_Die'\n\tFrom animation clip 'run'"
+ animation clip 'run'"
m_HasTransformHierarchy: 1
m_AllowConstantClipSamplingOptimization: 1
m_KeepAnimatorStateOnDisable: 0
@@ -2505,7 +2504,7 @@ GameObject:
- component: {fileID: 2977678645291439387}
- component: {fileID: 6016741491630227145}
- component: {fileID: 1054848692288420745}
- - component: {fileID: 3002808900780006287}
+ - component: {fileID: 6148458972729052386}
m_Layer: 6
m_Name: MonsterHP_Canvas
m_TagString: Untagged
@@ -2521,7 +2520,7 @@ RectTransform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7153040190580437744}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
- m_LocalPosition: {x: 0, y: 0, z: 0.060001373}
+ m_LocalPosition: {x: 0, y: 0, z: 0.85}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
@@ -2533,7 +2532,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
- m_AnchoredPosition: {x: 0, y: 2.743}
+ m_AnchoredPosition: {x: -0, y: 0.812}
m_SizeDelta: {x: 1, y: 1}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!223 &2977678645291439387
@@ -2599,7 +2598,7 @@ MonoBehaviour:
m_BlockingMask:
serializedVersion: 2
m_Bits: 4294967295
---- !u!114 &3002808900780006287
+--- !u!114 &6148458972729052386
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -2608,10 +2607,12 @@ MonoBehaviour:
m_GameObject: {fileID: 7153040190580437744}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 9eb5b341f7503494e9b0f88cc4b1c17d, type: 3}
+ m_Script: {fileID: 11500000, guid: 1ac18100e1f3bbd4d95b5aefec8b645e, type: 3}
m_Name:
m_EditorClassIdentifier:
- targetObject: {fileID: 0}
+ targetObject: {fileID: 3587750552762439828}
+ hpFilledImage: {fileID: 7307672912510681016}
+ hpText: {fileID: 2164660505891504432}
--- !u!1 &7576300459429704252
GameObject:
m_ObjectHideFlags: 0
@@ -3559,6 +3560,10 @@ PrefabInstance:
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
+ - target: {fileID: 6725208262828196419, guid: 3fe90e2c4622f754381371a281472296, type: 3}
+ propertyPath: m_Materials.Array.data[0]
+ value:
+ objectReference: {fileID: 2100000, guid: aea925dd0ba5758438a4e26ced28244e, type: 2}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
diff --git a/Assets/Epic Toon FX/Prefabs/Combat/Missiles/Fireball/FireballMissileFire 1.prefab b/Assets/Epic Toon FX/Prefabs/Combat/Missiles/Fireball/FireballMissileFire 1.prefab
index 69b92406..2438dc36 100644
--- a/Assets/Epic Toon FX/Prefabs/Combat/Missiles/Fireball/FireballMissileFire 1.prefab
+++ b/Assets/Epic Toon FX/Prefabs/Combat/Missiles/Fireball/FireballMissileFire 1.prefab
@@ -14583,6 +14583,35 @@ MonoBehaviour:
serializedVersion: 2
m_Bits: 64
arrowTip: {fileID: 0}
+ baseDamage: 10
+ fireEffect:
+ enabled: 1
+ bonusDamage: 5
+ dotDamagePerTick: 3
+ dotDuration: 4
+ dotTickInterval: 0.5
+ effectStrength: 1
+ iceEffect:
+ enabled: 0
+ bonusDamage: 2
+ dotDamagePerTick: 0
+ dotDuration: 3
+ dotTickInterval: 1
+ effectStrength: 0.5
+ poisonEffect:
+ enabled: 0
+ bonusDamage: 1
+ dotDamagePerTick: 2
+ dotDuration: 6
+ dotTickInterval: 1
+ effectStrength: 1
+ lightningEffect:
+ enabled: 0
+ bonusDamage: 8
+ dotDamagePerTick: 0
+ dotDuration: 1.5
+ dotTickInterval: 1
+ effectStrength: 1.5
showDebugRay: 1
--- !u!1 &166048
GameObject:
diff --git a/Assets/Scripts/Core/Utils/StatusEffectProcessor.cs b/Assets/Scripts/Core/Utils/StatusEffectProcessor.cs
index 9dda896c..3fb2038f 100644
--- a/Assets/Scripts/Core/Utils/StatusEffectProcessor.cs
+++ b/Assets/Scripts/Core/Utils/StatusEffectProcessor.cs
@@ -3,7 +3,8 @@ using UnityEngine.AI; // 내비게이션 기능을 불러올거에요 -> UnityEn
using System.Collections; // 코루틴을 사용할거에요 -> System.Collections를
///
-/// 화상, 독, 슬로우 등의 상태이상을 전담 처리하는 클래스
+/// 화상, 독, 슬로우, 감전 등의 상태이상을 전담 처리하는 클래스
+/// [UPGRADED] 틱 간격(tickInterval) 지원 오버로드 + ApplyPoison 추가
///
public class StatusEffectProcessor // 클래스를 선언할거에요 -> 상태이상 로직을 담당하는 클래스를
{
@@ -20,36 +21,136 @@ public class StatusEffectProcessor // 클래스를 선언할거에요 -> 상태
_animator = animator; // 값을 저장할거에요 -> 애니메이터를
}
- public void ApplyBurn(float damage, float duration) => _owner.StartCoroutine(BurnRoutine(damage, duration)); // 함수를 선언할거에요 -> 화상 코루틴 시작을
- public void ApplySlow(float amount, float duration) => _owner.StartCoroutine(SlowRoutine(amount, duration)); // 함수를 선언할거에요 -> 슬로우 코루틴 시작을
- public void ApplyShock(float damage, float duration) { _damageable.TakeDamage(damage); _owner.StartCoroutine(StunRoutine(duration)); } // 함수를 선언할거에요 -> 충격과 스턴 시작을
+ // ─────────────────────────────────────────────────────────
+ // 화상 (Burn)
+ // ─────────────────────────────────────────────────────────
- private IEnumerator BurnRoutine(float damage, float duration) // 코루틴 함수를 정의할거에요 -> 화상 로직을
+ ///
+ /// [기존] 화상 — 2-파라미터 (하위 호환, 기본 틱 간격 0.5초)
+ ///
+ public void ApplyBurn(float damage, float duration) // 함수를 선언할거에요 -> 화상 적용을 (2-파라미터 버전)
{
- float elapsed = 0f; // 변수를 초기화할거에요 -> 경과 시간을
- while (elapsed < duration) // 반복할거에요 -> 지속 시간 동안
- {
- _damageable.TakeDamage(damage * 0.5f); // 실행할거에요 -> 0.5초치 데미지를
- yield return new WaitForSeconds(0.5f); // 기다릴거에요 -> 0.5초를
- elapsed += 0.5f; // 값을 더할거에요 -> 경과 시간에
- }
+ _owner.StartCoroutine(DotRoutine("화상", damage, duration, 0.5f)); // 실행할거에요 -> 기본 틱 간격 0.5초로 도트 코루틴을
}
- private IEnumerator SlowRoutine(float amount, float duration) // 코루틴 함수를 정의할거에요 -> 슬로우 로직을
+ ///
+ /// [NEW] 화상 — 3-파라미터 (틱 간격 지정 가능)
+ ///
+ public void ApplyBurn(float damagePerTick, float duration, float tickInterval) // 함수를 선언할거에요 -> 화상 적용을 (3-파라미터 버전)
+ {
+ _owner.StartCoroutine(DotRoutine("화상", damagePerTick, duration, tickInterval)); // 실행할거에요 -> 지정 틱 간격으로 도트 코루틴을
+ }
+
+ // ─────────────────────────────────────────────────────────
+ // 독 (Poison)
+ // ─────────────────────────────────────────────────────────
+
+ ///
+ /// [NEW] 독 — 2-파라미터 (기본 틱 간격 1초)
+ ///
+ public void ApplyPoison(float damage, float duration) // 함수를 선언할거에요 -> 독 적용을 (2-파라미터 버전)
+ {
+ _owner.StartCoroutine(DotRoutine("독", damage, duration, 1f)); // 실행할거에요 -> 기본 틱 간격 1초로 도트 코루틴을
+ }
+
+ ///
+ /// [NEW] 독 — 3-파라미터 (틱 간격 지정 가능)
+ ///
+ public void ApplyPoison(float damagePerTick, float duration, float tickInterval) // 함수를 선언할거에요 -> 독 적용을 (3-파라미터 버전)
+ {
+ _owner.StartCoroutine(DotRoutine("독", damagePerTick, duration, tickInterval)); // 실행할거에요 -> 지정 틱 간격으로 도트 코루틴을
+ }
+
+ // ─────────────────────────────────────────────────────────
+ // 슬로우 (Slow)
+ // ─────────────────────────────────────────────────────────
+
+ ///
+ /// [기존] 슬로우 — 2-파라미터
+ /// amount: 감속 비율 (0~1 사이 값 또는 0~100 사이 값 모두 지원)
+ ///
+ public void ApplySlow(float amount, float duration) // 함수를 선언할거에요 -> 슬로우 적용을
+ {
+ _owner.StartCoroutine(SlowRoutine(amount, duration)); // 실행할거에요 -> 슬로우 코루틴을
+ }
+
+ // ─────────────────────────────────────────────────────────
+ // 감전/스턴 (Shock)
+ // ─────────────────────────────────────────────────────────
+
+ ///
+ /// [기존] 감전 — 즉발 데미지 + 스턴
+ ///
+ public void ApplyShock(float damage, float duration) // 함수를 선언할거에요 -> 감전 적용을
+ {
+ _damageable.TakeDamage(damage); // 실행할거에요 -> 즉발 데미지를
+ _owner.StartCoroutine(StunRoutine(duration)); // 실행할거에요 -> 스턴 코루틴을
+ }
+
+ // ─────────────────────────────────────────────────────────
+ // 코루틴들
+ // ─────────────────────────────────────────────────────────
+
+ ///
+ /// [NEW] 통합 도트 데미지 코루틴 — 화상/독 모두 이것을 사용
+ /// damagePerTick: 1틱당 데미지
+ /// duration: 총 지속 시간 (초)
+ /// tickInterval: 틱 간격 (초)
+ ///
+ private IEnumerator DotRoutine(string effectName, float damagePerTick, float duration, float tickInterval) // 코루틴을 정의할거에요 -> 통합 도트 데미지 로직을
+ {
+ if (tickInterval <= 0f) tickInterval = 0.5f; // 안전장치를 넣을거에요 -> 틱 간격이 0 이하면 0.5초로
+
+ float elapsed = 0f; // 변수를 초기화할거에요 -> 경과 시간을 0으로
+ Debug.Log($"[StatusEffect] {effectName} 시작! 틱당:{damagePerTick} 간격:{tickInterval}초 지속:{duration}초"); // 로그를 출력할거에요 -> 효과 시작 안내를
+
+ while (elapsed < duration) // 반복할거에요 -> 지속 시간이 끝날 때까지
+ {
+ _damageable.TakeDamage(damagePerTick); // 실행할거에요 -> 1틱 데미지를
+ yield return new WaitForSeconds(tickInterval); // 기다릴거에요 -> 틱 간격만큼
+ elapsed += tickInterval; // 값을 더할거에요 -> 경과 시간에 틱 간격을
+ }
+
+ Debug.Log($"[StatusEffect] {effectName} 종료!"); // 로그를 출력할거에요 -> 효과 종료 안내를
+ }
+
+ ///
+ /// [기존] 슬로우 코루틴
+ /// amount가 1 이하면 비율로(0.5 = 50% 감속), 1 초과면 퍼센트로(50 = 50% 감속)
+ ///
+ private IEnumerator SlowRoutine(float amount, float duration) // 코루틴을 정의할거에요 -> 슬로우 로직을
{
if (_agent == null) yield break; // 조건이 맞으면 종료할거에요 -> 에이전트가 없으면
+
float orgSpeed = _agent.speed; // 값을 저장할거에요 -> 원래 속도를
- _agent.speed *= (1f - Mathf.Clamp01(amount / 100f)); // 값을 바꿀거에요 -> 속도를 줄여서
+
+ // amount 해석: 1 이하면 비율(0.5 = 50% 감속), 1 초과면 퍼센트(50 = 50% 감속)
+ float slowRate = amount > 1f ? Mathf.Clamp01(amount / 100f) : Mathf.Clamp01(amount); // 값을 계산할거에요 -> 감속 비율을
+ _agent.speed *= (1f - slowRate); // 값을 바꿀거에요 -> 속도를 줄여서
+
+ Debug.Log($"[StatusEffect] 슬로우! 원래:{orgSpeed} → 감속:{_agent.speed} ({slowRate * 100}% 감속) {duration}초간"); // 로그를 출력할거에요 -> 슬로우 내역을
+
yield return new WaitForSeconds(duration); // 기다릴거에요 -> 지속 시간만큼
_agent.speed = orgSpeed; // 값을 복구할거에요 -> 원래 속도로
+
+ Debug.Log($"[StatusEffect] 슬로우 해제! 속도 복구: {orgSpeed}"); // 로그를 출력할거에요 -> 해제 안내를
}
- private IEnumerator StunRoutine(float duration) // 코루틴 함수를 정의할거에요 -> 스턴 로직을
+ ///
+ /// [기존] 스턴 코루틴 — 이동 정지 + 애니 멈춤
+ ///
+ private IEnumerator StunRoutine(float duration) // 코루틴을 정의할거에요 -> 스턴 로직을
{
if (_agent != null) _agent.isStopped = true; // 명령을 내릴거에요 -> 이동 정지를
if (_animator != null) _animator.speed = 0; // 값을 바꿀거에요 -> 애니 속도를 0으로
+
+ Debug.Log($"[StatusEffect] 스턴! {duration}초간 행동 불능"); // 로그를 출력할거에요 -> 스턴 안내를
+
yield return new WaitForSeconds(duration); // 기다릴거에요 -> 스턴 시간만큼
+
if (_agent != null && _agent.isOnNavMesh) _agent.isStopped = false; // 명령을 내릴거에요 -> 이동 재개를
if (_animator != null) _animator.speed = 1; // 값을 바꿀거에요 -> 애니 속도 복구를
+
+ Debug.Log($"[StatusEffect] 스턴 해제!"); // 로그를 출력할거에요 -> 해제 안내를
}
}
\ No newline at end of file
diff --git a/Assets/Scripts/Enemy/AI/MonsterClass.cs b/Assets/Scripts/Enemy/AI/MonsterClass.cs
index 9405ffb5..aabdfba7 100644
--- a/Assets/Scripts/Enemy/AI/MonsterClass.cs
+++ b/Assets/Scripts/Enemy/AI/MonsterClass.cs
@@ -69,7 +69,7 @@ public abstract class MonsterClass : MonoBehaviour, IDamageable // 추상 클래
agent = GetComponent(); // 가져올거에요 -> 에이전트를
audioSource = GetComponent(); // 가져올거에요 -> 오디오 소스를
mobRenderer = GetComponentInChildren(); // 가져올거에요 -> 렌더러를
- // statusProcessor = GetComponent(); // 가져올거에요 -> 상태이상 처리기를
+ statusProcessor = new StatusEffectProcessor(this, this, agent, animator); // 생성할거에요 -> 상태이상 처리기를 (this = 코루틴 주체 + IDamageable)
if (agent != null) agent.speed = moveSpeed; // 설정할거에요 -> 이동 속도를
@@ -352,15 +352,45 @@ public abstract class MonsterClass : MonoBehaviour, IDamageable // 추상 클래
// 상태이상
// ─────────────────────────────────────────────────────────
- public void ApplyStatusEffect(StatusEffectType type, float dmg, float dur) // 함수를 선언할거에요 -> 상태이상 적용을
+ ///
+ /// [기존] 상태이상 적용 — 3-파라미터 (하위 호환)
+ ///
+ public void ApplyStatusEffect(StatusEffectType type, float dmg, float dur) // 함수를 선언할거에요 -> 상태이상 적용을 (3-파라미터 버전)
+ {
+ ApplyStatusEffect(type, dmg, dur, 0.5f, 1f); // 실행할거에요 -> 5-파라미터 버전을 기본 틱간격 0.5초, 강도 1로
+ }
+
+ ///
+ /// [NEW] 상태이상 적용 — 5-파라미터 (Arrow 인스펙터 연동)
+ /// tickInterval: 도트 데미지 틱 간격 (초)
+ /// strength: 특수 효과 강도 (슬로우 비율, 스턴 시간 등)
+ ///
+ public void ApplyStatusEffect(StatusEffectType type, float dotDmg, float dur, float tickInterval, float strength) // 함수를 선언할거에요 -> 상태이상 적용을 (5-파라미터 버전)
{
if (isDead) return; // 중단할거에요 -> 죽었으면
- switch (type)
+ if (statusProcessor == null) // 조건이 맞으면 실행할거에요 -> 상태이상 처리기가 없으면
{
- case StatusEffectType.Burn: statusProcessor.ApplyBurn(dmg, dur); break; // 적용할거에요 -> 화상을
- case StatusEffectType.Slow: statusProcessor.ApplySlow(dmg, dur); break; // 적용할거에요 -> 슬로우를
- case StatusEffectType.Poison: statusProcessor.ApplyBurn(dmg, dur); break; // 적용할거에요 -> 독을
- case StatusEffectType.Shock: statusProcessor.ApplyShock(dmg, dur); break; // 적용할거에요 -> 충격을
+ Debug.LogWarning($"[MonsterClass] {gameObject.name}에 StatusEffectProcessor가 없습니다!"); // 경고를 출력할거에요 -> 누락 안내를
+ return; // 중단할거에요 -> 함수를
+ }
+
+ switch (type) // 분기할거에요 -> 상태이상 타입에 따라
+ {
+ case StatusEffectType.Burn: // 조건이 맞으면 실행할거에요 -> 화상이라면
+ statusProcessor.ApplyBurn(dotDmg, dur, tickInterval); // 적용할거에요 -> 화상을 (틱 간격 포함)
+ break; // 분기를 종료할거에요
+
+ case StatusEffectType.Slow: // 조건이 맞으면 실행할거에요 -> 슬로우라면
+ statusProcessor.ApplySlow(strength, dur); // 적용할거에요 -> 슬로우를 (strength = 감속 비율)
+ break; // 분기를 종료할거에요
+
+ case StatusEffectType.Poison: // 조건이 맞으면 실행할거에요 -> 독이라면
+ statusProcessor.ApplyPoison(dotDmg, dur, tickInterval); // 적용할거에요 -> 독을 (틱 간격 포함)
+ break; // 분기를 종료할거에요
+
+ case StatusEffectType.Shock: // 조건이 맞으면 실행할거에요 -> 감전이라면
+ statusProcessor.ApplyShock(dotDmg, dur); // 적용할거에요 -> 감전을 (스턴)
+ break; // 분기를 종료할거에요
}
}
diff --git a/Assets/Scripts/Player/Combat/Arrow.cs b/Assets/Scripts/Player/Combat/Arrow.cs
index 4f7c9b50..02a45100 100644
--- a/Assets/Scripts/Player/Combat/Arrow.cs
+++ b/Assets/Scripts/Player/Combat/Arrow.cs
@@ -4,7 +4,7 @@ using System.Collections; // 코루틴 기능을 사용할거에요 -> System.Co
///
/// 발사된 화살 발사체 (파티클 프리팹에 부착됨)
/// SphereCast 기반 정밀 충돌 감지 (기존 100% 유지)
-/// [NEW] 속성 데미지(DoT) 시스템 추가
+/// [UPGRADED] 속성 데미지를 인스펙터에서 속성별로 개별 조정 가능
///
public class PlayerArrow : MonoBehaviour // 클래스를 선언할거에요 -> MonoBehaviour를 상속받는 PlayerArrow를
{
@@ -14,11 +14,59 @@ public class PlayerArrow : MonoBehaviour // 클래스를 선언할거에요 -> M
[SerializeField] private LayerMask hitLayers; // 변수를 선언할거에요 -> 충돌 레이어인 hitLayers를
[SerializeField] private Transform arrowTip; // 변수를 선언할거에요 -> 화살 끝부분 위치인 arrowTip을
+ [Header("--- 기본 화살 데미지 ---")] // 인스펙터 창에 제목을 표시할거에요 -> --- 기본 화살 데미지 --- 를
+ [Tooltip("기본 화살 데미지 (속성과 무관한 물리 데미지)")]
+ [SerializeField] private float baseDamage = 10f; // 변수를 선언할거에요 -> 기본 물리 데미지인 baseDamage를
+
+ [Header("--- 🔥 불 속성 설정 ---")] // 인스펙터 창에 제목을 표시할거에요 -> 불 속성 섹션을
+ [SerializeField]
+ private ElementEffectData fireEffect = new ElementEffectData // 변수를 선언할거에요 -> 불 속성 데이터인 fireEffect를
+ {
+ bonusDamage = 5f, // 기본값을 설정할거에요 -> 즉발 추가 데미지를 5로
+ dotDamagePerTick = 3f, // 기본값을 설정할거에요 -> 틱당 화상 데미지를 3으로
+ dotDuration = 4f, // 기본값을 설정할거에요 -> 화상 지속시간을 4초로
+ dotTickInterval = 0.5f, // 기본값을 설정할거에요 -> 틱 간격을 0.5초로
+ effectStrength = 1f // 기본값을 설정할거에요 -> 효과 강도를 1로 (불은 순수 데미지라 참고용)
+ };
+
+ [Header("--- ❄️ 얼음 속성 설정 ---")] // 인스펙터 창에 제목을 표시할거에요 -> 얼음 속성 섹션을
+ [SerializeField]
+ private ElementEffectData iceEffect = new ElementEffectData // 변수를 선언할거에요 -> 얼음 속성 데이터인 iceEffect를
+ {
+ bonusDamage = 2f, // 기본값을 설정할거에요 -> 즉발 추가 데미지를 2로
+ dotDamagePerTick = 0f, // 기본값을 설정할거에요 -> 틱 데미지를 0으로 (얼음은 슬로우 위주)
+ dotDuration = 3f, // 기본값을 설정할거에요 -> 슬로우 지속시간을 3초로
+ dotTickInterval = 1f, // 기본값을 설정할거에요 -> 틱 간격을 1초로
+ effectStrength = 0.5f // 기본값을 설정할거에요 -> 슬로우 비율을 0.5 (50% 감속)로
+ };
+
+ [Header("--- ☠️ 독 속성 설정 ---")] // 인스펙터 창에 제목을 표시할거에요 -> 독 속성 섹션을
+ [SerializeField]
+ private ElementEffectData poisonEffect = new ElementEffectData // 변수를 선언할거에요 -> 독 속성 데이터인 poisonEffect를
+ {
+ bonusDamage = 1f, // 기본값을 설정할거에요 -> 즉발 추가 데미지를 1로
+ dotDamagePerTick = 2f, // 기본값을 설정할거에요 -> 틱당 독 데미지를 2로
+ dotDuration = 6f, // 기본값을 설정할거에요 -> 독 지속시간을 6초로
+ dotTickInterval = 1f, // 기본값을 설정할거에요 -> 틱 간격을 1초로
+ effectStrength = 1f // 기본값을 설정할거에요 -> 효과 강도를 1로
+ };
+
+ [Header("--- ⚡ 전기 속성 설정 ---")] // 인스펙터 창에 제목을 표시할거에요 -> 전기 속성 섹션을
+ [SerializeField]
+ private ElementEffectData lightningEffect = new ElementEffectData // 변수를 선언할거에요 -> 전기 속성 데이터인 lightningEffect를
+ {
+ bonusDamage = 8f, // 기본값을 설정할거에요 -> 즉발 추가 데미지를 8로
+ dotDamagePerTick = 0f, // 기본값을 설정할거에요 -> 틱 데미지를 0으로 (전기는 스턴 위주)
+ dotDuration = 1.5f, // 기본값을 설정할거에요 -> 스턴 지속시간을 1.5초로
+ dotTickInterval = 1f, // 기본값을 설정할거에요 -> 틱 간격을 1초로
+ effectStrength = 1.5f // 기본값을 설정할거에요 -> 스턴 지속시간을 1.5초로
+ };
+
[Header("--- 디버그 시각화 ---")] // 인스펙터 창에 제목을 표시할거에요 -> --- 디버그 시각화 --- 를
[SerializeField] private bool showDebugRay = true; // 변수를 선언할거에요 -> 디버그 레이 표시 여부인 showDebugRay를
- // 화살 스탯
- private float damage; // 변수를 선언할거에요 -> 데미지 damage를
+ // 화살 런타임 스탯 (Initialize에서 설정됨)
+ private float damage; // 변수를 선언할거에요 -> 최종 데미지 damage를
private float speed; // 변수를 선언할거에요 -> 속도 speed를
private float range; // 변수를 선언할거에요 -> 사거리 range를
private Vector3 startPos; // 변수를 선언할거에요 -> 시작 위치 startPos를
@@ -26,10 +74,8 @@ public class PlayerArrow : MonoBehaviour // 클래스를 선언할거에요 -> M
private bool isFired = false; // 변수를 초기화할거에요 -> 발사 여부 isFired를 거짓으로
private bool hasHit = false; // 변수를 초기화할거에요 -> 충돌 여부 hasHit을 거짓으로
- // [NEW] 속성 데미지 시스템
- private ArrowElementType elementType = ArrowElementType.None; // 변수를 초기화할거에요 -> 속성 타입 elementType을 없음으로
- private float elementDamage = 0f; // 변수를 초기화할거에요 -> 속성 데미지 elementDamage를 0으로
- private float elementDuration = 0f; // 변수를 초기화할거에요 -> 속성 지속 시간 elementDuration을 0으로
+ // 현재 적용 중인 속성 (Initialize에서 설정됨)
+ private ArrowElementType currentElement = ArrowElementType.None; // 변수를 초기화할거에요 -> 현재 속성을 없음으로
// Raycast 추적용
private Vector3 previousPosition; // 변수를 선언할거에요 -> 이전 위치 previousPosition을
@@ -56,7 +102,7 @@ public class PlayerArrow : MonoBehaviour // 클래스를 선언할거에요 -> M
}
///
- /// [MODIFIED] 초기화 — 속성 정보 포함 (7개 파라미터)
+ /// [UPGRADED] 초기화 — 속성 타입만 받고, 세부 수치는 인스펙터 설정값 사용
/// PlayerAttack.OnShootArrow()에서 호출됩니다.
///
public void Initialize(
@@ -64,22 +110,22 @@ public class PlayerArrow : MonoBehaviour // 클래스를 선언할거에요 -> M
float arrowSpeed,
float maxRange,
Vector3 direction,
- ArrowElementType element,
- float elemDmg,
- float elemDur) // 함수를 선언할거에요 -> 화살을 초기화하는 Initialize를
+ ArrowElementType element) // 함수를 선언할거에요 -> 화살을 초기화하는 Initialize를 (5개 파라미터)
{
- this.damage = dmg; // 값을 저장할거에요 -> 데미지를
this.speed = arrowSpeed; // 값을 저장할거에요 -> 속도를
this.range = maxRange; // 값을 저장할거에요 -> 사거리를
this.shootDirection = direction.normalized; // 값을 저장할거에요 -> 정규화된 발사 방향을
this.startPos = transform.position; // 값을 저장할거에요 -> 시작 위치를
this.previousPosition = transform.position; // 값을 저장할거에요 -> 이전 위치를 (레이캐스트용)
this.isFired = true; // 상태를 바꿀거에요 -> 발사됨 상태로
+ this.currentElement = element; // 값을 저장할거에요 -> 현재 속성 타입을
- // [NEW] 속성 정보 저장
- this.elementType = element; // 값을 저장할거에요 -> 속성 타입을
- this.elementDamage = elemDmg; // 값을 저장할거에요 -> 속성 데미지를
- this.elementDuration = elemDur; // 값을 저장할거에요 -> 속성 지속 시간을
+ // [UPGRADED] 기본 데미지 = 외부에서 받은 dmg + 속성 즉발 보너스 데미지
+ ElementEffectData effectData = GetElementData(element); // 데이터를 가져올거에요 -> 현재 속성에 맞는 설정값을
+ float bonusDmg = (effectData != null && effectData.enabled) ? effectData.bonusDamage : 0f; // 값을 계산할거에요 -> 속성 추가 즉발 데미지를
+ this.damage = dmg + bonusDmg; // 값을 저장할거에요 -> 기본 데미지 + 속성 보너스를 최종 데미지로
+
+ Debug.Log($"[Arrow] 초기화 — 기본:{dmg} + 속성보너스:{bonusDmg} = 최종:{this.damage} | 속성:{element}"); // 로그를 출력할거에요 -> 데미지 계산 내역을
// 발사 방향으로 회전
if (shootDirection != Vector3.zero) // 조건이 맞으면 실행할거에요 -> 방향이 유효하다면
@@ -101,13 +147,42 @@ public class PlayerArrow : MonoBehaviour // 클래스를 선언할거에요 -> M
Destroy(gameObject, 5f); // 파괴할거에요 -> 5초 뒤에 안전장치로
}
+ ///
+ /// [하위 호환성] 기존 7-파라미터 버전도 유지 (외부에서 직접 수치를 넘기는 경우)
+ ///
+ public void Initialize(
+ float dmg,
+ float arrowSpeed,
+ float maxRange,
+ Vector3 direction,
+ ArrowElementType element,
+ float elemDmg,
+ float elemDur) // 함수를 선언할거에요 -> 구버전 호환용 7-파라미터 초기화 함수를
+ {
+ Initialize(dmg, arrowSpeed, maxRange, direction, element); // 실행할거에요 -> 새 5-파라미터 초기화를 (인스펙터 값 사용)
+ }
+
///
/// [하위 호환성] 방향 없는 3-파라미터 버전
///
- public void Initialize(float dmg, float arrowSpeed, float maxRange) // 함수를 선언할거에요 -> 구버전 호환용 초기화 함수를
+ public void Initialize(float dmg, float arrowSpeed, float maxRange) // 함수를 선언할거에요 -> 구버전 호환용 3-파라미터 초기화 함수를
{
- Initialize(dmg, arrowSpeed, maxRange, transform.forward,
- ArrowElementType.None, 0f, 0f); // 실행할거에요 -> 신버전 초기화 함수를 기본값으로
+ Initialize(dmg, arrowSpeed, maxRange, transform.forward, ArrowElementType.None); // 실행할거에요 -> 신버전 초기화를 기본값으로
+ }
+
+ ///
+ /// [NEW] 속성 타입에 맞는 인스펙터 설정 데이터를 반환
+ ///
+ private ElementEffectData GetElementData(ArrowElementType element) // 함수를 선언할거에요 -> 속성 데이터를 가져오는 GetElementData를
+ {
+ switch (element) // 분기할거에요 -> 속성 타입에 따라
+ {
+ case ArrowElementType.Fire: return fireEffect; // 반환할거에요 -> 불 속성 설정값을
+ case ArrowElementType.Ice: return iceEffect; // 반환할거에요 -> 얼음 속성 설정값을
+ case ArrowElementType.Poison: return poisonEffect; // 반환할거에요 -> 독 속성 설정값을
+ case ArrowElementType.Lightning: return lightningEffect; // 반환할거에요 -> 전기 속성 설정값을
+ default: return null; // 반환할거에요 -> 없음 (일반 화살)
+ }
}
private void Update() // 함수를 실행할거에요 -> 매 프레임마다 Update를
@@ -164,7 +239,7 @@ public class PlayerArrow : MonoBehaviour // 클래스를 선언할거에요 -> M
}
///
- /// [MODIFIED] 충돌 처리 — 속성 데미지 적용 추가
+ /// [UPGRADED] 충돌 처리 — 인스펙터 설정값 기반 속성 데미지 적용
///
private void HandleHit(Collider hitCollider, Vector3 hitPoint) // 함수를 선언할거에요 -> 충돌 결과를 처리하는 HandleHit을
{
@@ -189,11 +264,11 @@ public class PlayerArrow : MonoBehaviour // 클래스를 선언할거에요 -> M
if (monster != null) // 조건이 맞으면 실행할거에요 -> 몬스터 스크립트가 있다면
{
- // 1. 기본 데미지 적용
- monster.TakeDamage(damage); // 실행할거에요 -> 데미지 입히기 함수를
- Debug.Log($"적 명중! 기본데미지: {damage}"); // 로그를 출력할거에요 -> 명중 메시지를
+ // 1. 최종 데미지 적용 (기본 + 속성 보너스가 이미 합산됨)
+ monster.TakeDamage(damage); // 실행할거에요 -> 최종 데미지를 입히는 함수를
+ Debug.Log($"[Arrow] 적 명중! 최종 데미지: {damage} | 속성: {currentElement}"); // 로그를 출력할거에요 -> 명중 메시지를
- // 2. [NEW] 속성 효과 적용
+ // 2. [UPGRADED] 속성 지속 효과(DoT) 적용 — 인스펙터 설정값 사용
ApplyElementEffect(monster); // 실행할거에요 -> 속성 효과 적용 함수를
}
@@ -206,41 +281,67 @@ public class PlayerArrow : MonoBehaviour // 클래스를 선언할거에요 -> M
}
else // 조건이 틀리면 실행할거에요 -> 그 외의 충돌이라면
{
- // 기타 충돌 (Unknown)
Destroy(gameObject, 0.05f); // 파괴할거에요 -> 화살을
}
}
///
- /// [NEW] 속성 효과 적용
- /// MonsterClass에 ApplyStatusEffect 메서드가 있어야 합니다.
+ /// [UPGRADED] 속성 효과 적용 — 인스펙터에서 설정한 값을 직접 사용
+ /// MonsterClass에 ApplyStatusEffect(StatusEffectType, float dotDmg, float duration, float tickInterval, float strength)
+ /// 오버로드가 필요합니다.
///
private void ApplyElementEffect(MonsterClass monster) // 함수를 선언할거에요 -> 속성 효과를 적용하는 ApplyElementEffect를
{
- if (elementType == ArrowElementType.None || elementDamage <= 0f) return; // 조건이 맞으면 중단할거에요 -> 속성이 없거나 데미지가 없다면
+ if (currentElement == ArrowElementType.None) return; // 조건이 맞으면 중단할거에요 -> 일반 화살이라면
- // MonsterClass에 ApplyStatusEffect가 있는지 확인
- switch (elementType) // 분기할거에요 -> 속성 타입에 따라
+ ElementEffectData data = GetElementData(currentElement); // 데이터를 가져올거에요 -> 현재 속성의 인스펙터 설정값을
+ if (data == null || !data.enabled) return; // 조건이 맞으면 중단할거에요 -> 설정이 없거나 비활성이라면
+
+ switch (currentElement) // 분기할거에요 -> 속성 타입에 따라
{
case ArrowElementType.Fire: // 조건이 맞으면 실행할거에요 -> 불 속성이라면
- monster.ApplyStatusEffect(StatusEffectType.Burn, elementDamage, elementDuration); // 실행할거에요 -> 화상 효과 적용을
- Debug.Log($"화염 효과! {elementDamage} 데미지 x {elementDuration}초"); // 로그를 출력할거에요 -> 효과 설명을
- break;
+ monster.ApplyStatusEffect( // 실행할거에요 -> 화상 상태이상을 적용하는 함수를
+ StatusEffectType.Burn, // 인자를 전달할거에요 -> 화상 타입을
+ data.dotDamagePerTick, // 인자를 전달할거에요 -> 틱당 데미지를 (인스펙터 값)
+ data.dotDuration, // 인자를 전달할거에요 -> 지속 시간을 (인스펙터 값)
+ data.dotTickInterval, // 인자를 전달할거에요 -> 틱 간격을 (인스펙터 값)
+ data.effectStrength // 인자를 전달할거에요 -> 효과 강도를 (인스펙터 값)
+ );
+ Debug.Log($"[Arrow] 🔥 화염! 틱당:{data.dotDamagePerTick} x {data.dotDuration}초 (간격:{data.dotTickInterval}초)"); // 로그를 출력할거에요 -> 화상 효과 내역을
+ break; // 분기를 종료할거에요
case ArrowElementType.Ice: // 조건이 맞으면 실행할거에요 -> 얼음 속성이라면
- monster.ApplyStatusEffect(StatusEffectType.Slow, elementDamage, elementDuration); // 실행할거에요 -> 슬로우 효과 적용을
- Debug.Log($"빙결 효과! 슬로우 {elementDuration}초"); // 로그를 출력할거에요 -> 효과 설명을
- break;
+ monster.ApplyStatusEffect( // 실행할거에요 -> 슬로우 상태이상을 적용하는 함수를
+ StatusEffectType.Slow, // 인자를 전달할거에요 -> 슬로우 타입을
+ data.dotDamagePerTick, // 인자를 전달할거에요 -> 틱당 데미지를 (얼음은 보통 0)
+ data.dotDuration, // 인자를 전달할거에요 -> 슬로우 지속 시간을
+ data.dotTickInterval, // 인자를 전달할거에요 -> 틱 간격을
+ data.effectStrength // 인자를 전달할거에요 -> 슬로우 비율을 (0.5면 50% 감속)
+ );
+ Debug.Log($"[Arrow] ❄️ 빙결! 슬로우:{data.effectStrength * 100}% x {data.dotDuration}초"); // 로그를 출력할거에요 -> 빙결 효과 내역을
+ break; // 분기를 종료할거에요
case ArrowElementType.Poison: // 조건이 맞으면 실행할거에요 -> 독 속성이라면
- monster.ApplyStatusEffect(StatusEffectType.Poison, elementDamage, elementDuration); // 실행할거에요 -> 독 효과 적용을
- Debug.Log($"독 효과! {elementDamage} 데미지 x {elementDuration}초"); // 로그를 출력할거에요 -> 효과 설명을
- break;
+ monster.ApplyStatusEffect( // 실행할거에요 -> 독 상태이상을 적용하는 함수를
+ StatusEffectType.Poison, // 인자를 전달할거에요 -> 독 타입을
+ data.dotDamagePerTick, // 인자를 전달할거에요 -> 틱당 독 데미지를
+ data.dotDuration, // 인자를 전달할거에요 -> 독 지속 시간을
+ data.dotTickInterval, // 인자를 전달할거에요 -> 틱 간격을
+ data.effectStrength // 인자를 전달할거에요 -> 효과 강도를
+ );
+ Debug.Log($"[Arrow] ☠️ 독! 틱당:{data.dotDamagePerTick} x {data.dotDuration}초 (간격:{data.dotTickInterval}초)"); // 로그를 출력할거에요 -> 독 효과 내역을
+ break; // 분기를 종료할거에요
- case ArrowElementType.Lightning: // 조건이 맞으면 실행할거에요 -> 번개 속성이라면
- monster.ApplyStatusEffect(StatusEffectType.Shock, elementDamage, elementDuration); // 실행할거에요 -> 감전(스턴) 효과 적용을
- Debug.Log($"감전 효과! {elementDamage} 범위 데미지"); // 로그를 출력할거에요 -> 효과 설명을
- break;
+ case ArrowElementType.Lightning: // 조건이 맞으면 실행할거에요 -> 전기 속성이라면
+ monster.ApplyStatusEffect( // 실행할거에요 -> 감전(스턴) 상태이상을 적용하는 함수를
+ StatusEffectType.Shock, // 인자를 전달할거에요 -> 감전 타입을
+ data.dotDamagePerTick, // 인자를 전달할거에요 -> 틱당 데미지를 (전기는 보통 0)
+ data.dotDuration, // 인자를 전달할거에요 -> 스턴 지속 시간을
+ data.dotTickInterval, // 인자를 전달할거에요 -> 틱 간격을
+ data.effectStrength // 인자를 전달할거에요 -> 스턴 시간을
+ );
+ Debug.Log($"[Arrow] ⚡ 감전! 스턴:{data.effectStrength}초 | 추가데미지:{data.bonusDamage}"); // 로그를 출력할거에요 -> 감전 효과 내역을
+ break; // 분기를 종료할거에요
}
}
@@ -269,6 +370,23 @@ public class PlayerArrow : MonoBehaviour // 클래스를 선언할거에요 -> M
}
}
+ ///
+ /// [PUBLIC] 외부에서 인스펙터 속성 데이터를 읽을 수 있는 접근자
+ /// UI 등에서 속성 정보를 표시할 때 사용
+ ///
+ public ElementEffectData GetCurrentElementData() // 함수를 선언할거에요 -> 현재 속성 데이터를 반환하는 접근자를
+ {
+ return GetElementData(currentElement); // 반환할거에요 -> 현재 속성의 설정 데이터를
+ }
+
+ ///
+ /// [PUBLIC] 현재 속성 타입 반환
+ ///
+ public ArrowElementType GetCurrentElement() // 함수를 선언할거에요 -> 현재 속성 타입을 반환하는 접근자를
+ {
+ return currentElement; // 반환할거에요 -> 현재 속성 타입을
+ }
+
///
/// Gizmo 디버그 시각화 — 기존 로직 유지 + 속성 색상 추가
///
@@ -288,15 +406,15 @@ public class PlayerArrow : MonoBehaviour // 클래스를 선언할거에요 -> M
Gizmos.color = Color.yellow; // 색상을 설정할거에요 -> 노란색으로
Gizmos.DrawLine(tipPosition, tipPosition + direction * raycastDistance); // 선을 그릴거에요 -> 궤적을 표시하는
- // [NEW] 속성별 색상 표시
- switch (elementType) // 분기할거에요 -> 속성 타입에 따라
+ // 속성별 색상 표시
+ switch (currentElement) // 분기할거에요 -> 속성 타입에 따라
{
case ArrowElementType.Fire: Gizmos.color = Color.red; break; // 색상을 바꿀거에요 -> 빨강으로
case ArrowElementType.Ice: Gizmos.color = Color.cyan; break; // 색상을 바꿀거에요 -> 청록으로
case ArrowElementType.Poison: Gizmos.color = Color.green; break; // 색상을 바꿀거에요 -> 초록으로
case ArrowElementType.Lightning: Gizmos.color = Color.yellow; break; // 색상을 바꿀거에요 -> 노랑으로
}
- if (elementType != ArrowElementType.None) // 조건이 맞으면 실행할거에요 -> 속성이 있다면
+ if (currentElement != ArrowElementType.None) // 조건이 맞으면 실행할거에요 -> 속성이 있다면
{
Gizmos.DrawWireSphere(transform.position, 0.5f); // 그림을 그릴거에요 -> 속성 표시용 구체를
}
diff --git a/Assets/Scripts/Player/Combat/ElementEffectData.cs b/Assets/Scripts/Player/Combat/ElementEffectData.cs
new file mode 100644
index 00000000..8598a5b7
--- /dev/null
+++ b/Assets/Scripts/Player/Combat/ElementEffectData.cs
@@ -0,0 +1,27 @@
+using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을
+
+///
+/// 속성별 데미지 설정을 담는 데이터 클래스
+/// 인스펙터에서 속성별로 데미지, 지속시간, 틱 간격을 조정할 수 있습니다.
+///
+[System.Serializable] // 인스펙터에서 보이게 할거에요 -> 이 클래스를
+public class ElementEffectData // 클래스를 선언할거에요 -> 속성 효과 데이터를 담는 ElementEffectData를
+{
+ [Tooltip("속성 활성화 여부")] // 툴팁을 표시할거에요 -> 마우스 올리면
+ public bool enabled = false; // 변수를 선언할거에요 -> 속성 사용 여부인 enabled를
+
+ [Tooltip("속성 추가 데미지 (기본 화살 데미지에 더해지는 즉발 추가 데미지)")]
+ public float bonusDamage = 5f; // 변수를 선언할거에요 -> 속성 즉발 추가 데미지인 bonusDamage를
+
+ [Tooltip("지속 데미지(DoT) 1틱당 데미지량")]
+ public float dotDamagePerTick = 3f; // 변수를 선언할거에요 -> 틱당 도트 데미지인 dotDamagePerTick을
+
+ [Tooltip("지속 데미지 총 지속 시간 (초)")]
+ public float dotDuration = 4f; // 변수를 선언할거에요 -> 도트 총 지속 시간인 dotDuration을
+
+ [Tooltip("지속 데미지 틱 간격 (초) — 예: 0.5면 0.5초마다 데미지")]
+ public float dotTickInterval = 0.5f; // 변수를 선언할거에요 -> 도트 틱 간격인 dotTickInterval을
+
+ [Tooltip("특수 효과 강도 (슬로우 비율, 스턴 시간 등 속성마다 다르게 활용)")]
+ public float effectStrength = 0.5f; // 변수를 선언할거에요 -> 특수 효과 강도인 effectStrength를
+}
\ No newline at end of file
diff --git a/Assets/Scripts/Player/Combat/ElementEffectData.cs.meta b/Assets/Scripts/Player/Combat/ElementEffectData.cs.meta
new file mode 100644
index 00000000..e63cb4f9
--- /dev/null
+++ b/Assets/Scripts/Player/Combat/ElementEffectData.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 35e52ffeb828c4a449da5a4e6638f64c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/Player/Stats/Stats.cs b/Assets/Scripts/Player/Stats/Stats.cs
index 5eab9201..336af621 100644
--- a/Assets/Scripts/Player/Stats/Stats.cs
+++ b/Assets/Scripts/Player/Stats/Stats.cs
@@ -18,15 +18,23 @@ public class Stats : MonoBehaviour // 클래스를 선언할거에요 -> MonoBeh
[Header("--- 밸런스 설정 ---")] // 인스펙터 창에 제목을 표시할거에요 -> --- 밸런스 설정 --- 을
[SerializeField] private float runSpeedMultiplier = 1.5f; // 변수를 선언할거에요 -> 달리기 속도 배율인 runSpeedMultiplier를
+ [Header("--- 최소값 제한 ---")] // 인스펙터 창에 제목을 표시할거에요 -> --- 최소값 제한 --- 을
+ [Tooltip("이동 속도가 이 값 아래로 내려가지 않습니다")]
+ [SerializeField] private float minMoveSpeed = 0.5f; // 변수를 선언할거에요 -> 최소 이동 속도인 minMoveSpeed를
+ [Tooltip("최대 체력이 이 값 아래로 내려가지 않습니다")]
+ [SerializeField] private float minMaxHealth = 1f; // 변수를 선언할거에요 -> 최소 체력인 minMaxHealth를
+ [Tooltip("공격력이 이 값 아래로 내려가지 않습니다")]
+ [SerializeField] private float minAttackDamage = 0f; // 변수를 선언할거에요 -> 최소 공격력인 minAttackDamage를
+
/* =========================
* 실제 게임 로직용 프로퍼티
* ========================= */
- public float MaxHealth => baseMaxHealth + bonusMaxHealth; // 프로퍼티를 선언할거에요 -> 총 최대 체력을 반환하는 MaxHealth를
- public float BaseAttackDamage => baseAttackDamage + bonusAttackDamage; // 프로퍼티를 선언할거에요 -> 총 기본 공격력을 반환하는 BaseAttackDamage를
- public float TotalAttackDamage => BaseAttackDamage + weaponDamage; // 프로퍼티를 선언할거에요 -> 최종 공격력(무기 포함)을 반환하는 TotalAttackDamage를
+ public float MaxHealth => Mathf.Max(baseMaxHealth + bonusMaxHealth, minMaxHealth); // 프로퍼티를 선언할거에요 -> 총 최대 체력을 최소값 이상으로 반환하는 MaxHealth를
+ public float BaseAttackDamage => Mathf.Max(baseAttackDamage + bonusAttackDamage, minAttackDamage); // 프로퍼티를 선언할거에요 -> 총 기본 공격력을 최소값 이상으로 반환하는 BaseAttackDamage를
+ public float TotalAttackDamage => Mathf.Max(BaseAttackDamage + weaponDamage, minAttackDamage); // 프로퍼티를 선언할거에요 -> 최종 공격력(무기 포함)을 최소값 이상으로 반환하는 TotalAttackDamage를
- // ✨ [수정] 이제 무게 페널티 없이 순수 속도만 계산합니다.
- public float CurrentMoveSpeed => baseMoveSpeed + bonusMoveSpeed; // 프로퍼티를 선언할거에요 -> 현재 이동 속도를 반환하는 CurrentMoveSpeed를
+ // ✨ [수정] Mathf.Max로 최소 속도 보장
+ public float CurrentMoveSpeed => Mathf.Max(baseMoveSpeed + bonusMoveSpeed, minMoveSpeed); // 프로퍼티를 선언할거에요 -> 현재 이동 속도를 최소값 이상으로 반환하는 CurrentMoveSpeed를
public float CurrentRunSpeed => CurrentMoveSpeed * runSpeedMultiplier; // 프로퍼티를 선언할거에요 -> 현재 달리기 속도를 반환하는 CurrentRunSpeed를
private void Update() // 함수를 실행할거에요 -> 매 프레임마다 Update를
diff --git a/Assets/Scripts/UI/Enemy/EnemyHP Ui.cs b/Assets/Scripts/UI/Enemy/EnemyHP Ui.cs
index 12adda1d..435e9ec0 100644
--- a/Assets/Scripts/UI/Enemy/EnemyHP Ui.cs
+++ b/Assets/Scripts/UI/Enemy/EnemyHP Ui.cs
@@ -1,42 +1,97 @@
-using UnityEngine; // 유니티 엔진의 기본 기능을 불러올거에요 -> UnityEngine을
-using System; // 기본 시스템 기능을 사용할거에요 -> System을
+using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을
+using UnityEngine.UI; // UI 기능을 불러올거에요 -> UnityEngine.UI를
+using TMPro; // TextMeshPro를 사용할거에요 -> TMPro를
-public class EnemyHealth : MonoBehaviour, IDamageable // 클래스를 선언할거에요 -> MonoBehaviour와 IDamageable을 상속받는 EnemyHealth를
+///
+/// 몬스터 체력바 UI — MonsterClass.OnHealthChanged 이벤트 연동
+/// 몬스터 프리팹의 MonsterHP_Canvas에 부착
+///
+public class MonsterHPUibar : MonoBehaviour // 클래스를 선언할거에요 -> 몬스터 체력바 UI를
{
- [Header("--- 능력치 ---")] // 인스펙터 창에 제목을 표시할거에요 -> --- 능력치 --- 를
- [SerializeField] private float maxHealth = 50f; // 변수를 선언할거에요 -> 최대 체력(50.0)을 maxHealth에
- private float _currentHealth; // 변수를 선언할거에요 -> 현재 체력을 저장할 _currentHealth를
+ [Header("--- 타겟 ---")] // 인스펙터 제목을 달거에요 -> 타겟 설정을
+ [Tooltip("MonsterClass가 붙어있는 몬스터 오브젝트")]
+ [SerializeField] private GameObject targetObject; // 변수를 선언할거에요 -> 타겟 오브젝트를 (몬스터 루트)
- public event Action OnHealthChanged; // 이벤트를 선언할거에요 -> 체력 변경 시 현재/최대를 알릴 OnHealthChanged를
- public bool IsDead { get; private set; } // 프로퍼티를 선언할거에요 -> 사망 여부를 외부에서 읽기만 가능하게 IsDead에
+ [Header("--- UI 요소 ---")] // 인스펙터 제목을 달거에요 -> UI 요소를
+ [Tooltip("HP_Filled (Image, Fill 방식)")]
+ [SerializeField] private Image hpFilledImage; // 변수를 선언할거에요 -> 체력바 Fill 이미지를
- private void Start() // 함수를 실행할거에요 -> 시작 시 Start를
+ [Tooltip("HP_Text (TMP) — '현재HP / 최대HP' 표시")]
+ [SerializeField] private TextMeshProUGUI hpText; // 변수를 선언할거에요 -> 체력 텍스트를
+
+ // 캐싱된 참조
+ private MonsterClass _monster; // 변수를 선언할거에요 -> 몬스터 클래스 참조를
+
+ private void Awake() // 함수를 실행할거에요 -> 초기화 Awake를
{
- _currentHealth = maxHealth; // 값을 초기화할거에요 -> 현재 체력을 최대 체력으로
- // ⭐ 시작하자마자 UI에 현재 숫자가 뜨도록 신호 발송
- OnHealthChanged?.Invoke(_currentHealth, maxHealth); // 이벤트를 실행할거에요 -> 초기 체력 상태를 알리기 위해
+ // 타겟이 비어있으면 부모에서 자동으로 찾기
+ if (targetObject == null) // 조건이 맞으면 실행할거에요 -> 타겟이 설정 안 됐다면
+ {
+ targetObject = transform.root.gameObject; // 할당할거에요 -> 최상위 부모 오브젝트를
+ }
+
+ if (targetObject != null) // 조건이 맞으면 실행할거에요 -> 타겟이 있다면
+ {
+ _monster = targetObject.GetComponent(); // 가져올거에요 -> MonsterClass를
+ if (_monster == null) // 조건이 맞으면 실행할거에요 -> 직접 못 찾았다면
+ {
+ _monster = targetObject.GetComponentInParent(); // 가져올거에요 -> 부모에서 MonsterClass를
+ }
+ }
}
- // IDamageable 인터페이스 구현
- public void TakeDamage(float amount) // 함수를 선언할거에요 -> 데미지를 입는 TakeDamage를
+ private void OnEnable() // 함수를 실행할거에요 -> 활성화 시 OnEnable을
{
- if (IsDead) return; // 조건이 맞으면 중단할거에요 -> 이미 죽었다면
+ if (_monster != null) // 조건이 맞으면 실행할거에요 -> 몬스터가 있다면
+ {
+ _monster.OnHealthChanged += OnMonsterHealthChanged; // 등록할거에요 -> 체력 변경 이벤트 리스너를
+ }
- _currentHealth = Mathf.Max(0, _currentHealth - amount); // 값을 계산할거에요 -> 체력에서 데미지를 빼고 0 밑으로 안 내려가게
-
- // ⭐ 피격 시 로그와 함께 UI 갱신 신호 발송
- Debug.Log($"{gameObject.name} 피격! 체력: {{{_currentHealth}/{maxHealth}}}"); // 로그를 출력할거에요 -> 피격 정보와 남은 체력을
- OnHealthChanged?.Invoke(_currentHealth, maxHealth); // 이벤트를 실행할거에요 -> 변경된 체력 정보를 알리기 위해
-
- if (_currentHealth <= 0) Die(); // 조건이 맞으면 실행할거에요 -> 체력이 0 이하라면 사망 처리 Die를
+ // 활성화 시 즉시 UI 갱신 (풀에서 재사용될 때 대비)
+ ResetUI(); // 실행할거에요 -> UI 초기화를
}
- private void Die() // 함수를 선언할거에요 -> 사망 처리를 하는 Die를
+ private void OnDisable() // 함수를 실행할거에요 -> 비활성화 시 OnDisable을
{
- if (IsDead) return; // 조건이 맞으면 중단할거에요 -> 이미 죽음 처리 중이라면
- IsDead = true; // 상태를 바꿀거에요 -> 사망 상태를 참으로
+ if (_monster != null) // 조건이 맞으면 실행할거에요 -> 몬스터가 있다면
+ {
+ _monster.OnHealthChanged -= OnMonsterHealthChanged; // 해제할거에요 -> 체력 변경 이벤트 리스너를
+ }
+ }
- Debug.Log($"{gameObject.name} 사망!"); // 로그를 출력할거에요 -> 사망 메시지를
- Destroy(gameObject, 1.5f); // 파괴할거에요 -> 이 오브젝트를 1.5초 뒤에
+ ///
+ /// MonsterClass.OnHealthChanged(currentHP, maxHP) 이벤트 콜백
+ ///
+ private void OnMonsterHealthChanged(float currentHP, float maxHP) // 함수를 선언할거에요 -> 체력 변경 콜백을
+ {
+ // Fill Amount 갱신
+ if (hpFilledImage != null) // 조건이 맞으면 실행할거에요 -> Fill 이미지가 있다면
+ {
+ float ratio = (maxHP > 0) ? currentHP / maxHP : 0f; // 비율을 계산할거에요 -> 현재 / 최대로
+ hpFilledImage.fillAmount = Mathf.Clamp01(ratio); // 값을 설정할거에요 -> 0~1 사이로 Fill Amount를
+ }
+
+ // 텍스트 갱신
+ if (hpText != null) // 조건이 맞으면 실행할거에요 -> 텍스트가 있다면
+ {
+ hpText.text = $"{currentHP:F0} / {maxHP:F0}"; // 텍스트를 설정할거에요 -> "현재 / 최대" 형식으로
+ }
+ }
+
+ ///
+ /// UI를 풀 체력 상태로 초기화 (스폰/재활성화 시)
+ ///
+ private void ResetUI() // 함수를 선언할거에요 -> UI 초기화를
+ {
+ if (hpFilledImage != null) // 조건이 맞으면 실행할거에요 -> Fill 이미지가 있다면
+ {
+ hpFilledImage.fillAmount = 1f; // 값을 설정할거에요 -> Fill을 꽉 채우기로
+ }
+
+ // _monster가 있으면 실제 스탯으로 텍스트 갱신
+ if (hpText != null) // 조건이 맞으면 실행할거에요 -> 텍스트가 있다면
+ {
+ hpText.text = ""; // 텍스트를 비울거에요 -> 초기화로 (첫 OnHealthChanged에서 갱신됨)
+ }
}
}
\ No newline at end of file
diff --git a/Assets/Scripts/UI/HUD/HP Ui bar.cs b/Assets/Scripts/UI/HUD/HP Ui bar.cs
index 70553fba..6f84f455 100644
--- a/Assets/Scripts/UI/HUD/HP Ui bar.cs
+++ b/Assets/Scripts/UI/HUD/HP Ui bar.cs
@@ -1,36 +1,100 @@
using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을
using UnityEngine.UI; // UI 기능을 불러올거에요 -> UnityEngine.UI를
+using TMPro; // TextMeshPro를 사용할거에요 -> TMPro를
+///
+/// 플레이어 체력바 UI — PlayerHealth 이벤트 연동
+/// HP_Filled (Image Fill) + Text (TMP) 실시간 갱신
+///
public class HPUibar : MonoBehaviour // 클래스를 선언할거에요 -> 체력바 UI를
{
- [Header("타겟")] // 인스펙터 제목을 달거에요 -> 타겟 설정을
- [SerializeField] private GameObject targetObject; // 변수를 선언할거에요 -> 타겟 오브젝트를
+ [Header("--- 타겟 ---")] // 인스펙터 제목을 달거에요 -> 타겟 설정을
+ [SerializeField] private GameObject targetObject; // 변수를 선언할거에요 -> 타겟 오브젝트를 (Player)
- // 컴포넌트들
- private Slider _slider; // 변수를 선언할거에요 -> 슬라이더를
- private IDamageable _damageable; // 변수를 선언할거에요 -> 데미지 인터페이스를
- // ⭐ [수정] TrainingDummy -> DummyBot
- private DummyBot _dummy; // 변수를 선언할거에요 -> 더미 봇 참조를
+ [Header("--- UI 요소 ---")] // 인스펙터 제목을 달거에요 -> UI 요소를
+ [Tooltip("HP_Filled (Image, Fill 방식)")]
+ [SerializeField] private Image hpFilledImage; // 변수를 선언할거에요 -> 체력바 Fill 이미지를
+
+ [Tooltip("Text (TMP) — '현재HP / 최대HP' 표시")]
+ [SerializeField] private TextMeshProUGUI hpText; // 변수를 선언할거에요 -> 체력 텍스트를
+
+ // 캐싱된 참조
+ private PlayerHealth _playerHealth; // 변수를 선언할거에요 -> 플레이어 체력 스크립트를
+ private Stats _playerStats; // 변수를 선언할거에요 -> 플레이어 스탯 스크립트를
private void Awake() // 함수를 실행할거에요 -> 초기화 Awake를
{
- _slider = GetComponent(); // 가져올거에요 -> 슬라이더를
if (targetObject != null) // 조건이 맞으면 실행할거에요 -> 타겟이 있다면
{
- _damageable = targetObject.GetComponent(); // 가져올거에요 -> 인터페이스를
- _dummy = targetObject.GetComponent(); // 가져올거에요 -> 더미 봇을
+ _playerHealth = targetObject.GetComponent(); // 가져올거에요 -> PlayerHealth를
+ _playerStats = targetObject.GetComponent(); // 가져올거에요 -> Stats를
}
}
- private void Update() // 함수를 실행할거에요 -> 매 프레임 업데이트를
+ private void OnEnable() // 함수를 실행할거에요 -> 활성화 시 OnEnable을
{
- if (_slider != null && _dummy != null) // 조건이 맞으면 실행할거에요 -> 슬라이더와 더미가 있다면
+ if (_playerHealth != null) // 조건이 맞으면 실행할거에요 -> 체력 스크립트가 있다면
{
- // _dummy에서 체력 정보를 가져오는 방식이 필요하다면 MonsterClass의 OnHealthChanged 이벤트를 쓰는 게 좋음
- // 하지만 기존 로직 유지를 위해 단순 접근 가정
- // _slider.value = ...
+ _playerHealth.OnHealthChanged.AddListener(OnHealthRatioChanged); // 등록할거에요 -> 체력 변경 이벤트 리스너를
}
}
- // (기존 코드에 있던 나머지 로직은 그대로 유지)
+ private void OnDisable() // 함수를 실행할거에요 -> 비활성화 시 OnDisable을
+ {
+ if (_playerHealth != null) // 조건이 맞으면 실행할거에요 -> 체력 스크립트가 있다면
+ {
+ _playerHealth.OnHealthChanged.RemoveListener(OnHealthRatioChanged); // 해제할거에요 -> 체력 변경 이벤트 리스너를
+ }
+ }
+
+ private void Start() // 함수를 실행할거에요 -> 시작 시 Start를
+ {
+ UpdateUI(); // 실행할거에요 -> 초기 UI 갱신을 (시작하자마자 올바른 값 표시)
+ }
+
+ ///
+ /// PlayerHealth.RefreshHealthUI()에서 ratio(0~1)를 받아 호출됨
+ ///
+ private void OnHealthRatioChanged(float ratio) // 함수를 선언할거에요 -> 체력 비율 변경 콜백을
+ {
+ // Fill Amount 갱신
+ if (hpFilledImage != null) // 조건이 맞으면 실행할거에요 -> Fill 이미지가 있다면
+ {
+ hpFilledImage.fillAmount = Mathf.Clamp01(ratio); // 값을 설정할거에요 -> 0~1 사이로 Fill Amount를
+ }
+
+ // 텍스트 갱신
+ UpdateHPText(); // 실행할거에요 -> 텍스트 갱신 함수를
+ }
+
+ ///
+ /// 텍스트를 "현재HP / 최대HP" 형식으로 갱신
+ ///
+ private void UpdateHPText() // 함수를 선언할거에요 -> HP 텍스트를 갱신하는 UpdateHPText를
+ {
+ if (hpText == null || _playerHealth == null || _playerStats == null) return; // 조건이 맞으면 중단할거에요 -> 필수 참조가 없으면
+
+ float current = _playerHealth.CurrentHP; // 값을 가져올거에요 -> 현재 체력을
+ float max = _playerStats.MaxHealth; // 값을 가져올거에요 -> 최대 체력을
+ hpText.text = $"{current:F0} / {max:F0}"; // 텍스트를 설정할거에요 -> "현재 / 최대" 형식으로
+ }
+
+ ///
+ /// 이벤트 없이 직접 UI를 갱신 (Start, 수동 호출용)
+ ///
+ private void UpdateUI() // 함수를 선언할거에요 -> 전체 UI 갱신을
+ {
+ if (_playerHealth == null || _playerStats == null) return; // 조건이 맞으면 중단할거에요 -> 참조가 없으면
+
+ float max = _playerStats.MaxHealth; // 값을 가져올거에요 -> 최대 체력을
+ float current = _playerHealth.CurrentHP; // 값을 가져올거에요 -> 현재 체력을
+ float ratio = (max > 0) ? current / max : 0f; // 비율을 계산할거에요 -> 현재/최대로
+
+ if (hpFilledImage != null) // 조건이 맞으면 실행할거에요 -> Fill 이미지가 있다면
+ {
+ hpFilledImage.fillAmount = Mathf.Clamp01(ratio); // 값을 설정할거에요 -> Fill Amount를
+ }
+
+ UpdateHPText(); // 실행할거에요 -> 텍스트 갱신을
+ }
}
\ No newline at end of file