ThreeKingdomsSlingshot/Assets/Scripts/Runtime/BalanceConfig.cs

147 lines
6.5 KiB
C#
Raw Normal View History

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;
전투 로직 #1080 — 알까기 충돌(보병) · 관통·아군 직전 정지(기병) · 밀며 함께 전진(창병) · 밀린 아군 공격 기회 · 궁병 제자리 사격(사거리 = 슬링샷)+접근 20 % · 궁수 근접 피해 50 %/피격 +50 % · 원화 1차·2차 반영(전장 배경 + 황건적 5 + 동탁군 4) · 테스트 22/22 - ActionResolver 전면 재작성: InfantryRoutine(접촉점 정지 · Push 접촉 방향 × collisionPushRatio · 적이면 공격+반격 뒤 밀림) · SpearRoutine(앞 유닛과 같은 속도로 함께 전진 · 접촉 간격 유지) · CavalryRoutine(벽만 막음 · StopBeforeUnclearedAllies) · ArcherRoutine(AttackRange = 슬링 · ArcherMoveAllow = 슬링 × archerRangeRatio) · PushedAllyAttack(규칙 ⑤ · ActionOutcome.Pushed/Notes) - Combat: Damage(melee) 궁병 근접 배율(근접 유닛 상대만) · FirstUnitOnPath · TouchingOpponent · FreeTravel ignore - BalanceConfig/Defaults: archerMeleeDealtRatio 0.5 · archerMeleeTakenRatio 1.5 · collisionPushRatio 1 · boardArt battlefield_01 (자산 재시드) - Board: Resources/Board/<boardArt> 원화 배경(격자·진영 띠 생략) · UnitArtImporter Board 규칙 · GameBootstrap - Resources/Units: hgj_infantry·spear·cavalry·archer·boss_zhangjiao · dz_infantry·spear·cavalry·archer (원화 세션 납품 정지 그림 · rig_frames_export static) · Resources/Board/battlefield_01.png - BattleManager Describe 「밀어냄 · 밀린 B → 적 공격」 · BattleHUD 궁병 줄 · 테스트 22종(신규·개정 7 + 원화 확인) · CLAUDE.md · README Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 06:56:02 +00:00
[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/<art>/ 아트 폴더 이름 · 비면 도형으로 표시")]
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/<art>/ 아트 폴더 이름 · 비면 도형으로 표시")]
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<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;
전투 로직 #1080 — 알까기 충돌(보병) · 관통·아군 직전 정지(기병) · 밀며 함께 전진(창병) · 밀린 아군 공격 기회 · 궁병 제자리 사격(사거리 = 슬링샷)+접근 20 % · 궁수 근접 피해 50 %/피격 +50 % · 원화 1차·2차 반영(전장 배경 + 황건적 5 + 동탁군 4) · 테스트 22/22 - ActionResolver 전면 재작성: InfantryRoutine(접촉점 정지 · Push 접촉 방향 × collisionPushRatio · 적이면 공격+반격 뒤 밀림) · SpearRoutine(앞 유닛과 같은 속도로 함께 전진 · 접촉 간격 유지) · CavalryRoutine(벽만 막음 · StopBeforeUnclearedAllies) · ArcherRoutine(AttackRange = 슬링 · ArcherMoveAllow = 슬링 × archerRangeRatio) · PushedAllyAttack(규칙 ⑤ · ActionOutcome.Pushed/Notes) - Combat: Damage(melee) 궁병 근접 배율(근접 유닛 상대만) · FirstUnitOnPath · TouchingOpponent · FreeTravel ignore - BalanceConfig/Defaults: archerMeleeDealtRatio 0.5 · archerMeleeTakenRatio 1.5 · collisionPushRatio 1 · boardArt battlefield_01 (자산 재시드) - Board: Resources/Board/<boardArt> 원화 배경(격자·진영 띠 생략) · UnitArtImporter Board 규칙 · GameBootstrap - Resources/Units: hgj_infantry·spear·cavalry·archer·boss_zhangjiao · dz_infantry·spear·cavalry·archer (원화 세션 납품 정지 그림 · rig_frames_export static) · Resources/Board/battlefield_01.png - BattleManager Describe 「밀어냄 · 밀린 B → 적 공격」 · BattleHUD 궁병 줄 · 테스트 22종(신규·개정 7 + 원화 확인) · CLAUDE.md · README Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 06:56:02 +00:00
[Header("궁병 규칙 (PD 2026-11-04 · #1080)")]
[Tooltip("궁병 사거리 = 슬링샷 최대 거리(얇은 원 · 안의 적은 제자리 사격). 그 밖 ~ 사거리 × (1 + 비율) 안(붉은 도넛)의 적은 사거리에 들어올 만큼만 다가가 사격 · 더 멀면 목표점까지 이동")]
public float archerRangeRatio = 0.2f;
전투 로직 #1080 — 알까기 충돌(보병) · 관통·아군 직전 정지(기병) · 밀며 함께 전진(창병) · 밀린 아군 공격 기회 · 궁병 제자리 사격(사거리 = 슬링샷)+접근 20 % · 궁수 근접 피해 50 %/피격 +50 % · 원화 1차·2차 반영(전장 배경 + 황건적 5 + 동탁군 4) · 테스트 22/22 - ActionResolver 전면 재작성: InfantryRoutine(접촉점 정지 · Push 접촉 방향 × collisionPushRatio · 적이면 공격+반격 뒤 밀림) · SpearRoutine(앞 유닛과 같은 속도로 함께 전진 · 접촉 간격 유지) · CavalryRoutine(벽만 막음 · StopBeforeUnclearedAllies) · ArcherRoutine(AttackRange = 슬링 · ArcherMoveAllow = 슬링 × archerRangeRatio) · PushedAllyAttack(규칙 ⑤ · ActionOutcome.Pushed/Notes) - Combat: Damage(melee) 궁병 근접 배율(근접 유닛 상대만) · FirstUnitOnPath · TouchingOpponent · FreeTravel ignore - BalanceConfig/Defaults: archerMeleeDealtRatio 0.5 · archerMeleeTakenRatio 1.5 · collisionPushRatio 1 · boardArt battlefield_01 (자산 재시드) - Board: Resources/Board/<boardArt> 원화 배경(격자·진영 띠 생략) · UnitArtImporter Board 규칙 · GameBootstrap - Resources/Units: hgj_infantry·spear·cavalry·archer·boss_zhangjiao · dz_infantry·spear·cavalry·archer (원화 세션 납품 정지 그림 · rig_frames_export static) · Resources/Board/battlefield_01.png - BattleManager Describe 「밀어냄 · 밀린 B → 적 공격」 · BattleHUD 궁병 줄 · 테스트 22종(신규·개정 7 + 원화 확인) · CLAUDE.md · README Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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;
[Header("표시")]
[Tooltip("유닛 아트(스프라이트) 세로 크기 · 월드 단위 · 장수는 반지름 배율만큼 커진다")]
public float unitArtHeight = 1.5f;
전투 로직 #1080 — 알까기 충돌(보병) · 관통·아군 직전 정지(기병) · 밀며 함께 전진(창병) · 밀린 아군 공격 기회 · 궁병 제자리 사격(사거리 = 슬링샷)+접근 20 % · 궁수 근접 피해 50 %/피격 +50 % · 원화 1차·2차 반영(전장 배경 + 황건적 5 + 동탁군 4) · 테스트 22/22 - ActionResolver 전면 재작성: InfantryRoutine(접촉점 정지 · Push 접촉 방향 × collisionPushRatio · 적이면 공격+반격 뒤 밀림) · SpearRoutine(앞 유닛과 같은 속도로 함께 전진 · 접촉 간격 유지) · CavalryRoutine(벽만 막음 · StopBeforeUnclearedAllies) · ArcherRoutine(AttackRange = 슬링 · ArcherMoveAllow = 슬링 × archerRangeRatio) · PushedAllyAttack(규칙 ⑤ · ActionOutcome.Pushed/Notes) - Combat: Damage(melee) 궁병 근접 배율(근접 유닛 상대만) · FirstUnitOnPath · TouchingOpponent · FreeTravel ignore - BalanceConfig/Defaults: archerMeleeDealtRatio 0.5 · archerMeleeTakenRatio 1.5 · collisionPushRatio 1 · boardArt battlefield_01 (자산 재시드) - Board: Resources/Board/<boardArt> 원화 배경(격자·진영 띠 생략) · UnitArtImporter Board 규칙 · GameBootstrap - Resources/Units: hgj_infantry·spear·cavalry·archer·boss_zhangjiao · dz_infantry·spear·cavalry·archer (원화 세션 납품 정지 그림 · rig_frames_export static) · Resources/Board/battlefield_01.png - BattleManager Describe 「밀어냄 · 밀린 B → 적 공격」 · BattleHUD 궁병 줄 · 테스트 22종(신규·개정 7 + 원화 확인) · CLAUDE.md · README Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 06:56:02 +00:00
[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;
}
}
}