using System; using System.Collections.Generic; using UnityEngine; namespace TKS { /// /// 밸런스·스테이지 데이터의 단일 출처(SOT). 코드에는 수치를 두지 않고 이 자산만 읽는다. /// 기본값은 에디터 셋업(ProjectSetup)이 최초 1회 시드한다. /// [CreateAssetMenu(menuName = "TKS/Balance Config", fileName = "BalanceConfig")] public class BalanceConfig : ScriptableObject { [Serializable] public class UnitStats { public UnitType type; public int maxHp; public int atk; public int def; [Tooltip("슬링샷으로 지정할 수 있는 최대 거리(월드 단위)")] public float slingMaxDistance; [Tooltip("창병 대기(견제) 반경 · 궁병은 쓰지 않음(사거리 = slingMaxDistance)")] public float attackRange; public float radius; } [Serializable] public class HeroDef { public string name; public UnitType type; public float hpMult = 1f; public float atkMult = 1f; [Tooltip("Resources/Units// 아트 폴더 이름 · 비면 도형으로 표시")] public string art = ""; } [Serializable] public class EnemySpawn { public string name; public UnitType type; public Vector2 position; public float hpMult = 1f; public float atkMult = 1f; public bool isBoss; [Tooltip("Resources/Units// 아트 폴더 이름 · 비면 도형으로 표시")] public string art = ""; } [Serializable] public class StageDef { public string title; public string subtitle; [Range(0f, 1f)] public float aiAccuracy = 0.6f; [Tooltip("이 거리 안에 아군이 들어와야 적이 움직인다(넓은 전장에서 부대별 교전)")] public float aiAggroRange = 11f; public List enemies = new List(); } [Header("병종 기본 능력치")] public UnitStats[] unitStats = new UnitStats[0]; [Header("상성 배율 (행 = 공격자, 열 = 피격자 · 순서 보병·기병·궁병·창병)")] public float[] matchup = new float[UnitTypeInfo.Count * UnitTypeInfo.Count]; [Header("전투 공식")] [Tooltip("피해 = 공격 × 상성 × (크리 배율) − 방어 × defenseFactor")] public float defenseFactor = 0.5f; [Tooltip("근접 공격을 받은 쪽이 되돌려주는 반격 피해 비율")] public float counterRatio = 0.5f; public float critMultiplier = 2f; [Tooltip("피해 난수 폭 (0.1 = ±10 %)")] public float damageVariance = 0.1f; [Tooltip("창병 대기 상태에서 움직이는 적에게 주는 피해 비율")] public float overwatchDamageRatio = 1f; public float bossHpMult = 1.5f; public float bossRadiusMult = 1.25f; [Tooltip("기병 관통 시 적을 옆으로 밀어내는 거리")] public float cavalryKnockback = 0.35f; [Header("궁병 규칙 (PD 2026-11-04 · #1080)")] [Tooltip("궁병 사거리 = 슬링샷 최대 거리(얇은 원 · 안의 적은 제자리 사격). 그 밖 ~ 사거리 × (1 + 비율) 안(붉은 도넛)의 적은 사거리에 들어올 만큼만 다가가 사격 · 더 멀면 목표점까지 이동")] public float archerRangeRatio = 0.2f; [Tooltip("궁병이 근접 유닛(보병·기병·창병)과 근접 전투(돌진·반격·관통·견제·밀려 닿은 공격)할 때 주는 피해 배율 · 원거리 사격은 1")] public float archerMeleeDealtRatio = 0.5f; [Tooltip("궁병이 근접 유닛에게 근접으로 맞을 때 받는 피해 배율(상성 배율과 곱)")] public float archerMeleeTakenRatio = 1.5f; [Header("충돌 (PD 2026-11-04 · #1080)")] [Tooltip("보병 알까기: 부딪힌 유닛(아군·적군)이 밀려나는 거리 = 충돌 시 남은 슬링샷 거리 × 비율 · 창병은 비율 없이 남은 거리만큼 밀며 함께 전진")] public float collisionPushRatio = 1f; [Header("표시")] [Tooltip("유닛 아트(스프라이트) 세로 크기 · 월드 단위 · 장수는 반지름 배율만큼 커진다")] public float unitArtHeight = 1.5f; [Tooltip("Resources/Board/<이름> 전장 배경 스프라이트(원화 세션 납품 · 5:8) · 비면 도형·격자 바닥")] public string boardArt = "battlefield_01"; [Header("조작")] [Tooltip("이보다 짧게 당기면 취소")] public float minDragDistance = 0.35f; [Tooltip("슬링샷 최종 목표점이 적 원 안에 들어왔다고 보는 여유")] public float finalHitTolerance = 0.15f; public float moveSpeed = 9f; [Header("전장·카메라 (화면보다 넓은 전장을 스크롤)")] public Vector2 boardSize = new Vector2(20f, 32f); [Tooltip("화면 세로에 보이는 월드 높이")] public float cameraViewHeight = 18f; public float cameraMinViewHeight = 10f; public float cameraMaxViewHeight = 36f; public Vector2[] playerSpawnPoints = new Vector2[0]; [Header("아군 영웅 (최대 5)")] public HeroDef[] heroes = new HeroDef[0]; [Header("스테이지")] public StageDef[] stages = new StageDef[0]; public UnitStats GetStats(UnitType t) { for (int i = 0; i < unitStats.Length; i++) { if (unitStats[i].type == t) return unitStats[i]; } throw new InvalidOperationException("BalanceConfig 에 " + t + " 능력치가 없습니다."); } public float Matchup(UnitType attacker, UnitType defender) { int idx = (int)attacker * UnitTypeInfo.Count + (int)defender; if (matchup == null || idx >= matchup.Length) return 1f; return matchup[idx]; } public void SetMatchup(UnitType attacker, UnitType defender, float value) { if (matchup == null || matchup.Length != UnitTypeInfo.Count * UnitTypeInfo.Count) matchup = new float[UnitTypeInfo.Count * UnitTypeInfo.Count]; matchup[(int)attacker * UnitTypeInfo.Count + (int)defender] = value; } } }