From 072d9151a68de7e883bba37c32897d3b19cfbc95 Mon Sep 17 00:00:00 2001 From: Lee Siwoo Date: Fri, 20 Mar 2026 16:08:31 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=EC=A0=84=ED=88=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=94=AC=20=EB=B0=8F=20Entity=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #5 --- .../Assets/Scenes/BattleTestScene.unity.meta | 7 + Fantasy-Grower/Assets/Scripts/Battle.meta | 8 ++ .../Assets/Scripts/Battle/Entity.cs | 9 ++ .../Assets/Scripts/Battle/Entity.cs.meta | 2 + Fantasy-Grower/Packages/manifest.json | 1 + Fantasy-Grower/Packages/packages-lock.json | 7 + .../SceneTemplateSettings.json | 121 ++++++++++++++++++ 7 files changed, 155 insertions(+) create mode 100644 Fantasy-Grower/Assets/Scenes/BattleTestScene.unity.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Entity.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Entity.cs.meta create mode 100644 Fantasy-Grower/ProjectSettings/SceneTemplateSettings.json diff --git a/Fantasy-Grower/Assets/Scenes/BattleTestScene.unity.meta b/Fantasy-Grower/Assets/Scenes/BattleTestScene.unity.meta new file mode 100644 index 0000000..a5831f9 --- /dev/null +++ b/Fantasy-Grower/Assets/Scenes/BattleTestScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b73d6b2ded5ddba478ef65d56a8c7fd9 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Battle.meta b/Fantasy-Grower/Assets/Scripts/Battle.meta new file mode 100644 index 0000000..9f4cf34 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fbd7b20813b6cfb418bffbe318b666aa +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs b/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs new file mode 100644 index 0000000..82499f0 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs @@ -0,0 +1,9 @@ +using UnityEngine; + +public abstract class Entity : MonoBehaviour +{ + public int Hp { get; set; } + public int Attack { get; set; } + public float CriticalPercentage { get; set; } = 0f; + +} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs.meta b/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs.meta new file mode 100644 index 0000000..20b5308 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: dc2992df8ca20684984a514a7cb32031 \ No newline at end of file diff --git a/Fantasy-Grower/Packages/manifest.json b/Fantasy-Grower/Packages/manifest.json index c320cc0..4a8ae41 100644 --- a/Fantasy-Grower/Packages/manifest.json +++ b/Fantasy-Grower/Packages/manifest.json @@ -1,6 +1,7 @@ { "dependencies": { "com.unity.collab-proxy": "2.11.3", + "com.unity.device-simulator.devices": "1.0.1", "com.unity.feature.2d": "2.0.1", "com.unity.ide.rider": "3.0.39", "com.unity.ide.visualstudio": "2.0.27", diff --git a/Fantasy-Grower/Packages/packages-lock.json b/Fantasy-Grower/Packages/packages-lock.json index f005646..2d05d0e 100644 --- a/Fantasy-Grower/Packages/packages-lock.json +++ b/Fantasy-Grower/Packages/packages-lock.json @@ -122,6 +122,13 @@ }, "url": "https://packages.unity.com" }, + "com.unity.device-simulator.devices": { + "version": "1.0.1", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, "com.unity.ext.nunit": { "version": "2.0.5", "depth": 1, diff --git a/Fantasy-Grower/ProjectSettings/SceneTemplateSettings.json b/Fantasy-Grower/ProjectSettings/SceneTemplateSettings.json new file mode 100644 index 0000000..ede5887 --- /dev/null +++ b/Fantasy-Grower/ProjectSettings/SceneTemplateSettings.json @@ -0,0 +1,121 @@ +{ + "templatePinStates": [], + "dependencyTypeInfos": [ + { + "userAdded": false, + "type": "UnityEngine.AnimationClip", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.Animations.AnimatorController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.AnimatorOverrideController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.Audio.AudioMixerController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.ComputeShader", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Cubemap", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.GameObject", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.LightingDataAsset", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.LightingSettings", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Material", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.MonoScript", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial2D", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.VolumeProfile", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.SceneAsset", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Shader", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.ShaderVariantCollection", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Texture", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Texture2D", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Timeline.TimelineAsset", + "defaultInstantiationMode": 0 + } + ], + "defaultDependencyTypeInfo": { + "userAdded": false, + "type": "", + "defaultInstantiationMode": 1 + }, + "newSceneOverride": 0 +} \ No newline at end of file From e3d51144e31257fbd8fb41337e3909c23b56e922 Mon Sep 17 00:00:00 2001 From: Lee Siwoo Date: Mon, 30 Mar 2026 09:52:15 +0900 Subject: [PATCH 02/11] =?UTF-8?q?modify:=20=EA=B8=B0=EC=A1=B4=20Goods=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94=20Core=20=ED=8F=B4=EB=8D=94=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Fantasy-Grower/Assets/Scripts/Core.meta | 8 ++++++++ Fantasy-Grower/Assets/Scripts/{ => Core}/Goods.meta | 0 Fantasy-Grower/Assets/Scripts/{ => Core}/Goods/SO_Gold.cs | 0 .../Assets/Scripts/{ => Core}/Goods/SO_Gold.cs.meta | 0 .../Assets/Scripts/{ => Core}/Goods/SO_Goods.cs | 0 .../Assets/Scripts/{ => Core}/Goods/SO_Goods.cs.meta | 0 .../Assets/Scripts/{ => Core}/Goods/SO_Mithril.cs | 0 .../Assets/Scripts/{ => Core}/Goods/SO_Mithril.cs.meta | 0 Fantasy-Grower/Assets/Scripts/{ => Core}/Goods/SO_SP.cs | 0 .../Assets/Scripts/{ => Core}/Goods/SO_SP.cs.meta | 0 .../Assets/Scripts/{ => Core}/Goods/SO_UpgradeScroll.cs | 0 .../Scripts/{ => Core}/Goods/SO_UpgradeScroll.cs.meta | 0 Fantasy-Grower/Assets/Scripts/{ => Core}/Goods/SO_XP.cs | 0 .../Assets/Scripts/{ => Core}/Goods/SO_XP.cs.meta | 0 14 files changed, 8 insertions(+) create mode 100644 Fantasy-Grower/Assets/Scripts/Core.meta rename Fantasy-Grower/Assets/Scripts/{ => Core}/Goods.meta (100%) rename Fantasy-Grower/Assets/Scripts/{ => Core}/Goods/SO_Gold.cs (100%) rename Fantasy-Grower/Assets/Scripts/{ => Core}/Goods/SO_Gold.cs.meta (100%) rename Fantasy-Grower/Assets/Scripts/{ => Core}/Goods/SO_Goods.cs (100%) rename Fantasy-Grower/Assets/Scripts/{ => Core}/Goods/SO_Goods.cs.meta (100%) rename Fantasy-Grower/Assets/Scripts/{ => Core}/Goods/SO_Mithril.cs (100%) rename Fantasy-Grower/Assets/Scripts/{ => Core}/Goods/SO_Mithril.cs.meta (100%) rename Fantasy-Grower/Assets/Scripts/{ => Core}/Goods/SO_SP.cs (100%) rename Fantasy-Grower/Assets/Scripts/{ => Core}/Goods/SO_SP.cs.meta (100%) rename Fantasy-Grower/Assets/Scripts/{ => Core}/Goods/SO_UpgradeScroll.cs (100%) rename Fantasy-Grower/Assets/Scripts/{ => Core}/Goods/SO_UpgradeScroll.cs.meta (100%) rename Fantasy-Grower/Assets/Scripts/{ => Core}/Goods/SO_XP.cs (100%) rename Fantasy-Grower/Assets/Scripts/{ => Core}/Goods/SO_XP.cs.meta (100%) diff --git a/Fantasy-Grower/Assets/Scripts/Core.meta b/Fantasy-Grower/Assets/Scripts/Core.meta new file mode 100644 index 0000000..4a8d2de --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 367738f021b059b4ebfd70b77bca4b40 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Goods.meta b/Fantasy-Grower/Assets/Scripts/Core/Goods.meta similarity index 100% rename from Fantasy-Grower/Assets/Scripts/Goods.meta rename to Fantasy-Grower/Assets/Scripts/Core/Goods.meta diff --git a/Fantasy-Grower/Assets/Scripts/Goods/SO_Gold.cs b/Fantasy-Grower/Assets/Scripts/Core/Goods/SO_Gold.cs similarity index 100% rename from Fantasy-Grower/Assets/Scripts/Goods/SO_Gold.cs rename to Fantasy-Grower/Assets/Scripts/Core/Goods/SO_Gold.cs diff --git a/Fantasy-Grower/Assets/Scripts/Goods/SO_Gold.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/Goods/SO_Gold.cs.meta similarity index 100% rename from Fantasy-Grower/Assets/Scripts/Goods/SO_Gold.cs.meta rename to Fantasy-Grower/Assets/Scripts/Core/Goods/SO_Gold.cs.meta diff --git a/Fantasy-Grower/Assets/Scripts/Goods/SO_Goods.cs b/Fantasy-Grower/Assets/Scripts/Core/Goods/SO_Goods.cs similarity index 100% rename from Fantasy-Grower/Assets/Scripts/Goods/SO_Goods.cs rename to Fantasy-Grower/Assets/Scripts/Core/Goods/SO_Goods.cs diff --git a/Fantasy-Grower/Assets/Scripts/Goods/SO_Goods.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/Goods/SO_Goods.cs.meta similarity index 100% rename from Fantasy-Grower/Assets/Scripts/Goods/SO_Goods.cs.meta rename to Fantasy-Grower/Assets/Scripts/Core/Goods/SO_Goods.cs.meta diff --git a/Fantasy-Grower/Assets/Scripts/Goods/SO_Mithril.cs b/Fantasy-Grower/Assets/Scripts/Core/Goods/SO_Mithril.cs similarity index 100% rename from Fantasy-Grower/Assets/Scripts/Goods/SO_Mithril.cs rename to Fantasy-Grower/Assets/Scripts/Core/Goods/SO_Mithril.cs diff --git a/Fantasy-Grower/Assets/Scripts/Goods/SO_Mithril.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/Goods/SO_Mithril.cs.meta similarity index 100% rename from Fantasy-Grower/Assets/Scripts/Goods/SO_Mithril.cs.meta rename to Fantasy-Grower/Assets/Scripts/Core/Goods/SO_Mithril.cs.meta diff --git a/Fantasy-Grower/Assets/Scripts/Goods/SO_SP.cs b/Fantasy-Grower/Assets/Scripts/Core/Goods/SO_SP.cs similarity index 100% rename from Fantasy-Grower/Assets/Scripts/Goods/SO_SP.cs rename to Fantasy-Grower/Assets/Scripts/Core/Goods/SO_SP.cs diff --git a/Fantasy-Grower/Assets/Scripts/Goods/SO_SP.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/Goods/SO_SP.cs.meta similarity index 100% rename from Fantasy-Grower/Assets/Scripts/Goods/SO_SP.cs.meta rename to Fantasy-Grower/Assets/Scripts/Core/Goods/SO_SP.cs.meta diff --git a/Fantasy-Grower/Assets/Scripts/Goods/SO_UpgradeScroll.cs b/Fantasy-Grower/Assets/Scripts/Core/Goods/SO_UpgradeScroll.cs similarity index 100% rename from Fantasy-Grower/Assets/Scripts/Goods/SO_UpgradeScroll.cs rename to Fantasy-Grower/Assets/Scripts/Core/Goods/SO_UpgradeScroll.cs diff --git a/Fantasy-Grower/Assets/Scripts/Goods/SO_UpgradeScroll.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/Goods/SO_UpgradeScroll.cs.meta similarity index 100% rename from Fantasy-Grower/Assets/Scripts/Goods/SO_UpgradeScroll.cs.meta rename to Fantasy-Grower/Assets/Scripts/Core/Goods/SO_UpgradeScroll.cs.meta diff --git a/Fantasy-Grower/Assets/Scripts/Goods/SO_XP.cs b/Fantasy-Grower/Assets/Scripts/Core/Goods/SO_XP.cs similarity index 100% rename from Fantasy-Grower/Assets/Scripts/Goods/SO_XP.cs rename to Fantasy-Grower/Assets/Scripts/Core/Goods/SO_XP.cs diff --git a/Fantasy-Grower/Assets/Scripts/Goods/SO_XP.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/Goods/SO_XP.cs.meta similarity index 100% rename from Fantasy-Grower/Assets/Scripts/Goods/SO_XP.cs.meta rename to Fantasy-Grower/Assets/Scripts/Core/Goods/SO_XP.cs.meta From 82760665270d14f1d36ae6739c6eb8ae567166eb Mon Sep 17 00:00:00 2001 From: Lee Siwoo Date: Mon, 30 Mar 2026 09:53:02 +0900 Subject: [PATCH 03/11] =?UTF-8?q?feat:=20=EA=B8=B0=EB=B3=B8=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #5 --- .../Assets/Scenes/BattleTestScene.unity | 690 ++++++++++++++++++ .../Assets/Scripts/Battle/AttackCollider.cs | 21 + .../Scripts/Battle/AttackCollider.cs.meta | 2 + .../Assets/Scripts/Battle/Enemy.meta | 8 + .../Assets/Scripts/Battle/Enemy/Enemy.cs | 9 + .../Assets/Scripts/Battle/Enemy/Enemy.cs.meta | 2 + .../Assets/Scripts/Battle/Enemy/TestEnemy.cs | 6 + .../Scripts/Battle/Enemy/TestEnemy.cs.meta | 2 + .../Assets/Scripts/Battle/Entity.cs | 34 +- .../Assets/Scripts/Battle/Player.meta | 8 + .../Assets/Scripts/Battle/Player/Player.cs | 9 + .../Scripts/Battle/Player/Player.cs.meta | 2 + .../Assets/Scripts/Battle/Player/Warrior.cs | 6 + .../Scripts/Battle/Player/Warrior.cs.meta | 2 + .../Assets/Scripts/Core/EntityStatData.cs | 9 + .../Scripts/Core/EntityStatData.cs.meta | 2 + Fantasy-Grower/Assets/Scripts/SO.meta | 8 + .../Assets/Scripts/SO/StatData.meta | 8 + .../Assets/Scripts/SO/StatData/Enemy.meta | 8 + .../SO/StatData/Enemy/TestEnemyStat.asset | 17 + .../StatData/Enemy/TestEnemyStat.asset.meta | 8 + .../Assets/Scripts/SO/StatData/Player.meta | 8 + .../SO/StatData/Player/WarriorStat.asset | 17 + .../SO/StatData/Player/WarriorStat.asset.meta | 8 + 24 files changed, 891 insertions(+), 3 deletions(-) create mode 100644 Fantasy-Grower/Assets/Scenes/BattleTestScene.unity create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Enemy.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Player.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Player/Warrior.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Player/Warrior.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/EntityStatData.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/EntityStatData.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/SO.meta create mode 100644 Fantasy-Grower/Assets/Scripts/SO/StatData.meta create mode 100644 Fantasy-Grower/Assets/Scripts/SO/StatData/Enemy.meta create mode 100644 Fantasy-Grower/Assets/Scripts/SO/StatData/Enemy/TestEnemyStat.asset create mode 100644 Fantasy-Grower/Assets/Scripts/SO/StatData/Enemy/TestEnemyStat.asset.meta create mode 100644 Fantasy-Grower/Assets/Scripts/SO/StatData/Player.meta create mode 100644 Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WarriorStat.asset create mode 100644 Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WarriorStat.asset.meta diff --git a/Fantasy-Grower/Assets/Scenes/BattleTestScene.unity b/Fantasy-Grower/Assets/Scenes/BattleTestScene.unity new file mode 100644 index 0000000..bdbc048 --- /dev/null +++ b/Fantasy-Grower/Assets/Scenes/BattleTestScene.unity @@ -0,0 +1,690 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 3 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 0 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 1 + m_PVRFilteringGaussRadiusAO: 1 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &301892951 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 301892952} + - component: {fileID: 301892953} + - component: {fileID: 301892954} + - component: {fileID: 301892955} + m_Layer: 0 + m_Name: AttackCollider + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!4 &301892952 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 301892951} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 2.76, y: 0.18, z: 0} + m_LocalScale: {x: 5.036067, y: 1.32278, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1107896317} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!212 &301892953 +SpriteRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 301892951} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 0 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, type: 3} + m_Color: {r: 0.43710685, g: 0.8935439, b: 1, a: 1} + m_FlipX: 0 + m_FlipY: 0 + m_DrawMode: 0 + m_Size: {x: 1, y: 1} + m_AdaptiveModeThreshold: 0.5 + m_SpriteTileMode: 0 + m_WasSpriteAssigned: 1 + m_MaskInteraction: 0 + m_SpriteSortPoint: 0 +--- !u!61 &301892954 +BoxCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 301892951} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 1 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 1, y: 1} + newSize: {x: 1, y: 1} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Size: {x: 1, y: 1} + m_EdgeRadius: 0 +--- !u!114 &301892955 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 301892951} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0ef49aa0271c9724c9e7489b33226faf, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!1 &597768151 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 597768154} + - component: {fileID: 597768153} + - component: {fileID: 597768152} + - component: {fileID: 597768155} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &597768152 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 597768151} + m_Enabled: 1 +--- !u!20 &597768153 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 597768151} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 1 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &597768154 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 597768151} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &597768155 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 597768151} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 2 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + m_VolumeTrigger: {fileID: 0} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 0 + m_Antialiasing: 0 + m_AntialiasingQuality: 2 + m_StopNaN: 0 + m_Dithering: 0 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_AllowHDROutput: 1 + m_UseScreenCoordOverride: 0 + m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0} + m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_Version: 2 + m_TaaSettings: + m_Quality: 3 + m_FrameInfluence: 0.1 + m_JitterScale: 1 + m_MipBias: 0 + m_VarianceClampScale: 0.9 + m_ContrastAdaptiveSharpening: 0 +--- !u!1 &1107896316 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1107896317} + - component: {fileID: 1107896318} + - component: {fileID: 1107896319} + m_Layer: 0 + m_Name: Player + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1107896317 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1107896316} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -1, y: 0.5, z: 0} + m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} + m_ConstrainProportionsScale: 1 + m_Children: + - {fileID: 301892952} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!212 &1107896318 +SpriteRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1107896316} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 0 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_Sprite: {fileID: 7482667652216324306, guid: 48e93eef0688c4a259cb0eddcd8661f7, type: 3} + m_Color: {r: 0.119439125, g: 1, b: 0, a: 1} + m_FlipX: 0 + m_FlipY: 0 + m_DrawMode: 0 + m_Size: {x: 1, y: 1} + m_AdaptiveModeThreshold: 0.5 + m_SpriteTileMode: 0 + m_WasSpriteAssigned: 1 + m_MaskInteraction: 0 + m_SpriteSortPoint: 0 +--- !u!114 &1107896319 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1107896316} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 388f00d7b35e3f04b88f0364310333ef, type: 3} + m_Name: + m_EditorClassIdentifier: + statData: {fileID: 11400000, guid: fe352f708efa65540819d5d6bf040e6b, type: 2} +--- !u!1 &1330448690 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1330448692} + - component: {fileID: 1330448691} + - component: {fileID: 1330448693} + - component: {fileID: 1330448694} + - component: {fileID: 1330448695} + m_Layer: 0 + m_Name: Enemy + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!212 &1330448691 +SpriteRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1330448690} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 0 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_Sprite: {fileID: 7482667652216324306, guid: 48e93eef0688c4a259cb0eddcd8661f7, type: 3} + m_Color: {r: 1, g: 0, b: 0.02439022, a: 1} + m_FlipX: 0 + m_FlipY: 0 + m_DrawMode: 0 + m_Size: {x: 1, y: 1} + m_AdaptiveModeThreshold: 0.5 + m_SpriteTileMode: 0 + m_WasSpriteAssigned: 1 + m_MaskInteraction: 0 + m_SpriteSortPoint: 0 +--- !u!4 &1330448692 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1330448690} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1, y: 0.5, z: 0} + m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} + m_ConstrainProportionsScale: 1 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1330448693 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1330448690} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5180275fdefeff94fbdcd78cf158830b, type: 3} + m_Name: + m_EditorClassIdentifier: + statData: {fileID: 11400000, guid: 947637eac36383b49827043094c83699, type: 2} +--- !u!61 &1330448694 +BoxCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1330448690} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 1, y: 1} + newSize: {x: 1, y: 1} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Size: {x: 1, y: 1} + m_EdgeRadius: 0 +--- !u!50 &1330448695 +Rigidbody2D: + serializedVersion: 5 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1330448690} + m_BodyType: 1 + m_Simulated: 1 + m_UseFullKinematicContacts: 0 + m_UseAutoMass: 0 + m_Mass: 1 + m_LinearDamping: 0 + m_AngularDamping: 0.05 + m_GravityScale: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_Interpolate: 0 + m_SleepingMode: 1 + m_CollisionDetection: 0 + m_Constraints: 0 +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 597768154} + - {fileID: 1107896317} + - {fileID: 1330448692} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs b/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs new file mode 100644 index 0000000..9843330 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs @@ -0,0 +1,21 @@ +using UnityEngine; + +[RequireComponent(typeof(Collider2D))] +public class AttackCollider : MonoBehaviour +{ + private Player player; + + private void Awake() + { + player = GetComponentInParent(); + } + + private void OnTriggerEnter2D(Collider2D collision) + { + Enemy target; + if (collision.gameObject.TryGetComponent(out target)) + { + target.TakeDamage(player.AttackPower); + } + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs.meta b/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs.meta new file mode 100644 index 0000000..7391df6 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0ef49aa0271c9724c9e7489b33226faf \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy.meta b/Fantasy-Grower/Assets/Scripts/Battle/Enemy.meta new file mode 100644 index 0000000..f4d740b --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6c9cc4f3fcf3949418b95e3ddd80dbe7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs new file mode 100644 index 0000000..f381933 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs @@ -0,0 +1,9 @@ +using UnityEngine; + +public class Enemy : Entity +{ + public override void Death() + { + Debug.Log("TestEnemy 죽음"); + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs.meta b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs.meta new file mode 100644 index 0000000..bed3a1b --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6d282a7221d9d894fafad9c8a8e3f9d7 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs new file mode 100644 index 0000000..44a520b --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs @@ -0,0 +1,6 @@ +using UnityEngine; + +public class TestEnemy : Enemy +{ + +} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs.meta b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs.meta new file mode 100644 index 0000000..9289d4b --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5180275fdefeff94fbdcd78cf158830b \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs b/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs index 82499f0..1c94d11 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs @@ -2,8 +2,36 @@ public abstract class Entity : MonoBehaviour { - public int Hp { get; set; } - public int Attack { get; set; } - public float CriticalPercentage { get; set; } = 0f; + public int Hp { get; private set; } + public int AttackPower { get; private set; } + public float CriticalPercentage { get; private set; } + [SerializeField] + private EntityStatData statData; + + protected virtual void Awake() + { + if (statData == null) + { + Debug.LogWarning("[경고] StatData가 비어 있습니다!"); + return; + } + + Hp = statData.Hp; + AttackPower = statData.AttackPower; + CriticalPercentage = statData.CriticalPercentage; + } + + public virtual void Attack() { } + + public virtual void Death() { } + + public virtual void TakeDamage(int damage) + { + Debug.Log($"데미지 받음: {damage}"); + Hp = Mathf.Max(0, Hp - damage); + + if (Hp <= 0) + Death(); + } } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Player.meta b/Fantasy-Grower/Assets/Scripts/Battle/Player.meta new file mode 100644 index 0000000..a1022b6 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Player.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f7761a8f7b495b84994ff95723efb1a3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs b/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs new file mode 100644 index 0000000..70a6528 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs @@ -0,0 +1,9 @@ +using UnityEngine; + +public class Player : Entity +{ + public override void Attack() + { + Debug.Log("공격함!"); + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs.meta b/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs.meta new file mode 100644 index 0000000..a3918e3 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e179c8fd34877d8478a588e4fb7330f5 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Player/Warrior.cs b/Fantasy-Grower/Assets/Scripts/Battle/Player/Warrior.cs new file mode 100644 index 0000000..ea53885 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Player/Warrior.cs @@ -0,0 +1,6 @@ +using UnityEngine; + +public class Warrior : Player +{ + +} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Player/Warrior.cs.meta b/Fantasy-Grower/Assets/Scripts/Battle/Player/Warrior.cs.meta new file mode 100644 index 0000000..0429a59 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Player/Warrior.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 388f00d7b35e3f04b88f0364310333ef \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Core/EntityStatData.cs b/Fantasy-Grower/Assets/Scripts/Core/EntityStatData.cs new file mode 100644 index 0000000..694bf62 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/EntityStatData.cs @@ -0,0 +1,9 @@ +using UnityEngine; + +[CreateAssetMenu(fileName = "EntityStat", menuName = "Stat/Entity")] +public class EntityStatData : ScriptableObject +{ + public int Hp; + public int AttackPower; + public float CriticalPercentage; +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/EntityStatData.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/EntityStatData.cs.meta new file mode 100644 index 0000000..732a2d1 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/EntityStatData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5df7739d4b9038a4d9cb6566170314b6 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/SO.meta b/Fantasy-Grower/Assets/Scripts/SO.meta new file mode 100644 index 0000000..768b8e5 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/SO.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ebf9135db08abe44fa9cd411fc12b818 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/SO/StatData.meta b/Fantasy-Grower/Assets/Scripts/SO/StatData.meta new file mode 100644 index 0000000..8c6933c --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/SO/StatData.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a808d23574297d7489802be26a33176d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/SO/StatData/Enemy.meta b/Fantasy-Grower/Assets/Scripts/SO/StatData/Enemy.meta new file mode 100644 index 0000000..7e513e8 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/SO/StatData/Enemy.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a03e8e8a3fadc1347bd03fa932568758 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/SO/StatData/Enemy/TestEnemyStat.asset b/Fantasy-Grower/Assets/Scripts/SO/StatData/Enemy/TestEnemyStat.asset new file mode 100644 index 0000000..5280c5e --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/SO/StatData/Enemy/TestEnemyStat.asset @@ -0,0 +1,17 @@ +%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: 5df7739d4b9038a4d9cb6566170314b6, type: 3} + m_Name: TestEnemyStat + m_EditorClassIdentifier: + Hp: 150 + AttackPower: 10 + CriticalPercentage: 0 diff --git a/Fantasy-Grower/Assets/Scripts/SO/StatData/Enemy/TestEnemyStat.asset.meta b/Fantasy-Grower/Assets/Scripts/SO/StatData/Enemy/TestEnemyStat.asset.meta new file mode 100644 index 0000000..3a86e88 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/SO/StatData/Enemy/TestEnemyStat.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 947637eac36383b49827043094c83699 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/SO/StatData/Player.meta b/Fantasy-Grower/Assets/Scripts/SO/StatData/Player.meta new file mode 100644 index 0000000..636e09e --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/SO/StatData/Player.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 27c989ae2ea147c4ea465b4b1414b4c9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WarriorStat.asset b/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WarriorStat.asset new file mode 100644 index 0000000..9bbd478 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WarriorStat.asset @@ -0,0 +1,17 @@ +%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: 5df7739d4b9038a4d9cb6566170314b6, type: 3} + m_Name: WarriorStat + m_EditorClassIdentifier: + Hp: 200 + AttackPower: 20 + CriticalPercentage: 0 diff --git a/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WarriorStat.asset.meta b/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WarriorStat.asset.meta new file mode 100644 index 0000000..74febaa --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WarriorStat.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fe352f708efa65540819d5d6bf040e6b +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: From e6230a27237381f316d1106b470659c573b39836 Mon Sep 17 00:00:00 2001 From: Lee Siwoo Date: Mon, 30 Mar 2026 11:12:20 +0900 Subject: [PATCH 04/11] =?UTF-8?q?feat:=20=EC=8A=A4=ED=82=AC=20SO=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #5 --- .../Assets/Scripts/Battle/Entity.cs | 2 ++ .../Assets/Scripts/Battle/Player/Skill.meta | 8 ++++++++ .../Assets/Scripts/Core/EntityStatData.cs | 2 ++ .../Assets/Scripts/Core/SkillData.cs | 17 +++++++++++++++++ .../Assets/Scripts/Core/SkillData.cs.meta | 2 ++ .../Assets/Scripts/SO/SkillData.meta | 8 ++++++++ .../Assets/Scripts/SO/SkillData/Warrior.meta | 8 ++++++++ .../SO/SkillData/Warrior/BasicSkill.asset | 19 +++++++++++++++++++ .../SkillData/Warrior/BasicSkill.asset.meta | 8 ++++++++ 9 files changed, 74 insertions(+) create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Player/Skill.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillData.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillData.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/SO/SkillData.meta create mode 100644 Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior.meta create mode 100644 Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior/BasicSkill.asset create mode 100644 Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior/BasicSkill.asset.meta diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs b/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs index 1c94d11..0f43fcd 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs @@ -3,7 +3,9 @@ public abstract class Entity : MonoBehaviour { public int Hp { get; private set; } + public float DamageReduction { get; private set; } public int AttackPower { get; private set; } + public float AttackSpeed { get; private set; } public float CriticalPercentage { get; private set; } [SerializeField] diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Player/Skill.meta b/Fantasy-Grower/Assets/Scripts/Battle/Player/Skill.meta new file mode 100644 index 0000000..207cfbe --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Player/Skill.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 266463dea591bea43896a7d7e410e628 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Core/EntityStatData.cs b/Fantasy-Grower/Assets/Scripts/Core/EntityStatData.cs index 694bf62..6984092 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/EntityStatData.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/EntityStatData.cs @@ -4,6 +4,8 @@ public class EntityStatData : ScriptableObject { public int Hp; + public float DamageReduction; public int AttackPower; + public float AttackSpeed; public float CriticalPercentage; } diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillData.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillData.cs new file mode 100644 index 0000000..5d706a1 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillData.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +[CreateAssetMenu(fileName = "SkillStat", menuName = "Stat/Skill")] +public abstract class SkillData : ScriptableObject +{ + public string SkillName; + public Sprite SkillIcon; + + [TextArea] + public string SkillDescription; + + [Space(40)] + public float Cooldown; + public int Damage; + + public abstract void UseSkill(); +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillData.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillData.cs.meta new file mode 100644 index 0000000..6beff3a --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0c074805eb6fdc84eb032f40ad30f1e5 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/SO/SkillData.meta b/Fantasy-Grower/Assets/Scripts/SO/SkillData.meta new file mode 100644 index 0000000..8ba5971 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/SO/SkillData.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 18ac5e714cc0639468bf457b5c43a336 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior.meta b/Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior.meta new file mode 100644 index 0000000..f6bea78 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ff9d46ab3f948624cb4e5d932dc372ad +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior/BasicSkill.asset b/Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior/BasicSkill.asset new file mode 100644 index 0000000..a9adaba --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior/BasicSkill.asset @@ -0,0 +1,19 @@ +%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/SkillData/Warrior/BasicSkill.asset.meta b/Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior/BasicSkill.asset.meta new file mode 100644 index 0000000..6ab5c5f --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/SO/SkillData/Warrior/BasicSkill.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: da12e4dcc00447545828f224e06be591 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: From d92a0d8030a01dbdff6b07468dd96f9b832c2374 Mon Sep 17 00:00:00 2001 From: Lee Siwoo Date: Fri, 3 Apr 2026 13:59:15 +0900 Subject: [PATCH 05/11] =?UTF-8?q?modify:=20=EA=B3=B5=EA=B2=A9=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=83=80=EA=B2=9F=20=EC=A7=80?= =?UTF-8?q?=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #5 --- .../Assets/Scripts/Battle/AttackCollider.cs | 27 ++++++++++++++----- .../Assets/Scripts/Battle/Enemy/Enemy.cs | 8 +----- .../Assets/Scripts/Battle/Enemy/TestEnemy.cs | 5 +++- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs b/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs index 9843330..2b609d2 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs @@ -3,19 +3,32 @@ [RequireComponent(typeof(Collider2D))] public class AttackCollider : MonoBehaviour { - private Player player; + public EntityType type; + + private Entity entity; private void Awake() { - player = GetComponentInParent(); + entity = GetComponentInParent(); } private void OnTriggerEnter2D(Collider2D collision) { - Enemy target; - if (collision.gameObject.TryGetComponent(out target)) - { - target.TakeDamage(player.AttackPower); - } + Entity target; + if (!collision.gameObject.TryGetComponent(out target)) + return; + + bool shouldHit = + (type == EntityType.Player && target is Enemy) + || (type == EntityType.Enemy && target is Player); + + if (shouldHit) + target.TakeDamage(entity.AttackPower); } } + +public enum EntityType +{ + Player, + Enemy, +} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs index f381933..9bfdf59 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs @@ -1,9 +1,3 @@ using UnityEngine; -public class Enemy : Entity -{ - public override void Death() - { - Debug.Log("TestEnemy 죽음"); - } -} +public class Enemy : Entity { } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs index 44a520b..ee34352 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs @@ -2,5 +2,8 @@ public class TestEnemy : Enemy { - + public override void Death() + { + Debug.Log("TestEnemy 죽음"); + } } From ea93326603c8b92a8bca72a47428a313eb32f91f Mon Sep 17 00:00:00 2001 From: Lee Siwoo Date: Mon, 6 Apr 2026 10:42:26 +0900 Subject: [PATCH 06/11] =?UTF-8?q?feat:=20=EC=9E=90=EB=8F=99=20=EC=A0=84?= =?UTF-8?q?=ED=88=AC=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #5 --- Fantasy-Grower/.claude/commands/commit.md | 20 + Fantasy-Grower/.claude/commands/push.md | 22 + Fantasy-Grower/.claude/settings.local.json | 7 + Fantasy-Grower/.gemini/commands/commit.toml | 64 ++ Fantasy-Grower/.gemini/commands/pr.toml | 160 ++++ Fantasy-Grower/Assets/Prefabs.meta | 8 + Fantasy-Grower/Assets/Prefabs/Enemy.meta | 8 + .../Assets/Prefabs/Enemy/Enemy.prefab | 343 ++++++++ .../Assets/Prefabs/Enemy/Enemy.prefab.meta | 7 + .../Assets/Scenes/BattleTestScene.unity | 738 ++++++++++++++---- .../Assets/Scripts/Battle/AttackCollider.cs | 10 +- .../Scripts/Battle/AutoAttackController.cs | 50 ++ .../Battle/AutoAttackController.cs.meta | 2 + .../Assets/Scripts/Battle/BATTLELOOP_GUIDE.md | 212 +++++ .../Scripts/Battle/BATTLELOOP_GUIDE.md.meta | 7 + .../Assets/Scripts/Battle/BattleManager.cs | 210 +++++ .../Scripts/Battle/BattleManager.cs.meta | 2 + .../Assets/Scripts/Battle/Data.meta | 8 + .../Assets/Scripts/Battle/Data/DungeonData.cs | 25 + .../Scripts/Battle/Data/DungeonData.cs.meta | 2 + .../Scripts/Battle/Data/EnemyRewardData.cs | 13 + .../Battle/Data/EnemyRewardData.cs.meta | 2 + .../Assets/Scripts/Battle/Data/WaveData.cs | 23 + .../Scripts/Battle/Data/WaveData.cs.meta | 2 + .../Assets/Scripts/Battle/DungeonDataSO.meta | 8 + .../DungeonDataSO/TestDungeonData.asset | 23 + .../DungeonDataSO/TestDungeonData.asset.meta | 8 + .../Assets/Scripts/Battle/Enemy/Enemy.cs | 24 +- .../Assets/Scripts/Battle/Enemy/EnemyAI.cs | 73 ++ .../Scripts/Battle/Enemy/EnemyAI.cs.meta | 2 + .../Assets/Scripts/Battle/Enemy/TestEnemy.cs | 29 + .../Assets/Scripts/Battle/Entity.cs | 40 +- .../Assets/Scripts/Battle/Player/Player.cs | 25 +- .../Assets/Scripts/Battle/RewardSO.meta | 8 + .../Battle/RewardSO/TestRewardData.asset | 16 + .../Battle/RewardSO/TestRewardData.asset.meta | 8 + .../Assets/Scripts/Battle/WaveController.cs | 84 ++ .../Scripts/Battle/WaveController.cs.meta | 2 + .../Assets/Scripts/Battle/WaveDataSO.meta | 8 + .../Battle/WaveDataSO/TestWaveData.asset | 17 + .../Battle/WaveDataSO/TestWaveData.asset.meta | 8 + .../Assets/Scripts/Core/DamageCalculator.cs | 26 + .../Scripts/Core/DamageCalculator.cs.meta | 2 + .../Assets/Scripts/Core/EntityStatModifier.cs | 25 + .../Scripts/Core/EntityStatModifier.cs.meta | 2 + .../Assets/Scripts/Core/SkillData.cs | 6 + .../Assets/Scripts/Core/SkillTree.meta | 8 + .../Scripts/Core/SkillTree/ActiveSkillData.cs | 7 + .../Core/SkillTree/ActiveSkillData.cs.meta | 2 + .../Core/SkillTree/PassiveSkillData.cs | 17 + .../Core/SkillTree/PassiveSkillData.cs.meta | 2 + .../Scripts/Core/SkillTree/SKILLTREE_GUIDE.md | 215 +++++ .../Core/SkillTree/SKILLTREE_GUIDE.md.meta | 7 + .../Scripts/Core/SkillTree/SkillNodeData.cs | 19 + .../Core/SkillTree/SkillNodeData.cs.meta | 2 + .../Core/SkillTree/SkillTreeComponent.cs | 154 ++++ .../Core/SkillTree/SkillTreeComponent.cs.meta | 2 + .../Scripts/Core/SkillTree/SkillTreeData.cs | 38 + .../Core/SkillTree/SkillTreeData.cs.meta | 2 + .../Core/SkillTree/SkillTreeValidator.cs | 43 + .../Core/SkillTree/SkillTreeValidator.cs.meta | 2 + .../Scripts/Core/SkillTree/Strategies.meta | 8 + .../Strategies/BranchingTreeStrategy.cs | 37 + .../Strategies/BranchingTreeStrategy.cs.meta | 2 + .../Strategies/ISkillTreeStrategy.cs | 19 + .../Strategies/ISkillTreeStrategy.cs.meta | 2 + .../Strategies/LinearTreeStrategy.cs | 48 ++ .../Strategies/LinearTreeStrategy.cs.meta | 2 + .../Strategies/RowSelectTreeStrategy.cs | 32 + .../Strategies/RowSelectTreeStrategy.cs.meta | 2 + .../SO/StatData/Enemy/TestEnemyStat.asset | 2 + .../SO/StatData/Player/WarriorStat.asset | 2 + Fantasy-Grower/CLAUDE.md | 176 +++++ 73 files changed, 3099 insertions(+), 142 deletions(-) create mode 100644 Fantasy-Grower/.claude/commands/commit.md create mode 100644 Fantasy-Grower/.claude/commands/push.md create mode 100644 Fantasy-Grower/.claude/settings.local.json create mode 100644 Fantasy-Grower/.gemini/commands/commit.toml create mode 100644 Fantasy-Grower/.gemini/commands/pr.toml create mode 100644 Fantasy-Grower/Assets/Prefabs.meta create mode 100644 Fantasy-Grower/Assets/Prefabs/Enemy.meta create mode 100644 Fantasy-Grower/Assets/Prefabs/Enemy/Enemy.prefab create mode 100644 Fantasy-Grower/Assets/Prefabs/Enemy/Enemy.prefab.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/AutoAttackController.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/AutoAttackController.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/BATTLELOOP_GUIDE.md create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/BATTLELOOP_GUIDE.md.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/BattleManager.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/BattleManager.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Data.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Data/DungeonData.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Data/DungeonData.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Data/EnemyRewardData.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Data/EnemyRewardData.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Data/WaveData.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Data/WaveData.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/DungeonDataSO.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/DungeonDataSO/TestDungeonData.asset create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/DungeonDataSO/TestDungeonData.asset.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/RewardSO.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/RewardSO/TestRewardData.asset create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/RewardSO/TestRewardData.asset.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/WaveDataSO.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/WaveDataSO/TestWaveData.asset create mode 100644 Fantasy-Grower/Assets/Scripts/Battle/WaveDataSO/TestWaveData.asset.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/DamageCalculator.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/DamageCalculator.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/EntityStatModifier.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/EntityStatModifier.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillData.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillData.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillData.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillData.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/SKILLTREE_GUIDE.md create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/SKILLTREE_GUIDE.md.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillNodeData.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillNodeData.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeData.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeData.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeValidator.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeValidator.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/BranchingTreeStrategy.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/BranchingTreeStrategy.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/ISkillTreeStrategy.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/ISkillTreeStrategy.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/LinearTreeStrategy.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/LinearTreeStrategy.cs.meta create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/RowSelectTreeStrategy.cs create mode 100644 Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/RowSelectTreeStrategy.cs.meta create mode 100644 Fantasy-Grower/CLAUDE.md diff --git a/Fantasy-Grower/.claude/commands/commit.md b/Fantasy-Grower/.claude/commands/commit.md new file mode 100644 index 0000000..93ce5ee --- /dev/null +++ b/Fantasy-Grower/.claude/commands/commit.md @@ -0,0 +1,20 @@ +Perform the following steps in order to git commit the current project changes. + +## Steps + +1. Run `git status --porcelain` to check the list of changed files. + - If there are no changes, notify "No changes to commit." and exit. + +2. Run `git diff HEAD` to analyze the changes. + - If there are no staged changes, also check unstaged changes with `git diff`. + +3. Based on the changed files and content, write a commit message in Conventional Commits format in English. + - Format: `type: subject` (choose from feat / fix / refactor / docs / chore) + - Summarize the changes concisely + - Example: `feat: add /report slash command` + +4. Show me the generated commit message and proceed with the commit. + +5. Stage all changes with `git add -A`, then commit with `git commit -m "..."`. + +6. After the commit, run `git log --oneline -1` to verify and display the result. diff --git a/Fantasy-Grower/.claude/commands/push.md b/Fantasy-Grower/.claude/commands/push.md new file mode 100644 index 0000000..1c72ee7 --- /dev/null +++ b/Fantasy-Grower/.claude/commands/push.md @@ -0,0 +1,22 @@ +Perform the following steps in order to commit and push the current project changes to the remote repository. + +## Steps + +1. Run `git status --porcelain` to check the list of changed files. + - If there are no changes, skip to step 5 to push any unpushed commits. + +2. Run `git diff HEAD` to analyze the changes. + - If there are no staged changes, also check unstaged changes with `git diff`. + +3. Based on the changed files and content, write a commit message in Conventional Commits format. + - Format: `type: subject` (choose from feat / fix / refactor / docs / chore) + - **Write the subject in Korean** + - Summarize the changes concisely + - Example: `feat: /report 슬래시 커맨드 추가` + +4. Stage all changes with `git add -A`, then commit with `git commit -m "..."`. + +5. Run `git push` to push to the remote repository. + - If the upstream is not set, run `git push -u origin HEAD` instead. + +6. After the push, run `git log --oneline -1` to verify and display the result. \ No newline at end of file diff --git a/Fantasy-Grower/.claude/settings.local.json b/Fantasy-Grower/.claude/settings.local.json new file mode 100644 index 0000000..25df048 --- /dev/null +++ b/Fantasy-Grower/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(npm install:*)" + ] + } +} diff --git a/Fantasy-Grower/.gemini/commands/commit.toml b/Fantasy-Grower/.gemini/commands/commit.toml new file mode 100644 index 0000000..ae5d907 --- /dev/null +++ b/Fantasy-Grower/.gemini/commands/commit.toml @@ -0,0 +1,64 @@ +description = "Git 변경사항을 논리적 단위로 분리하여 커밋합니다." +prompt = """ +Create Git commits following the project's commit conventions. +## Current Changes +### git status +!{git status} +### git diff (unstaged) +!{git diff} +### git diff (staged) +!{git diff --cached} +--- +## Commit Message Format +{type}: {Korean description} +**Types:** +| Type | When to use | +|--------|-------------| +| feat | New file(s) added (new system / component / UI / shader / animation / scene) | +| fix | Broken behavior fixed, or missing reference / config corrected | +| modify | Existing file(s) modified — rename, restructure, method added to existing class | +| docs | Documentation changes only | +| chore | Tooling, CI/CD, dependency updates, config changes with no behavior change | +| asset | Art / audio / VFX / animation asset added or updated (no code change) | +**Boundary rules:** +- New script file added (System / Manager / Controller / Handler) → `feat` +- New method added to an existing script → `modify` +- Missing component reference or Inspector binding corrected → `fix` +- New system file + its initialization/registration together → `feat` (one logical unit) +- New scene or prefab added → `feat` +- Existing scene or prefab modified → `modify` +- New UI screen or panel added → `feat` +- Existing UI layout or logic modified → `modify` +- New shader or VFX graph added → `feat` +- Shader / VFX parameter tuning only → `modify` +- Art / audio / animation asset added or swapped → `asset` +- Refactoring without behavior change → `modify` +- Input binding / build setting / project setting changed → `chore` +**Description rules:** +- Written in **Korean** +- Short and imperative (단문) +- No trailing punctuation (`.`, `!`, etc.) +- Prefer verb style over noun style +**Examples:** +- feat: 전투 콤보 시스템 추가 +- feat: 인벤토리 UI 화면 추가 +- fix: 점프 중 이동 입력 씹히는 문제 수정 +- fix: PlayerController Inspector 참조 누락 수정 +- modify: 캐릭터 이동 속도 보간 방식 변경 +- modify: 보스 패턴 스크립트 리팩토링 +- asset: 플레이어 공격 이펙트 VFX 추가 +- chore: Input System 패키지 버전 업데이트 +**Do NOT:** +- Add Claude/Gemini as co-author +- Write descriptions in English +- Add a commit body — subject line only +--- +## Steps +1. Analyze all changes from the git status and diff above. +2. Categorize changes into **logical units** — group files that belong to the same feature or fix. +3. For each logical unit: + a. Stage only the relevant files with `git add ` + b. Write a concise commit message following the format above + c. Execute `git commit -m "message"` +4. After all commits, verify with `git log --oneline -n {number of commits made}`. +""" \ No newline at end of file diff --git a/Fantasy-Grower/.gemini/commands/pr.toml b/Fantasy-Grower/.gemini/commands/pr.toml new file mode 100644 index 0000000..5b2b6f6 --- /dev/null +++ b/Fantasy-Grower/.gemini/commands/pr.toml @@ -0,0 +1,160 @@ +description = "현재 브랜치 기반으로 GitHub PR을 생성합니다. 사용법: /pr 또는 /pr {base-branch}" + +prompt = """ +Generate and create a GitHub Pull Request based on the current branch. + +## Runtime Context + +### Current branch +!{git branch --show-current} + +### Recent tags +!{git tag --sort=-v:refname | head -10} + +### Existing release branches +!{git branch -a | grep release} + +### User-provided argument (base branch override) +{{args}} + +--- + +## Step 0. Determine behavior + +- If `{{args}}` is **not empty**: set Base Branch = `{{args}}`, skip to **Case 3** immediately. +- If `{{args}}` is **empty**: check the current branch name and follow the rules below. + +--- + +## Case 1: Current branch is `develop` + +**Step 1.** Determine the latest version from tags and release branches. + +**Step 2.** Analyze changes from `main`: +- `git log main..HEAD --oneline` +- `git diff main...HEAD --stat` + +**Step 3.** Recommend a version bump (Major / Minor / Patch) and explain why briefly. + +**Step 4.** Ask the user: "현재 버전: {current_version} / 추천: {recommended_version} ({reason}) — 사용할 버전 번호를 입력해주세요. (예: 1.0.1)" + +**Step 5.** After the user replies with a version number: +- Run: `git checkout -b release/{version}` +- Analyze changes from `main` for the PR body + +**Step 6.** Write the PR body following the **PR Body Template** below. Save to `PR_BODY.md`. + +**Step 7.** Run: +``` +gh pr create --title "release/{version}" --body-file PR_BODY.md --base main +``` + +**Step 8.** Run: `rm PR_BODY.md` + +--- + +## Case 2: Current branch matches `release/x.x.x` + +**Step 1.** Extract version from branch name (e.g., `release/1.2.0` → `1.2.0`). + +**Step 2.** Analyze changes from `main`: +- `git log main..HEAD --oneline` +- `git diff main...HEAD --stat` + +**Step 3.** Write PR body following the **PR Body Template** below. Save to `PR_BODY.md`. + +**Step 4.** Run: +``` +gh pr create --title "release/{version}" --body-file PR_BODY.md --base main +``` + +**Step 5.** Run: `rm PR_BODY.md` + +--- + +## Case 3: Any other branch (or base branch was specified via argument) + +**Step 1.** Set Base Branch: +- If `{{args}}` is not empty → Base Branch = `{{args}}` +- Otherwise → Base Branch = `develop` + +**Step 2.** Analyze changes from Base Branch: +- `git log {Base Branch}..HEAD --oneline` +- `git diff {Base Branch}...HEAD --stat` +- `git diff {Base Branch}...HEAD` + +**Step 3.** Suggest **three PR title options** following the **PR Title Convention** below. + +**Step 4.** Write the PR body following the **PR Body Template** below. Save to `PR_BODY.md`. + +**Step 5.** Show the PR body preview and the three title options to the user. Ask: +"PR 제목을 선택해주세요. (1 / 2 / 3 / 직접 입력)" + +**Step 6.** After the user selects or types a title: +``` +gh pr create --title "{chosen title}" --body-file PR_BODY.md --base {Base Branch} +``` + +**Step 7.** Run: `rm PR_BODY.md` + +--- + +## PR Title Convention + +Format: `{type}: {Korean description}` + +**Types:** +- `feature` — new system / UI / scene / shader / mechanic added +- `fix` — bug fix or missing component reference / binding corrected +- `update` — modification to existing script, prefab, or UI +- `asset` — art / audio / VFX / animation asset added or updated +- `refactor` — refactoring without behavior change +- `docs` — documentation changes +- `chore` — tooling, CI/CD, build setting, dependency updates + +**Rules:** +- Description in Korean +- Short and imperative (단문) +- No trailing punctuation + +**Examples:** +- `feature: 콤보 전투 시스템 추가` +- `fix: 점프 중 이동 입력 씹히는 문제 수정` +- `update: 보스 패턴 밸런스 조정` +- `asset: 플레이어 공격 VFX 추가` +- `refactor: PlayerController 구조 정리` + +--- + +## PR Body Template + +Use this exact structure (keep the emoji headers): + +``` +## 📚작업 내용 + +- {change item 1} +- {change item 2} + +## ◀️참고 사항 + +{additional notes, context, before/after comparisons if relevant. Write "." if nothing to add.} + +## ✅체크리스트 + +> `[ ]`안에 x를 작성하면 체크박스를 체크할 수 있습니다. + +- [x] 현재 의도하고자 하는 기능이 정상적으로 작동하나요? +- [x] 변경한 기능이 다른 기능을 깨뜨리지 않나요? + + +> *추후 필요한 체크리스트는 업데이트 될 예정입니다.* +``` + +**Writing rules:** +- Fill `작업 내용` bullets by grouping commits meaningfully — not one bullet per commit +- `참고 사항`: Inspector 설정, 씬 구성, 에셋 경로, before/after 비교 등. Write `"."` if nothing to add +- Keep total body under 2500 characters +- All text content in Korean (keep section header emojis as-is) +- No emojis in body text — section headers only +""" \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Prefabs.meta b/Fantasy-Grower/Assets/Prefabs.meta new file mode 100644 index 0000000..49b9e67 --- /dev/null +++ b/Fantasy-Grower/Assets/Prefabs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 71292b37a0e8bed4abca4ad42fc54a6f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Prefabs/Enemy.meta b/Fantasy-Grower/Assets/Prefabs/Enemy.meta new file mode 100644 index 0000000..b1c68d2 --- /dev/null +++ b/Fantasy-Grower/Assets/Prefabs/Enemy.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: beff064fe6e40fd4c88854db06cd2e05 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Prefabs/Enemy/Enemy.prefab b/Fantasy-Grower/Assets/Prefabs/Enemy/Enemy.prefab new file mode 100644 index 0000000..48b1adb --- /dev/null +++ b/Fantasy-Grower/Assets/Prefabs/Enemy/Enemy.prefab @@ -0,0 +1,343 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &4193620460523036667 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5678651543255339951} + - component: {fileID: 5311048965299034863} + - component: {fileID: 3158600927115594800} + - component: {fileID: 846388253315880859} + - component: {fileID: 5471916572661113379} + - component: {fileID: 6811259245424818294} + m_Layer: 0 + m_Name: Enemy + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5678651543255339951 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4193620460523036667} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1, y: 0.5, z: 0} + m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} + m_ConstrainProportionsScale: 1 + m_Children: + - {fileID: 9122292137280017520} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!212 &5311048965299034863 +SpriteRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4193620460523036667} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 0 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_Sprite: {fileID: 7482667652216324306, guid: 48e93eef0688c4a259cb0eddcd8661f7, type: 3} + m_Color: {r: 1, g: 0, b: 0.02439022, a: 1} + m_FlipX: 0 + m_FlipY: 0 + m_DrawMode: 0 + m_Size: {x: 1, y: 1} + m_AdaptiveModeThreshold: 0.5 + m_SpriteTileMode: 0 + m_WasSpriteAssigned: 1 + m_MaskInteraction: 0 + m_SpriteSortPoint: 0 +--- !u!61 &3158600927115594800 +BoxCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4193620460523036667} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 1, y: 1} + newSize: {x: 1, y: 1} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Size: {x: 1, y: 1} + m_EdgeRadius: 0 +--- !u!50 &846388253315880859 +Rigidbody2D: + serializedVersion: 5 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4193620460523036667} + m_BodyType: 1 + m_Simulated: 1 + m_UseFullKinematicContacts: 0 + m_UseAutoMass: 0 + m_Mass: 1 + m_LinearDamping: 0 + m_AngularDamping: 0.05 + m_GravityScale: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_Interpolate: 0 + m_SleepingMode: 1 + m_CollisionDetection: 0 + m_Constraints: 0 +--- !u!114 &5471916572661113379 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4193620460523036667} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d42734d476595bc40835c071beb08767, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &6811259245424818294 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4193620460523036667} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5180275fdefeff94fbdcd78cf158830b, type: 3} + m_Name: + m_EditorClassIdentifier: + statData: {fileID: 11400000, guid: 947637eac36383b49827043094c83699, type: 2} + _rewardData: {fileID: 11400000, guid: c7bbabc7ae6924a4ca2dd98f7f57abf7, type: 2} + _gold: {fileID: 0} + _xp: {fileID: 0} +--- !u!1 &9090472883474544113 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 9122292137280017520} + - component: {fileID: 3119395137440285820} + - component: {fileID: 1204192879292246265} + - component: {fileID: 5944105346798640134} + m_Layer: 0 + m_Name: AttackHitbox + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &9122292137280017520 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9090472883474544113} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -2.06, y: 0.18000007, z: 0} + m_LocalScale: {x: 3.15, y: 1.32278, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 5678651543255339951} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!212 &3119395137440285820 +SpriteRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9090472883474544113} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 0 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 3 + m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, type: 3} + m_Color: {r: 0, g: 0.4271555, b: 1, a: 1} + m_FlipX: 0 + m_FlipY: 0 + m_DrawMode: 0 + m_Size: {x: 1, y: 1} + m_AdaptiveModeThreshold: 0.5 + m_SpriteTileMode: 0 + m_WasSpriteAssigned: 1 + m_MaskInteraction: 0 + m_SpriteSortPoint: 0 +--- !u!61 &1204192879292246265 +BoxCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9090472883474544113} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 1 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 1, y: 1} + newSize: {x: 1, y: 1} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Size: {x: 1, y: 1} + m_EdgeRadius: 0 +--- !u!114 &5944105346798640134 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9090472883474544113} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0ef49aa0271c9724c9e7489b33226faf, type: 3} + m_Name: + m_EditorClassIdentifier: + type: 1 diff --git a/Fantasy-Grower/Assets/Prefabs/Enemy/Enemy.prefab.meta b/Fantasy-Grower/Assets/Prefabs/Enemy/Enemy.prefab.meta new file mode 100644 index 0000000..796201b --- /dev/null +++ b/Fantasy-Grower/Assets/Prefabs/Enemy/Enemy.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1e9e570aba71bd04990b068952d11e1a +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scenes/BattleTestScene.unity b/Fantasy-Grower/Assets/Scenes/BattleTestScene.unity index bdbc048..1dd53ff 100644 --- a/Fantasy-Grower/Assets/Scenes/BattleTestScene.unity +++ b/Fantasy-Grower/Assets/Scenes/BattleTestScene.unity @@ -119,6 +119,81 @@ NavMeshSettings: debug: m_Flags: 0 m_NavMeshData: {fileID: 0} +--- !u!1 &212572579 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 212572580} + - component: {fileID: 212572581} + m_Layer: 0 + m_Name: WaveController + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &212572580 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 212572579} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &212572581 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 212572579} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0e69eb428298bef47b787286759308c2, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!1 &258795091 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 258795092} + m_Layer: 0 + m_Name: Spawn1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &258795092 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 258795091} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.76, y: 0.5, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1308738509} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &301892951 GameObject: m_ObjectHideFlags: 0 @@ -137,7 +212,7 @@ GameObject: m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 - m_IsActive: 0 + m_IsActive: 1 --- !u!4 &301892952 Transform: m_ObjectHideFlags: 0 @@ -196,7 +271,7 @@ SpriteRenderer: m_LightmapParameters: {fileID: 0} m_SortingLayerID: 0 m_SortingLayer: 0 - m_SortingOrder: 0 + m_SortingOrder: 2 m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, type: 3} m_Color: {r: 0.43710685, g: 0.8935439, b: 1, a: 1} m_FlipX: 0 @@ -266,6 +341,119 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 0ef49aa0271c9724c9e7489b33226faf, type: 3} m_Name: m_EditorClassIdentifier: + type: 0 +--- !u!1 &568675756 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 568675757} + m_Layer: 0 + m_Name: Spawn2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &568675757 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 568675756} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1, y: 0.5, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1308738509} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &572941894 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 572941895} + - component: {fileID: 572941897} + - component: {fileID: 572941896} + m_Layer: 5 + m_Name: Text (Legacy) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &572941895 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 572941894} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1589174768} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &572941896 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 572941894} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 144 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 0 + m_MaxSize: 151 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 'Start + +' +--- !u!222 &572941897 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 572941894} + m_CullTransparentMesh: 1 --- !u!1 &597768151 GameObject: m_ObjectHideFlags: 0 @@ -403,6 +591,273 @@ MonoBehaviour: m_MipBias: 0 m_VarianceClampScale: 0.9 m_ContrastAdaptiveSharpening: 0 +--- !u!1 &863810191 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 863810195} + - component: {fileID: 863810194} + - component: {fileID: 863810193} + - component: {fileID: 863810192} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &863810192 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 863810191} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &863810193 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 863810191} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 1 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 1920, y: 1080} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0.3 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &863810194 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 863810191} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 1 + m_Camera: {fileID: 597768153} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &863810195 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 863810191} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1589174768} + m_Father: {fileID: 0} + 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: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &984133403 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 984133406} + - component: {fileID: 984133405} + - component: {fileID: 984133404} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &984133404 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 984133403} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 01614664b831546d2ae94a42149d80ac, type: 3} + m_Name: + m_EditorClassIdentifier: + m_SendPointerHoverToParent: 1 + m_MoveRepeatDelay: 0.5 + m_MoveRepeatRate: 0.1 + m_XRTrackingOrigin: {fileID: 0} + m_ActionsAsset: {fileID: -944628639613478452, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_PointAction: {fileID: -1654692200621890270, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_MoveAction: {fileID: -8784545083839296357, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_SubmitAction: {fileID: 392368643174621059, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_CancelAction: {fileID: 7727032971491509709, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_LeftClickAction: {fileID: 3001919216989983466, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_MiddleClickAction: {fileID: -2185481485913320682, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_RightClickAction: {fileID: -4090225696740746782, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_ScrollWheelAction: {fileID: 6240969308177333660, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_TrackedDevicePositionAction: {fileID: 6564999863303420839, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_TrackedDeviceOrientationAction: {fileID: 7970375526676320489, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_DeselectOnBackgroundClick: 1 + m_PointerBehavior: 0 + m_CursorLockBehavior: 0 + m_ScrollDeltaPerTick: 6 +--- !u!114 &984133405 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 984133403} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &984133406 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 984133403} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1047311747 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1047311748} + m_Layer: 0 + m_Name: Spawn3 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1047311748 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1047311747} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.43, y: 0.5, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1308738509} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1084119749 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1084119750} + - component: {fileID: 1084119751} + m_Layer: 0 + m_Name: BattleManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1084119750 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1084119749} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1084119751 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1084119749} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c944bded2de69ce47982eaff981c54e7, type: 3} + m_Name: + m_EditorClassIdentifier: + _player: {fileID: 1107896319} + _autoAttack: {fileID: 1107896320} + _waveController: {fileID: 212572581} + _spawnPoints: + - {fileID: 258795092} + - {fileID: 568675757} + - {fileID: 1047311748} + _dungeonData: {fileID: 11400000, guid: 0a0276ae91b1a0c40a8184ce9a87f3be, type: 2} + _gold: {fileID: 0} + _xp: {fileID: 0} + _mithril: {fileID: 0} --- !u!1 &1107896316 GameObject: m_ObjectHideFlags: 0 @@ -414,6 +869,7 @@ GameObject: - component: {fileID: 1107896317} - component: {fileID: 1107896318} - component: {fileID: 1107896319} + - component: {fileID: 1107896320} m_Layer: 0 m_Name: Player m_TagString: Untagged @@ -505,7 +961,20 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: statData: {fileID: 11400000, guid: fe352f708efa65540819d5d6bf040e6b, type: 2} ---- !u!1 &1330448690 +--- !u!114 &1107896320 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1107896316} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 41833eeeae4a47b4e956be891274a363, type: 3} + m_Name: + m_EditorClassIdentifier: + _waveController: {fileID: 212572581} +--- !u!1 &1308738508 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -513,178 +982,173 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 1330448692} - - component: {fileID: 1330448691} - - component: {fileID: 1330448693} - - component: {fileID: 1330448694} - - component: {fileID: 1330448695} + - component: {fileID: 1308738509} m_Layer: 0 - m_Name: Enemy + m_Name: SpawnPoint m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!212 &1330448691 -SpriteRenderer: +--- !u!4 &1308738509 +Transform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1330448690} - m_Enabled: 1 - m_CastShadows: 0 - m_ReceiveShadows: 0 - m_DynamicOccludee: 1 - m_StaticShadowCaster: 0 - m_MotionVectors: 1 - m_LightProbeUsage: 1 - m_ReflectionProbeUsage: 1 - m_RayTracingMode: 0 - m_RayTraceProcedural: 0 - m_RayTracingAccelStructBuildFlagsOverride: 0 - m_RayTracingAccelStructBuildFlags: 1 - m_SmallMeshCulling: 1 - m_RenderingLayerMask: 1 - m_RendererPriority: 0 - m_Materials: - - {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2} - m_StaticBatchInfo: - firstSubMesh: 0 - subMeshCount: 0 - m_StaticBatchRoot: {fileID: 0} - m_ProbeAnchor: {fileID: 0} - m_LightProbeVolumeOverride: {fileID: 0} - m_ScaleInLightmap: 1 - m_ReceiveGI: 1 - m_PreserveUVs: 0 - m_IgnoreNormalsForChartDetection: 0 - m_ImportantGI: 0 - m_StitchLightmapSeams: 1 - m_SelectedEditorRenderState: 0 - m_MinimumChartSize: 4 - m_AutoUVMaxDistance: 0.5 - m_AutoUVMaxAngle: 89 - m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 - m_SortingOrder: 0 - m_Sprite: {fileID: 7482667652216324306, guid: 48e93eef0688c4a259cb0eddcd8661f7, type: 3} - m_Color: {r: 1, g: 0, b: 0.02439022, a: 1} - m_FlipX: 0 - m_FlipY: 0 - m_DrawMode: 0 - m_Size: {x: 1, y: 1} - m_AdaptiveModeThreshold: 0.5 - m_SpriteTileMode: 0 - m_WasSpriteAssigned: 1 - m_MaskInteraction: 0 - m_SpriteSortPoint: 0 ---- !u!4 &1330448692 -Transform: + m_GameObject: {fileID: 1308738508} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 258795092} + - {fileID: 568675757} + - {fileID: 1047311748} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1589174767 +GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1330448690} - serializedVersion: 2 + serializedVersion: 6 + m_Component: + - component: {fileID: 1589174768} + - component: {fileID: 1589174771} + - component: {fileID: 1589174770} + - component: {fileID: 1589174769} + m_Layer: 5 + m_Name: Start + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1589174768 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1589174767} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 1, y: 0.5, z: 0} - m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} - m_ConstrainProportionsScale: 1 - m_Children: [] - m_Father: {fileID: 0} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 572941895} + m_Father: {fileID: 863810195} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &1330448693 + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -0.0000019073, y: -734} + m_SizeDelta: {x: 1029.7, y: 250.7} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1589174769 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1330448690} + m_GameObject: {fileID: 1589174767} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 5180275fdefeff94fbdcd78cf158830b, type: 3} + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} m_Name: m_EditorClassIdentifier: - statData: {fileID: 11400000, guid: 947637eac36383b49827043094c83699, type: 2} ---- !u!61 &1330448694 -BoxCollider2D: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1589174770} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1084119751} + m_TargetAssemblyTypeName: BattleManager, Assembly-CSharp + m_MethodName: StartDungeon + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 +--- !u!114 &1589174770 +MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1330448690} + m_GameObject: {fileID: 1589174767} m_Enabled: 1 - serializedVersion: 3 - m_Density: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} - m_IncludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_ExcludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_LayerOverridePriority: 0 - m_ForceSendLayers: - serializedVersion: 2 - m_Bits: 4294967295 - m_ForceReceiveLayers: - serializedVersion: 2 - m_Bits: 4294967295 - m_ContactCaptureLayers: - serializedVersion: 2 - m_Bits: 4294967295 - m_CallbackLayers: - serializedVersion: 2 - m_Bits: 4294967295 - m_IsTrigger: 0 - m_UsedByEffector: 0 - m_CompositeOperation: 0 - m_CompositeOrder: 0 - m_Offset: {x: 0, y: 0} - m_SpriteTilingProperty: - border: {x: 0, y: 0, z: 0, w: 0} - pivot: {x: 0.5, y: 0.5} - oldSize: {x: 1, y: 1} - newSize: {x: 1, y: 1} - adaptiveTilingThreshold: 0.5 - drawMode: 0 - adaptiveTiling: 0 - m_AutoTiling: 0 - m_Size: {x: 1, y: 1} - m_EdgeRadius: 0 ---- !u!50 &1330448695 -Rigidbody2D: - serializedVersion: 5 + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &1589174771 +CanvasRenderer: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1330448690} - m_BodyType: 1 - m_Simulated: 1 - m_UseFullKinematicContacts: 0 - m_UseAutoMass: 0 - m_Mass: 1 - m_LinearDamping: 0 - m_AngularDamping: 0.05 - m_GravityScale: 1 - m_Material: {fileID: 0} - m_IncludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_ExcludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_Interpolate: 0 - m_SleepingMode: 1 - m_CollisionDetection: 0 - m_Constraints: 0 + m_GameObject: {fileID: 1589174767} + m_CullTransparentMesh: 1 --- !u!1660057539 &9223372036854775807 SceneRoots: m_ObjectHideFlags: 0 m_Roots: - {fileID: 597768154} + - {fileID: 1084119750} + - {fileID: 212572580} + - {fileID: 1308738509} - {fileID: 1107896317} - - {fileID: 1330448692} + - {fileID: 863810195} + - {fileID: 984133406} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs b/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs index 2b609d2..a584c00 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs @@ -22,8 +22,14 @@ private void OnTriggerEnter2D(Collider2D collision) (type == EntityType.Player && target is Enemy) || (type == EntityType.Enemy && target is Player); - if (shouldHit) - target.TakeDamage(entity.AttackPower); + if (!shouldHit) return; + + var (damage, _) = DamageCalculator.Calculate( + entity.AttackPower, + target.DamageReduction, + entity.CriticalPercentage + ); + target.TakeDamage(damage); } } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/AutoAttackController.cs b/Fantasy-Grower/Assets/Scripts/Battle/AutoAttackController.cs new file mode 100644 index 0000000..3b01d74 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/AutoAttackController.cs @@ -0,0 +1,50 @@ +using System.Collections; +using UnityEngine; + +/// +/// 플레이어의 자동 공격 타이밍을 관리하는 컴포넌트. +/// AttackSpeed 스탯을 기반으로 초당 공격 횟수를 결정한다. +/// BattleManager가 StartAutoAttack() / StopAutoAttack()으로 제어한다. +/// +[RequireComponent(typeof(Player))] +public class AutoAttackController : MonoBehaviour +{ + [SerializeField] + private WaveController _waveController; + + private Player _player; + private Coroutine _attackCoroutine; + + private void Awake() + { + _player = GetComponent(); + } + + public void StartAutoAttack() + { + StopAutoAttack(); + _attackCoroutine = StartCoroutine(AutoAttackLoop()); + } + + public void StopAutoAttack() + { + if (_attackCoroutine != null) + { + StopCoroutine(_attackCoroutine); + _attackCoroutine = null; + } + } + + private IEnumerator AutoAttackLoop() + { + while (true) + { + if (_waveController.GetFirstAliveEnemy() != null) + _player.Attack(); // AttackCollider가 실제 피해를 처리 + + // AttackSpeed = 초당 공격 횟수 (예: 1.0 → 1초마다 공격) + float interval = _player.AttackSpeed > 0f ? 1f / _player.AttackSpeed : 1f; + yield return new WaitForSeconds(interval); + } + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/AutoAttackController.cs.meta b/Fantasy-Grower/Assets/Scripts/Battle/AutoAttackController.cs.meta new file mode 100644 index 0000000..cc57eef --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/AutoAttackController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 41833eeeae4a47b4e956be891274a363 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Battle/BATTLELOOP_GUIDE.md b/Fantasy-Grower/Assets/Scripts/Battle/BATTLELOOP_GUIDE.md new file mode 100644 index 0000000..171b4ac --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/BATTLELOOP_GUIDE.md @@ -0,0 +1,212 @@ +# 전투 루프 시스템 사용 가이드 + +## 개요 + +방치형 자동 전투 루프 시스템. +플레이어는 AttackSpeed 기반으로 적을 자동 공격하고, 적은 EnemyAI로 플레이어를 공격한다. +던전은 웨이브 단위로 구성되며, 모든 웨이브 클리어 시 보상을 지급한다. + +``` +BattleManager (상태 머신) +├── WaveController — 적 스폰/생존 추적 +├── AutoAttackController — 플레이어 자동 공격 코루틴 +├── DamageCalculator — 데미지 계산 (DamageReduction + CriticalPercentage) +└── 데이터 SO + ├── DungeonData — 던전 전체 (웨이브 목록 + 클리어 보상) + ├── WaveData — 단일 웨이브 (적 프리팹 × 수량) + └── EnemyRewardData — 적 사망 보상 (Gold, XP) +``` + +--- + +## 1. 데이터 에셋 만들기 + +### 1-1. EnemyRewardData SO 생성 + +`Assets > Create > Battle/EnemyRewardData` + +| 필드 | 설명 | +|------|------| +| `goldAmount` | 적 사망 시 지급되는 Gold 양 | +| `xpAmount` | 적 사망 시 지급되는 XP 양 | + +### 1-2. WaveData SO 생성 + +`Assets > Create > Battle/WaveData` + +| 필드 | 설명 | +|------|------| +| `entries[]` | 스폰할 적 프리팹과 수량 목록 | + +**예시 (웨이브 1):** +- entries[0]: TestEnemyPrefab × 3 +- entries[1]: EliteEnemyPrefab × 1 + +### 1-3. DungeonData SO 생성 + +`Assets > Create > Battle/DungeonData` + +| 필드 | 설명 | +|------|------| +| `dungeonType` | `Basic` / `Gold` / `Weapon` / `Boss` | +| `waves[]` | 웨이브 SO 목록 (순서대로 진행) | +| `bonusGoldReward` | 던전 클리어 보너스 Gold | +| `bonusXpReward` | 던전 클리어 보너스 XP | +| `mithrilAsset` | Boss 던전 전용 — Mithril SO 연결 | +| `mithrilRewardAmount` | Boss 클리어 시 Mithril 지급량 | + +--- + +## 2. 적 프리팹 구성하기 + +Enemy 프리팹에는 다음 컴포넌트가 있어야 한다: + +| 컴포넌트 | 용도 | +|----------|------| +| `Enemy` (또는 서브클래스) | 전투 엔티티, 사망 보상 지급 | +| `EnemyAI` | 자동 공격 코루틴 | +| `EntityStatData` SO 연결 | HP/공격력/공격속도 등 스탯 | +| `EnemyRewardData` SO 연결 | 사망 보상 | +| `SO_Gold` / `SO_XP` SO 연결 | 보상 지급 대상 | + +**AttackCollider가 있는 경우 (물리 판정):** +- 프리팹 하위에 `AttackHitbox` 오브젝트 추가 +- `AttackCollider.cs` 부착, `type = Enemy`, Collider2D(trigger) 부착 +- EnemyAI는 `Attack()` → 충돌로 피해 처리 + +**AttackCollider가 없는 경우 (직접 호출):** +- EnemyAI가 `DamageCalculator`를 사용하여 `_player.TakeDamage()` 직접 호출 + +--- + +## 3. 씬 구성하기 + +``` +BattleScene +├── BattleManager [BattleManager.cs] +├── WaveController [WaveController.cs] +├── SpawnPoints/ +│ ├── SpawnPoint_1 (빈 GameObject) +│ └── SpawnPoint_2 +└── Warrior [Warrior.cs] [AutoAttackController.cs] + └── AttackHitbox [AttackCollider.cs] type=Player +``` + +**BattleManager Inspector 연결:** + +| 필드 | 연결 대상 | +|------|-----------| +| `_player` | Warrior GameObject | +| `_autoAttack` | Warrior의 AutoAttackController 컴포넌트 | +| `_waveController` | WaveController GameObject | +| `_spawnPoints` | SpawnPoint_1, SpawnPoint_2 ... | +| `_dungeonData` | 위에서 만든 DungeonData SO | +| `_gold / _xp / _mithril` | 프로젝트의 재화 SO 에셋 | + +**AutoAttackController Inspector 연결:** + +| 필드 | 연결 대상 | +|------|-----------| +| `_waveController` | WaveController GameObject | + +--- + +## 4. 전투 루프 상태 흐름 + +``` +Idle + ↓ StartDungeon() 호출 +WaveStart ← (1.5초 딜레이 후 다음 웨이브) + ↓ 적 스폰 + EnemyAI 초기화 +Fighting + ↓ OnAllEnemiesDead (WaveController → BattleManager) +WaveCleared + ↓ 마지막 웨이브였다면 +DungeonCleared → 보너스 보상 지급 + ↓ 중간 웨이브였다면 1.5초 후 WaveStart 반복 + +(언제든) 플레이어 HP = 0 +PlayerDead → RetryDungeon() 또는 Exit 처리 +``` + +--- + +## 5. 데미지 계산 공식 + +`DamageCalculator.Calculate(rawAttackPower, targetDamageReduction, attackerCriticalPercentage)` + +``` +감소 데미지 = Max(1, RoundToInt(공격력 × (1 - DamageReduction))) +크리티컬 여부 = Random(0~100) < CriticalPercentage +최종 데미지 = 크리티컬 ? 감소 데미지 × 2 : 감소 데미지 +``` + +| 스탯 | 범위 | 예시 | +|------|------|------| +| `DamageReduction` | 0.0 ~ 1.0 | `0.2` = 20% 피해 감소 | +| `CriticalPercentage` | 0 ~ 100 | `25` = 25% 크리티컬 확률 | + +--- + +## 6. 런타임 사용법 + +### 던전 시작 (UI 버튼) +```csharp +BattleManager battleManager = FindObjectOfType(); +battleManager.StartDungeon(); +``` + +### 재시도 (사망 화면 버튼) +```csharp +battleManager.RetryDungeon(); +``` + +### 상태 변화 구독 (UI 패널 전환) +```csharp +battleManager.OnStateChanged += state => +{ + switch (state) + { + case BattleState.Fighting: ShowBattleUI(); break; + case BattleState.DungeonCleared: ShowClearUI(); break; + case BattleState.PlayerDead: ShowDeadUI(); break; + } +}; + +battleManager.OnWaveChanged += waveIndex => +{ + waveText.text = $"Wave {waveIndex + 1}"; +}; +``` + +--- + +## 7. 새 던전 타입 추가 방법 + +1. `DungeonType` 열거형에 새 타입 추가 +2. `DungeonData` SO 생성 후 `dungeonType` 선택 +3. `BattleManager.StartDungeon()`에 분기 추가 (Gold 던전처럼 별도 씬이 필요한 경우) +4. `EnterDungeonCleared()`에 타입별 특수 보상 로직 추가 + +--- + +## 8. 클래스 의존 관계 + +``` +[DungeonData] ──► [WaveData] ──► EnemyPrefab + │ + ▼ +[BattleManager] + ├── [AutoAttackController] ──► [WaveController] + │ │ │ + │ Player.Attack() SpawnWave() + │ │ OnAllEnemiesDead + │ ▼ │ + │ [AttackCollider] ──► [DamageCalculator] ◄── [EnemyAI] + │ ▲ + │ Entity.TakeDamage() + └── Entity.OnDied ──► HandlePlayerDied / OnEnemyDied + +[Enemy] ──► [EnemyRewardData] + ──► SO_Gold / SO_XP (사망 시 Increase) +``` diff --git a/Fantasy-Grower/Assets/Scripts/Battle/BATTLELOOP_GUIDE.md.meta b/Fantasy-Grower/Assets/Scripts/Battle/BATTLELOOP_GUIDE.md.meta new file mode 100644 index 0000000..a35a7b4 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/BATTLELOOP_GUIDE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1c40028fec0a29247811b5b384c743a8 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Battle/BattleManager.cs b/Fantasy-Grower/Assets/Scripts/Battle/BattleManager.cs new file mode 100644 index 0000000..7db19f5 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/BattleManager.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public enum BattleState +{ + Idle, + WaveStart, + Fighting, + WaveCleared, + DungeonCleared, + PlayerDead, +} + +/// +/// 전투 루프의 중심 오케스트레이터. +/// 던전 시작 → 웨이브 스폰 → 전투 → 클리어/사망 상태 전환을 관리한다. +/// UI 레이어는 OnStateChanged / OnWaveChanged 이벤트를 구독하여 화면을 갱신한다. +/// +public class BattleManager : MonoBehaviour +{ + // ─── Inspector 연결 ────────────────────────────────────────── + [SerializeField] + private Player _player; + + [SerializeField] + private AutoAttackController _autoAttack; + + [SerializeField] + private WaveController _waveController; + + [SerializeField] + private Transform[] _spawnPoints; + + [Header("던전 데이터")] + [SerializeField] + private DungeonData _dungeonData; + + [Header("재화 SO")] + [SerializeField] + private SO_Gold _gold; + + [SerializeField] + private SO_XP _xp; + + [SerializeField] + private SO_Mithril _mithril; + + // ─── 런타임 상태 ───────────────────────────────────────────── + private BattleState _state = BattleState.Idle; + private int _currentWaveIndex; + private List _currentWaveEnemies; + + // ─── UI 알림 이벤트 ─────────────────────────────────────────── + /// 상태가 변경될 때마다 발화된다. UI 패널 전환에 사용한다. + public event Action OnStateChanged; + + /// 새 웨이브가 시작될 때 웨이브 번호(0-based)를 전달한다. + public event Action OnWaveChanged; + + // ─── 유니티 라이프사이클 ────────────────────────────────────── + private void Awake() + { + _waveController.OnAllEnemiesDead += HandleAllEnemiesDead; + _player.OnDied += HandlePlayerDied; + } + + private void OnDestroy() + { + _waveController.OnAllEnemiesDead -= HandleAllEnemiesDead; + _player.OnDied -= HandlePlayerDied; + } + + // ─── 공개 API (UI 버튼에서 호출) ───────────────────────────── + /// 던전을 시작한다. + public void StartDungeon() + { + if (_dungeonData == null) + { + Debug.LogError("[BattleManager] DungeonData가 연결되지 않았습니다."); + return; + } + + if (_dungeonData.dungeonType == DungeonType.Gold) + { + Debug.Log("[BattleManager] 골드 던전은 미니게임 씬으로 전환해야 합니다."); + // TODO: SceneManager.LoadScene("GoldDungeonScene"); + return; + } + + _currentWaveIndex = 0; + TransitionTo(BattleState.WaveStart); + } + + /// 플레이어 사망 후 던전을 처음부터 재시도한다. + public void RetryDungeon() + { + _waveController.Clear(); + _player.ResetHp(); + _currentWaveIndex = 0; + TransitionTo(BattleState.WaveStart); + } + + // ─── 상태 머신 ──────────────────────────────────────────────── + private void TransitionTo(BattleState newState) + { + _state = newState; + OnStateChanged?.Invoke(_state); + Debug.Log($"[BattleManager] 상태 전환: {newState}"); + + switch (_state) + { + case BattleState.WaveStart: + EnterWaveStart(); + break; + case BattleState.Fighting: + EnterFighting(); + break; + case BattleState.WaveCleared: + EnterWaveCleared(); + break; + case BattleState.DungeonCleared: + EnterDungeonCleared(); + break; + case BattleState.PlayerDead: + EnterPlayerDead(); + break; + } + } + + private void EnterWaveStart() + { + Debug.Log( + $"[BattleManager] 웨이브 {_currentWaveIndex + 1} / {_dungeonData.waves.Length} 시작" + ); + OnWaveChanged?.Invoke(_currentWaveIndex); + + WaveData wave = _dungeonData.waves[_currentWaveIndex]; + _currentWaveEnemies = _waveController.SpawnWave(wave, _spawnPoints); + + foreach (Enemy e in _currentWaveEnemies) + e.GetComponent()?.Initialize(_player); + + TransitionTo(BattleState.Fighting); + } + + private void EnterFighting() + { + _autoAttack.StartAutoAttack(); + + foreach (Enemy e in _currentWaveEnemies) + e.GetComponent()?.StartAttacking(); + } + + private void HandleAllEnemiesDead() + { + _autoAttack.StopAutoAttack(); + + foreach (Enemy e in _currentWaveEnemies) + e.GetComponent()?.StopAttacking(); + + TransitionTo(BattleState.WaveCleared); + } + + private void EnterWaveCleared() + { + _currentWaveIndex++; + + if (_currentWaveIndex >= _dungeonData.waves.Length) + { + TransitionTo(BattleState.DungeonCleared); + } + else + { + StartCoroutine(DelayedTransition(BattleState.WaveStart, 1.5f)); + } + } + + private void EnterDungeonCleared() + { + Debug.Log("[BattleManager] 던전 클리어!"); + + _gold?.Increase(_dungeonData.bonusGoldReward); + _xp?.Increase(_dungeonData.bonusXpReward); + + if (_dungeonData.dungeonType == DungeonType.Boss && _dungeonData.mithrilAsset != null) + _dungeonData.mithrilAsset.Increase(_dungeonData.mithrilRewardAmount); + } + + private void HandlePlayerDied(Entity _) + { + _autoAttack.StopAutoAttack(); + _waveController.Clear(); + TransitionTo(BattleState.PlayerDead); + } + + private void EnterPlayerDead() + { + Debug.Log("[BattleManager] 플레이어 사망."); + // UI에서 OnStateChanged(PlayerDead)를 받아 재시도/종료 화면을 표시한다. + } + + // ─── 내부 유틸 ──────────────────────────────────────────────── + private IEnumerator DelayedTransition(BattleState next, float delay) + { + yield return new WaitForSeconds(delay); + TransitionTo(next); + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/BattleManager.cs.meta b/Fantasy-Grower/Assets/Scripts/Battle/BattleManager.cs.meta new file mode 100644 index 0000000..f9d43b8 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/BattleManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c944bded2de69ce47982eaff981c54e7 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Data.meta b/Fantasy-Grower/Assets/Scripts/Battle/Data.meta new file mode 100644 index 0000000..3ce9908 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Data.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 248f25b9d8d2ef64d9a0fa68984db2a6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Data/DungeonData.cs b/Fantasy-Grower/Assets/Scripts/Battle/Data/DungeonData.cs new file mode 100644 index 0000000..ddfd7ac --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Data/DungeonData.cs @@ -0,0 +1,25 @@ +using UnityEngine; + +public enum DungeonType { Basic, Gold, Weapon, Boss } + +/// +/// 던전 전체 구성 데이터 (웨이브 목록 + 던전 유형 + 클리어 보상). +/// BattleManager에 연결하여 던전을 정의한다. +/// +[CreateAssetMenu(fileName = "DungeonData", menuName = "Battle/DungeonData")] +public class DungeonData : ScriptableObject +{ + [Header("던전 유형")] + public DungeonType dungeonType; + + [Header("웨이브 목록 (순서대로 진행)")] + public WaveData[] waves; + + [Header("던전 클리어 보너스 보상")] + public uint bonusGoldReward; + public uint bonusXpReward; + + [Header("보스 던전 전용 보상")] + public SO_Mithril mithrilAsset; + public uint mithrilRewardAmount; +} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Data/DungeonData.cs.meta b/Fantasy-Grower/Assets/Scripts/Battle/Data/DungeonData.cs.meta new file mode 100644 index 0000000..cd367e6 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Data/DungeonData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 12ba244a718458e4097ed6e5be48a7f4 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Data/EnemyRewardData.cs b/Fantasy-Grower/Assets/Scripts/Battle/Data/EnemyRewardData.cs new file mode 100644 index 0000000..e82e8c2 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Data/EnemyRewardData.cs @@ -0,0 +1,13 @@ +using UnityEngine; + +/// +/// 적 사망 시 지급되는 보상 데이터. +/// Enemy 프리팹에 할당되며, 같은 적 종류는 하나의 SO 에셋을 공유한다. +/// +[CreateAssetMenu(fileName = "EnemyRewardData", menuName = "Battle/EnemyRewardData")] +public class EnemyRewardData : ScriptableObject +{ + [Header("사망 보상")] + public uint goldAmount; + public uint xpAmount; +} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Data/EnemyRewardData.cs.meta b/Fantasy-Grower/Assets/Scripts/Battle/Data/EnemyRewardData.cs.meta new file mode 100644 index 0000000..521b838 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Data/EnemyRewardData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9e3fab7113fbdbd4e86847cd9d15e830 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Data/WaveData.cs b/Fantasy-Grower/Assets/Scripts/Battle/Data/WaveData.cs new file mode 100644 index 0000000..2c390ec --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Data/WaveData.cs @@ -0,0 +1,23 @@ +using System.Linq; +using UnityEngine; + +/// +/// 던전의 단일 웨이브 구성 데이터. +/// 적 프리팹과 수량을 지정하여 에디터에서 웨이브를 설계한다. +/// +[CreateAssetMenu(fileName = "WaveData", menuName = "Battle/WaveData")] +public class WaveData : ScriptableObject +{ + [System.Serializable] + public struct EnemySpawnEntry + { + public GameObject enemyPrefab; + public int count; + } + + [Header("스폰 목록 (적 종류 × 수량)")] + public EnemySpawnEntry[] entries; + + /// 이 웨이브의 총 적 수 + public int TotalEnemyCount => entries?.Sum(e => e.count) ?? 0; +} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Data/WaveData.cs.meta b/Fantasy-Grower/Assets/Scripts/Battle/Data/WaveData.cs.meta new file mode 100644 index 0000000..c3b274a --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Data/WaveData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 94f9a6b0432990949b668a67629525a6 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Battle/DungeonDataSO.meta b/Fantasy-Grower/Assets/Scripts/Battle/DungeonDataSO.meta new file mode 100644 index 0000000..1767d78 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/DungeonDataSO.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0ddf79eadc388df46a1e0706cdcdf31b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Battle/DungeonDataSO/TestDungeonData.asset b/Fantasy-Grower/Assets/Scripts/Battle/DungeonDataSO/TestDungeonData.asset new file mode 100644 index 0000000..0540bad --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/DungeonDataSO/TestDungeonData.asset @@ -0,0 +1,23 @@ +%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: 12ba244a718458e4097ed6e5be48a7f4, type: 3} + m_Name: TestDungeonData + m_EditorClassIdentifier: + dungeonType: 0 + waves: + - {fileID: 11400000, guid: fed3edd92df95224e95ac553fd8093dc, type: 2} + - {fileID: 11400000, guid: fed3edd92df95224e95ac553fd8093dc, type: 2} + - {fileID: 11400000, guid: fed3edd92df95224e95ac553fd8093dc, type: 2} + bonusGoldReward: 1000 + bonusXpReward: 100 + mithrilAsset: {fileID: 0} + mithrilRewardAmount: 0 diff --git a/Fantasy-Grower/Assets/Scripts/Battle/DungeonDataSO/TestDungeonData.asset.meta b/Fantasy-Grower/Assets/Scripts/Battle/DungeonDataSO/TestDungeonData.asset.meta new file mode 100644 index 0000000..44cee4e --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/DungeonDataSO/TestDungeonData.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0a0276ae91b1a0c40a8184ce9a87f3be +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs index 9bfdf59..931dbe5 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs @@ -1,3 +1,25 @@ using UnityEngine; -public class Enemy : Entity { } +/// +/// 적 엔티티 기반 클래스. +/// 사망 시 EnemyRewardData에 정의된 Gold/XP를 지급한다. +/// +public class Enemy : Entity +{ + [SerializeField] private EnemyRewardData _rewardData; + [SerializeField] private SO_Gold _gold; + [SerializeField] private SO_XP _xp; + + public override void Death() + { + base.Death(); // OnDied 이벤트 발화 (WaveController가 구독 중) + + if (_rewardData != null) + { + _gold?.Increase(_rewardData.goldAmount); + _xp?.Increase(_rewardData.xpAmount); + } + + Destroy(gameObject, 0.5f); // 사망 연출 시간 확보 후 제거 + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs new file mode 100644 index 0000000..b7fb725 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs @@ -0,0 +1,73 @@ +using System.Collections; +using UnityEngine; + +/// +/// 적의 자동 공격 AI. +/// BattleManager가 스폰 후 Initialize()와 StartAttacking()을 호출하여 동작을 시작한다. +/// +[RequireComponent(typeof(Enemy))] +public class EnemyAI : MonoBehaviour +{ + private Enemy _enemy; + private Player _player; + private Coroutine _attackCoroutine; + + private void Awake() + { + _enemy = GetComponent(); + } + + /// 타겟 플레이어를 설정한다. BattleManager가 스폰 직후 호출한다. + public void Initialize(Player player) + { + _player = player; + } + + public void StartAttacking() + { + StopAttacking(); + _attackCoroutine = StartCoroutine(AttackLoop()); + } + + public void StopAttacking() + { + if (_attackCoroutine != null) + { + StopCoroutine(_attackCoroutine); + _attackCoroutine = null; + } + } + + private IEnumerator AttackLoop() + { + while (true) + { + if (_player != null && _player.Hp > 0) + { + // Enemy 프리팹에 AttackCollider가 있으면 Attack()으로 애니메이션+충돌 처리. + // AttackCollider가 없는 경우 DamageCalculator로 직접 피해 적용. + if (HasAttackCollider()) + { + _enemy.Attack(); + } + else + { + var (damage, _) = DamageCalculator.Calculate( + _enemy.AttackPower, + _player.DamageReduction, + _enemy.CriticalPercentage + ); + _player.TakeDamage(damage); + } + } + + float interval = _enemy.AttackSpeed > 0f ? 1f / _enemy.AttackSpeed : 2f; + yield return new WaitForSeconds(interval); + } + } + + private bool HasAttackCollider() + { + return GetComponentInChildren(includeInactive: true) != null; + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs.meta b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs.meta new file mode 100644 index 0000000..7bc74a6 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d42734d476595bc40835c071beb08767 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs index ee34352..6592d30 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs @@ -1,9 +1,38 @@ +using System.Collections; using UnityEngine; public class TestEnemy : Enemy { + private AttackCollider col; + + protected override void Awake() + { + base.Awake(); + col = GetComponentInChildren(); + col.gameObject.SetActive(false); + } + public override void Death() { Debug.Log("TestEnemy 죽음"); + base.Death(); // Enemy.Death() → 보상 지급 + OnDied 이벤트 + } + + private bool _isAttacking; + + public override void Attack() + { + if (_isAttacking) + return; + StartCoroutine(attackCoroutine()); + } + + private IEnumerator attackCoroutine() + { + _isAttacking = true; + col.gameObject.SetActive(true); + yield return new WaitForSeconds(0.2f); // 히트 판정 윈도우 (고정) + col.gameObject.SetActive(false); + _isAttacking = false; } } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs b/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs index 0f43fcd..a7f4f7d 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs @@ -1,8 +1,10 @@ +using System; using UnityEngine; public abstract class Entity : MonoBehaviour { public int Hp { get; private set; } + public int MaxHp { get; private set; } public float DamageReduction { get; private set; } public int AttackPower { get; private set; } public float AttackSpeed { get; private set; } @@ -11,6 +13,9 @@ public abstract class Entity : MonoBehaviour [SerializeField] private EntityStatData statData; + /// HP가 0이 되어 Death()가 호출될 때 발화된다. + public event Action OnDied; + protected virtual void Awake() { if (statData == null) @@ -20,20 +25,53 @@ protected virtual void Awake() } Hp = statData.Hp; + MaxHp = statData.Hp; + DamageReduction = statData.DamageReduction; AttackPower = statData.AttackPower; + AttackSpeed = statData.AttackSpeed; CriticalPercentage = statData.CriticalPercentage; } public virtual void Attack() { } - public virtual void Death() { } + /// HP를 MaxHp로 복구한다. 던전 재시도 시 사용. + public void ResetHp() + { + Hp = MaxHp; + } + + public virtual void Death() + { + OnDied?.Invoke(this); + } public virtual void TakeDamage(int damage) { + if (Hp <= 0) + return; // 이미 사망 — 중복 호출 무시 + Debug.Log($"데미지 받음: {damage}"); Hp = Mathf.Max(0, Hp - damage); if (Hp <= 0) Death(); } + + /// + /// 해금된 패시브 스킬 효과를 스탯에 반영한다. + /// SkillTreeComponent.RecalculatePassives()에서 패시브 집계 후 호출된다. + /// statData 기반값에 modifier를 합산하여 런타임 스탯을 갱신한다. + /// + public void ApplyStatModifier(EntityStatModifier modifier) + { + if (statData == null) + return; + + MaxHp = statData.Hp + modifier.BonusHp; + Hp = MaxHp; + DamageReduction = statData.DamageReduction + modifier.BonusDamageReduction; + AttackPower = statData.AttackPower + modifier.BonusAttackPower; + AttackSpeed = statData.AttackSpeed + modifier.BonusAttackSpeed; + CriticalPercentage = statData.CriticalPercentage + modifier.BonusCriticalPercentage; + } } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs b/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs index 70a6528..613cb15 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs @@ -1,9 +1,32 @@ +using System.Collections; using UnityEngine; public class Player : Entity { + private AttackCollider col; + + protected override void Awake() + { + base.Awake(); + col = GetComponentInChildren(); + col.gameObject.SetActive(false); + } + + private bool _isAttacking; + public override void Attack() { - Debug.Log("공격함!"); + if (_isAttacking) + return; + StartCoroutine(attackCoroutine()); + } + + private IEnumerator attackCoroutine() + { + _isAttacking = true; + col.gameObject.SetActive(true); + yield return new WaitForSeconds(0.2f); // 히트 판정 윈도우 (고정) + col.gameObject.SetActive(false); + _isAttacking = false; } } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/RewardSO.meta b/Fantasy-Grower/Assets/Scripts/Battle/RewardSO.meta new file mode 100644 index 0000000..33b5732 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/RewardSO.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fc27e9c9c9df7fa49b689c716ae8f9dc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Battle/RewardSO/TestRewardData.asset b/Fantasy-Grower/Assets/Scripts/Battle/RewardSO/TestRewardData.asset new file mode 100644 index 0000000..1a8a974 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/RewardSO/TestRewardData.asset @@ -0,0 +1,16 @@ +%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: 9e3fab7113fbdbd4e86847cd9d15e830, type: 3} + m_Name: TestRewardData + m_EditorClassIdentifier: + goldAmount: 100 + xpAmount: 100 diff --git a/Fantasy-Grower/Assets/Scripts/Battle/RewardSO/TestRewardData.asset.meta b/Fantasy-Grower/Assets/Scripts/Battle/RewardSO/TestRewardData.asset.meta new file mode 100644 index 0000000..077f55e --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/RewardSO/TestRewardData.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c7bbabc7ae6924a4ca2dd98f7f57abf7 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs b/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs new file mode 100644 index 0000000..c3e4ab2 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +/// +/// 현재 웨이브의 적 스폰과 생존 수를 추적하는 컴포넌트. +/// 모든 적이 사망하면 OnAllEnemiesDead 이벤트를 발화한다. +/// +public class WaveController : MonoBehaviour +{ + /// 현재 웨이브의 모든 적이 사망했을 때 발화된다. + public event Action OnAllEnemiesDead; + + private int _aliveCount; + private readonly List _activeEnemies = new(); + + /// + /// 웨이브 데이터에 따라 적을 스폰한다. + /// 스폰된 Enemy 인스턴스 목록을 반환한다. + /// + public List SpawnWave(WaveData waveData, Transform[] spawnPoints) + { + _activeEnemies.Clear(); + int spawnIndex = 0; + + foreach (var entry in waveData.entries) + { + for (int i = 0; i < entry.count; i++) + { + Vector3 pos = spawnPoints[spawnIndex % spawnPoints.Length].position; + GameObject go = Instantiate(entry.enemyPrefab, pos, Quaternion.identity); + + Enemy enemy = go.GetComponent(); + if (enemy == null) + { + Debug.LogWarning($"[WaveController] 프리팹 {entry.enemyPrefab.name}에 Enemy 컴포넌트가 없습니다."); + Destroy(go); + continue; + } + + _activeEnemies.Add(enemy); + enemy.OnDied += OnEnemyDied; + spawnIndex++; + } + } + + _aliveCount = _activeEnemies.Count; + + if (_aliveCount == 0) + { + Debug.LogWarning("[WaveController] 웨이브에 적이 없습니다. 즉시 완료 처리됩니다."); + OnAllEnemiesDead?.Invoke(); + } + + return _activeEnemies; + } + + /// 현재 살아있는 첫 번째 적을 반환한다. 없으면 null. + public Enemy GetFirstAliveEnemy() + => _activeEnemies.FirstOrDefault(e => e != null && e.Hp > 0); + + public IReadOnlyList ActiveEnemies => _activeEnemies.AsReadOnly(); + + /// 살아있는 적을 모두 파괴하고 상태를 초기화한다. 재시도 또는 씬 정리 시 사용. + public void Clear() + { + foreach (Enemy e in _activeEnemies) + { + if (e != null) + Destroy(e.gameObject); + } + _activeEnemies.Clear(); + _aliveCount = 0; + } + + private void OnEnemyDied(Entity entity) + { + _aliveCount = Mathf.Max(0, _aliveCount - 1); + + if (_aliveCount == 0) + OnAllEnemiesDead?.Invoke(); + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs.meta b/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs.meta new file mode 100644 index 0000000..58bf494 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0e69eb428298bef47b787286759308c2 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Battle/WaveDataSO.meta b/Fantasy-Grower/Assets/Scripts/Battle/WaveDataSO.meta new file mode 100644 index 0000000..b8fd227 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/WaveDataSO.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: feb40389af2e72c428f55053aed87492 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Battle/WaveDataSO/TestWaveData.asset b/Fantasy-Grower/Assets/Scripts/Battle/WaveDataSO/TestWaveData.asset new file mode 100644 index 0000000..93b19c5 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/WaveDataSO/TestWaveData.asset @@ -0,0 +1,17 @@ +%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: 94f9a6b0432990949b668a67629525a6, type: 3} + m_Name: TestWaveData + m_EditorClassIdentifier: + entries: + - enemyPrefab: {fileID: 4193620460523036667, guid: 1e9e570aba71bd04990b068952d11e1a, type: 3} + count: 3 diff --git a/Fantasy-Grower/Assets/Scripts/Battle/WaveDataSO/TestWaveData.asset.meta b/Fantasy-Grower/Assets/Scripts/Battle/WaveDataSO/TestWaveData.asset.meta new file mode 100644 index 0000000..099ba04 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Battle/WaveDataSO/TestWaveData.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fed3edd92df95224e95ac553fd8093dc +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Core/DamageCalculator.cs b/Fantasy-Grower/Assets/Scripts/Core/DamageCalculator.cs new file mode 100644 index 0000000..a6498d0 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/DamageCalculator.cs @@ -0,0 +1,26 @@ +using UnityEngine; + +/// +/// 데미지 계산 공식을 담당하는 정적 유틸리티. +/// AttackCollider와 EnemyAI에서 공통으로 사용한다. +/// +public static class DamageCalculator +{ + /// + /// 최종 데미지와 크리티컬 여부를 계산한다. + /// + /// 공격자의 공격력 + /// 피격자의 피해 감소율 (0.0 ~ 1.0, 예: 0.2 = 20% 감소) + /// 공격자의 크리티컬 확률 (0 ~ 100, 예: 25f = 25%) + /// (최종 데미지, 크리티컬 여부) + public static (int damage, bool isCritical) Calculate( + int rawAttackPower, + float targetDamageReduction, + float attackerCriticalPercentage) + { + int reduced = Mathf.Max(1, Mathf.RoundToInt(rawAttackPower * (1f - targetDamageReduction))); + bool isCritical = Random.value * 100f < attackerCriticalPercentage; + int finalDamage = isCritical ? reduced * 2 : reduced; + return (finalDamage, isCritical); + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/DamageCalculator.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/DamageCalculator.cs.meta new file mode 100644 index 0000000..d1eb4ef --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/DamageCalculator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4ba6586415045e8438d89eae6a3efede \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Core/EntityStatModifier.cs b/Fantasy-Grower/Assets/Scripts/Core/EntityStatModifier.cs new file mode 100644 index 0000000..f688013 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/EntityStatModifier.cs @@ -0,0 +1,25 @@ +using UnityEngine; + +[System.Serializable] +public struct EntityStatModifier +{ + public int BonusHp; + public float BonusDamageReduction; + public int BonusAttackPower; + public float BonusAttackSpeed; + public float BonusCriticalPercentage; + + public static EntityStatModifier operator +(EntityStatModifier a, EntityStatModifier b) + { + return new EntityStatModifier + { + BonusHp = a.BonusHp + b.BonusHp, + BonusDamageReduction = a.BonusDamageReduction + b.BonusDamageReduction, + BonusAttackPower = a.BonusAttackPower + b.BonusAttackPower, + BonusAttackSpeed = a.BonusAttackSpeed + b.BonusAttackSpeed, + BonusCriticalPercentage = a.BonusCriticalPercentage + b.BonusCriticalPercentage, + }; + } + + public static EntityStatModifier Zero => default; +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/EntityStatModifier.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/EntityStatModifier.cs.meta new file mode 100644 index 0000000..3e0f328 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/EntityStatModifier.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: aee70212a874f0a4c9732365e2cd86e3 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillData.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillData.cs index 5d706a1..c402812 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillData.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillData.cs @@ -1,5 +1,7 @@ using UnityEngine; +public enum SkillCategory { Active, Passive } + [CreateAssetMenu(fileName = "SkillStat", menuName = "Stat/Skill")] public abstract class SkillData : ScriptableObject { @@ -13,5 +15,9 @@ public abstract class SkillData : ScriptableObject public float Cooldown; public int Damage; + [Header("스킬 트리")] + public SkillCategory Category; + public int SPCost; + public abstract void UseSkill(); } diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree.meta new file mode 100644 index 0000000..5bcfdc0 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3ddd8d8697a19d54dac553ecfd98c572 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillData.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillData.cs new file mode 100644 index 0000000..85d815b --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillData.cs @@ -0,0 +1,7 @@ +using UnityEngine; + +/// +/// 액티브 스킬 데이터의 추상 기반 클래스. +/// UseSkill()을 구현하여 스킬별 발동 로직을 정의한다. +/// +public abstract class ActiveSkillData : SkillData { } diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillData.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillData.cs.meta new file mode 100644 index 0000000..9b98f1b --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 99ca49da1f93f30469491baaf8907351 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillData.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillData.cs new file mode 100644 index 0000000..95e73f8 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillData.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +/// +/// 패시브 스킬 데이터의 추상 기반 클래스. +/// ApplyPassive()에서 EntityStatModifier를 수정하여 스탯 보정값을 정의한다. +/// UseSkill()은 패시브 스킬에서 호출되지 않으므로 빈 구현으로 봉인된다. +/// +public abstract class PassiveSkillData : SkillData +{ + public override void UseSkill() { } + + /// + /// 패시브 효과를 스탯 수정자에 누산한다. + /// SkillTreeComponent.RecalculatePassives()에서 일괄 호출된다. + /// + public abstract void ApplyPassive(ref EntityStatModifier modifier); +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillData.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillData.cs.meta new file mode 100644 index 0000000..3330ff5 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: eda03d011e0724e48b96373340d00955 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SKILLTREE_GUIDE.md b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SKILLTREE_GUIDE.md new file mode 100644 index 0000000..8d2b8cb --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SKILLTREE_GUIDE.md @@ -0,0 +1,215 @@ +# 스킬 트리 시스템 사용 가이드 + +## 개요 + +스킬 트리 시스템은 ScriptableObject 기반 데이터 설정 + 전략 패턴 기반 해금 규칙으로 구성된다. +직업별 트리 방식(검사 분기형 / 궁수 행-택1 / 법사 선형)을 코드 수정 없이 에디터에서 선택할 수 있다. + +``` +SkillData (추상) +├── ActiveSkillData (추상) → 구체 스킬 클래스 상속 +└── PassiveSkillData (추상) → 구체 스킬 클래스 상속 + +SkillNodeData (SO) → 트리의 단일 노드, 에디터에서 연결 +SkillTreeData (SO) → 직업별 트리 전체, StrategyType 선택 +SkillTreeComponent → 플레이어에 붙는 런타임 컴포넌트 +``` + +--- + +## 1. 스킬 클래스 만들기 + +### 1-1. 액티브 스킬 + +`ActiveSkillData`를 상속하고 `UseSkill()`을 구현한다. +`[CreateAssetMenu]`를 붙여 에디터에서 에셋 생성이 가능하게 한다. + +```csharp +[CreateAssetMenu(menuName = "ScriptableObjects/SkillData/Warrior/WhirlSlash")] +public class WhirlSlashData : ActiveSkillData +{ + public int HitCount = 3; + + public override void UseSkill() + { + // 회선 베기: 전방 3마리 공격 + Debug.Log($"회선 베기 발동! {HitCount}마리 공격"); + } +} +``` + +### 1-2. 패시브 스킬 + +`PassiveSkillData`를 상속하고 `ApplyPassive()`에서 `EntityStatModifier`를 수정한다. +`UseSkill()`은 구현하지 않아도 된다 (부모에서 빈 구현으로 봉인). + +```csharp +[CreateAssetMenu(menuName = "ScriptableObjects/SkillData/Warrior/SpeedUp")] +public class SpeedUpData : PassiveSkillData +{ + public float BonusAttackSpeed = 0.3f; + + public override void ApplyPassive(ref EntityStatModifier modifier) + { + modifier.BonusAttackSpeed += BonusAttackSpeed; + } +} +``` + +--- + +## 2. 에디터에서 노드 구성하기 + +### 2-1. SkillNodeData 에셋 생성 + +`Assets > Create > ScriptableObjects/SkillTree/Node` + +| 필드 | 설명 | 예시 | +|---|---|---| +| `Skill` | 이 노드가 담는 스킬 에셋 참조 | WhirlSlash 에셋 | +| `Prerequisites` | 이 노드 해금에 필요한 선행 노드 목록 | 기본 베기 노드 | +| `TierIndex` | 티어 번호 (0 = 기본 평타, 1 = 1티어 ...) | `1` | +| `SlotIndex` | 같은 티어 내 분기 그룹 구분 | `0` | +| `AttributeTag` | 법사 속성 구분 (법사 전용) | `"Fire"` | + +**검사 예시 구성:** + +``` +[TierIndex=0, SlotIndex=0] 기본 베기 (Prerequisites: 없음) +[TierIndex=0, SlotIndex=0] 기본 찌르기 (Prerequisites: 없음) + ↓ (둘 중 하나만 선택 가능) +[TierIndex=1, SlotIndex=1] 회선 베기 (Prerequisites: [기본 베기]) +[TierIndex=1, SlotIndex=2] 머리치기 (Prerequisites: [기본 베기]) +[TierIndex=1, SlotIndex=3] 관통 (Prerequisites: [기본 찌르기]) +[TierIndex=1, SlotIndex=4] 연속 찌르기 (Prerequisites: [기본 찌르기]) +``` + +> **검사 분기 규칙**: 같은 `TierIndex + SlotIndex` 조합을 가진 노드끼리는 하나만 선택 가능. +> 평타 두 개(`SlotIndex=0`)는 서로 배타적이다. + +### 2-2. SkillTreeData 에셋 생성 + +`Assets > Create > ScriptableObjects/SkillTree/Tree` + +| 필드 | 설명 | +|---|---| +| `JobClassName` | `"Warrior"` / `"Archer"` / `"Mage"` | +| `StrategyType` | `Branching` / `RowSelect` / `Linear` | +| `AllNodes` | 위에서 만든 모든 SkillNodeData 에셋 등록 | +| `MaxActiveSkillSlots` | 장착 가능한 액티브 스킬 수 (기본 3) | + +--- + +## 3. 플레이어에 컴포넌트 연결하기 + +1. 씬의 Warrior 오브젝트 선택 +2. `Add Component > SkillTreeComponent` 추가 +3. 인스펙터에서: + - `Tree Data` → 위에서 만든 `SkillTreeData` 에셋 연결 + - `Sp Resource` → 프로젝트의 `SO_SP` 에셋 연결 + +--- + +## 4. 런타임 사용법 + +### 4-1. 노드 해금 + +```csharp +SkillTreeComponent skillTree = player.GetComponent(); + +// 해금 가능 여부 확인 (SP 조건 + 전략 조건 통합) +if (skillTree.CanUnlock(whirlSlashNode)) +{ + skillTree.TryUnlockNode(whirlSlashNode); + // → SP 자동 차감, 패시브라면 Entity 스탯 자동 재계산 +} +``` + +### 4-2. 액티브 스킬 장착 + +```csharp +// 슬롯 0에 장착 (해금된 스킬만 장착 가능) +skillTree.TryEquipActiveSkill(whirlSlashData, slotIndex: 0); + +// 슬롯 해제 +skillTree.UnequipActiveSkill(slotIndex: 0); + +// 현재 장착된 스킬 조회 +ActiveSkillData equipped = skillTree.GetEquippedSkill(0); +equipped?.UseSkill(); +``` + +### 4-3. 전투 루프에서 스킬 발동 + +```csharp +// 장착된 모든 액티브 스킬 순회 (쿨타임 관리는 별도 구현 필요) +foreach (var skill in skillTree.GetEquippedActives()) +{ + if (skill != null) + skill.UseSkill(); +} +``` + +--- + +## 5. 직업별 트리 설정 요약 + +### 검사 — `StrategyType: Branching` + +- 같은 `TierIndex + SlotIndex` 그룹에서 하나만 선택 +- 선행 노드 중 **하나라도** 해금되면 다음 노드 진행 가능 +- 패시브 트리도 동일한 Branching 전략 사용 (방어 라인 / 공격 라인 분기) + +### 궁수 — `StrategyType: RowSelect` + +- 각 `TierIndex`(행)에서 단 하나만 선택 +- 이전 행(`TierIndex - 1`)에서 선택이 완료되어야 다음 행 선택 가능 +- `TierIndex: 0` → 1행, `TierIndex: 1` → 2행, `TierIndex: 2` → 3행 + +### 법사 — `StrategyType: Linear` + +- 첫 번째 노드(`TierIndex == 0`) 해금 시 `AttributeTag`(속성)가 자동 확정 +- 이후 확정된 속성의 `AttributeTag`를 가진 노드만 해금 가능 +- 모든 선행 노드가 해금되어야 다음 노드 진행 가능 (선형) + +--- + +## 6. 신규 직업 추가 방법 + +1. `Player`를 상속하는 클래스 생성 (예: `Archer`) +2. 해당 직업의 스킬 클래스들 작성 (`ActiveSkillData` / `PassiveSkillData` 상속) +3. 에디터에서 `SkillNodeData` 에셋 구성 +4. `SkillTreeData` 에셋 생성 후 `StrategyType` 선택 +5. 플레이어 오브젝트에 `SkillTreeComponent` 추가 후 에셋 연결 + +--- + +## 7. 클래스 의존 관계 + +``` +[SO_SP]──────────────────────────────────┐ + │ +[EntityStatData]──►[Entity]◄─────────────┤ + ▲ │ + RequireComponent │ +[SkillTreeData]──►[SkillTreeComponent]───┘ + │ │ + │ CreateStrategy() │ TryUnlockNode() + ▼ ▼ +[ISkillTreeStrategy] [SkillTreeValidator] + ▲ ▲ ▲ + │ │ │ +Branch Row Linear +Strategy Strategy Strategy + +[SkillNodeData]──►[SkillData] + ▲ ▲ + [Active] [Passive] + │ ApplyPassive() + ▼ + [EntityStatModifier] + │ + ▼ + [Entity] + ApplyStatModifier() +``` diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SKILLTREE_GUIDE.md.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SKILLTREE_GUIDE.md.meta new file mode 100644 index 0000000..e1b7b90 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SKILLTREE_GUIDE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a1a6995e0b6702047a66259fc13303d4 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillNodeData.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillNodeData.cs new file mode 100644 index 0000000..4cf217b --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillNodeData.cs @@ -0,0 +1,19 @@ +using UnityEngine; + +/// +/// 스킬 트리의 단일 노드. 에디터에서 드래그&드롭으로 선행 노드를 연결한다. +/// +[CreateAssetMenu(fileName = "SkillNode", menuName = "ScriptableObjects/SkillTree/Node")] +public class SkillNodeData : ScriptableObject +{ + [Header("스킬 참조")] + public SkillData Skill; + + [Header("트리 구조")] + public SkillNodeData[] Prerequisites; + public int TierIndex; // 0 = 기본 평타, 1 = 1티어, 2 = 2티어, ... + public int SlotIndex; // 같은 티어 내 위치 + + [Header("속성 태그 (법사 전용)")] + public string AttributeTag; // "Fire" / "Ice" / "Wind" +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillNodeData.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillNodeData.cs.meta new file mode 100644 index 0000000..37a3a9b --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillNodeData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: dfcb3861838a4ec46924a354ad034191 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs new file mode 100644 index 0000000..703712d --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs @@ -0,0 +1,154 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +/// +/// 플레이어의 스킬 트리 런타임 상태를 관리하는 컴포넌트. +/// 해금 상태를 Dictionary로 보관하여 ScriptableObject 에셋 오염을 방지한다. +/// +[RequireComponent(typeof(Player))] +public class SkillTreeComponent : MonoBehaviour +{ + [SerializeField] private SkillTreeData _treeData; + [SerializeField] private SO_SP _spResource; + + // ScriptableObject를 직접 수정하지 않고 런타임 상태를 Dictionary로 격리 + private Dictionary _unlockedState; + + // 장착된 액티브 스킬 슬롯 (null = 비어있음) + private List _equippedActives; + + private ISkillTreeStrategy _strategy; + private Entity _entity; + + private void Awake() + { + _entity = GetComponent(); + _unlockedState = new Dictionary(); + _strategy = _treeData != null ? _treeData.CreateStrategy() : null; + + int slotCount = _treeData != null ? _treeData.MaxActiveSkillSlots : 3; + _equippedActives = new List(new ActiveSkillData[slotCount]); + + if (_treeData == null) + Debug.LogWarning("[SkillTreeComponent] SkillTreeData가 비어 있습니다!"); + + if (_spResource == null) + Debug.LogWarning("[SkillTreeComponent] SO_SP가 비어 있습니다!"); + + // 모든 노드를 초기 잠금 상태로 등록 + if (_treeData != null && _treeData.AllNodes != null) + { + foreach (var node in _treeData.AllNodes) + _unlockedState[node] = false; + } + } + + // ─── 해금 흐름 ─────────────────────────────────────────────── + + /// + /// UI에서 노드 선택 시 진입점. SP 소비 + 전략 조건 모두 통과해야 해금된다. + /// + public bool TryUnlockNode(SkillNodeData node) + { + if (node == null || _strategy == null) return false; + + if (IsUnlocked(node)) + { + Debug.Log($"[SkillTree] {node.Skill?.SkillName}은 이미 해금되어 있습니다."); + return false; + } + + if (!CanUnlock(node)) + { + Debug.Log($"[SkillTree] {node.Skill?.SkillName} 해금 조건 미충족."); + return false; + } + + // SP 소비 + _spResource.Decrease((uint)node.Skill.SPCost); + + _unlockedState[node] = true; + _strategy.OnNodeUnlocked(node, _unlockedState); + + Debug.Log($"[SkillTree] {node.Skill?.SkillName} 해금 완료."); + + RecalculatePassives(); + return true; + } + + /// + /// 해금된 모든 패시브 스킬 효과를 재계산하여 Entity 스탯에 반영한다. + /// + private void RecalculatePassives() + { + var modifier = EntityStatModifier.Zero; + + foreach (var kv in _unlockedState) + { + if (!kv.Value) continue; + if (kv.Key.Skill is PassiveSkillData passive) + passive.ApplyPassive(ref modifier); + } + + _entity.ApplyStatModifier(modifier); + } + + // ─── 액티브 스킬 장착 ───────────────────────────────────────── + + /// + /// 해금된 액티브 스킬을 지정 슬롯에 장착한다. + /// + public bool TryEquipActiveSkill(ActiveSkillData skill, int slotIndex) + { + if (skill == null || slotIndex < 0 || slotIndex >= _equippedActives.Count) + return false; + + if (!IsUnlocked(FindNodeBySkill(skill))) + { + Debug.Log($"[SkillTree] {skill.SkillName}이 해금되지 않았습니다."); + return false; + } + + _equippedActives[slotIndex] = skill; + return true; + } + + public void UnequipActiveSkill(int slotIndex) + { + if (slotIndex >= 0 && slotIndex < _equippedActives.Count) + _equippedActives[slotIndex] = null; + } + + public ActiveSkillData GetEquippedSkill(int slotIndex) + { + if (slotIndex < 0 || slotIndex >= _equippedActives.Count) return null; + return _equippedActives[slotIndex]; + } + + // ─── 조회 ──────────────────────────────────────────────────── + + public bool IsUnlocked(SkillNodeData node) + { + return node != null && _unlockedState.TryGetValue(node, out bool v) && v; + } + + /// + /// SP 조건과 전략 조건을 통합하여 해금 가능 여부를 반환한다. + /// + public bool CanUnlock(SkillNodeData node) + { + if (node == null || node.Skill == null) return false; + if (!SkillTreeValidator.HasEnoughSP(node, _spResource)) return false; + return _strategy.CanUnlock(node, _unlockedState); + } + + public IReadOnlyList GetEquippedActives() => _equippedActives.AsReadOnly(); + + // ─── 내부 유틸 ──────────────────────────────────────────────── + + private SkillNodeData FindNodeBySkill(SkillData skill) + { + return _treeData?.AllNodes?.FirstOrDefault(n => n.Skill == skill); + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs.meta new file mode 100644 index 0000000..422b4f8 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 276db779e5ff7bc41a98d39b1eff25d2 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeData.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeData.cs new file mode 100644 index 0000000..fea3f09 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeData.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using UnityEngine; + +public enum SkillTreeStrategyType { Branching, RowSelect, Linear } + +/// +/// 직업별 스킬 트리 전체 정의. 에디터에서 AllNodes에 노드를 등록하고 +/// StrategyType으로 해금 규칙 방식을 선택한다. +/// +[CreateAssetMenu(fileName = "SkillTreeData", menuName = "ScriptableObjects/SkillTree/Tree")] +public class SkillTreeData : ScriptableObject +{ + [Header("직업 정보")] + public string JobClassName; + + [Header("트리 전략")] + public SkillTreeStrategyType StrategyType; + + [Header("노드 목록")] + public List AllNodes; + + [Header("액티브 슬롯 제한")] + public int MaxActiveSkillSlots = 3; + + /// + /// StrategyType에 맞는 전략 인스턴스를 생성한다. + /// + public ISkillTreeStrategy CreateStrategy() + { + return StrategyType switch + { + SkillTreeStrategyType.Branching => new BranchingTreeStrategy(), + SkillTreeStrategyType.RowSelect => new RowSelectTreeStrategy(), + SkillTreeStrategyType.Linear => new LinearTreeStrategy(), + _ => new BranchingTreeStrategy(), + }; + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeData.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeData.cs.meta new file mode 100644 index 0000000..16d8ca3 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 714ac6181a5a647479fea7f51342d8b1 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeValidator.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeValidator.cs new file mode 100644 index 0000000..84e70b9 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeValidator.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; + +/// +/// 스킬 트리 노드 해금의 공통 조건을 검증하는 정적 유틸리티. +/// 전략별 규칙(ISkillTreeStrategy)과 독립적으로 동작한다. +/// +public static class SkillTreeValidator +{ + /// + /// 선행 노드가 모두 해금되어 있는지 확인한다. + /// + public static bool ArePrerequisitesMet( + SkillNodeData node, + IReadOnlyDictionary state) + { + if (node.Prerequisites == null || node.Prerequisites.Length == 0) + return true; + + return node.Prerequisites + .All(pre => pre != null && state.TryGetValue(pre, out bool unlocked) && unlocked); + } + + /// + /// SP가 해금 비용을 충당할 수 있는지 확인한다. + /// + public static bool HasEnoughSP(SkillNodeData node, SO_SP sp) + { + if (node.Skill == null) return false; + return sp.Get() >= (uint)node.Skill.SPCost; + } + + /// + /// 액티브 스킬 슬롯에 여유가 있는지 확인한다. + /// + public static bool HasActiveSlotAvailable( + IReadOnlyList equipped, + int maxSlots) + { + int occupiedSlots = equipped.Count(skill => skill != null); + return occupiedSlots < maxSlots; + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeValidator.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeValidator.cs.meta new file mode 100644 index 0000000..6d29bdd --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeValidator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 58849c074fe740b44b9e720530b1d432 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies.meta new file mode 100644 index 0000000..b8b0846 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 605c3580e70fa6c4fac66d892266c59c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/BranchingTreeStrategy.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/BranchingTreeStrategy.cs new file mode 100644 index 0000000..fed5f24 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/BranchingTreeStrategy.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Linq; + +/// +/// 검사 분기형 트리 전략. +/// 같은 TierIndex 내에서 SlotIndex가 동일한 그룹(분기 지점)에는 하나만 선택 가능하다. +/// 선행 노드 중 하나라도 해금되어 있으면 진행 가능하다. +/// +public class BranchingTreeStrategy : ISkillTreeStrategy +{ + public bool CanUnlock(SkillNodeData node, IReadOnlyDictionary state) + { + // 선행 노드가 없으면 (기본 노드) 즉시 해금 가능 + if (node.Prerequisites == null || node.Prerequisites.Length == 0) + return true; + + // 선행 노드 중 하나라도 해금되어 있으면 진행 가능 + bool anyPrerequisiteMet = node.Prerequisites + .Any(pre => pre != null && state.TryGetValue(pre, out bool unlocked) && unlocked); + + if (!anyPrerequisiteMet) + return false; + + // 같은 TierIndex + SlotIndex를 가진 노드(분기 그룹)에서 이미 다른 노드가 해금되었다면 불가 + bool conflictExists = state + .Where(kv => kv.Value) + .Select(kv => kv.Key) + .Any(unlocked => + unlocked != node && + unlocked.TierIndex == node.TierIndex && + unlocked.SlotIndex == node.SlotIndex); + + return !conflictExists; + } + + public void OnNodeUnlocked(SkillNodeData node, IReadOnlyDictionary state) { } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/BranchingTreeStrategy.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/BranchingTreeStrategy.cs.meta new file mode 100644 index 0000000..94ac0f5 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/BranchingTreeStrategy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 384b1f6796b7a3746a3dfd7e33eb91a3 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/ISkillTreeStrategy.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/ISkillTreeStrategy.cs new file mode 100644 index 0000000..a33425d --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/ISkillTreeStrategy.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +/// +/// 직업별 스킬 트리 해금 규칙을 추상화하는 전략 인터페이스. +/// 검사(분기형), 궁수(행-택1), 법사(선형) 각각 구현한다. +/// +public interface ISkillTreeStrategy +{ + /// + /// 현재 해금 상태와 트리 규칙을 기반으로 노드 해금 가능 여부를 판별한다. + /// SP 조건 및 선행 노드 조건은 SkillTreeValidator에서 별도 검증한다. + /// + bool CanUnlock(SkillNodeData node, IReadOnlyDictionary state); + + /// + /// 노드 해금 직후 호출된다. 전략별 추가 상태 처리에 사용한다. + /// + void OnNodeUnlocked(SkillNodeData node, IReadOnlyDictionary state); +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/ISkillTreeStrategy.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/ISkillTreeStrategy.cs.meta new file mode 100644 index 0000000..ed21dd3 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/ISkillTreeStrategy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2e528846e5f2cbf46878fea6ac1d9b67 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/LinearTreeStrategy.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/LinearTreeStrategy.cs new file mode 100644 index 0000000..eabe425 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/LinearTreeStrategy.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +/// +/// 법사 선형 속성 트리 전략. +/// 속성(Fire/Ice/Wind) 선택 후 해당 AttributeTag 라인만 진행 가능하다. +/// 선행 노드가 모두 해금되어야 다음 노드 해금 가능하다. +/// +public class LinearTreeStrategy : ISkillTreeStrategy +{ + private string _selectedAttribute; + + /// + /// 속성을 선택한다. 첫 번째 노드를 해금할 때 자동 결정되며, + /// 이후 다른 속성 라인은 해금 불가 상태가 된다. + /// + public void SelectAttribute(string attribute) + { + _selectedAttribute = attribute; + } + + public bool CanUnlock(SkillNodeData node, IReadOnlyDictionary state) + { + // 속성이 아직 선택되지 않은 경우: 어떤 속성이든 첫 번째 노드(TierIndex == 0) 해금 가능 + if (string.IsNullOrEmpty(_selectedAttribute)) + return node.TierIndex == 0; + + // 선택된 속성 라인만 허용 + if (node.AttributeTag != _selectedAttribute) + return false; + + // 선행 노드가 없으면 해금 가능 + if (node.Prerequisites == null || node.Prerequisites.Length == 0) + return true; + + // 선형 트리: 모든 선행 노드가 해금되어 있어야 한다 + return node.Prerequisites + .All(pre => pre != null && state.TryGetValue(pre, out bool unlocked) && unlocked); + } + + public void OnNodeUnlocked(SkillNodeData node, IReadOnlyDictionary state) + { + // 첫 번째 노드 해금 시 속성을 확정한다 + if (string.IsNullOrEmpty(_selectedAttribute) && !string.IsNullOrEmpty(node.AttributeTag)) + SelectAttribute(node.AttributeTag); + } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/LinearTreeStrategy.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/LinearTreeStrategy.cs.meta new file mode 100644 index 0000000..8f41489 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/LinearTreeStrategy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b5fa10981812ba04da5883734c774600 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/RowSelectTreeStrategy.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/RowSelectTreeStrategy.cs new file mode 100644 index 0000000..f5c94ef --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/RowSelectTreeStrategy.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Linq; + +/// +/// 궁수 행-택1 트리 전략 (롤토체스식). +/// 각 TierIndex(행)에서 단 하나의 노드만 선택 가능하다. +/// 이전 행이 선택되어야 다음 행 선택 가능하다. +/// +public class RowSelectTreeStrategy : ISkillTreeStrategy +{ + public bool CanUnlock(SkillNodeData node, IReadOnlyDictionary state) + { + // 이미 이 행에서 선택된 노드가 있으면 불가 + bool rowAlreadySelected = state + .Any(kv => kv.Value && kv.Key != node && kv.Key.TierIndex == node.TierIndex); + + if (rowAlreadySelected) + return false; + + // 첫 번째 행(TierIndex == 0)은 선행 조건 없이 해금 가능 + if (node.TierIndex == 0) + return true; + + // 이전 행(TierIndex - 1)에서 선택된 노드가 있어야 다음 행 선택 가능 + bool previousRowSelected = state + .Any(kv => kv.Value && kv.Key.TierIndex == node.TierIndex - 1); + + return previousRowSelected; + } + + public void OnNodeUnlocked(SkillNodeData node, IReadOnlyDictionary state) { } +} diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/RowSelectTreeStrategy.cs.meta b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/RowSelectTreeStrategy.cs.meta new file mode 100644 index 0000000..8ef0994 --- /dev/null +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/RowSelectTreeStrategy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: be906072356950a44b2f302420dbf745 \ No newline at end of file diff --git a/Fantasy-Grower/Assets/Scripts/SO/StatData/Enemy/TestEnemyStat.asset b/Fantasy-Grower/Assets/Scripts/SO/StatData/Enemy/TestEnemyStat.asset index 5280c5e..39f2c02 100644 --- a/Fantasy-Grower/Assets/Scripts/SO/StatData/Enemy/TestEnemyStat.asset +++ b/Fantasy-Grower/Assets/Scripts/SO/StatData/Enemy/TestEnemyStat.asset @@ -13,5 +13,7 @@ MonoBehaviour: m_Name: TestEnemyStat m_EditorClassIdentifier: Hp: 150 + DamageReduction: 0 AttackPower: 10 + AttackSpeed: 1 CriticalPercentage: 0 diff --git a/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WarriorStat.asset b/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WarriorStat.asset index 9bbd478..259557f 100644 --- a/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WarriorStat.asset +++ b/Fantasy-Grower/Assets/Scripts/SO/StatData/Player/WarriorStat.asset @@ -13,5 +13,7 @@ MonoBehaviour: m_Name: WarriorStat m_EditorClassIdentifier: Hp: 200 + DamageReduction: 0 AttackPower: 20 + AttackSpeed: 1 CriticalPercentage: 0 diff --git a/Fantasy-Grower/CLAUDE.md b/Fantasy-Grower/CLAUDE.md new file mode 100644 index 0000000..193db39 --- /dev/null +++ b/Fantasy-Grower/CLAUDE.md @@ -0,0 +1,176 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**판타지 키우기 (Fantasy Grower)** — 2D 방치형 RPG 모바일 게임 (Unity 6.0.0, Android) + +플레이어가 직업/무기/스킬 트리를 조합하여 빌드를 만들고, 던전을 자동 전투로 진행하며 성장하는 방치형 RPG. + +## Development Environment + +- **Engine**: Unity 6.0.0+ +- **IDE**: Visual Studio 2022 or JetBrains Rider (open `Fantasy-Grower.sln`) +- **Rendering**: Universal Render Pipeline (URP) 2D +- **Input**: Unity New Input System +- **Platform**: Android (target), Desktop (test) + +Unity에는 별도 CLI 빌드 커맨드가 없음. 모든 빌드/실행은 Unity Editor 내에서 수행: +- **Play**: Unity Editor > Play 버튼 +- **Build**: File > Build and Run +- **Tests**: Window > General > Test Runner (Unity Test Framework 사용) + +## Code Architecture + +### Class Hierarchy + +``` +MonoBehaviour +└── Entity (abstract) — HP, 공격력, TakeDamage(), Death() + ├── Player — Attack() 구현 + │ └── Warrior — 검사 서브클래스 + └── Enemy — Enemy 기반 + └── TestEnemy — Death() 오버라이드 +``` + +### Key Design Patterns + +**ScriptableObject 기반 데이터 분리** +- `EntityStatData` — HP, 공격력, 공격속도, 크리티컬 등 스탯 저장 +- `SkillData` (abstract) — 스킬 데이터 베이스 +- `SO_Goods` 계열 — 재화 시스템 (Gold, XP, SP, Mithril, UpgradeScroll) + +**전투 히트 판정** +- `AttackCollider` 컴포넌트가 2D Trigger 충돌로 피해 처리 +- `EntityType` enum으로 아군 피해 방지 (Player는 Enemy만, Enemy는 Player만 타격) +- 피해 = 공격자의 `AttackPower` 직접 적용 + +**재화(Goods) 시스템** +- `SO_Goods` 추상 클래스: `Get()`, `Increase()`, `Decrease()` +- XP는 `Decrease()` 불가 (`[Obsolete]` 처리) +- 범위 검사로 과소비 방지 + +### Directory Structure + +``` +Assets/Scripts/ +├── Battle/ +│ ├── Entity.cs — 전투 엔티티 기반 클래스 +│ ├── AttackCollider.cs — 공격 히트박스 컴포넌트 +│ ├── Player/ — 플레이어 계열 +│ └── Enemy/ — 적 계열 +└── Core/ + ├── EntityStatData.cs — 스탯 ScriptableObject + ├── SkillData.cs — 스킬 데이터 기반 + └── Goods/ — 재화 ScriptableObject 계열 +``` + +--- + +## 게임 기획서 요약 (판타지 키우기) + +> 구현 시 이 기획을 기준으로 삼을 것. + +### 성장 요소 + +| 요소 | 용도 | +|------|------| +| Gold | 무기 구매, 강화, 상점 새로고침 | +| XP | 캐릭터 레벨업 | +| SP (Skill Point) | 스킬 트리 강화, 레벨업 시 획득 | +| 강화 스크롤 | 무기 강화 (무기 종류별 스크롤 별도) | +| 미스릴 | 무기 합성 | + +### 직업 시스템 (3종) + +| 직업 | 특징 | +|------|------| +| 검사 | 높은 체력, 안정적 전투, 광역/단일 빌드 | +| 궁수 | 낮은 체력, 크리티컬 특화, 무기 패시브 중요 | +| 법사 | 중간 체력, 불/얼음/바람 속성 중 하나 특화 | + +**법사 속성 특징**: +- 불: 지속 피해, 광역 +- 얼음: 적 이속/공속 감소 (유틸) +- 바람: 공격 속도 증가, 낮은 쿨타임 + +### 무기 시스템 + +**등급**: S > A > B > C +- A 이상: 특수 패시브 제공 +- S등급: 제련(합성)으로만 제작 가능 + +**직업별 무기**: +- 검사: 레이피어(크리), 롱소드(균형), 대검(공격력) +- 궁수: 대궁(공격력+크리), 석궁(공속+유틸) +- 법사: 지팡이(쿨타임 감소), 마법서(속성 강화) + +**장비 관리**: +- 강화: 강화 스크롤 사용, 등급 상승 → 필요 스크롤 증가 +- 합성: 미스릴 + 무기 2개 → 새 무기 (평균 등급) +- 판매: Gold 획득 + +### 던전 시스템 (4종) + +| 던전 | 내용 | +|------|------| +| 기본 던전 | 자동 전투, Gold+XP 획득, 웨이브 진행, 정예 몬스터 등장 | +| 골드 던전 | 광산 미니게임, 화면 클릭으로 채굴, 30~60초 제한, 미스릴 극소확률 드랍 | +| 무기 던전 | 자동 전투, C등급 무기 드랍, 낮은 확률로 B등급/스크롤 드랍 | +| 보스 던전 | 강력한 정예 다수, 클리어 시 미스릴+A등급 무기+XP 획득 | + +### 상점 시스템 + +- **대장간**: 랜덤 무기 판매, Gold로 새로고침, 고객 등급 시스템 (구매/강화할수록 등급↑, 높은 등급 무기 제공) +- **마도 상점**: 강화 스크롤 판매, Gold로 새로고침 + +### 스킬 트리 + +공통: 레벨업 시 SP 획득, 액티브 스킬 최대 3개 장착, 패시브/액티브 분리 + +#### 검사 — 액티브 트리 (분기형) + +| 티어 | 스킬 | 타입 | 특징 | +|------|------|------|------| +| 평타 | 기본 베기 | 평타 | 2마리 공격, 데미지 낮음, 공격속도 빠름 | +| 평타 | 기본 찌르기 | 평타 | 1명 공격, 데미지 높음, 공격속도 느림 | +| 1티어 | 회선 베기 | 액티브 | 3마리 공격, 데미지 낮음, 쿨타임 보통 | +| 1티어 | 머리치기 | 액티브 | 2마리 공격, 데미지 보통, 쿨타임 김, 2초 기절 | +| 1티어 | 과통 | 액티브 | 2마리 공격, 데미지 높음, 쿨타임 꽤 김 | +| 1티어 | 연속 찌르기 | 액티브 | 1마리 공격, 데미지 높음, 쿨타임 보통 | +| 2티어 | 검무 | 액티브 | 4마리 공격, 데미지 낮음, 쿨타임 김 | +| 2티어 | 버티기 | 액티브 | 쿨타임 김, 적 수만큼 반피감 | +| 2티어 | 호흡 | 액티브 | 쿨타임 많이 김, 잔심 제외 모든 스킬 쿨초 | +| 2티어 | 일검사 | 액티브 | 1마리 공격, 데미지 높음, 쿨타임 김, 치명타로 즉사 | +| 최종 | 최종 스킬 | 액티브 | (미정) | + +#### 검사 — 패시브 트리 (2갈래) + +**방어 라인**: 천 갑옷(피해 감소) → 사전 준비(전 스킬 쿨감) → 부러진 칼(HP 50% 이하시 초당 n 회복) → 단련(최대체력+공격력 n%) + +**공격 라인**: 속돌(공속 상승) → 슬로우스타터(처치마다 공격력 증가, 면전 입장시 초기화) → 부러진 갑옷(HP 50% 이하시 피해 n% 회복) → 단련(공유 최종 노드) + +#### 궁수 — 롤토체스식 3택1 (3행) + +매 행마다 후보 중 1개를 선택. + +| 행 | 스킬 목록 | +|----|-----------| +| 1행 | 정신집중(다음 공격 데미지 n배), 곡사(광역 낮은 데미지), 멀티샷(75% 데미지로 2발), 대형 화살(관통+슬로우 디버프), 육감(정예 몬스터 추가 피해) | +| 2행 | 발사(평타: 단일, 크리 20% 보정), 연속 사격(5초 크리화), 은화살(공격시 100%), 낙하탄(공격속도 n% 증가), 헤드샷(최대체력 n% 피해), 숙련(사거리 증가) | +| 3행 | 준통 사격(3마리 공격), 저격(단일 높은 데미지), 역경(크리율 n% 증가), 독탄화살(3발, 데미지 매우 높음), 영광(크리 피해 증가) | + +#### 법사 — 속성별 선형 트리 + +**불**: 불 원소(평타, 광역) → 화상(패시브: 매초 n데미지 상태이상) → 화염구(단일) → 불기둥(장판형) → [재(화상 추가피해) / 폭발 마법(범위 증가)] → 메테오(광역, 쿨 매우 김) → [마법 단련(액티브 데미지+) / 4도 화상(화상 중첩 가능)] + +**얼음**: 얼음 원소(평타, 단일, 느림) → 동상(패시브: 이속/공속 감소) → 빙결피(광역) → 얼음파편(단일, 높음) → [냉기강화(동상 효과 강화) / 얼음 갑옷(피격 시 10초 피해감소)] → 눈보라(광역, 쿨 매우 김) → [마법 단련 / 영하(동상 중첩 시 이동 불가)] + +**바람**: 바람 원소(평타, 2명, 빠름) → 칼바람(패시브: 공격마다 상처 1 중첩, 5중첩 시 데미지) → 바람 칼날(15초 공속 상승, 3명 공격) → 토네이도(광역, 에이본 효과) → [가속(적 공격 일정확률 무시) / 순풍(기본공격 추가 공격 확률)] → 돌풍(광역, 쿨 매우 김) → [마법 단련 / 나락(칼바람 발동 시 모든 스킬 쿨감)] + +### 게임 루프 + +``` +던전 진행 → Gold/XP 획득 → 레벨업+SP 획득 → 스킬 트리 강화 → 무기 획득/강화 → 더 높은 던전 도전 +``` From aa54e08413097991580e9add6e6e9cff7e227068 Mon Sep 17 00:00:00 2001 From: Lee Siwoo Date: Mon, 6 Apr 2026 10:52:25 +0900 Subject: [PATCH 07/11] =?UTF-8?q?fix:=20meta=20=EA=B3=A0=EC=95=84=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #18 --- Fantasy-Grower/Assets/Scripts/Battle/Player/Skill.meta | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 Fantasy-Grower/Assets/Scripts/Battle/Player/Skill.meta diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Player/Skill.meta b/Fantasy-Grower/Assets/Scripts/Battle/Player/Skill.meta deleted file mode 100644 index 207cfbe..0000000 --- a/Fantasy-Grower/Assets/Scripts/Battle/Player/Skill.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 266463dea591bea43896a7d7e410e628 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: From 5a995584aa0b1cc8d381a732bffcdceef12eb809 Mon Sep 17 00:00:00 2001 From: Lee Siwoo Date: Mon, 6 Apr 2026 10:56:42 +0900 Subject: [PATCH 08/11] =?UTF-8?q?fix:=20C#=20=ED=8F=AC=EB=A9=A7=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #18 --- .../Assets/Scripts/Battle/AttackCollider.cs | 5 ++-- .../Scripts/Battle/AutoAttackController.cs | 2 +- .../Assets/Scripts/Battle/BattleManager.cs | 2 +- .../Assets/Scripts/Battle/Data/DungeonData.cs | 10 ++++++-- .../Scripts/Battle/Data/EnemyRewardData.cs | 2 +- .../Assets/Scripts/Battle/Data/WaveData.cs | 2 +- .../Assets/Scripts/Battle/Enemy/Enemy.cs | 17 ++++++++----- .../Assets/Scripts/Battle/Enemy/TestEnemy.cs | 2 +- .../Assets/Scripts/Battle/Entity.cs | 2 +- .../Assets/Scripts/Battle/Player/Player.cs | 2 +- .../Assets/Scripts/Battle/Player/Warrior.cs | 7 ++---- .../Assets/Scripts/Battle/WaveController.cs | 9 +++---- .../Assets/Scripts/Core/DamageCalculator.cs | 5 ++-- .../Assets/Scripts/Core/EntityStatData.cs | 2 +- .../Assets/Scripts/Core/EntityStatModifier.cs | 2 +- .../Assets/Scripts/Core/SkillData.cs | 8 +++++-- .../Scripts/Core/SkillTree/ActiveSkillData.cs | 2 +- .../Core/SkillTree/PassiveSkillData.cs | 2 +- .../Scripts/Core/SkillTree/SkillNodeData.cs | 6 ++--- .../Core/SkillTree/SkillTreeComponent.cs | 24 ++++++++++++------- .../Scripts/Core/SkillTree/SkillTreeData.cs | 11 ++++++--- .../Core/SkillTree/SkillTreeValidator.cs | 17 ++++++------- .../Strategies/BranchingTreeStrategy.cs | 19 +++++++++------ .../Strategies/ISkillTreeStrategy.cs | 2 +- .../Strategies/LinearTreeStrategy.cs | 7 +++--- .../Strategies/RowSelectTreeStrategy.cs | 17 ++++++++----- 26 files changed, 113 insertions(+), 73 deletions(-) diff --git a/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs b/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs index a584c00..a61c5de 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; [RequireComponent(typeof(Collider2D))] public class AttackCollider : MonoBehaviour @@ -22,7 +22,8 @@ private void OnTriggerEnter2D(Collider2D collision) (type == EntityType.Player && target is Enemy) || (type == EntityType.Enemy && target is Player); - if (!shouldHit) return; + if (!shouldHit) + return; var (damage, _) = DamageCalculator.Calculate( entity.AttackPower, diff --git a/Fantasy-Grower/Assets/Scripts/Battle/AutoAttackController.cs b/Fantasy-Grower/Assets/Scripts/Battle/AutoAttackController.cs index 3b01d74..f49aba5 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/AutoAttackController.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/AutoAttackController.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using UnityEngine; /// diff --git a/Fantasy-Grower/Assets/Scripts/Battle/BattleManager.cs b/Fantasy-Grower/Assets/Scripts/Battle/BattleManager.cs index 7db19f5..d881d6f 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/BattleManager.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/BattleManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using UnityEngine; diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Data/DungeonData.cs b/Fantasy-Grower/Assets/Scripts/Battle/Data/DungeonData.cs index ddfd7ac..da5ddc9 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Data/DungeonData.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Data/DungeonData.cs @@ -1,6 +1,12 @@ -using UnityEngine; +using UnityEngine; -public enum DungeonType { Basic, Gold, Weapon, Boss } +public enum DungeonType +{ + Basic, + Gold, + Weapon, + Boss, +} /// /// 던전 전체 구성 데이터 (웨이브 목록 + 던전 유형 + 클리어 보상). diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Data/EnemyRewardData.cs b/Fantasy-Grower/Assets/Scripts/Battle/Data/EnemyRewardData.cs index e82e8c2..5cba192 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Data/EnemyRewardData.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Data/EnemyRewardData.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; /// /// 적 사망 시 지급되는 보상 데이터. diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Data/WaveData.cs b/Fantasy-Grower/Assets/Scripts/Battle/Data/WaveData.cs index 2c390ec..71a8074 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Data/WaveData.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Data/WaveData.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using UnityEngine; /// diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs index 931dbe5..0bf973f 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; /// /// 적 엔티티 기반 클래스. @@ -6,13 +6,18 @@ /// public class Enemy : Entity { - [SerializeField] private EnemyRewardData _rewardData; - [SerializeField] private SO_Gold _gold; - [SerializeField] private SO_XP _xp; + [SerializeField] + private EnemyRewardData _rewardData; + + [SerializeField] + private SO_Gold _gold; + + [SerializeField] + private SO_XP _xp; public override void Death() { - base.Death(); // OnDied 이벤트 발화 (WaveController가 구독 중) + base.Death(); // OnDied 이벤트 발화 (WaveController가 구독 중) if (_rewardData != null) { @@ -20,6 +25,6 @@ public override void Death() _xp?.Increase(_rewardData.xpAmount); } - Destroy(gameObject, 0.5f); // 사망 연출 시간 확보 후 제거 + Destroy(gameObject, 0.5f); // 사망 연출 시간 확보 후 제거 } } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs index 6592d30..5dd8396 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using UnityEngine; public class TestEnemy : Enemy diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs b/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs index a7f4f7d..5b66848 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Entity.cs @@ -1,4 +1,4 @@ -using System; +using System; using UnityEngine; public abstract class Entity : MonoBehaviour diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs b/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs index 613cb15..bd1763e 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using UnityEngine; public class Player : Entity diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Player/Warrior.cs b/Fantasy-Grower/Assets/Scripts/Battle/Player/Warrior.cs index ea53885..e058176 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Player/Warrior.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Player/Warrior.cs @@ -1,6 +1,3 @@ -using UnityEngine; +using UnityEngine; -public class Warrior : Player -{ - -} +public class Warrior : Player { } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs b/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs index c3e4ab2..e264b01 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using UnityEngine; @@ -34,7 +34,9 @@ public List SpawnWave(WaveData waveData, Transform[] spawnPoints) Enemy enemy = go.GetComponent(); if (enemy == null) { - Debug.LogWarning($"[WaveController] 프리팹 {entry.enemyPrefab.name}에 Enemy 컴포넌트가 없습니다."); + Debug.LogWarning( + $"[WaveController] 프리팹 {entry.enemyPrefab.name}에 Enemy 컴포넌트가 없습니다." + ); Destroy(go); continue; } @@ -57,8 +59,7 @@ public List SpawnWave(WaveData waveData, Transform[] spawnPoints) } /// 현재 살아있는 첫 번째 적을 반환한다. 없으면 null. - public Enemy GetFirstAliveEnemy() - => _activeEnemies.FirstOrDefault(e => e != null && e.Hp > 0); + public Enemy GetFirstAliveEnemy() => _activeEnemies.FirstOrDefault(e => e != null && e.Hp > 0); public IReadOnlyList ActiveEnemies => _activeEnemies.AsReadOnly(); diff --git a/Fantasy-Grower/Assets/Scripts/Core/DamageCalculator.cs b/Fantasy-Grower/Assets/Scripts/Core/DamageCalculator.cs index a6498d0..2580717 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/DamageCalculator.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/DamageCalculator.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; /// /// 데미지 계산 공식을 담당하는 정적 유틸리티. @@ -16,7 +16,8 @@ public static class DamageCalculator public static (int damage, bool isCritical) Calculate( int rawAttackPower, float targetDamageReduction, - float attackerCriticalPercentage) + float attackerCriticalPercentage + ) { int reduced = Mathf.Max(1, Mathf.RoundToInt(rawAttackPower * (1f - targetDamageReduction))); bool isCritical = Random.value * 100f < attackerCriticalPercentage; diff --git a/Fantasy-Grower/Assets/Scripts/Core/EntityStatData.cs b/Fantasy-Grower/Assets/Scripts/Core/EntityStatData.cs index 6984092..c47604a 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/EntityStatData.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/EntityStatData.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; [CreateAssetMenu(fileName = "EntityStat", menuName = "Stat/Entity")] public class EntityStatData : ScriptableObject diff --git a/Fantasy-Grower/Assets/Scripts/Core/EntityStatModifier.cs b/Fantasy-Grower/Assets/Scripts/Core/EntityStatModifier.cs index f688013..cca521c 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/EntityStatModifier.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/EntityStatModifier.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; [System.Serializable] public struct EntityStatModifier diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillData.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillData.cs index c402812..a808978 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillData.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillData.cs @@ -1,6 +1,10 @@ -using UnityEngine; +using UnityEngine; -public enum SkillCategory { Active, Passive } +public enum SkillCategory +{ + Active, + Passive, +} [CreateAssetMenu(fileName = "SkillStat", menuName = "Stat/Skill")] public abstract class SkillData : ScriptableObject diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillData.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillData.cs index 85d815b..dc8827f 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillData.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/ActiveSkillData.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; /// /// 액티브 스킬 데이터의 추상 기반 클래스. diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillData.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillData.cs index 95e73f8..caddb82 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillData.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/PassiveSkillData.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; /// /// 패시브 스킬 데이터의 추상 기반 클래스. diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillNodeData.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillNodeData.cs index 4cf217b..fb8a05c 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillNodeData.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillNodeData.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; /// /// 스킬 트리의 단일 노드. 에디터에서 드래그&드롭으로 선행 노드를 연결한다. @@ -11,8 +11,8 @@ public class SkillNodeData : ScriptableObject [Header("트리 구조")] public SkillNodeData[] Prerequisites; - public int TierIndex; // 0 = 기본 평타, 1 = 1티어, 2 = 2티어, ... - public int SlotIndex; // 같은 티어 내 위치 + public int TierIndex; // 0 = 기본 평타, 1 = 1티어, 2 = 2티어, ... + public int SlotIndex; // 같은 티어 내 위치 [Header("속성 태그 (법사 전용)")] public string AttributeTag; // "Fire" / "Ice" / "Wind" diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs index 703712d..8f8da23 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using UnityEngine; @@ -9,8 +9,11 @@ [RequireComponent(typeof(Player))] public class SkillTreeComponent : MonoBehaviour { - [SerializeField] private SkillTreeData _treeData; - [SerializeField] private SO_SP _spResource; + [SerializeField] + private SkillTreeData _treeData; + + [SerializeField] + private SO_SP _spResource; // ScriptableObject를 직접 수정하지 않고 런타임 상태를 Dictionary로 격리 private Dictionary _unlockedState; @@ -51,7 +54,8 @@ private void Awake() /// public bool TryUnlockNode(SkillNodeData node) { - if (node == null || _strategy == null) return false; + if (node == null || _strategy == null) + return false; if (IsUnlocked(node)) { @@ -86,7 +90,8 @@ private void RecalculatePassives() foreach (var kv in _unlockedState) { - if (!kv.Value) continue; + if (!kv.Value) + continue; if (kv.Key.Skill is PassiveSkillData passive) passive.ApplyPassive(ref modifier); } @@ -122,7 +127,8 @@ public void UnequipActiveSkill(int slotIndex) public ActiveSkillData GetEquippedSkill(int slotIndex) { - if (slotIndex < 0 || slotIndex >= _equippedActives.Count) return null; + if (slotIndex < 0 || slotIndex >= _equippedActives.Count) + return null; return _equippedActives[slotIndex]; } @@ -138,8 +144,10 @@ public bool IsUnlocked(SkillNodeData node) /// public bool CanUnlock(SkillNodeData node) { - if (node == null || node.Skill == null) return false; - if (!SkillTreeValidator.HasEnoughSP(node, _spResource)) return false; + if (node == null || node.Skill == null) + return false; + if (!SkillTreeValidator.HasEnoughSP(node, _spResource)) + return false; return _strategy.CanUnlock(node, _unlockedState); } diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeData.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeData.cs index fea3f09..2282e80 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeData.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeData.cs @@ -1,7 +1,12 @@ -using System.Collections.Generic; +using System.Collections.Generic; using UnityEngine; -public enum SkillTreeStrategyType { Branching, RowSelect, Linear } +public enum SkillTreeStrategyType +{ + Branching, + RowSelect, + Linear, +} /// /// 직업별 스킬 트리 전체 정의. 에디터에서 AllNodes에 노드를 등록하고 @@ -31,7 +36,7 @@ public ISkillTreeStrategy CreateStrategy() { SkillTreeStrategyType.Branching => new BranchingTreeStrategy(), SkillTreeStrategyType.RowSelect => new RowSelectTreeStrategy(), - SkillTreeStrategyType.Linear => new LinearTreeStrategy(), + SkillTreeStrategyType.Linear => new LinearTreeStrategy(), _ => new BranchingTreeStrategy(), }; } diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeValidator.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeValidator.cs index 84e70b9..0df9451 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeValidator.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeValidator.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; /// @@ -12,13 +12,15 @@ public static class SkillTreeValidator /// public static bool ArePrerequisitesMet( SkillNodeData node, - IReadOnlyDictionary state) + IReadOnlyDictionary state + ) { if (node.Prerequisites == null || node.Prerequisites.Length == 0) return true; - return node.Prerequisites - .All(pre => pre != null && state.TryGetValue(pre, out bool unlocked) && unlocked); + return node.Prerequisites.All(pre => + pre != null && state.TryGetValue(pre, out bool unlocked) && unlocked + ); } /// @@ -26,16 +28,15 @@ public static bool ArePrerequisitesMet( /// public static bool HasEnoughSP(SkillNodeData node, SO_SP sp) { - if (node.Skill == null) return false; + if (node.Skill == null) + return false; return sp.Get() >= (uint)node.Skill.SPCost; } /// /// 액티브 스킬 슬롯에 여유가 있는지 확인한다. /// - public static bool HasActiveSlotAvailable( - IReadOnlyList equipped, - int maxSlots) + public static bool HasActiveSlotAvailable(IReadOnlyList equipped, int maxSlots) { int occupiedSlots = equipped.Count(skill => skill != null); return occupiedSlots < maxSlots; diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/BranchingTreeStrategy.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/BranchingTreeStrategy.cs index fed5f24..b462446 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/BranchingTreeStrategy.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/BranchingTreeStrategy.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; /// @@ -15,8 +15,9 @@ public bool CanUnlock(SkillNodeData node, IReadOnlyDictionary pre != null && state.TryGetValue(pre, out bool unlocked) && unlocked); + bool anyPrerequisiteMet = node.Prerequisites.Any(pre => + pre != null && state.TryGetValue(pre, out bool unlocked) && unlocked + ); if (!anyPrerequisiteMet) return false; @@ -26,12 +27,16 @@ public bool CanUnlock(SkillNodeData node, IReadOnlyDictionary kv.Value) .Select(kv => kv.Key) .Any(unlocked => - unlocked != node && - unlocked.TierIndex == node.TierIndex && - unlocked.SlotIndex == node.SlotIndex); + unlocked != node + && unlocked.TierIndex == node.TierIndex + && unlocked.SlotIndex == node.SlotIndex + ); return !conflictExists; } - public void OnNodeUnlocked(SkillNodeData node, IReadOnlyDictionary state) { } + public void OnNodeUnlocked( + SkillNodeData node, + IReadOnlyDictionary state + ) { } } diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/ISkillTreeStrategy.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/ISkillTreeStrategy.cs index a33425d..2fa785b 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/ISkillTreeStrategy.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/ISkillTreeStrategy.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; /// /// 직업별 스킬 트리 해금 규칙을 추상화하는 전략 인터페이스. diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/LinearTreeStrategy.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/LinearTreeStrategy.cs index eabe425..340058b 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/LinearTreeStrategy.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/LinearTreeStrategy.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using UnityEngine; @@ -35,8 +35,9 @@ public bool CanUnlock(SkillNodeData node, IReadOnlyDictionary pre != null && state.TryGetValue(pre, out bool unlocked) && unlocked); + return node.Prerequisites.All(pre => + pre != null && state.TryGetValue(pre, out bool unlocked) && unlocked + ); } public void OnNodeUnlocked(SkillNodeData node, IReadOnlyDictionary state) diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/RowSelectTreeStrategy.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/RowSelectTreeStrategy.cs index f5c94ef..8749bbc 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/RowSelectTreeStrategy.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/RowSelectTreeStrategy.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; /// @@ -11,8 +11,9 @@ public class RowSelectTreeStrategy : ISkillTreeStrategy public bool CanUnlock(SkillNodeData node, IReadOnlyDictionary state) { // 이미 이 행에서 선택된 노드가 있으면 불가 - bool rowAlreadySelected = state - .Any(kv => kv.Value && kv.Key != node && kv.Key.TierIndex == node.TierIndex); + bool rowAlreadySelected = state.Any(kv => + kv.Value && kv.Key != node && kv.Key.TierIndex == node.TierIndex + ); if (rowAlreadySelected) return false; @@ -22,11 +23,15 @@ public bool CanUnlock(SkillNodeData node, IReadOnlyDictionary kv.Value && kv.Key.TierIndex == node.TierIndex - 1); + bool previousRowSelected = state.Any(kv => + kv.Value && kv.Key.TierIndex == node.TierIndex - 1 + ); return previousRowSelected; } - public void OnNodeUnlocked(SkillNodeData node, IReadOnlyDictionary state) { } + public void OnNodeUnlocked( + SkillNodeData node, + IReadOnlyDictionary state + ) { } } From d724700f289accc49d73efcd57c1f64ebad67eac Mon Sep 17 00:00:00 2001 From: Lee Siwoo Date: Mon, 6 Apr 2026 11:40:44 +0900 Subject: [PATCH 09/11] =?UTF-8?q?fix:=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EC=BB=A8=EB=B2=A4=EC=85=98=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A7=80=EC=A0=81=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #18 --- .../Assets/Scripts/Battle/AttackCollider.cs | 6 +- .../Scripts/Battle/AutoAttackController.cs | 22 +- .../Assets/Scripts/Battle/BattleManager.cs | 84 +++---- .../Assets/Scripts/Battle/Data/DungeonData.cs | 12 +- .../Scripts/Battle/Data/EnemyRewardData.cs | 4 +- .../Assets/Scripts/Battle/Data/WaveData.cs | 8 +- .../Assets/Scripts/Battle/Enemy/Enemy.cs | 12 +- .../Assets/Scripts/Battle/Enemy/EnemyAI.cs | 48 ++-- .../Assets/Scripts/Battle/Enemy/TestEnemy.cs | 12 +- .../Assets/Scripts/Battle/Player/Player.cs | 12 +- .../Assets/Scripts/Battle/WaveController.cs | 8 +- .../Assets/Scripts/Core/Goods/SO_Goods.cs | 3 +- .../Core/SkillTree/SkillTreeComponent.cs | 66 ++--- .../Strategies/LinearTreeStrategy.cs | 10 +- Fantasy-Grower/CLAUDE.md | 229 ++++++++++-------- 15 files changed, 280 insertions(+), 256 deletions(-) diff --git a/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs b/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs index a61c5de..8f748fe 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/AttackCollider.cs @@ -3,7 +3,8 @@ [RequireComponent(typeof(Collider2D))] public class AttackCollider : MonoBehaviour { - public EntityType type; + [SerializeField] + private EntityType type; private Entity entity; @@ -14,8 +15,7 @@ private void Awake() private void OnTriggerEnter2D(Collider2D collision) { - Entity target; - if (!collision.gameObject.TryGetComponent(out target)) + if (!collision.gameObject.TryGetComponent(out Entity target)) return; bool shouldHit = diff --git a/Fantasy-Grower/Assets/Scripts/Battle/AutoAttackController.cs b/Fantasy-Grower/Assets/Scripts/Battle/AutoAttackController.cs index f49aba5..d175599 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/AutoAttackController.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/AutoAttackController.cs @@ -10,28 +10,28 @@ public class AutoAttackController : MonoBehaviour { [SerializeField] - private WaveController _waveController; + private WaveController waveController; - private Player _player; - private Coroutine _attackCoroutine; + private Player player; + private Coroutine attackCoroutine; private void Awake() { - _player = GetComponent(); + player = GetComponent(); } public void StartAutoAttack() { StopAutoAttack(); - _attackCoroutine = StartCoroutine(AutoAttackLoop()); + attackCoroutine = StartCoroutine(AutoAttackLoop()); } public void StopAutoAttack() { - if (_attackCoroutine != null) + if (attackCoroutine != null) { - StopCoroutine(_attackCoroutine); - _attackCoroutine = null; + StopCoroutine(attackCoroutine); + attackCoroutine = null; } } @@ -39,11 +39,11 @@ private IEnumerator AutoAttackLoop() { while (true) { - if (_waveController.GetFirstAliveEnemy() != null) - _player.Attack(); // AttackCollider가 실제 피해를 처리 + if (waveController.GetFirstAliveEnemy() != null) + player.Attack(); // AttackCollider가 실제 피해를 처리 // AttackSpeed = 초당 공격 횟수 (예: 1.0 → 1초마다 공격) - float interval = _player.AttackSpeed > 0f ? 1f / _player.AttackSpeed : 1f; + float interval = player.AttackSpeed > 0f ? 1f / player.AttackSpeed : 1f; yield return new WaitForSeconds(interval); } } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/BattleManager.cs b/Fantasy-Grower/Assets/Scripts/Battle/BattleManager.cs index d881d6f..2c5c881 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/BattleManager.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/BattleManager.cs @@ -22,35 +22,35 @@ public class BattleManager : MonoBehaviour { // ─── Inspector 연결 ────────────────────────────────────────── [SerializeField] - private Player _player; + private Player player; [SerializeField] - private AutoAttackController _autoAttack; + private AutoAttackController autoAttack; [SerializeField] - private WaveController _waveController; + private WaveController waveController; [SerializeField] - private Transform[] _spawnPoints; + private Transform[] spawnPoints; [Header("던전 데이터")] [SerializeField] - private DungeonData _dungeonData; + private DungeonData dungeonData; [Header("재화 SO")] [SerializeField] - private SO_Gold _gold; + private SO_Gold gold; [SerializeField] - private SO_XP _xp; + private SO_XP xp; [SerializeField] - private SO_Mithril _mithril; + private SO_Mithril mithril; // ─── 런타임 상태 ───────────────────────────────────────────── - private BattleState _state = BattleState.Idle; - private int _currentWaveIndex; - private List _currentWaveEnemies; + private BattleState state = BattleState.Idle; + private int currentWaveIndex; + private List currentWaveEnemies; // ─── UI 알림 이벤트 ─────────────────────────────────────────── /// 상태가 변경될 때마다 발화된다. UI 패널 전환에 사용한다. @@ -62,54 +62,54 @@ public class BattleManager : MonoBehaviour // ─── 유니티 라이프사이클 ────────────────────────────────────── private void Awake() { - _waveController.OnAllEnemiesDead += HandleAllEnemiesDead; - _player.OnDied += HandlePlayerDied; + waveController.OnAllEnemiesDead += HandleAllEnemiesDead; + player.OnDied += HandlePlayerDied; } private void OnDestroy() { - _waveController.OnAllEnemiesDead -= HandleAllEnemiesDead; - _player.OnDied -= HandlePlayerDied; + waveController.OnAllEnemiesDead -= HandleAllEnemiesDead; + player.OnDied -= HandlePlayerDied; } // ─── 공개 API (UI 버튼에서 호출) ───────────────────────────── /// 던전을 시작한다. public void StartDungeon() { - if (_dungeonData == null) + if (dungeonData == null) { Debug.LogError("[BattleManager] DungeonData가 연결되지 않았습니다."); return; } - if (_dungeonData.dungeonType == DungeonType.Gold) + if (dungeonData.DungeonType == DungeonType.Gold) { Debug.Log("[BattleManager] 골드 던전은 미니게임 씬으로 전환해야 합니다."); // TODO: SceneManager.LoadScene("GoldDungeonScene"); return; } - _currentWaveIndex = 0; + currentWaveIndex = 0; TransitionTo(BattleState.WaveStart); } /// 플레이어 사망 후 던전을 처음부터 재시도한다. public void RetryDungeon() { - _waveController.Clear(); - _player.ResetHp(); - _currentWaveIndex = 0; + waveController.Clear(); + player.ResetHp(); + currentWaveIndex = 0; TransitionTo(BattleState.WaveStart); } // ─── 상태 머신 ──────────────────────────────────────────────── private void TransitionTo(BattleState newState) { - _state = newState; - OnStateChanged?.Invoke(_state); + state = newState; + OnStateChanged?.Invoke(state); Debug.Log($"[BattleManager] 상태 전환: {newState}"); - switch (_state) + switch (state) { case BattleState.WaveStart: EnterWaveStart(); @@ -132,32 +132,32 @@ private void TransitionTo(BattleState newState) private void EnterWaveStart() { Debug.Log( - $"[BattleManager] 웨이브 {_currentWaveIndex + 1} / {_dungeonData.waves.Length} 시작" + $"[BattleManager] 웨이브 {currentWaveIndex + 1} / {dungeonData.Waves.Length} 시작" ); - OnWaveChanged?.Invoke(_currentWaveIndex); + OnWaveChanged?.Invoke(currentWaveIndex); - WaveData wave = _dungeonData.waves[_currentWaveIndex]; - _currentWaveEnemies = _waveController.SpawnWave(wave, _spawnPoints); + WaveData wave = dungeonData.Waves[currentWaveIndex]; + currentWaveEnemies = waveController.SpawnWave(wave, spawnPoints); - foreach (Enemy e in _currentWaveEnemies) - e.GetComponent()?.Initialize(_player); + foreach (Enemy e in currentWaveEnemies) + e.GetComponent()?.Initialize(player); TransitionTo(BattleState.Fighting); } private void EnterFighting() { - _autoAttack.StartAutoAttack(); + autoAttack.StartAutoAttack(); - foreach (Enemy e in _currentWaveEnemies) + foreach (Enemy e in currentWaveEnemies) e.GetComponent()?.StartAttacking(); } private void HandleAllEnemiesDead() { - _autoAttack.StopAutoAttack(); + autoAttack.StopAutoAttack(); - foreach (Enemy e in _currentWaveEnemies) + foreach (Enemy e in currentWaveEnemies) e.GetComponent()?.StopAttacking(); TransitionTo(BattleState.WaveCleared); @@ -165,9 +165,9 @@ private void HandleAllEnemiesDead() private void EnterWaveCleared() { - _currentWaveIndex++; + currentWaveIndex++; - if (_currentWaveIndex >= _dungeonData.waves.Length) + if (currentWaveIndex >= dungeonData.Waves.Length) { TransitionTo(BattleState.DungeonCleared); } @@ -181,17 +181,17 @@ private void EnterDungeonCleared() { Debug.Log("[BattleManager] 던전 클리어!"); - _gold?.Increase(_dungeonData.bonusGoldReward); - _xp?.Increase(_dungeonData.bonusXpReward); + gold?.Increase(dungeonData.BonusGoldReward); + xp?.Increase(dungeonData.BonusXpReward); - if (_dungeonData.dungeonType == DungeonType.Boss && _dungeonData.mithrilAsset != null) - _dungeonData.mithrilAsset.Increase(_dungeonData.mithrilRewardAmount); + if (dungeonData.DungeonType == DungeonType.Boss && dungeonData.MithrilAsset != null) + dungeonData.MithrilAsset.Increase(dungeonData.MithrilRewardAmount); } private void HandlePlayerDied(Entity _) { - _autoAttack.StopAutoAttack(); - _waveController.Clear(); + autoAttack.StopAutoAttack(); + waveController.Clear(); TransitionTo(BattleState.PlayerDead); } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Data/DungeonData.cs b/Fantasy-Grower/Assets/Scripts/Battle/Data/DungeonData.cs index da5ddc9..8fec5a5 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Data/DungeonData.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Data/DungeonData.cs @@ -16,16 +16,16 @@ public enum DungeonType public class DungeonData : ScriptableObject { [Header("던전 유형")] - public DungeonType dungeonType; + public DungeonType DungeonType; [Header("웨이브 목록 (순서대로 진행)")] - public WaveData[] waves; + public WaveData[] Waves; [Header("던전 클리어 보너스 보상")] - public uint bonusGoldReward; - public uint bonusXpReward; + public uint BonusGoldReward; + public uint BonusXpReward; [Header("보스 던전 전용 보상")] - public SO_Mithril mithrilAsset; - public uint mithrilRewardAmount; + public SO_Mithril MithrilAsset; + public uint MithrilRewardAmount; } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Data/EnemyRewardData.cs b/Fantasy-Grower/Assets/Scripts/Battle/Data/EnemyRewardData.cs index 5cba192..975a480 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Data/EnemyRewardData.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Data/EnemyRewardData.cs @@ -8,6 +8,6 @@ public class EnemyRewardData : ScriptableObject { [Header("사망 보상")] - public uint goldAmount; - public uint xpAmount; + public uint GoldAmount; + public uint XpAmount; } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Data/WaveData.cs b/Fantasy-Grower/Assets/Scripts/Battle/Data/WaveData.cs index 71a8074..ab8b155 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Data/WaveData.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Data/WaveData.cs @@ -11,13 +11,13 @@ public class WaveData : ScriptableObject [System.Serializable] public struct EnemySpawnEntry { - public GameObject enemyPrefab; - public int count; + public GameObject EnemyPrefab; + public int Count; } [Header("스폰 목록 (적 종류 × 수량)")] - public EnemySpawnEntry[] entries; + public EnemySpawnEntry[] Entries; /// 이 웨이브의 총 적 수 - public int TotalEnemyCount => entries?.Sum(e => e.count) ?? 0; + public int TotalEnemyCount => Entries?.Sum(e => e.Count) ?? 0; } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs index 0bf973f..a3b07ae 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/Enemy.cs @@ -7,22 +7,22 @@ public class Enemy : Entity { [SerializeField] - private EnemyRewardData _rewardData; + private EnemyRewardData rewardData; [SerializeField] - private SO_Gold _gold; + private SO_Gold gold; [SerializeField] - private SO_XP _xp; + private SO_XP xp; public override void Death() { base.Death(); // OnDied 이벤트 발화 (WaveController가 구독 중) - if (_rewardData != null) + if (rewardData != null) { - _gold?.Increase(_rewardData.goldAmount); - _xp?.Increase(_rewardData.xpAmount); + gold?.Increase(rewardData.GoldAmount); + xp?.Increase(rewardData.XpAmount); } Destroy(gameObject, 0.5f); // 사망 연출 시간 확보 후 제거 diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs index b7fb725..5951d5d 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs @@ -8,33 +8,42 @@ [RequireComponent(typeof(Enemy))] public class EnemyAI : MonoBehaviour { - private Enemy _enemy; - private Player _player; - private Coroutine _attackCoroutine; + private Enemy enemy; + private Player player; + private Coroutine attackCoroutine; + + private bool hasAttackCollider + { + get { return childCollider != null; } + set { hasAttackCollider = value; } + } + + private AttackCollider childCollider; private void Awake() { - _enemy = GetComponent(); + enemy = GetComponent(); + childCollider = GetComponentInChildren(includeInactive: true); } /// 타겟 플레이어를 설정한다. BattleManager가 스폰 직후 호출한다. public void Initialize(Player player) { - _player = player; + this.player = player; } public void StartAttacking() { StopAttacking(); - _attackCoroutine = StartCoroutine(AttackLoop()); + attackCoroutine = StartCoroutine(AttackLoop()); } public void StopAttacking() { - if (_attackCoroutine != null) + if (attackCoroutine != null) { - StopCoroutine(_attackCoroutine); - _attackCoroutine = null; + StopCoroutine(attackCoroutine); + attackCoroutine = null; } } @@ -42,32 +51,27 @@ private IEnumerator AttackLoop() { while (true) { - if (_player != null && _player.Hp > 0) + if (player != null && player.Hp > 0) { // Enemy 프리팹에 AttackCollider가 있으면 Attack()으로 애니메이션+충돌 처리. // AttackCollider가 없는 경우 DamageCalculator로 직접 피해 적용. - if (HasAttackCollider()) + if (hasAttackCollider) { - _enemy.Attack(); + enemy.Attack(); } else { var (damage, _) = DamageCalculator.Calculate( - _enemy.AttackPower, - _player.DamageReduction, - _enemy.CriticalPercentage + enemy.AttackPower, + player.DamageReduction, + enemy.CriticalPercentage ); - _player.TakeDamage(damage); + player.TakeDamage(damage); } } - float interval = _enemy.AttackSpeed > 0f ? 1f / _enemy.AttackSpeed : 2f; + float interval = enemy.AttackSpeed > 0f ? 1f / enemy.AttackSpeed : 2f; yield return new WaitForSeconds(interval); } } - - private bool HasAttackCollider() - { - return GetComponentInChildren(includeInactive: true) != null; - } } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs index 5dd8396..36d7129 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/TestEnemy.cs @@ -18,21 +18,21 @@ public override void Death() base.Death(); // Enemy.Death() → 보상 지급 + OnDied 이벤트 } - private bool _isAttacking; + private bool isAttacking; public override void Attack() { - if (_isAttacking) + if (isAttacking) return; - StartCoroutine(attackCoroutine()); + StartCoroutine(AttackCoroutine()); } - private IEnumerator attackCoroutine() + private IEnumerator AttackCoroutine() { - _isAttacking = true; + isAttacking = true; col.gameObject.SetActive(true); yield return new WaitForSeconds(0.2f); // 히트 판정 윈도우 (고정) col.gameObject.SetActive(false); - _isAttacking = false; + isAttacking = false; } } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs b/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs index bd1763e..d8afb34 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Player/Player.cs @@ -12,21 +12,21 @@ protected override void Awake() col.gameObject.SetActive(false); } - private bool _isAttacking; + private bool isAttacking; public override void Attack() { - if (_isAttacking) + if (isAttacking) return; - StartCoroutine(attackCoroutine()); + StartCoroutine(AttackCoroutine()); } - private IEnumerator attackCoroutine() + private IEnumerator AttackCoroutine() { - _isAttacking = true; + isAttacking = true; col.gameObject.SetActive(true); yield return new WaitForSeconds(0.2f); // 히트 판정 윈도우 (고정) col.gameObject.SetActive(false); - _isAttacking = false; + isAttacking = false; } } diff --git a/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs b/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs index e264b01..dc7cb9f 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs @@ -24,18 +24,18 @@ public List SpawnWave(WaveData waveData, Transform[] spawnPoints) _activeEnemies.Clear(); int spawnIndex = 0; - foreach (var entry in waveData.entries) + foreach (var entry in waveData.Entries) { - for (int i = 0; i < entry.count; i++) + for (int i = 0; i < entry.Count; i++) { Vector3 pos = spawnPoints[spawnIndex % spawnPoints.Length].position; - GameObject go = Instantiate(entry.enemyPrefab, pos, Quaternion.identity); + GameObject go = Instantiate(entry.EnemyPrefab, pos, Quaternion.identity); Enemy enemy = go.GetComponent(); if (enemy == null) { Debug.LogWarning( - $"[WaveController] 프리팹 {entry.enemyPrefab.name}에 Enemy 컴포넌트가 없습니다." + $"[WaveController] 프리팹 {entry.EnemyPrefab.name}에 Enemy 컴포넌트가 없습니다." ); Destroy(go); continue; diff --git a/Fantasy-Grower/Assets/Scripts/Core/Goods/SO_Goods.cs b/Fantasy-Grower/Assets/Scripts/Core/Goods/SO_Goods.cs index 72394f2..20e03c0 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/Goods/SO_Goods.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/Goods/SO_Goods.cs @@ -31,8 +31,7 @@ public virtual void Decrease(uint amount) { if (amount > value) { - // ȭ - Debug.LogError($"{GoodsName}() {amount - value}ŭ մϴ!!!"); + Debug.LogError($"{GoodsName}은(는) {amount - value}만큼 부족합니다!!!"); return; } diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs index 8f8da23..6e8f47c 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/SkillTreeComponent.cs @@ -10,40 +10,40 @@ public class SkillTreeComponent : MonoBehaviour { [SerializeField] - private SkillTreeData _treeData; + private SkillTreeData treeData; [SerializeField] - private SO_SP _spResource; + private SO_SP spResource; // ScriptableObject를 직접 수정하지 않고 런타임 상태를 Dictionary로 격리 - private Dictionary _unlockedState; + private Dictionary unlockedState; // 장착된 액티브 스킬 슬롯 (null = 비어있음) - private List _equippedActives; + private List equippedActives; - private ISkillTreeStrategy _strategy; - private Entity _entity; + private ISkillTreeStrategy strategy; + private Entity entity; private void Awake() { - _entity = GetComponent(); - _unlockedState = new Dictionary(); - _strategy = _treeData != null ? _treeData.CreateStrategy() : null; + entity = GetComponent(); + unlockedState = new Dictionary(); + strategy = treeData != null ? treeData.CreateStrategy() : null; - int slotCount = _treeData != null ? _treeData.MaxActiveSkillSlots : 3; - _equippedActives = new List(new ActiveSkillData[slotCount]); + int slotCount = treeData != null ? treeData.MaxActiveSkillSlots : 3; + equippedActives = new List(new ActiveSkillData[slotCount]); - if (_treeData == null) + if (treeData == null) Debug.LogWarning("[SkillTreeComponent] SkillTreeData가 비어 있습니다!"); - if (_spResource == null) + if (spResource == null) Debug.LogWarning("[SkillTreeComponent] SO_SP가 비어 있습니다!"); // 모든 노드를 초기 잠금 상태로 등록 - if (_treeData != null && _treeData.AllNodes != null) + if (treeData != null && treeData.AllNodes != null) { - foreach (var node in _treeData.AllNodes) - _unlockedState[node] = false; + foreach (var node in treeData.AllNodes) + unlockedState[node] = false; } } @@ -54,7 +54,7 @@ private void Awake() /// public bool TryUnlockNode(SkillNodeData node) { - if (node == null || _strategy == null) + if (node == null || strategy == null) return false; if (IsUnlocked(node)) @@ -70,10 +70,10 @@ public bool TryUnlockNode(SkillNodeData node) } // SP 소비 - _spResource.Decrease((uint)node.Skill.SPCost); + spResource.Decrease((uint)node.Skill.SPCost); - _unlockedState[node] = true; - _strategy.OnNodeUnlocked(node, _unlockedState); + unlockedState[node] = true; + strategy.OnNodeUnlocked(node, unlockedState); Debug.Log($"[SkillTree] {node.Skill?.SkillName} 해금 완료."); @@ -88,7 +88,7 @@ private void RecalculatePassives() { var modifier = EntityStatModifier.Zero; - foreach (var kv in _unlockedState) + foreach (var kv in unlockedState) { if (!kv.Value) continue; @@ -96,7 +96,7 @@ private void RecalculatePassives() passive.ApplyPassive(ref modifier); } - _entity.ApplyStatModifier(modifier); + entity.ApplyStatModifier(modifier); } // ─── 액티브 스킬 장착 ───────────────────────────────────────── @@ -106,7 +106,7 @@ private void RecalculatePassives() /// public bool TryEquipActiveSkill(ActiveSkillData skill, int slotIndex) { - if (skill == null || slotIndex < 0 || slotIndex >= _equippedActives.Count) + if (skill == null || slotIndex < 0 || slotIndex >= equippedActives.Count) return false; if (!IsUnlocked(FindNodeBySkill(skill))) @@ -115,28 +115,28 @@ public bool TryEquipActiveSkill(ActiveSkillData skill, int slotIndex) return false; } - _equippedActives[slotIndex] = skill; + equippedActives[slotIndex] = skill; return true; } public void UnequipActiveSkill(int slotIndex) { - if (slotIndex >= 0 && slotIndex < _equippedActives.Count) - _equippedActives[slotIndex] = null; + if (slotIndex >= 0 && slotIndex < equippedActives.Count) + equippedActives[slotIndex] = null; } public ActiveSkillData GetEquippedSkill(int slotIndex) { - if (slotIndex < 0 || slotIndex >= _equippedActives.Count) + if (slotIndex < 0 || slotIndex >= equippedActives.Count) return null; - return _equippedActives[slotIndex]; + return equippedActives[slotIndex]; } // ─── 조회 ──────────────────────────────────────────────────── public bool IsUnlocked(SkillNodeData node) { - return node != null && _unlockedState.TryGetValue(node, out bool v) && v; + return node != null && unlockedState.TryGetValue(node, out bool v) && v; } /// @@ -146,17 +146,17 @@ public bool CanUnlock(SkillNodeData node) { if (node == null || node.Skill == null) return false; - if (!SkillTreeValidator.HasEnoughSP(node, _spResource)) + if (!SkillTreeValidator.HasEnoughSP(node, spResource)) return false; - return _strategy.CanUnlock(node, _unlockedState); + return strategy.CanUnlock(node, unlockedState); } - public IReadOnlyList GetEquippedActives() => _equippedActives.AsReadOnly(); + public IReadOnlyList GetEquippedActives() => equippedActives.AsReadOnly(); // ─── 내부 유틸 ──────────────────────────────────────────────── private SkillNodeData FindNodeBySkill(SkillData skill) { - return _treeData?.AllNodes?.FirstOrDefault(n => n.Skill == skill); + return treeData?.AllNodes?.FirstOrDefault(n => n.Skill == skill); } } diff --git a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/LinearTreeStrategy.cs b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/LinearTreeStrategy.cs index 340058b..11bd526 100644 --- a/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/LinearTreeStrategy.cs +++ b/Fantasy-Grower/Assets/Scripts/Core/SkillTree/Strategies/LinearTreeStrategy.cs @@ -9,7 +9,7 @@ /// public class LinearTreeStrategy : ISkillTreeStrategy { - private string _selectedAttribute; + private string selectedAttribute; /// /// 속성을 선택한다. 첫 번째 노드를 해금할 때 자동 결정되며, @@ -17,17 +17,17 @@ public class LinearTreeStrategy : ISkillTreeStrategy /// public void SelectAttribute(string attribute) { - _selectedAttribute = attribute; + selectedAttribute = attribute; } public bool CanUnlock(SkillNodeData node, IReadOnlyDictionary state) { // 속성이 아직 선택되지 않은 경우: 어떤 속성이든 첫 번째 노드(TierIndex == 0) 해금 가능 - if (string.IsNullOrEmpty(_selectedAttribute)) + if (string.IsNullOrEmpty(selectedAttribute)) return node.TierIndex == 0; // 선택된 속성 라인만 허용 - if (node.AttributeTag != _selectedAttribute) + if (node.AttributeTag != selectedAttribute) return false; // 선행 노드가 없으면 해금 가능 @@ -43,7 +43,7 @@ public bool CanUnlock(SkillNodeData node, IReadOnlyDictionary state) { // 첫 번째 노드 해금 시 속성을 확정한다 - if (string.IsNullOrEmpty(_selectedAttribute) && !string.IsNullOrEmpty(node.AttributeTag)) + if (string.IsNullOrEmpty(selectedAttribute) && !string.IsNullOrEmpty(node.AttributeTag)) SelectAttribute(node.AttributeTag); } } diff --git a/Fantasy-Grower/CLAUDE.md b/Fantasy-Grower/CLAUDE.md index 193db39..f679941 100644 --- a/Fantasy-Grower/CLAUDE.md +++ b/Fantasy-Grower/CLAUDE.md @@ -4,9 +4,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -**판타지 키우기 (Fantasy Grower)** — 2D 방치형 RPG 모바일 게임 (Unity 6.0.0, Android) +**Fantasy Grower** — 2D Idle RPG Mobile Game (Unity 6.0.0, Android) -플레이어가 직업/무기/스킬 트리를 조합하여 빌드를 만들고, 던전을 자동 전투로 진행하며 성장하는 방치형 RPG. +Players build characters by combining jobs, weapons, and skill trees, then progress through dungeons via auto-combat. ## Development Environment @@ -16,10 +16,31 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - **Input**: Unity New Input System - **Platform**: Android (target), Desktop (test) -Unity에는 별도 CLI 빌드 커맨드가 없음. 모든 빌드/실행은 Unity Editor 내에서 수행: -- **Play**: Unity Editor > Play 버튼 +No CLI build commands. All build/run operations are performed inside the Unity Editor: +- **Play**: Unity Editor > Play button - **Build**: File > Build and Run -- **Tests**: Window > General > Test Runner (Unity Test Framework 사용) +- **Tests**: Window > General > Test Runner (Unity Test Framework) + +## C# Naming Conventions + +Follows Microsoft C# naming conventions. + +| Target | Rule | Example | +|--------|------|---------| +| Class / Struct | PascalCase | `EntityStatData`, `AttackCollider` | +| Interface | `I` + PascalCase | `ISkillData`, `IAttackable` | +| Method | PascalCase | `TakeDamage()`, `GetGoods()` | +| Property | PascalCase | `AttackPower`, `MaxHealth` | +| public field | PascalCase | `public int AttackPower;` | +| private field | camelCase | `private int attackPower;` | +| Local variable | camelCase | `int currentHp = 0;` | +| Parameter | camelCase | `void TakeDamage(int damageAmount)` | +| Constant (`const`) | PascalCase | `const int MaxLevel = 100;` | +| enum type | PascalCase | `EntityType` | +| enum value | PascalCase | `EntityType.Player`, `EntityType.Enemy` | +| ScriptableObject class | PascalCase (no prefix) | `GoodsBase`, `SkillData` (~~`SO_Goods`~~ forbidden) | + +> **Note**: Hungarian prefixes such as `SO_`, `m_`, `_` are not allowed. ## Code Architecture @@ -27,150 +48,150 @@ Unity에는 별도 CLI 빌드 커맨드가 없음. 모든 빌드/실행은 Unity ``` MonoBehaviour -└── Entity (abstract) — HP, 공격력, TakeDamage(), Death() - ├── Player — Attack() 구현 - │ └── Warrior — 검사 서브클래스 - └── Enemy — Enemy 기반 - └── TestEnemy — Death() 오버라이드 +└── Entity (abstract) — HP, AttackPower, TakeDamage(), Death() + ├── Player — Implements Attack() + │ └── Warrior — Swordsman subclass + └── Enemy — Enemy base + └── TestEnemy — Overrides Death() ``` ### Key Design Patterns -**ScriptableObject 기반 데이터 분리** -- `EntityStatData` — HP, 공격력, 공격속도, 크리티컬 등 스탯 저장 -- `SkillData` (abstract) — 스킬 데이터 베이스 -- `SO_Goods` 계열 — 재화 시스템 (Gold, XP, SP, Mithril, UpgradeScroll) +**ScriptableObject-based data separation** +- `EntityStatData` — Stores stats: HP, AttackPower, AttackSpeed, CriticalChance, etc. +- `SkillData` (abstract) — Skill data base class +- `GoodsBase` family — Currency system (Gold, XP, SP, Mithril, UpgradeScroll) -**전투 히트 판정** -- `AttackCollider` 컴포넌트가 2D Trigger 충돌로 피해 처리 -- `EntityType` enum으로 아군 피해 방지 (Player는 Enemy만, Enemy는 Player만 타격) -- 피해 = 공격자의 `AttackPower` 직접 적용 +**Combat hit detection** +- `AttackCollider` component handles damage via 2D Trigger collision +- `EntityType` enum prevents friendly fire (`EntityType.Player` hits `EntityType.Enemy` only, and vice versa) +- Damage = attacker's `AttackPower` applied directly -**재화(Goods) 시스템** -- `SO_Goods` 추상 클래스: `Get()`, `Increase()`, `Decrease()` -- XP는 `Decrease()` 불가 (`[Obsolete]` 처리) -- 범위 검사로 과소비 방지 +**Goods (Currency) system** +- `GoodsBase` abstract class: `Get()`, `Increase()`, `Decrease()` +- XP cannot call `Decrease()` (marked `[Obsolete]`) +- Range checks prevent overspending ### Directory Structure ``` Assets/Scripts/ ├── Battle/ -│ ├── Entity.cs — 전투 엔티티 기반 클래스 -│ ├── AttackCollider.cs — 공격 히트박스 컴포넌트 -│ ├── Player/ — 플레이어 계열 -│ └── Enemy/ — 적 계열 +│ ├── Entity.cs — Combat entity base class +│ ├── AttackCollider.cs — Attack hitbox component +│ ├── Player/ — Player classes +│ └── Enemy/ — Enemy classes └── Core/ - ├── EntityStatData.cs — 스탯 ScriptableObject - ├── SkillData.cs — 스킬 데이터 기반 - └── Goods/ — 재화 ScriptableObject 계열 + ├── EntityStatData.cs — Stat ScriptableObject + ├── SkillData.cs — Skill data base + └── Goods/ — Currency ScriptableObject family ``` --- -## 게임 기획서 요약 (판타지 키우기) +## Game Design Document Summary -> 구현 시 이 기획을 기준으로 삼을 것. +> Use this as the reference for all implementation decisions. -### 성장 요소 +### Growth Resources -| 요소 | 용도 | -|------|------| -| Gold | 무기 구매, 강화, 상점 새로고침 | -| XP | 캐릭터 레벨업 | -| SP (Skill Point) | 스킬 트리 강화, 레벨업 시 획득 | -| 강화 스크롤 | 무기 강화 (무기 종류별 스크롤 별도) | -| 미스릴 | 무기 합성 | +| Resource | Usage | +|----------|-------| +| Gold | Buy/upgrade weapons, refresh shop | +| XP | Character level up | +| SP (Skill Point) | Upgrade skill tree; gained on level up | +| Upgrade Scroll | Weapon upgrade (separate scroll per weapon type) | +| Mithril | Weapon synthesis | -### 직업 시스템 (3종) +### Job System (3 types) -| 직업 | 특징 | -|------|------| -| 검사 | 높은 체력, 안정적 전투, 광역/단일 빌드 | -| 궁수 | 낮은 체력, 크리티컬 특화, 무기 패시브 중요 | -| 법사 | 중간 체력, 불/얼음/바람 속성 중 하나 특화 | +| Job | Traits | +|-----|--------| +| Warrior | High HP, stable combat, AoE/single-target builds | +| Archer | Low HP, crit-focused, weapon passives are key | +| Mage | Medium HP, specializes in one element: Fire / Ice / Wind | -**법사 속성 특징**: -- 불: 지속 피해, 광역 -- 얼음: 적 이속/공속 감소 (유틸) -- 바람: 공격 속도 증가, 낮은 쿨타임 +**Mage element traits**: +- Fire: DoT damage, AoE +- Ice: Reduces enemy move/attack speed (utility) +- Wind: Increases attack speed, low cooldowns -### 무기 시스템 +### Weapon System -**등급**: S > A > B > C -- A 이상: 특수 패시브 제공 -- S등급: 제련(합성)으로만 제작 가능 +**Grades**: S > A > B > C +- A and above: provide special passives +- S grade: craftable only via synthesis (smelting) -**직업별 무기**: -- 검사: 레이피어(크리), 롱소드(균형), 대검(공격력) -- 궁수: 대궁(공격력+크리), 석궁(공속+유틸) -- 법사: 지팡이(쿨타임 감소), 마법서(속성 강화) +**Job weapons**: +- Warrior: Rapier (crit), Longsword (balanced), Greatsword (attack power) +- Archer: Longbow (attack+crit), Crossbow (attack speed+utility) +- Mage: Staff (cooldown reduction), Spellbook (element boost) -**장비 관리**: -- 강화: 강화 스크롤 사용, 등급 상승 → 필요 스크롤 증가 -- 합성: 미스릴 + 무기 2개 → 새 무기 (평균 등급) -- 판매: Gold 획득 +**Equipment management**: +- Upgrade: Uses Upgrade Scrolls; higher grade requires more scrolls +- Synthesis: Mithril + 2 weapons → new weapon (average grade) +- Sell: Gain Gold -### 던전 시스템 (4종) +### Dungeon System (4 types) -| 던전 | 내용 | -|------|------| -| 기본 던전 | 자동 전투, Gold+XP 획득, 웨이브 진행, 정예 몬스터 등장 | -| 골드 던전 | 광산 미니게임, 화면 클릭으로 채굴, 30~60초 제한, 미스릴 극소확률 드랍 | -| 무기 던전 | 자동 전투, C등급 무기 드랍, 낮은 확률로 B등급/스크롤 드랍 | -| 보스 던전 | 강력한 정예 다수, 클리어 시 미스릴+A등급 무기+XP 획득 | +| Dungeon | Description | +|---------|-------------| +| Basic Dungeon | Auto-combat; rewards Gold+XP; wave progression; elite monsters appear | +| Gold Dungeon | Mining minigame; tap screen to mine; 30–60s time limit; rare Mithril drop | +| Weapon Dungeon | Auto-combat; drops C-grade weapons; low chance for B-grade/scrolls | +| Boss Dungeon | Many powerful elites; rewards Mithril+A-grade weapon+XP on clear | -### 상점 시스템 +### Shop System -- **대장간**: 랜덤 무기 판매, Gold로 새로고침, 고객 등급 시스템 (구매/강화할수록 등급↑, 높은 등급 무기 제공) -- **마도 상점**: 강화 스크롤 판매, Gold로 새로고침 +- **Blacksmith**: Random weapon stock; refresh with Gold; customer rank system (rank up by buying/upgrading → better weapon grades available) +- **Magic Shop**: Sells Upgrade Scrolls; refresh with Gold -### 스킬 트리 +### Skill Tree -공통: 레벨업 시 SP 획득, 액티브 스킬 최대 3개 장착, 패시브/액티브 분리 +Common rules: Gain SP on level up; equip up to 3 active skills; passives and actives are separate trees. -#### 검사 — 액티브 트리 (분기형) +#### Warrior — Active Tree (branching) -| 티어 | 스킬 | 타입 | 특징 | -|------|------|------|------| -| 평타 | 기본 베기 | 평타 | 2마리 공격, 데미지 낮음, 공격속도 빠름 | -| 평타 | 기본 찌르기 | 평타 | 1명 공격, 데미지 높음, 공격속도 느림 | -| 1티어 | 회선 베기 | 액티브 | 3마리 공격, 데미지 낮음, 쿨타임 보통 | -| 1티어 | 머리치기 | 액티브 | 2마리 공격, 데미지 보통, 쿨타임 김, 2초 기절 | -| 1티어 | 과통 | 액티브 | 2마리 공격, 데미지 높음, 쿨타임 꽤 김 | -| 1티어 | 연속 찌르기 | 액티브 | 1마리 공격, 데미지 높음, 쿨타임 보통 | -| 2티어 | 검무 | 액티브 | 4마리 공격, 데미지 낮음, 쿨타임 김 | -| 2티어 | 버티기 | 액티브 | 쿨타임 김, 적 수만큼 반피감 | -| 2티어 | 호흡 | 액티브 | 쿨타임 많이 김, 잔심 제외 모든 스킬 쿨초 | -| 2티어 | 일검사 | 액티브 | 1마리 공격, 데미지 높음, 쿨타임 김, 치명타로 즉사 | -| 최종 | 최종 스킬 | 액티브 | (미정) | +| Tier | Skill | Type | Description | +|------|-------|------|-------------| +| Basic | Slash | Basic Attack | Hits 2 enemies, low damage, fast attack speed | +| Basic | Thrust | Basic Attack | Hits 1 enemy, high damage, slow attack speed | +| Tier 1 | Spin Slash | Active | Hits 3 enemies, low damage, normal cooldown | +| Tier 1 | Headbutt | Active | Hits 2 enemies, medium damage, long cooldown, 2s stun | +| Tier 1 | Pierce | Active | Hits 2 enemies, high damage, fairly long cooldown | +| Tier 1 | Rapid Thrust | Active | Hits 1 enemy, high damage, normal cooldown | +| Tier 2 | Sword Dance | Active | Hits 4 enemies, low damage, long cooldown | +| Tier 2 | Endure | Active | Long cooldown; reduces HP by half per enemy present | +| Tier 2 | Breath | Active | Very long cooldown; resets all skill cooldowns except itself | +| Tier 2 | One-Sword | Active | Hits 1 enemy, high damage, long cooldown; insta-kill on crit | +| Final | Final Skill | Active | (TBD) | -#### 검사 — 패시브 트리 (2갈래) +#### Warrior — Passive Tree (2 branches) -**방어 라인**: 천 갑옷(피해 감소) → 사전 준비(전 스킬 쿨감) → 부러진 칼(HP 50% 이하시 초당 n 회복) → 단련(최대체력+공격력 n%) +**Defense line**: Cloth Armor (damage reduction) → Preparation (reduce all skill CDs) → Broken Blade (recover n HP/s below 50% HP) → Tempering (max HP + attack power n%) -**공격 라인**: 속돌(공속 상승) → 슬로우스타터(처치마다 공격력 증가, 면전 입장시 초기화) → 부러진 갑옷(HP 50% 이하시 피해 n% 회복) → 단련(공유 최종 노드) +**Offense line**: Quick Stone (attack speed up) → Slow Starter (attack power increases per kill, resets on entering dungeon) → Broken Armor (recover n% damage dealt below 50% HP) → Tempering (shared final node) -#### 궁수 — 롤토체스식 3택1 (3행) +#### Archer — TFT-style Pick 1 of 3 (3 rows) -매 행마다 후보 중 1개를 선택. +Choose 1 option per row. -| 행 | 스킬 목록 | -|----|-----------| -| 1행 | 정신집중(다음 공격 데미지 n배), 곡사(광역 낮은 데미지), 멀티샷(75% 데미지로 2발), 대형 화살(관통+슬로우 디버프), 육감(정예 몬스터 추가 피해) | -| 2행 | 발사(평타: 단일, 크리 20% 보정), 연속 사격(5초 크리화), 은화살(공격시 100%), 낙하탄(공격속도 n% 증가), 헤드샷(최대체력 n% 피해), 숙련(사거리 증가) | -| 3행 | 준통 사격(3마리 공격), 저격(단일 높은 데미지), 역경(크리율 n% 증가), 독탄화살(3발, 데미지 매우 높음), 영광(크리 피해 증가) | +| Row | Skills | +|-----|--------| +| Row 1 | Focus (next attack deals n× damage), Arc Shot (AoE, low damage), Multi-Shot (2 shots at 75% damage), Heavy Arrow (pierce + slow debuff), Sixth Sense (bonus damage to elite monsters) | +| Row 2 | Fire (basic: single, +20% crit), Rapid Fire (5s crit window), Silver Arrow (100% on attack), Falling Shot (+n% attack speed), Headshot (n% of max HP damage), Expertise (range increase) | +| Row 3 | Spread Shot (hits 3 enemies), Snipe (single, very high damage), Adversity (+n% crit rate), Poison Arrow (3 shots, very high damage), Glory (crit damage increase) | -#### 법사 — 속성별 선형 트리 +#### Mage — Linear Tree per Element -**불**: 불 원소(평타, 광역) → 화상(패시브: 매초 n데미지 상태이상) → 화염구(단일) → 불기둥(장판형) → [재(화상 추가피해) / 폭발 마법(범위 증가)] → 메테오(광역, 쿨 매우 김) → [마법 단련(액티브 데미지+) / 4도 화상(화상 중첩 가능)] +**Fire**: Fire Element (basic, AoE) → Burn (passive: n damage/s status) → Fireball (single) → Fire Pillar (ground AoE) → [Ash (bonus burn damage) / Explosion (AoE size up)] → Meteor (AoE, very long CD) → [Magic Mastery (active damage+) / 4th Degree Burn (burn stackable)] -**얼음**: 얼음 원소(평타, 단일, 느림) → 동상(패시브: 이속/공속 감소) → 빙결피(광역) → 얼음파편(단일, 높음) → [냉기강화(동상 효과 강화) / 얼음 갑옷(피격 시 10초 피해감소)] → 눈보라(광역, 쿨 매우 김) → [마법 단련 / 영하(동상 중첩 시 이동 불가)] +**Ice**: Ice Element (basic, single, slow) → Frostbite (passive: reduces move/attack speed) → Ice Shatter (AoE) → Ice Shard (single, high damage) → [Chill Boost (stronger frostbite) / Ice Armor (damage reduction for 10s on hit)] → Blizzard (AoE, very long CD) → [Magic Mastery / Sub-Zero (frostbite stacks → immobilize)] -**바람**: 바람 원소(평타, 2명, 빠름) → 칼바람(패시브: 공격마다 상처 1 중첩, 5중첩 시 데미지) → 바람 칼날(15초 공속 상승, 3명 공격) → 토네이도(광역, 에이본 효과) → [가속(적 공격 일정확률 무시) / 순풍(기본공격 추가 공격 확률)] → 돌풍(광역, 쿨 매우 김) → [마법 단련 / 나락(칼바람 발동 시 모든 스킬 쿨감)] +**Wind**: Wind Element (basic, hits 2, fast) → Gale (passive: stack 1 wound per attack, deal damage at 5 stacks) → Wind Blade (15s attack speed buff, hits 3) → Tornado (AoE, knockback effect) → [Accelerate (chance to ignore enemy attack) / Tailwind (chance for extra basic attack)] → Gust (AoE, very long CD) → [Magic Mastery / Abyss (Gale proc resets all skill CDs)] -### 게임 루프 +### Game Loop ``` -던전 진행 → Gold/XP 획득 → 레벨업+SP 획득 → 스킬 트리 강화 → 무기 획득/강화 → 더 높은 던전 도전 -``` +Clear dungeon → Earn Gold/XP → Level up + gain SP → Upgrade skill tree → Acquire/upgrade weapons → Challenge higher dungeons +``` \ No newline at end of file From 5cac0d536334eece0138c8f943d2021a8c05355f Mon Sep 17 00:00:00 2001 From: Lee Siwoo Date: Mon, 6 Apr 2026 11:55:57 +0900 Subject: [PATCH 10/11] =?UTF-8?q?fix:=20=EC=8A=A4=ED=8F=B0=20=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #18 --- Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs b/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs index dc7cb9f..82738eb 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/WaveController.cs @@ -21,6 +21,12 @@ public class WaveController : MonoBehaviour /// public List SpawnWave(WaveData waveData, Transform[] spawnPoints) { + if (spawnPoints == null || spawnPoints.Length == 0) + { + Debug.LogError("[WaveController] 스폰 포인트가 설정되지 않았습니다."); + return null; + } + _activeEnemies.Clear(); int spawnIndex = 0; From 8aad5979212ff4fb7599e95b769dc68a6cb8f224 Mon Sep 17 00:00:00 2001 From: Lee Siwoo Date: Mon, 6 Apr 2026 11:56:48 +0900 Subject: [PATCH 11/11] =?UTF-8?q?fix:=20hasAttackCollider=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=8D=BC=ED=8B=B0=20=ED=98=95=ED=83=9C=20=EB=B3=80?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #18 --- Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs index 5951d5d..4ce106f 100644 --- a/Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs +++ b/Fantasy-Grower/Assets/Scripts/Battle/Enemy/EnemyAI.cs @@ -12,11 +12,7 @@ public class EnemyAI : MonoBehaviour private Player player; private Coroutine attackCoroutine; - private bool hasAttackCollider - { - get { return childCollider != null; } - set { hasAttackCollider = value; } - } + private bool hasAttackCollider => childCollider != null; private AttackCollider childCollider;