2026-11-03 19:10:10 +00:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
|
|
namespace TKS
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 밸런스·스테이지 데이터의 단일 출처(SOT). 코드에는 수치를 두지 않고 이 자산만 읽는다.
|
|
|
|
|
|
/// 기본값은 에디터 셋업(ProjectSetup)이 최초 1회 시드한다.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[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;
|
2026-11-04 06:56:02 +00:00
|
|
|
|
[Tooltip("창병 대기(견제) 반경 · 궁병은 쓰지 않음(사거리 = slingMaxDistance)")]
|
2026-11-03 19:10:10 +00:00
|
|
|
|
public float attackRange;
|
|
|
|
|
|
public float radius;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
|
public class HeroDef
|
|
|
|
|
|
{
|
|
|
|
|
|
public string name;
|
|
|
|
|
|
public UnitType type;
|
|
|
|
|
|
public float hpMult = 1f;
|
|
|
|
|
|
public float atkMult = 1f;
|
2026-11-04 04:05:46 +00:00
|
|
|
|
[Tooltip("Resources/Units/<art>/ 아트 폴더 이름 · 비면 도형으로 표시")]
|
|
|
|
|
|
public string art = "";
|
2026-11-03 19:10:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
|
public class EnemySpawn
|
|
|
|
|
|
{
|
|
|
|
|
|
public string name;
|
|
|
|
|
|
public UnitType type;
|
|
|
|
|
|
public Vector2 position;
|
|
|
|
|
|
public float hpMult = 1f;
|
|
|
|
|
|
public float atkMult = 1f;
|
|
|
|
|
|
public bool isBoss;
|
2026-11-04 04:05:46 +00:00
|
|
|
|
[Tooltip("Resources/Units/<art>/ 아트 폴더 이름 · 비면 도형으로 표시")]
|
|
|
|
|
|
public string art = "";
|
2026-11-03 19:10:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
|
public class StageDef
|
|
|
|
|
|
{
|
|
|
|
|
|
public string title;
|
|
|
|
|
|
public string subtitle;
|
|
|
|
|
|
[Range(0f, 1f)] public float aiAccuracy = 0.6f;
|
2026-11-03 19:29:16 +00:00
|
|
|
|
[Tooltip("이 거리 안에 아군이 들어와야 적이 움직인다(넓은 전장에서 부대별 교전)")]
|
|
|
|
|
|
public float aiAggroRange = 11f;
|
2026-11-03 19:10:10 +00:00
|
|
|
|
public List<EnemySpawn> enemies = new List<EnemySpawn>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[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;
|
|
|
|
|
|
|
2026-11-04 06:56:02 +00:00
|
|
|
|
[Header("궁병 규칙 (PD 2026-11-04 · #1080)")]
|
|
|
|
|
|
[Tooltip("궁병 사거리 = 슬링샷 최대 거리(얇은 원 · 안의 적은 제자리 사격). 그 밖 ~ 사거리 × (1 + 비율) 안(붉은 도넛)의 적은 사거리에 들어올 만큼만 다가가 사격 · 더 멀면 목표점까지 이동")]
|
2026-11-04 04:05:46 +00:00
|
|
|
|
public float archerRangeRatio = 0.2f;
|
2026-11-04 06:56:02 +00:00
|
|
|
|
[Tooltip("궁병이 근접 유닛(보병·기병·창병)과 근접 전투(돌진·반격·관통·견제·밀려 닿은 공격)할 때 주는 피해 배율 · 원거리 사격은 1")]
|
|
|
|
|
|
public float archerMeleeDealtRatio = 0.5f;
|
|
|
|
|
|
[Tooltip("궁병이 근접 유닛에게 근접으로 맞을 때 받는 피해 배율(상성 배율과 곱)")]
|
|
|
|
|
|
public float archerMeleeTakenRatio = 1.5f;
|
|
|
|
|
|
|
|
|
|
|
|
[Header("충돌 (PD 2026-11-04 · #1080)")]
|
|
|
|
|
|
[Tooltip("보병 알까기: 부딪힌 유닛(아군·적군)이 밀려나는 거리 = 충돌 시 남은 슬링샷 거리 × 비율 · 창병은 비율 없이 남은 거리만큼 밀며 함께 전진")]
|
|
|
|
|
|
public float collisionPushRatio = 1f;
|
2026-11-04 08:32:07 +00:00
|
|
|
|
[Tooltip("보병 알까기: 부딪힌 보병 자신이 반대 방향으로 튕겨나는 거리 = 충돌 시 남은 슬링샷 거리 × 비율 (PD #1081 「자신도 튕겨나야」)")]
|
|
|
|
|
|
public float collisionSelfBounceRatio = 0.5f;
|
2026-11-04 04:05:46 +00:00
|
|
|
|
|
|
|
|
|
|
[Header("표시")]
|
|
|
|
|
|
[Tooltip("유닛 아트(스프라이트) 세로 크기 · 월드 단위 · 장수는 반지름 배율만큼 커진다")]
|
|
|
|
|
|
public float unitArtHeight = 1.5f;
|
2026-11-04 06:56:02 +00:00
|
|
|
|
[Tooltip("Resources/Board/<이름> 전장 배경 스프라이트(원화 세션 납품 · 5:8) · 비면 도형·격자 바닥")]
|
|
|
|
|
|
public string boardArt = "battlefield_01";
|
2026-11-04 04:05:46 +00:00
|
|
|
|
|
2026-11-03 19:10:10 +00:00
|
|
|
|
[Header("조작")]
|
|
|
|
|
|
[Tooltip("이보다 짧게 당기면 취소")]
|
|
|
|
|
|
public float minDragDistance = 0.35f;
|
|
|
|
|
|
[Tooltip("슬링샷 최종 목표점이 적 원 안에 들어왔다고 보는 여유")]
|
|
|
|
|
|
public float finalHitTolerance = 0.15f;
|
|
|
|
|
|
public float moveSpeed = 9f;
|
|
|
|
|
|
|
2026-11-03 19:29:16 +00:00
|
|
|
|
[Header("전장·카메라 (화면보다 넓은 전장을 스크롤)")]
|
|
|
|
|
|
public Vector2 boardSize = new Vector2(20f, 32f);
|
|
|
|
|
|
[Tooltip("화면 세로에 보이는 월드 높이")]
|
|
|
|
|
|
public float cameraViewHeight = 18f;
|
|
|
|
|
|
public float cameraMinViewHeight = 10f;
|
|
|
|
|
|
public float cameraMaxViewHeight = 36f;
|
2026-11-03 19:10:10 +00:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|