근접 3병종 특징 재정의(#1111 · PD 2026-11-10) — 창병 견제 제거 · 크게 밀치고 적게 밀림 / 기병 복귀 제거 · 관통 이동 / 보병 = 밀치고 튕기며 연쇄 공격
- 창병: BalanceConfig.spearPushRatio 1.5 신설(보병 1.0) · spearRecoilRatio 0.5 → 0.2 · 목표점 명중 → 대기(견제) 삭제 · HUD 견제 반경·대기 중 문구 삭제 - 기병: 목표점 명중 제자리 복귀 삭제(위진소요 추가 관통 유지) · PreviewCavalryReturn · 조준 복귀 영역(ShowReturnZones) 삭제 · 결과 문구 정리 - 적 AI: ai_weights cav_return · cav_stranded 삭제(기획 tables 원본과 같음) · 관통 점수 = 적중 수 × 10 - 보병: #1098 알까기 + 연쇄 그대로(목표점 명중 추가 이동 1회 유지 = PD 확인 대상) - 튜토리얼 1-3 이름 「창병 밀치기」 · 팁 3행 새 정의 · CLAUDE.md 충돌 규칙 · 근접 3병종 줄 - 견제·복귀 스킬 15개 = 발동 안 함 · 재설계 PD 결정 대기 → ApplyOverwatch 등 내부는 남겨 둠 · SkillTests.Spear_OverwatchSkills_* [Ignore] - 확인(고친 부분만): 재컴파일·재임포트 error 0 · 테스트 13 통과 · 1 건너뜀 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d48b33728a
commit
b383e355c8
|
|
@ -2,13 +2,11 @@
|
||||||
dmg_player,1.0,플레이어 유닛에게 준 피해 1당
|
dmg_player,1.0,플레이어 유닛에게 준 피해 1당
|
||||||
kill_base,50,처치 1기당 기본
|
kill_base,50,처치 1기당 기본
|
||||||
kill_hp_ratio,30,처치한 유닛의 행동 전 HP 비율 × 값
|
kill_hp_ratio,30,처치한 유닛의 행동 전 HP 비율 × 값
|
||||||
dmg_own,-0.8,자기·아군(적 측) 피해 1당(반격·오사·견제·기믹 포함)
|
dmg_own,-0.8,자기·아군(적 측) 피해 1당(반격·오사·기믹 포함)
|
||||||
gimmick_dest_bonus,30,밀림·튕김 목적지 기믹(함정·독늪·불·강·폭약 반경)에 플레이어 유닛이 들어가면 피해 환산 + 값(자기 편이면 −)
|
gimmick_dest_bonus,30,밀림·튕김 목적지 기믹(함정·독늪·불·강·폭약 반경)에 플레이어 유닛이 들어가면 피해 환산 + 값(자기 편이면 −)
|
||||||
chain,20,보병 연쇄 교환 1건당
|
chain,20,보병 연쇄 교환 1건당
|
||||||
support_reach,15,창병이 민 아군(적 측)이 다음 턴 닿을 수 있는 플레이어 유닛 수 × 값
|
support_reach,15,창병이 민 아군(적 측)이 다음 턴 닿을 수 있는 플레이어 유닛 수 × 값
|
||||||
pierce_hit,10,기병 관통 적중 1기당
|
pierce_hit,10,기병 관통 적중 1기당
|
||||||
cav_return,15,기병 복귀 성공
|
|
||||||
cav_stranded,-20,기병 관통 후 복귀 실패(적진 잔류)
|
|
||||||
archer_cover,15,궁병 종료 위치가 목책 뒤(플레이어 근접 유닛과의 직선에 목책)
|
archer_cover,15,궁병 종료 위치가 목책 뒤(플레이어 근접 유닛과의 직선에 목책)
|
||||||
high_ground,10,궁·책 종료 위치가 고지
|
high_ground,10,궁·책 종료 위치가 고지
|
||||||
cav_line,-15,궁·책 종료 위치가 플레이어 기병 관통선 위
|
cav_line,-15,궁·책 종료 위치가 플레이어 기병 관통선 위
|
||||||
|
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
stage,tip_ko
|
stage,tip_ko
|
||||||
1-1,보병은 튕겨내고 창병은 밀쳐내고 물러난다 · 기병은 꿰뚫고 돌아온다 · 궁병은 목책 너머를 쏜다 · 책사는 뭉친 적을 태우고 덤불을 치운다
|
1-1,보병은 밀치고 튕겨나며 다른 적까지 친다 · 창병은 크게 밀쳐내고 조금만 밀린다 · 기병은 적을 꿰뚫고 지나간다 · 궁병은 목책 너머를 쏜다 · 책사는 뭉친 적을 태우고 덤불을 치운다
|
||||||
1-2,보병은 튕겨내고 창병은 밀쳐내고 물러난다 · 기병은 꿰뚫고 돌아온다 · 궁병은 목책 너머를 쏜다 · 책사는 뭉친 적을 태우고 덤불을 치운다
|
1-2,보병은 밀치고 튕겨나며 다른 적까지 친다 · 창병은 크게 밀쳐내고 조금만 밀린다 · 기병은 적을 꿰뚫고 지나간다 · 궁병은 목책 너머를 쏜다 · 책사는 뭉친 적을 태우고 덤불을 치운다
|
||||||
1-3,보병은 튕겨내고 창병은 밀쳐내고 물러난다 · 기병은 꿰뚫고 돌아온다 · 궁병은 목책 너머를 쏜다 · 책사는 뭉친 적을 태우고 덤불을 치운다
|
1-3,보병은 밀치고 튕겨나며 다른 적까지 친다 · 창병은 크게 밀쳐내고 조금만 밀린다 · 기병은 적을 꿰뚫고 지나간다 · 궁병은 목책 너머를 쏜다 · 책사는 뭉친 적을 태우고 덤불을 치운다
|
||||||
|
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
chapter,stage,title,type,place,faction,infantry,spear,cavalry,archer,strategist,generals,bg_theme,note
|
chapter,stage,title,type,place,faction,infantry,spear,cavalry,archer,strategist,generals,bg_theme,note
|
||||||
1,1,탁현 외곽 — 황건 척후,normal,탁현,hgj,3,0,0,0,0,,마을 어귀,튜토리얼(조준·이동)
|
1,1,탁현 외곽 — 황건 척후,normal,탁현,hgj,3,0,0,0,0,,마을 어귀,튜토리얼(조준·이동)
|
||||||
1,2,누상촌 약탈대,normal,누상촌,hgj,3,1,0,0,0,,마을,튜토리얼(기병 관통)
|
1,2,누상촌 약탈대,normal,누상촌,hgj,3,1,0,0,0,,마을,튜토리얼(기병 관통)
|
||||||
1,3,대흥산 길목,normal,대흥산,hgj,3,2,0,0,0,,산길,튜토리얼(창병 견제)
|
1,3,대흥산 길목,normal,대흥산,hgj,3,2,0,0,0,,산길,튜토리얼(창병 밀치기)
|
||||||
1,4,대흥산 — 등무,elite,대흥산,hgj,3,2,0,1,0,hgj_boss_dengmao:spear:false,산기슭,
|
1,4,대흥산 — 등무,elite,대흥산,hgj,3,2,0,1,0,hgj_boss_dengmao:spear:false,산기슭,
|
||||||
1,5,대흥산 전투 — 정원지,boss,대흥산,hgj,4,2,0,1,0,hgj_boss_chengyuanzhi:infantry:true,산기슭 평지,
|
1,5,대흥산 전투 — 정원지,boss,대흥산,hgj,4,2,0,1,0,hgj_boss_chengyuanzhi:infantry:true,산기슭 평지,
|
||||||
1,6,청주 포위망,normal,청주,hgj,4,2,0,2,0,,성 밖 들판,
|
1,6,청주 포위망,normal,청주,hgj,4,2,0,2,0,,성 밖 들판,
|
||||||
|
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ MonoBehaviour:
|
||||||
\xD7 \uAC12"
|
\xD7 \uAC12"
|
||||||
- key: dmg_own
|
- key: dmg_own
|
||||||
value: -0.8
|
value: -0.8
|
||||||
desc: "\uC790\uAE30\xB7\uC544\uAD70(\uC801 \uCE21) \uD53C\uD574 1\uB2F9(\uBC18\uACA9\xB7\uC624\uC0AC\xB7\uACAC\uC81C\xB7\uAE30\uBBF9
|
desc: "\uC790\uAE30\xB7\uC544\uAD70(\uC801 \uCE21) \uD53C\uD574 1\uB2F9(\uBC18\uACA9\xB7\uC624\uC0AC\xB7\uAE30\uBBF9
|
||||||
\uD3EC\uD568)"
|
\uD3EC\uD568)"
|
||||||
- key: gimmick_dest_bonus
|
- key: gimmick_dest_bonus
|
||||||
value: 30
|
value: 30
|
||||||
|
|
@ -44,13 +44,6 @@ MonoBehaviour:
|
||||||
- key: pierce_hit
|
- key: pierce_hit
|
||||||
value: 10
|
value: 10
|
||||||
desc: "\uAE30\uBCD1 \uAD00\uD1B5 \uC801\uC911 1\uAE30\uB2F9"
|
desc: "\uAE30\uBCD1 \uAD00\uD1B5 \uC801\uC911 1\uAE30\uB2F9"
|
||||||
- key: cav_return
|
|
||||||
value: 15
|
|
||||||
desc: "\uAE30\uBCD1 \uBCF5\uADC0 \uC131\uACF5"
|
|
||||||
- key: cav_stranded
|
|
||||||
value: -20
|
|
||||||
desc: "\uAE30\uBCD1 \uAD00\uD1B5 \uD6C4 \uBCF5\uADC0 \uC2E4\uD328(\uC801\uC9C4
|
|
||||||
\uC794\uB958)"
|
|
||||||
- key: archer_cover
|
- key: archer_cover
|
||||||
value: 15
|
value: 15
|
||||||
desc: "\uAD81\uBCD1 \uC885\uB8CC \uC704\uCE58\uAC00 \uBAA9\uCC45 \uB4A4(\uD50C\uB808\uC774\uC5B4
|
desc: "\uAD81\uBCD1 \uC885\uB8CC \uC704\uCE58\uAC00 \uBAA9\uCC45 \uB4A4(\uD50C\uB808\uC774\uC5B4
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,8 @@ MonoBehaviour:
|
||||||
strategistAllyDamageRatio: 1
|
strategistAllyDamageRatio: 1
|
||||||
collisionPushRatio: 1
|
collisionPushRatio: 1
|
||||||
collisionSelfBounceRatio: 0.5
|
collisionSelfBounceRatio: 0.5
|
||||||
|
spearPushRatio: 1.5
|
||||||
|
spearRecoilRatio: 0.2
|
||||||
unitArtHeight: 1.5
|
unitArtHeight: 1.5
|
||||||
mountedArtMaxTop: 1.3
|
mountedArtMaxTop: 1.3
|
||||||
boardArt: battlefield_01
|
boardArt: battlefield_01
|
||||||
|
|
@ -290,7 +292,7 @@ MonoBehaviour:
|
||||||
place: "\uB300\uD765\uC0B0"
|
place: "\uB300\uD765\uC0B0"
|
||||||
faction: hgj
|
faction: hgj
|
||||||
bgTheme: "\uC0B0\uAE38"
|
bgTheme: "\uC0B0\uAE38"
|
||||||
note: "\uD29C\uD1A0\uB9AC\uC5BC(\uCC3D\uBCD1 \uACAC\uC81C)"
|
note: "\uD29C\uD1A0\uB9AC\uC5BC(\uCC3D\uBCD1 \uBC00\uCE58\uAE30)"
|
||||||
- title: "\uB300\uD765\uC0B0 \u2014 \uB4F1\uBB34"
|
- title: "\uB300\uD765\uC0B0 \u2014 \uB4F1\uBB34"
|
||||||
subtitle: "184\uB144"
|
subtitle: "184\uB144"
|
||||||
aiAccuracy: 0.5
|
aiAccuracy: 0.5
|
||||||
|
|
|
||||||
|
|
@ -1044,7 +1044,7 @@
|
||||||
{
|
{
|
||||||
"stage": 3,
|
"stage": 3,
|
||||||
"id": "1-3",
|
"id": "1-3",
|
||||||
"type": "튜토리얼(창병 견제)",
|
"type": "튜토리얼(창병 밀치기)",
|
||||||
"boss": false,
|
"boss": false,
|
||||||
"place": "대흥산",
|
"place": "대흥산",
|
||||||
"title": "대흥산 길목",
|
"title": "대흥산 길목",
|
||||||
|
|
|
||||||
|
|
@ -7752,21 +7752,27 @@ MonoBehaviour:
|
||||||
- stage: 1-1
|
- stage: 1-1
|
||||||
chapter: 1
|
chapter: 1
|
||||||
stageNo: 1
|
stageNo: 1
|
||||||
text: "\uBCF4\uBCD1\uC740 \uD295\uACA8\uB0B4\uACE0 \uCC3D\uBCD1\uC740 \uBC00\uCCD0\uB0B4\uACE0
|
text: "\uBCF4\uBCD1\uC740 \uBC00\uCE58\uACE0 \uD295\uACA8\uB098\uBA70 \uB2E4\uB978
|
||||||
\uBB3C\uB7EC\uB09C\uB2E4 \xB7 \uAE30\uBCD1\uC740 \uAFF0\uB6AB\uACE0 \uB3CC\uC544\uC628\uB2E4
|
\uC801\uAE4C\uC9C0 \uCE5C\uB2E4 \xB7 \uCC3D\uBCD1\uC740 \uD06C\uAC8C \uBC00\uCCD0\uB0B4\uACE0
|
||||||
\xB7 \uAD81\uBCD1\uC740 \uBAA9\uCC45 \uB108\uBA38\uB97C \uC3DC\uB2E4 \xB7 \uCC45\uC0AC\uB294
|
\uC870\uAE08\uB9CC \uBC00\uB9B0\uB2E4 \xB7 \uAE30\uBCD1\uC740 \uC801\uC744
|
||||||
\uBB49\uCE5C \uC801\uC744 \uD0DC\uC6B0\uACE0 \uB364\uBD88\uC744 \uCE58\uC6B4\uB2E4"
|
\uAFF0\uB6AB\uACE0 \uC9C0\uB098\uAC04\uB2E4 \xB7 \uAD81\uBCD1\uC740 \uBAA9\uCC45
|
||||||
|
\uB108\uBA38\uB97C \uC3DC\uB2E4 \xB7 \uCC45\uC0AC\uB294 \uBB49\uCE5C \uC801\uC744
|
||||||
|
\uD0DC\uC6B0\uACE0 \uB364\uBD88\uC744 \uCE58\uC6B4\uB2E4"
|
||||||
- stage: 1-2
|
- stage: 1-2
|
||||||
chapter: 1
|
chapter: 1
|
||||||
stageNo: 2
|
stageNo: 2
|
||||||
text: "\uBCF4\uBCD1\uC740 \uD295\uACA8\uB0B4\uACE0 \uCC3D\uBCD1\uC740 \uBC00\uCCD0\uB0B4\uACE0
|
text: "\uBCF4\uBCD1\uC740 \uBC00\uCE58\uACE0 \uD295\uACA8\uB098\uBA70 \uB2E4\uB978
|
||||||
\uBB3C\uB7EC\uB09C\uB2E4 \xB7 \uAE30\uBCD1\uC740 \uAFF0\uB6AB\uACE0 \uB3CC\uC544\uC628\uB2E4
|
\uC801\uAE4C\uC9C0 \uCE5C\uB2E4 \xB7 \uCC3D\uBCD1\uC740 \uD06C\uAC8C \uBC00\uCCD0\uB0B4\uACE0
|
||||||
\xB7 \uAD81\uBCD1\uC740 \uBAA9\uCC45 \uB108\uBA38\uB97C \uC3DC\uB2E4 \xB7 \uCC45\uC0AC\uB294
|
\uC870\uAE08\uB9CC \uBC00\uB9B0\uB2E4 \xB7 \uAE30\uBCD1\uC740 \uC801\uC744
|
||||||
\uBB49\uCE5C \uC801\uC744 \uD0DC\uC6B0\uACE0 \uB364\uBD88\uC744 \uCE58\uC6B4\uB2E4"
|
\uAFF0\uB6AB\uACE0 \uC9C0\uB098\uAC04\uB2E4 \xB7 \uAD81\uBCD1\uC740 \uBAA9\uCC45
|
||||||
|
\uB108\uBA38\uB97C \uC3DC\uB2E4 \xB7 \uCC45\uC0AC\uB294 \uBB49\uCE5C \uC801\uC744
|
||||||
|
\uD0DC\uC6B0\uACE0 \uB364\uBD88\uC744 \uCE58\uC6B4\uB2E4"
|
||||||
- stage: 1-3
|
- stage: 1-3
|
||||||
chapter: 1
|
chapter: 1
|
||||||
stageNo: 3
|
stageNo: 3
|
||||||
text: "\uBCF4\uBCD1\uC740 \uD295\uACA8\uB0B4\uACE0 \uCC3D\uBCD1\uC740 \uBC00\uCCD0\uB0B4\uACE0
|
text: "\uBCF4\uBCD1\uC740 \uBC00\uCE58\uACE0 \uD295\uACA8\uB098\uBA70 \uB2E4\uB978
|
||||||
\uBB3C\uB7EC\uB09C\uB2E4 \xB7 \uAE30\uBCD1\uC740 \uAFF0\uB6AB\uACE0 \uB3CC\uC544\uC628\uB2E4
|
\uC801\uAE4C\uC9C0 \uCE5C\uB2E4 \xB7 \uCC3D\uBCD1\uC740 \uD06C\uAC8C \uBC00\uCCD0\uB0B4\uACE0
|
||||||
\xB7 \uAD81\uBCD1\uC740 \uBAA9\uCC45 \uB108\uBA38\uB97C \uC3DC\uB2E4 \xB7 \uCC45\uC0AC\uB294
|
\uC870\uAE08\uB9CC \uBC00\uB9B0\uB2E4 \xB7 \uAE30\uBCD1\uC740 \uC801\uC744
|
||||||
\uBB49\uCE5C \uC801\uC744 \uD0DC\uC6B0\uACE0 \uB364\uBD88\uC744 \uCE58\uC6B4\uB2E4"
|
\uAFF0\uB6AB\uACE0 \uC9C0\uB098\uAC04\uB2E4 \xB7 \uAD81\uBCD1\uC740 \uBAA9\uCC45
|
||||||
|
\uB108\uBA38\uB97C \uC3DC\uB2E4 \xB7 \uCC45\uC0AC\uB294 \uBB49\uCE5C \uC801\uC744
|
||||||
|
\uD0DC\uC6B0\uACE0 \uB364\uBD88\uC744 \uCE58\uC6B4\uB2E4"
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,8 @@ namespace TKS.EditorTools
|
||||||
c.strategistAllyDamageRatio = 1f; // 광역 아군 피해 = 적과 동일(PD 확인 대상)
|
c.strategistAllyDamageRatio = 1f; // 광역 아군 피해 = 적과 동일(PD 확인 대상)
|
||||||
c.collisionPushRatio = 1f; // PD 2026-11-04 #1080: 보병 알까기 밀어내기 = 남은 거리 그대로
|
c.collisionPushRatio = 1f; // PD 2026-11-04 #1080: 보병 알까기 밀어내기 = 남은 거리 그대로
|
||||||
c.collisionSelfBounceRatio = 0.5f; // PD 2026-11-04 #1081: 보병 자신도 반대로 튕김(남은 거리의 절반 · 손맛 조정 대상)
|
c.collisionSelfBounceRatio = 0.5f; // PD 2026-11-04 #1081: 보병 자신도 반대로 튕김(남은 거리의 절반 · 손맛 조정 대상)
|
||||||
c.spearRecoilRatio = 0.5f; // PD 2026-11-06 #1098: 창병 밀치기 반작용(밀린 거리의 절반 · 함께 전진 폐기)
|
c.spearPushRatio = 1.5f; // PD 2026-11-10: 창병 = 적을 크게 밀침(남은 거리 × 1.5 · 보병 1.0)
|
||||||
|
c.spearRecoilRatio = 0.2f; // PD 2026-11-10: 창병 자신은 적게 밀림(밀린 거리 × 0.2 = 남은 거리 × 0.3 · 보병 자기 튕김 0.5) · 옛 0.5(#1098)
|
||||||
c.unitArtHeight = 1.5f;
|
c.unitArtHeight = 1.5f;
|
||||||
c.mountedArtMaxTop = 1.3f; // 기마 머리 끝 상한 = 표준 키 × 1.3(PD 2026-11-09)
|
c.mountedArtMaxTop = 1.3f; // 기마 머리 끝 상한 = 표준 키 × 1.3(PD 2026-11-09)
|
||||||
c.boardArt = "battlefield_01"; // 원화 세션 1차 납품(2026-11-04) · Resources/Board/
|
c.boardArt = "battlefield_01"; // 원화 세션 1차 납품(2026-11-04) · Resources/Board/
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ namespace TKS
|
||||||
/// <summary>코드가 읽는 키(표에 없으면 임포트 오류).</summary>
|
/// <summary>코드가 읽는 키(표에 없으면 임포트 오류).</summary>
|
||||||
public static readonly string[] Keys =
|
public static readonly string[] Keys =
|
||||||
{
|
{
|
||||||
"dmg_player", "kill_base", "kill_hp_ratio", "dmg_own", "gimmick_dest_bonus", "chain", "support_reach", "pierce_hit", "cav_return", "cav_stranded",
|
"dmg_player", "kill_base", "kill_hp_ratio", "dmg_own", "gimmick_dest_bonus", "chain", "support_reach", "pierce_hit",
|
||||||
"archer_cover", "high_ground", "cav_line", "splash_hit", "route_open", "zone_end", "banner", "skill_waste", "approach", "boss_accuracy", "mode_accuracy",
|
"archer_cover", "high_ground", "cav_line", "splash_hit", "route_open", "zone_end", "banner", "skill_waste", "approach", "boss_accuracy", "mode_accuracy",
|
||||||
"noise_power", "top_n", "dirs", "dirs_many", "many_units", "power_1", "power_2", "power_3", "budget_ms", "boss_active", "active_max_chapter"
|
"noise_power", "top_n", "dirs", "dirs_many", "many_units", "power_1", "power_2", "power_3", "budget_ms", "boss_active", "active_max_chapter"
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -312,8 +312,8 @@ namespace TKS
|
||||||
}
|
}
|
||||||
if (hit.Count > 0)
|
if (hit.Count > 0)
|
||||||
{
|
{
|
||||||
s += w["pierce_hit"] * hit.Count + (o.Returned ? w["cav_return"] : w["cav_stranded"]);
|
s += w["pierce_hit"] * hit.Count; // 복귀 성공·실패 점수 = 기병 복귀 제거로 삭제(PD 2026-11-10)
|
||||||
parts.Add("관통 " + hit.Count + (o.Returned ? " · 복귀" : " · 잔류"));
|
parts.Add("관통 " + hit.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,10 @@ namespace TKS
|
||||||
/// 슬링샷 벡터(시작점 → 목표점)를 병종 규칙대로 이동·공격으로 해석한다 (PD 2026-11-04 #1080 충돌 규칙).
|
/// 슬링샷 벡터(시작점 → 목표점)를 병종 규칙대로 이동·공격으로 해석한다 (PD 2026-11-04 #1080 충돌 규칙).
|
||||||
/// ① 보병: 경로 위 첫 유닛(아군·적군)에 부딪히면 알까기처럼 그 유닛을 남은 힘만큼 밀어내고 자신은 반대로 튕김 · 적이면 공격(반격 받음) · 목표점 명중 시 추가 이동 1회 ·
|
/// ① 보병: 경로 위 첫 유닛(아군·적군)에 부딪히면 알까기처럼 그 유닛을 남은 힘만큼 밀어내고 자신은 반대로 튕김 · 적이면 공격(반격 받음) · 목표점 명중 시 추가 이동 1회 ·
|
||||||
/// 적과 싸운 뒤 튕겨나는 도중 다른 적과 부딪히면 그 적과도 피해·반격 교환 + 다시 튕김(연쇄 1회 · 아군이면 밀기만 · PD 2026-11-06 #1098)
|
/// 적과 싸운 뒤 튕겨나는 도중 다른 적과 부딪히면 그 적과도 피해·반격 교환 + 다시 튕김(연쇄 1회 · 아군이면 밀기만 · PD 2026-11-06 #1098)
|
||||||
/// ② 기병: 유닛을 무시하고 관통 · 경로 위 적은 모두 피해(옆으로 밀림) · 아군은 지나칠 거리가 안 나오면 그 아군 직전에 멈춤 · 목표점 명중 시 제자리 복귀
|
/// ② 기병 = 적을 관통해 이동: 유닛을 무시하고 관통 · 경로 위 적은 모두 피해(옆으로 밀림) · 아군은 지나칠 거리가 안 나오면 그 아군 직전에 멈춤 ·
|
||||||
/// ③ 창병: 첫 유닛에 부딪히면 그 유닛을 남은 거리만큼 밀치고 자신은 그 × spearRecoilRatio 만큼 반대로 밀림(반작용 · 함께 전진 폐기 · PD #1098) ·
|
/// 제자리 복귀 없음(PD 2026-11-10 「유저가 인지하기 어려움 → 제거 · 기병 특징 = 적을 관통해 이동」)
|
||||||
/// 적이면 피해(반격 받음) · 아군이면 밀기만 · 목표점 명중 시 대기(견제)
|
/// ③ 창병 = 적을 크게 밀치고 자신은 적게 밀림: 첫 유닛에 부딪히면 그 유닛을 남은 거리 × spearPushRatio 만큼 밀치고 자신은 그 × spearRecoilRatio 만큼 반대로 밀림 ·
|
||||||
|
/// 적이면 피해(반격 받음) · 아군이면 밀기만 · 대기(견제) 없음(PD 2026-11-10 「유저가 인지하기 어려움 → 제거」)
|
||||||
/// ④ 궁병: 사거리 = 슬링샷 최대 거리 · 조준 방향 사거리 안의 적은 제자리에서 사격 · 사거리 밖 ~ ×(1+비율) 안이면 들어올 만큼만 다가가 사격 ·
|
/// ④ 궁병: 사거리 = 슬링샷 최대 거리 · 조준 방향 사거리 안의 적은 제자리에서 사격 · 사거리 밖 ~ ×(1+비율) 안이면 들어올 만큼만 다가가 사격 ·
|
||||||
/// 더 멀면 목표점까지 이동(쏘지 않음) · 사격 = 타겟 방향 화살 투사체 → 경로 위 첫 유닛(아군 포함)이 맞음 · 관통 없음 · 반격 없음 ·
|
/// 더 멀면 목표점까지 이동(쏘지 않음) · 사격 = 타겟 방향 화살 투사체 → 경로 위 첫 유닛(아군 포함)이 맞음 · 관통 없음 · 반격 없음 ·
|
||||||
/// 첫 피격 = 타겟 + 목표점 명중 시 크리티컬(PD 2026-11-05 #1087 ⑥) · 근접 전투는 피해 50 % · 피격 +50 %(Combat.Damage)
|
/// 첫 피격 = 타겟 + 목표점 명중 시 크리티컬(PD 2026-11-05 #1087 ⑥) · 근접 전투는 피해 50 % · 피격 +50 %(Combat.Damage)
|
||||||
|
|
@ -294,7 +295,6 @@ namespace TKS
|
||||||
DashEnd(u); // 슬링샷 직진 끝 = 돌진 이펙트 소멸(밀기·반작용은 반응 · PD 2026-11-10)
|
DashEnd(u); // 슬링샷 직진 끝 = 돌진 이펙트 소멸(밀기·반작용은 반응 · PD 2026-11-10)
|
||||||
float remaining = Mathf.Max(0f, dist - hitDist);
|
float remaining = Mathf.Max(0f, dist - hitDist);
|
||||||
bool enemy = first.Faction != u.Faction;
|
bool enemy = first.Faction != u.Faction;
|
||||||
bool isFinal = enemy && Combat.EnemyAtPoint(u, target, cfg.finalHitTolerance) == first;
|
|
||||||
|
|
||||||
if (enemy)
|
if (enemy)
|
||||||
{
|
{
|
||||||
|
|
@ -306,8 +306,9 @@ namespace TKS
|
||||||
o.MovedOnly = true;
|
o.MovedOnly = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 밀치기 거리 = 남은 거리 × 철척사모(push_mult) × 대상 거구(knockback_taken_pct) · 상대만 밀리고 창병은 그 × spearRecoilRatio 만큼 반대로(반작용 · PD #1098)
|
// 밀치기 거리 = 남은 거리 × spearPushRatio(보병보다 크게 · PD 2026-11-10) × 철척사모(push_mult) × 대상 거구(knockback_taken_pct) ·
|
||||||
float push = remaining * SkillMods.PushMult(u, true) * SkillMods.KnockbackTakenMult(first, true);
|
// 상대만 밀리고 창병은 그 × spearRecoilRatio 만큼 반대로(반작용 · 보병 자기 튕김보다 작게 · PD #1098 · 2026-11-10)
|
||||||
|
float push = remaining * cfg.spearPushRatio * SkillMods.PushMult(u, true) * SkillMods.KnockbackTakenMult(first, true);
|
||||||
if (first.IsAlive && push > 0.01f)
|
if (first.IsAlive && push > 0.01f)
|
||||||
{
|
{
|
||||||
bool pushedDone = false;
|
bool pushedDone = false;
|
||||||
|
|
@ -332,12 +333,7 @@ namespace TKS
|
||||||
if (!enemy) yield return PushedAllyAttack(first, o, u);
|
if (!enemy) yield return PushedAllyAttack(first, o, u);
|
||||||
}
|
}
|
||||||
if (enemy && u.IsAlive) SpearHitMore(u, first, o);
|
if (enemy && u.IsAlive) SpearHitMore(u, first, o);
|
||||||
|
// 목표점 명중 → 대기(견제) = 제거(PD 2026-11-10) · 견제 스킬(청룡언월·후계·교변 …)은 재설계 결정 대기라 BattleManager.ApplyOverwatch 등은 남겨 둠(대기 유닛이 없어 발동 안 함)
|
||||||
if (isFinal && u.IsAlive)
|
|
||||||
{
|
|
||||||
u.SetOverwatch(true);
|
|
||||||
o.Overwatch = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>삼첨도(spear_hit_two): 밀며 전진 끝에 창병·밀린 적과 닿은 다른 적까지 합쳐 최대 n 기에 동시 피해(반격 없음).</summary>
|
/// <summary>삼첨도(spear_hit_two): 밀며 전진 끝에 창병·밀린 적과 닿은 다른 적까지 합쳐 최대 n 기에 동시 피해(반격 없음).</summary>
|
||||||
|
|
@ -370,44 +366,16 @@ namespace TKS
|
||||||
battle.NotifyChanged();
|
battle.NotifyChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 기병: 관통 ----------
|
// ---------- 기병: 관통(제자리 복귀 없음 · PD 2026-11-10) ----------
|
||||||
|
|
||||||
/// <summary>
|
/// <param name="first">false = 위진소요의 추가 관통(다시 반복하지 않음 · exclude = 앞서 관통한 적 제외).</param>
|
||||||
/// 기병 조준 미리보기(PD 2026-11-10 「기병도 궁수와 같이 슬링샷 조작 시 공격 후 복귀 발동 지점을 알 수 있도록」) — 이 조준으로 놓으면
|
|
||||||
/// 공격 뒤 출발점으로 돌아오는가(CavalryRoutine 의 willReturn 과 같은 조건 · 첫 관통) · returnFrom = 복귀가 시작되는 자리(관통 끝).
|
|
||||||
/// 예약 중인 액티브의 관통 효과(위진소요 · 단기구주)는 발동 전이라 SkillMods 에 이미 걸린 것만 본다.
|
|
||||||
/// </summary>
|
|
||||||
public bool PreviewCavalryReturn(Unit u, Vector2 aim, out Vector2 returnFrom)
|
|
||||||
{
|
|
||||||
returnFrom = u != null ? u.Position : Vector2.zero;
|
|
||||||
if (u == null || u.Type != UnitType.Cavalry || u.BonusMoveAvailable) return false;
|
|
||||||
Vector2 origin = u.Position;
|
|
||||||
Vector2 target = ReachPoint(u, aim);
|
|
||||||
Vector2 delta = target - origin;
|
|
||||||
float dist = delta.magnitude;
|
|
||||||
if (dist < 0.001f) return false;
|
|
||||||
Vector2 dir = delta / dist;
|
|
||||||
float travel = Combat.FreeTravel(u, origin, dir, dist, false, false);
|
|
||||||
travel = StopBeforeUnclearedAllies(u, origin, dir, travel);
|
|
||||||
float gDist;
|
|
||||||
Vector2 gNormal;
|
|
||||||
var gim = BlockAhead(u, origin, dir, dist, travel + 0.05f, out gDist, out gNormal);
|
|
||||||
var enemies = Combat.EnemiesOnPath(u, origin, dir, travel, new List<float>());
|
|
||||||
Unit finalHit = Combat.EnemyAtPoint(u, target, cfg.finalHitTolerance);
|
|
||||||
bool hitFinal = finalHit != null && enemies.Contains(finalHit);
|
|
||||||
int repeat = hitFinal && gim == null ? SkillMods.Count(u, SkillKeys.PierceRepeat, 0) : 0;
|
|
||||||
returnFrom = origin + dir * travel;
|
|
||||||
return gim == null && repeat <= 0 && (hitFinal || (enemies.Count > 0 && SkillMods.Has(u, SkillKeys.PierceReturnOnMiss)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <param name="first">false = 위진소요의 추가 관통(다시 반복·복귀하지 않음 · exclude = 앞서 관통한 적 제외).</param>
|
|
||||||
IEnumerator CavalryRoutine(Unit u, Vector2 origin, Vector2 dir, float dist, Vector2 target, bool moveOnly, ActionOutcome o, bool first, List<Unit> exclude = null)
|
IEnumerator CavalryRoutine(Unit u, Vector2 origin, Vector2 dir, float dist, Vector2 target, bool moveOnly, ActionOutcome o, bool first, List<Unit> exclude = null)
|
||||||
{
|
{
|
||||||
float travel = Combat.FreeTravel(u, origin, dir, dist, false, false); // 유닛은 무시 · 벽·장애물 기믹만
|
float travel = Combat.FreeTravel(u, origin, dir, dist, false, false); // 유닛은 무시 · 벽·장애물 기믹만
|
||||||
travel = StopBeforeUnclearedAllies(u, origin, dir, travel); // 지나칠 수 없는 아군 직전에서 멈춤
|
travel = StopBeforeUnclearedAllies(u, origin, dir, travel); // 지나칠 수 없는 아군 직전에서 멈춤
|
||||||
float gDist;
|
float gDist;
|
||||||
Vector2 gNormal;
|
Vector2 gNormal;
|
||||||
var gim = BlockAhead(u, origin, dir, dist, travel + 0.05f, out gDist, out gNormal); // 장애물 = 관통 불가 → 거기서 정지 · 복귀 없음(PD #1097)
|
var gim = BlockAhead(u, origin, dir, dist, travel + 0.05f, out gDist, out gNormal); // 장애물 = 관통 불가 → 거기서 정지(PD #1097)
|
||||||
var dists = new List<float>();
|
var dists = new List<float>();
|
||||||
var enemies = moveOnly ? new List<Unit>() : Combat.EnemiesOnPath(u, origin, dir, travel, dists);
|
var enemies = moveOnly ? new List<Unit>() : Combat.EnemiesOnPath(u, origin, dir, travel, dists);
|
||||||
if (exclude != null)
|
if (exclude != null)
|
||||||
|
|
@ -421,12 +389,11 @@ namespace TKS
|
||||||
}
|
}
|
||||||
Unit finalHit = moveOnly ? null : Combat.EnemyAtPoint(u, target, cfg.finalHitTolerance);
|
Unit finalHit = moveOnly ? null : Combat.EnemyAtPoint(u, target, cfg.finalHitTolerance);
|
||||||
bool hitFinal = first && finalHit != null && enemies.Contains(finalHit);
|
bool hitFinal = first && finalHit != null && enemies.Contains(finalHit);
|
||||||
// 단기구주: 빗나가도(관통한 적이 있으면) 출발점 복귀 · 위진소요: 명중 시 복귀 대신 같은 방향 한 번 더 관통
|
// 위진소요: 목표점 명중 시 같은 방향 한 번 더 관통 · 제자리 복귀(옛 목표점 명중 · 단기구주 빗나가도 복귀) = 제거(PD 2026-11-10)
|
||||||
int repeat = hitFinal && gim == null ? SkillMods.Count(u, SkillKeys.PierceRepeat, 0) : 0;
|
int repeat = hitFinal && gim == null ? SkillMods.Count(u, SkillKeys.PierceRepeat, 0) : 0;
|
||||||
bool willReturn = gim == null && first && repeat <= 0 && (hitFinal || (enemies.Count > 0 && SkillMods.Has(u, SkillKeys.PierceReturnOnMiss)));
|
|
||||||
|
|
||||||
Vector2 end = origin + dir * travel;
|
Vector2 end = origin + dir * travel;
|
||||||
if (!willReturn && repeat <= 0) end = BackOffFromEnemies(u, origin, dir, travel);
|
if (repeat <= 0) end = BackOffFromEnemies(u, origin, dir, travel);
|
||||||
|
|
||||||
if (enemies.Count == 0)
|
if (enemies.Count == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -452,7 +419,7 @@ namespace TKS
|
||||||
});
|
});
|
||||||
u.Position = end;
|
u.Position = end;
|
||||||
o.Path.Add(end);
|
o.Path.Add(end);
|
||||||
DashEnd(u); // 관통 돌격 끝 = 돌진 이펙트 소멸(복귀 때 다시 재생 · PD 2026-11-10)
|
DashEnd(u); // 관통 돌격 끝 = 돌진 이펙트 소멸(PD 2026-11-10)
|
||||||
for (; idx < enemies.Count; idx++)
|
for (; idx < enemies.Count; idx++)
|
||||||
{
|
{
|
||||||
if (dists[idx] <= travel + 0.01f)
|
if (dists[idx] <= travel + 0.01f)
|
||||||
|
|
@ -496,17 +463,6 @@ namespace TKS
|
||||||
DashBegin(u, dir); // 추가 관통 = 돌진 이펙트 다시
|
DashBegin(u, dir); // 추가 관통 = 돌진 이펙트 다시
|
||||||
yield return CavalryRoutine(u, from, dir, dist, from + dir * dist, false, o, false, hit);
|
yield return CavalryRoutine(u, from, dir, dist, from + dir * dist, false, o, false, hit);
|
||||||
}
|
}
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (willReturn && u.IsAlive)
|
|
||||||
{
|
|
||||||
yield return Fx.Wait(0.1f);
|
|
||||||
DashBegin(u, origin - u.Position); // 복귀 = 복귀 방향으로 돌진 이펙트 다시 재생 → 도착하면 소멸(PD 2026-11-10)
|
|
||||||
if (u.View != null) u.View.TurnAround(origin - u.Position); // 복귀 = 돌아서서 감(그림 좌우 반전 · PD 2026-11-10 · 도착 뒤에도 마지막 이동 방향 PD #1082)
|
|
||||||
yield return Move(u, origin, o, false);
|
|
||||||
DashEnd(u);
|
|
||||||
o.Returned = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ namespace TKS
|
||||||
public int def;
|
public int def;
|
||||||
[Tooltip("슬링샷으로 지정할 수 있는 최대 거리(월드 단위)")]
|
[Tooltip("슬링샷으로 지정할 수 있는 최대 거리(월드 단위)")]
|
||||||
public float slingMaxDistance;
|
public float slingMaxDistance;
|
||||||
[Tooltip("창병 대기(견제) 반경 · 궁병은 쓰지 않음(사거리 = slingMaxDistance)")]
|
[Tooltip("옛 창병 대기(견제) 반경 · 견제 제거(PD 2026-11-10)로 지금은 쓰지 않음 · 궁병도 쓰지 않음(사거리 = slingMaxDistance)")]
|
||||||
public float attackRange;
|
public float attackRange;
|
||||||
public float radius;
|
public float radius;
|
||||||
}
|
}
|
||||||
|
|
@ -146,8 +146,10 @@ namespace TKS
|
||||||
public float collisionPushRatio = 1f;
|
public float collisionPushRatio = 1f;
|
||||||
[Tooltip("보병 알까기: 부딪힌 보병 자신이 반대 방향으로 튕겨나는 거리 = 충돌 시 남은 슬링샷 거리 × 비율 (PD #1081 「자신도 튕겨나야」)")]
|
[Tooltip("보병 알까기: 부딪힌 보병 자신이 반대 방향으로 튕겨나는 거리 = 충돌 시 남은 슬링샷 거리 × 비율 (PD #1081 「자신도 튕겨나야」)")]
|
||||||
public float collisionSelfBounceRatio = 0.5f;
|
public float collisionSelfBounceRatio = 0.5f;
|
||||||
[Tooltip("창병 밀치기 반작용(PD 2026-11-06 #1098): 상대가 밀리는 거리(남은 거리 × 밀기 배율) × 비율만큼 창병이 반대로 밀린다(벽·유닛에 닿으면 정지 · 피해 없음) · 「밀며 함께 전진」 폐기")]
|
[Tooltip("창병 밀치기(PD 2026-11-10 「창병 = 적을 크게 밀치지만 자신은 적게 밀린다」): 상대가 밀리는 거리 = 충돌 시 남은 슬링샷 거리 × 비율 × 밀기 배율(철척사모) · 보병(collisionPushRatio)보다 크게")]
|
||||||
public float spearRecoilRatio = 0.5f;
|
public float spearPushRatio = 1.5f;
|
||||||
|
[Tooltip("창병 밀치기 반작용(PD 2026-11-06 #1098 · 2026-11-10 「자신은 적게 밀린다」): 상대가 밀린 거리 × 비율만큼 창병이 반대로 밀린다(벽·유닛에 닿으면 정지 · 피해 없음) · 보병 자기 튕김(collisionSelfBounceRatio)보다 작게")]
|
||||||
|
public float spearRecoilRatio = 0.2f;
|
||||||
|
|
||||||
[Header("표시")]
|
[Header("표시")]
|
||||||
[Tooltip("유닛 아트(스프라이트) 세로 크기 · 월드 단위 · 장수는 반지름 배율만큼 커진다")]
|
[Tooltip("유닛 아트(스프라이트) 세로 크기 · 월드 단위 · 장수는 반지름 배율만큼 커진다")]
|
||||||
|
|
|
||||||
|
|
@ -329,7 +329,6 @@ namespace TKS
|
||||||
defText.text = u.Def.ToString();
|
defText.text = u.Def.ToString();
|
||||||
string extra = "슬링 " + SkillMods.SlingMax(u).ToString("0.#");
|
string extra = "슬링 " + SkillMods.SlingMax(u).ToString("0.#");
|
||||||
if (UnitTypeInfo.IsRanged(u.Type)) extra = "사거리 " + SkillMods.Range(u).ToString("0.#");
|
if (UnitTypeInfo.IsRanged(u.Type)) extra = "사거리 " + SkillMods.Range(u).ToString("0.#");
|
||||||
else if (u.Type == UnitType.Spear) extra = "견제 " + SkillMods.OverwatchRadius(u).ToString("0.#");
|
|
||||||
if (u.Type == UnitType.Strategist) extra = "광역 " + SkillMods.SplashRadius(u, battle.Config).ToString("0.#");
|
if (u.Type == UnitType.Strategist) extra = "광역 " + SkillMods.SplashRadius(u, battle.Config).ToString("0.#");
|
||||||
extraText.text = extra;
|
extraText.text = extra;
|
||||||
string state = u.Faction == Faction.Player ? "아군" : "적군";
|
string state = u.Faction == Faction.Player ? "아군" : "적군";
|
||||||
|
|
@ -338,7 +337,6 @@ namespace TKS
|
||||||
if (u.BonusMoveAvailable) state = "추가 이동";
|
if (u.BonusMoveAvailable) state = "추가 이동";
|
||||||
else if (u.ActedThisTurn) state = "행동 완료";
|
else if (u.ActedThisTurn) state = "행동 완료";
|
||||||
}
|
}
|
||||||
if (u.Overwatch) state += " · 대기 중";
|
|
||||||
stateText.text = state;
|
stateText.text = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -582,14 +580,12 @@ namespace TKS
|
||||||
string line3 = "슬링 " + SkillMods.SlingMax(u).ToString("0.#");
|
string line3 = "슬링 " + SkillMods.SlingMax(u).ToString("0.#");
|
||||||
if (UnitTypeInfo.IsRanged(u.Type)) line3 += " 사거리 " + SkillMods.Range(u).ToString("0.#") + " (접근 +" + u.ArcherMoveAllow.ToString("0.#") + ")";
|
if (UnitTypeInfo.IsRanged(u.Type)) line3 += " 사거리 " + SkillMods.Range(u).ToString("0.#") + " (접근 +" + u.ArcherMoveAllow.ToString("0.#") + ")";
|
||||||
if (u.Type == UnitType.Strategist) line3 += " 광역 " + SkillMods.SplashRadius(u, battle.Config).ToString("0.#");
|
if (u.Type == UnitType.Strategist) line3 += " 광역 " + SkillMods.SplashRadius(u, battle.Config).ToString("0.#");
|
||||||
if (u.Type == UnitType.Spear) line3 += " 견제 반경 " + SkillMods.OverwatchRadius(u).ToString("0.#");
|
|
||||||
string state = "";
|
string state = "";
|
||||||
if (u.Faction == Faction.Player)
|
if (u.Faction == Faction.Player)
|
||||||
{
|
{
|
||||||
if (u.BonusMoveAvailable) state = " [추가 이동]";
|
if (u.BonusMoveAvailable) state = " [추가 이동]";
|
||||||
else if (u.ActedThisTurn) state = " [행동 완료]";
|
else if (u.ActedThisTurn) state = " [행동 완료]";
|
||||||
}
|
}
|
||||||
if (u.Overwatch) state += " [대기 중]";
|
|
||||||
unitText.text = line1 + "\n" + line2 + "\n" + line3 + state;
|
unitText.text = line1 + "\n" + line2 + "\n" + line3 + state;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -687,8 +687,6 @@ namespace TKS
|
||||||
}
|
}
|
||||||
foreach (var n in o.Notes) s += " · " + n;
|
foreach (var n in o.Notes) s += " · " + n;
|
||||||
if (o.BonusMove) s += " · 추가 이동!";
|
if (o.BonusMove) s += " · 추가 이동!";
|
||||||
if (o.Returned) s += " · 제자리 복귀";
|
|
||||||
if (o.Overwatch) s += " · 대기(견제) 발동";
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,24 +57,6 @@ namespace TKS
|
||||||
{
|
{
|
||||||
get { return marks; }
|
get { return marks; }
|
||||||
}
|
}
|
||||||
/// <summary>
|
|
||||||
/// 기병 복귀 영역(PD 2026-11-10 「기병도 궁수와 같이 공격 후 복귀 발동 지점을 알 수 있도록」 → 「궁병처럼 채워진 영역 형태로」):
|
|
||||||
/// 슬링샷 목표점을 이 원 안에 놓으면 공격 뒤 출발점으로 돌아온다 = 적마다 (유닛 원 반지름 + 최종 명중 허용 거리) 원 ·
|
|
||||||
/// 그 적을 노렸을 때 복귀 조건(ActionResolver.PreviewCavalryReturn)이 참이고 슬링샷 거리 안인 적만 · 지금 노리는 원은 진하게.
|
|
||||||
/// 색 = IB 하늘색(궁병 사거리 도넛과 같은 채운 원 · 바닥 층).
|
|
||||||
/// </summary>
|
|
||||||
static readonly Color ReturnZoneColor = new Color(0.468f, 0.866f, 0.897f, 0.3f);
|
|
||||||
static readonly Color ReturnZoneAimColor = new Color(0.468f, 0.866f, 0.897f, 0.55f);
|
|
||||||
/// <summary>유닛 위(11 · 속성 방울 12 아래) — 원 크기가 적 발판과 거의 같아 바닥 층이면 적 그림에 가려 안 보임(2026-11-10 실측).</summary>
|
|
||||||
const int ReturnZoneOrder = 11;
|
|
||||||
readonly List<SpriteRenderer> returnZones = new List<SpriteRenderer>();
|
|
||||||
int returnZoneCount;
|
|
||||||
|
|
||||||
public int ReturnZoneCount
|
|
||||||
{
|
|
||||||
get { return returnZoneCount; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AimArrowVisible
|
public bool AimArrowVisible
|
||||||
{
|
{
|
||||||
get { return ibArrow != null && ibArrow.ArrowVisible; }
|
get { return ibArrow != null && ibArrow.ArrowVisible; }
|
||||||
|
|
@ -144,34 +126,6 @@ namespace TKS
|
||||||
marks = ago.AddComponent<IbUnitMarks>();
|
marks = ago.AddComponent<IbUnitMarks>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>기병 복귀 영역 갱신 — u = 조준 중(또는 선택된) 아군 기병 · aim = 지금 목표점(없으면 null) · 그 밖 = 모두 숨김.</summary>
|
|
||||||
void ShowReturnZones(Unit u, Vector2? aim)
|
|
||||||
{
|
|
||||||
int n = 0;
|
|
||||||
if (u != null && u.IsAlive && u.Type == UnitType.Cavalry && u.Faction == Faction.Player && !u.BonusMoveAvailable && battle.CanAct(u))
|
|
||||||
{
|
|
||||||
float tol = battle.Config.finalHitTolerance;
|
|
||||||
float sling = SkillMods.SlingMax(u);
|
|
||||||
foreach (var e in battle.AliveUnits(Faction.Enemy))
|
|
||||||
{
|
|
||||||
float r = e.Radius + tol;
|
|
||||||
if (Vector2.Distance(e.Position, u.Position) - r > sling) continue;
|
|
||||||
Vector2 from;
|
|
||||||
if (!battle.Resolver.PreviewCavalryReturn(u, e.Position, out from)) continue;
|
|
||||||
if (n >= returnZones.Count)
|
|
||||||
returnZones.Add(ProceduralSprites.NewSprite(transform, "ReturnZone", ProceduralSprites.Circle, ReturnZoneColor, 1f, ReturnZoneOrder));
|
|
||||||
var z = returnZones[n++];
|
|
||||||
z.transform.position = new Vector3(e.Position.x, e.Position.y, 0f);
|
|
||||||
z.transform.localScale = new Vector3(r * 2f, r * 2f, 1f);
|
|
||||||
z.color = aim.HasValue && Vector2.Distance(aim.Value, e.Position) <= r ? ReturnZoneAimColor : ReturnZoneColor;
|
|
||||||
if (!z.gameObject.activeSelf) z.gameObject.SetActive(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = n; i < returnZones.Count; i++)
|
|
||||||
if (returnZones[i].gameObject.activeSelf) returnZones[i].gameObject.SetActive(false);
|
|
||||||
returnZoneCount = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
LineRenderer MakeLine(string name, float width, Color color, int order)
|
LineRenderer MakeLine(string name, float width, Color color, int order)
|
||||||
{
|
{
|
||||||
var go = new GameObject(name);
|
var go = new GameObject(name);
|
||||||
|
|
@ -377,7 +331,6 @@ namespace TKS
|
||||||
range.Show(u);
|
range.Show(u);
|
||||||
if (u != null && UnitTypeInfo.IsRanged(u.Type)) range.SetArcherReachable(battle.AnyEnemyWithinArcherReach(u));
|
if (u != null && UnitTypeInfo.IsRanged(u.Type)) range.SetArcherReachable(battle.AnyEnemyWithinArcherReach(u));
|
||||||
}
|
}
|
||||||
ShowReturnZones(u, null); // 기병 = 궁병 사거리 도넛처럼 선택하면 복귀 영역
|
|
||||||
if (marks != null) marks.ShowTurn(u != null && u.Faction == Faction.Player && battle.CanAct(u) ? u : null); // IB ShowUserTurnMark
|
if (marks != null) marks.ShowTurn(u != null && u.Faction == Faction.Player && battle.CanAct(u) ? u : null); // IB ShowUserTurnMark
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -413,8 +366,6 @@ namespace TKS
|
||||||
targetRing.color = finalHit != null ? TargetHitColor : TargetFreeColor;
|
targetRing.color = finalHit != null ? TargetHitColor : TargetFreeColor;
|
||||||
targetRing.transform.localScale = finalHit != null ? new Vector3(0.9f, 0.9f, 1f) : new Vector3(0.7f, 0.7f, 1f);
|
targetRing.transform.localScale = finalHit != null ? new Vector3(0.9f, 0.9f, 1f) : new Vector3(0.7f, 0.7f, 1f);
|
||||||
|
|
||||||
ShowReturnZones(u, aim); // 기병 복귀 영역(지금 노리는 원 진하게)
|
|
||||||
|
|
||||||
ClearHighlights();
|
ClearHighlights();
|
||||||
var shot = battle.Resolver.PreviewShot(u, aim, moveOnly);
|
var shot = battle.Resolver.PreviewShot(u, aim, moveOnly);
|
||||||
var gl = battle.Gimmicks;
|
var gl = battle.Gimmicks;
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ namespace TKS.Tests
|
||||||
// ---------- 기병 ----------
|
// ---------- 기병 ----------
|
||||||
|
|
||||||
[UnityTest]
|
[UnityTest]
|
||||||
public IEnumerator Cavalry_PiercesAllOnPath_AndReturnsOnFinalHit()
|
public IEnumerator Cavalry_PiercesAllOnPath_NoReturnOnFinalHit()
|
||||||
{
|
{
|
||||||
yield return Fresh();
|
yield return Fresh();
|
||||||
var cav = bm.SpawnUnit(UnitType.Cavalry, Faction.Player, new Vector2(0f, -4f), "기병");
|
var cav = bm.SpawnUnit(UnitType.Cavalry, Faction.Player, new Vector2(0f, -4f), "기병");
|
||||||
|
|
@ -178,8 +178,9 @@ namespace TKS.Tests
|
||||||
yield return bm.ResolveForTest(cav, e2.Position, false, o);
|
yield return bm.ResolveForTest(cav, e2.Position, false, o);
|
||||||
Assert.Less(e1.Hp, e1.MaxHp, "경로 위 첫 적 관통 피해");
|
Assert.Less(e1.Hp, e1.MaxHp, "경로 위 첫 적 관통 피해");
|
||||||
Assert.Less(e2.Hp, e2.MaxHp, "경로 위 둘째 적 관통 피해");
|
Assert.Less(e2.Hp, e2.MaxHp, "경로 위 둘째 적 관통 피해");
|
||||||
Assert.IsTrue(o.Returned, "최종 목표점 명중 → 제자리 복귀");
|
Assert.IsFalse(o.Returned, "목표점 명중해도 제자리 복귀 없음(PD 2026-11-10 제거)");
|
||||||
Assert.Less(Vector2.Distance(cav.Position, new Vector2(0f, -4f)), 0.05f);
|
Assert.Greater(cav.Position.y, e1.Position.y, "첫 적을 꿰뚫고 지나가 그 너머에 남음");
|
||||||
|
Assert.GreaterOrEqual(Vector2.Distance(cav.Position, e2.Position), cav.Radius + e2.Radius - 0.05f, "적과 겹치지 않음");
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[UnityTest]
|
||||||
|
|
@ -577,7 +578,7 @@ namespace TKS.Tests
|
||||||
// ---------- 창병 ----------
|
// ---------- 창병 ----------
|
||||||
|
|
||||||
[UnityTest]
|
[UnityTest]
|
||||||
public IEnumerator Spear_PlowsEnemyForward_OverwatchOnFinalHit_DamagesMovingEnemy()
|
public IEnumerator Spear_PushesEnemyFar_RecoilsLittle_NoOverwatch()
|
||||||
{
|
{
|
||||||
yield return Fresh();
|
yield return Fresh();
|
||||||
var spear = bm.SpawnUnit(UnitType.Spear, Faction.Player, new Vector2(0f, -3f), "창병");
|
var spear = bm.SpawnUnit(UnitType.Spear, Faction.Player, new Vector2(0f, -3f), "창병");
|
||||||
|
|
@ -587,21 +588,11 @@ namespace TKS.Tests
|
||||||
Assert.Contains(foe, o.Attacked);
|
Assert.Contains(foe, o.Attacked);
|
||||||
Assert.Less(foe.Hp, foe.MaxHp, "부딪힌 적에게 피해");
|
Assert.Less(foe.Hp, foe.MaxHp, "부딪힌 적에게 피해");
|
||||||
Assert.Contains(foe, o.Pushed, "부딪힌 상대를 밀친다");
|
Assert.Contains(foe, o.Pushed, "부딪힌 상대를 밀친다");
|
||||||
Assert.Greater(foe.Position.y, 0.5f, "적이 남은 거리만큼 앞으로 밀림");
|
|
||||||
float remaining = CastContact(spear, foe); // 당긴 3 − 접촉까지(3 − 접촉 거리)
|
float remaining = CastContact(spear, foe); // 당긴 3 − 접촉까지(3 − 접촉 거리)
|
||||||
Assert.AreEqual(-CastContact(spear, foe) - remaining * cfg.spearRecoilRatio, spear.Position.y, 0.1f, "창병 = 밀친 거리 × spearRecoilRatio 만큼 반대로(PD #1098 · 함께 전진 폐기)");
|
float push = remaining * cfg.spearPushRatio; // PD 2026-11-10 「적을 크게 밀친다」
|
||||||
Assert.IsTrue(o.Overwatch && spear.Overwatch, "최종 목표점 명중 → 대기(견제)");
|
Assert.AreEqual(push, foe.Position.y, 0.1f, "적 = 남은 거리 × spearPushRatio 만큼 앞으로 밀림");
|
||||||
|
Assert.AreEqual(-CastContact(spear, foe) - push * cfg.spearRecoilRatio, spear.Position.y, 0.1f, "창병 = 밀친 거리 × spearRecoilRatio 만큼만 반대로(PD 2026-11-10 「자신은 적게 밀린다」)");
|
||||||
int hp = foe.Hp;
|
Assert.IsFalse(o.Overwatch || spear.Overwatch, "목표점 명중해도 대기(견제) 없음(PD 2026-11-10 제거)");
|
||||||
var path = new List<Vector2> { foe.Position, foe.Position + new Vector2(1.5f, 0f) };
|
|
||||||
yield return bm.ApplyOverwatch(foe, path);
|
|
||||||
Assert.Less(foe.Hp, hp, "반경 안에서 움직인 적에게 피해");
|
|
||||||
|
|
||||||
// 반경 밖에서만 움직인 적은 피해 없음
|
|
||||||
var far = bm.SpawnUnit(UnitType.Infantry, Faction.Enemy, new Vector2(5f, 6f), "먼적");
|
|
||||||
int farHp = far.Hp;
|
|
||||||
yield return bm.ApplyOverwatch(far, new List<Vector2> { far.Position, far.Position + new Vector2(-1f, 0f) });
|
|
||||||
Assert.AreEqual(farHp, far.Hp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[UnityTest]
|
||||||
|
|
@ -620,7 +611,7 @@ namespace TKS.Tests
|
||||||
Assert.LessOrEqual(Gap(ally, foe), Combat.ContactTolerance + 0.05f, "밀린 아군이 적과 닿음");
|
Assert.LessOrEqual(Gap(ally, foe), Combat.ContactTolerance + 0.05f, "밀린 아군이 적과 닿음");
|
||||||
float contactY = -2f - CastContact(spear, ally);
|
float contactY = -2f - CastContact(spear, ally);
|
||||||
float push = 4f - (contactY + 4f); // 남은 거리 = 당긴 4 − 접촉까지
|
float push = 4f - (contactY + 4f); // 남은 거리 = 당긴 4 − 접촉까지
|
||||||
Assert.AreEqual(contactY - push * cfg.spearRecoilRatio, spear.Position.y, 0.1f, "창병 반작용(PD #1098) = 밀친 거리 × 비율만큼 뒤로");
|
Assert.AreEqual(contactY - push * cfg.spearPushRatio * cfg.spearRecoilRatio, spear.Position.y, 0.1f, "창병 반작용 = 밀친 거리(남은 거리 × spearPushRatio) × spearRecoilRatio 만큼 뒤로(PD #1098 · 2026-11-10)");
|
||||||
Assert.Less(foe.Hp, foe.MaxHp, "밀린 아군이 적을 공격(규칙 ⑤)");
|
Assert.Less(foe.Hp, foe.MaxHp, "밀린 아군이 적을 공격(규칙 ⑤)");
|
||||||
Assert.AreEqual(spear.MaxHp, spear.Hp, "창병은 피해 없음");
|
Assert.AreEqual(spear.MaxHp, spear.Hp, "창병은 피해 없음");
|
||||||
Assert.AreEqual(1, o.Notes.Count);
|
Assert.AreEqual(1, o.Notes.Count);
|
||||||
|
|
|
||||||
|
|
@ -625,7 +625,7 @@ namespace TKS.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[UnityTest]
|
||||||
public IEnumerator Rule_SpearPushesTarget_RecoilsHalf_NoAdvanceTogether()
|
public IEnumerator Rule_SpearPushesTargetFar_RecoilsLittle_NoOverwatch()
|
||||||
{
|
{
|
||||||
yield return Fresh();
|
yield return Fresh();
|
||||||
var spear = bm.SpawnUnit(UnitType.Spear, Faction.Player, new Vector2(0f, -3f), "창병");
|
var spear = bm.SpawnUnit(UnitType.Spear, Faction.Player, new Vector2(0f, -3f), "창병");
|
||||||
|
|
@ -634,11 +634,13 @@ namespace TKS.Tests
|
||||||
float cc = spear.Radius * Combat.CastRadiusScale + foe.Radius;
|
float cc = spear.Radius * Combat.CastRadiusScale + foe.Radius;
|
||||||
var o = new ActionOutcome();
|
var o = new ActionOutcome();
|
||||||
yield return Act(spear, foe.Position, o);
|
yield return Act(spear, foe.Position, o);
|
||||||
float push = cc; // 남은 거리 = 당긴 3 − 접촉까지(3 − cc)
|
float push = cc * cfg.spearPushRatio; // 남은 거리(당긴 3 − 접촉까지 = cc) × 창병 밀치기 배율(PD 2026-11-10 「크게 밀친다」)
|
||||||
Assert.AreEqual(push, foe.Position.y, 0.08f, "상대 = 남은 거리만큼 밀림");
|
Assert.AreEqual(push, foe.Position.y, 0.08f, "상대 = 남은 거리 × spearPushRatio 만큼 밀림");
|
||||||
Assert.AreEqual(-cc - push * cfg.spearRecoilRatio, spear.Position.y, 0.08f, "창병 = 그 × spearRecoilRatio 만큼 반대로(함께 전진 없음)");
|
Assert.AreEqual(-cc - push * cfg.spearRecoilRatio, spear.Position.y, 0.08f, "창병 = 그 × spearRecoilRatio 만큼 반대로(함께 전진 없음)");
|
||||||
|
Assert.Greater(cfg.spearPushRatio, cfg.collisionPushRatio, "창병은 보병보다 크게 민다(PD 2026-11-10)");
|
||||||
|
Assert.Less(cfg.spearPushRatio * cfg.spearRecoilRatio, cfg.collisionSelfBounceRatio, "창병 자신은 보병 자기 튕김보다 적게 밀린다(PD 2026-11-10)");
|
||||||
Assert.Less(foe.Hp, foe.MaxHp, "접촉 순간 공격");
|
Assert.Less(foe.Hp, foe.MaxHp, "접촉 순간 공격");
|
||||||
Assert.IsTrue(o.Overwatch, "명중 = 견제 유지");
|
Assert.IsFalse(o.Overwatch || spear.Overwatch, "목표점 명중해도 대기(견제) 없음(PD 2026-11-10 제거)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -366,8 +366,8 @@ namespace TKS.Tests
|
||||||
e2.MaxHp = e2.Hp = 10000;
|
e2.MaxHp = e2.Hp = 10000;
|
||||||
var o2 = new ActionOutcome();
|
var o2 = new ActionOutcome();
|
||||||
yield return Act(zy, new Vector2(0f, 2f), o2);
|
yield return Act(zy, new Vector2(0f, 2f), o2);
|
||||||
Assert.IsTrue(o2.Returned, "빗나가도 출발점 복귀");
|
Assert.IsFalse(o2.Returned, "기병 복귀 제거(PD 2026-11-10) = 단기구주 「빗나가도 출발점 복귀」 발동 안 함(스킬 재설계 PD 결정 대기)");
|
||||||
Assert.Less(Vector2.Distance(zy.Position, new Vector2(0f, -4f)), 0.05f);
|
Assert.Greater(zy.Position.y, e2.Position.y, "적을 꿰뚫고 지나가 그 너머에 남음");
|
||||||
Assert.AreEqual(0, o2.CounterTaken, "단기구주 = 반격 없음");
|
Assert.AreEqual(0, o2.CounterTaken, "단기구주 = 반격 없음");
|
||||||
var disp = o2.Displaced.Find(x => x.Unit == e2);
|
var disp = o2.Displaced.Find(x => x.Unit == e2);
|
||||||
Assert.IsNotNull(disp.Unit);
|
Assert.IsNotNull(disp.Unit);
|
||||||
|
|
@ -384,7 +384,7 @@ namespace TKS.Tests
|
||||||
var o3 = new ActionOutcome();
|
var o3 = new ActionOutcome();
|
||||||
yield return Act(zl, t1.Position, o3);
|
yield return Act(zl, t1.Position, o3);
|
||||||
Assert.Less(t2.Hp, t2.MaxHp, "명중 → 같은 방향 한 번 더 관통");
|
Assert.Less(t2.Hp, t2.MaxHp, "명중 → 같은 방향 한 번 더 관통");
|
||||||
Assert.IsFalse(o3.Returned, "복귀 대신 추가 관통");
|
Assert.IsFalse(o3.Returned, "추가 관통 · 복귀 없음");
|
||||||
Assert.AreEqual(t1.MaxHp - t1.Hp, o3.DamageDealt - (t2.MaxHp - t2.Hp), "앞서 관통한 적은 다시 맞지 않음");
|
Assert.AreEqual(t1.MaxHp - t1.Hp, o3.DamageDealt - (t2.MaxHp - t2.Hp), "앞서 관통한 적은 다시 맞지 않음");
|
||||||
var ps = Eff("zhangliao_p", SkillKeys.PierceDmgPctPerHit);
|
var ps = Eff("zhangliao_p", SkillKeys.PierceDmgPctPerHit);
|
||||||
int hits = bm.Skills.Of(zl).PierceHits;
|
int hits = bm.Skills.Of(zl).PierceHits;
|
||||||
|
|
@ -416,6 +416,7 @@ namespace TKS.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[UnityTest]
|
||||||
|
[Ignore("창병 견제 제거(PD 2026-11-10) — 대기 유닛이 생기지 않아 견제 스킬(청룡언월·후계·삼대 노신·교변·하북 명장 …)이 발동하지 않음 · 스킬 재설계 PD 결정 대기")]
|
||||||
public IEnumerator Spear_OverwatchSkills_Radius_Unlimited_Count_DmgMult_HealOnKill()
|
public IEnumerator Spear_OverwatchSkills_Radius_Unlimited_Count_DmgMult_HealOnKill()
|
||||||
{
|
{
|
||||||
yield return Fresh();
|
yield return Fresh();
|
||||||
|
|
@ -518,7 +519,7 @@ namespace TKS.Tests
|
||||||
float rem = CastContact(ch, t);
|
float rem = CastContact(ch, t);
|
||||||
var o3 = new ActionOutcome();
|
var o3 = new ActionOutcome();
|
||||||
yield return Act(ch, t.Position, o3);
|
yield return Act(ch, t.Position, o3);
|
||||||
Assert.AreEqual(rem * Eff("chengpu_a", SkillKeys.PushMult).P, t.Position.y, 0.12f, "push_mult(창병 = 밀며 전진 거리)");
|
Assert.AreEqual(rem * cfg.spearPushRatio * Eff("chengpu_a", SkillKeys.PushMult).P, t.Position.y, 0.12f, "push_mult(창병 밀치기 = 남은 거리 × spearPushRatio × p · PD 2026-11-10)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 궁병 · 책사 ----------
|
// ---------- 궁병 · 책사 ----------
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ Unity.exe -projectPath <proj> -executeMethod TKS.EditorTools.ProjectSetup.AutoPl
|
||||||
- 규칙 해석 변경 = `Assets/Scripts/Runtime/ActionResolver.cs`(병종 4종) · `BattleManager.cs`(턴·승패·창병 대기) · `Combat.cs`(피해 공식·경로 질의). 변경 시 `Assets/Tests/PlayMode/BattleFlowTests.cs` 에 테스트 추가 후 22종 이상 전부 통과 확인.
|
- 규칙 해석 변경 = `Assets/Scripts/Runtime/ActionResolver.cs`(병종 4종) · `BattleManager.cs`(턴·승패·창병 대기) · `Combat.cs`(피해 공식·경로 질의). 변경 시 `Assets/Tests/PlayMode/BattleFlowTests.cs` 에 테스트 추가 후 22종 이상 전부 통과 확인.
|
||||||
- 콜라이더 질의 전 `Physics2D.SyncTransforms()` · 유닛 제거는 `SetActive(false)` 후 `Destroy` · 자식 렌더러가 있는 표시물은 GameObject 통째로 끈다.
|
- 콜라이더 질의 전 `Physics2D.SyncTransforms()` · 유닛 제거는 `SetActive(false)` 후 `Destroy` · 자식 렌더러가 있는 표시물은 GameObject 통째로 끈다.
|
||||||
- **궁병 규칙(PD 2026-11-04 · #1080)**: 사거리 = `slingMaxDistance`(얇은 원). 조준 방향 사거리 안의 적(가장자리 간격 기준) = **제자리 사격** · 사거리 밖 ~ ×(1 + `archerRangeRatio`)(붉은 도넛) 안의 적 = 들어올 만큼만(최대 `ArcherMoveAllow` = 슬링 × 비율) 다가가 사격 · 더 멀면 **목표점까지 이동만**(#1079). 반격 없음 · 목표점 명중 크리. **근접 전투**(돌진·반격·관통·견제·밀려 닿은 공격)에서 궁병은 근접 유닛 상대 주는 피해 × `archerMeleeDealtRatio`(0.5) · 받는 피해 × `archerMeleeTakenRatio`(1.5) — `Combat.Damage(…, melee)` · 원거리 사격은 패널티 없음.
|
- **궁병 규칙(PD 2026-11-04 · #1080)**: 사거리 = `slingMaxDistance`(얇은 원). 조준 방향 사거리 안의 적(가장자리 간격 기준) = **제자리 사격** · 사거리 밖 ~ ×(1 + `archerRangeRatio`)(붉은 도넛) 안의 적 = 들어올 만큼만(최대 `ArcherMoveAllow` = 슬링 × 비율) 다가가 사격 · 더 멀면 **목표점까지 이동만**(#1079). 반격 없음 · 목표점 명중 크리. **근접 전투**(돌진·반격·관통·견제·밀려 닿은 공격)에서 궁병은 근접 유닛 상대 주는 피해 × `archerMeleeDealtRatio`(0.5) · 받는 피해 × `archerMeleeTakenRatio`(1.5) — `Combat.Damage(…, melee)` · 원거리 사격은 패널티 없음.
|
||||||
- **충돌 규칙(PD 2026-11-04 · #1080)** — `ActionResolver`: 보병 = 경로 첫 유닛(아군·적군)에 부딪히면 **알까기**(등속 접근 → 부딪힌 순간 피해·반격을 즉시 교환(`ImpactExchange`) → **양쪽이 동시에 튕김**: 상대는 접촉 방향으로 남은 거리 × `collisionPushRatio` · 자신은 반대 방향으로 남은 거리 × `collisionSelfBounceRatio` · PD #1081) · 기병 = 유닛 무시 관통(적 피해 · 아군은 지나칠 거리가 안 나오면 **그 아군 직전 정지** `StopBeforeUnclearedAllies`) · 창병 = 첫 유닛을 **밀며 함께 전진**(접촉 간격 유지 · 앞 유닛이 막히면 둘 다 정지 · 적이면 피해 · 아군은 밀기만) · **규칙 ⑤** = 밀린 같은 진영 유닛이 상대와 닿으면(`Combat.TouchingOpponent` · 간격 ≤ 0.12) 그 자리에서 근접 공격 1회(조작마다 · 자기 행동 소모 없음 · `ActionOutcome.Notes`). 적 AI 도 같은 코드(대칭). `UnitStats.attackRange` 는 창병 견제 반경만.
|
- **충돌 규칙(PD 2026-11-04 · #1080)** — `ActionResolver`: 보병 = 경로 첫 유닛(아군·적군)에 부딪히면 **알까기**(등속 접근 → 부딪힌 순간 피해·반격을 즉시 교환(`ImpactExchange`) → **양쪽이 동시에 튕김**: 상대는 접촉 방향으로 남은 거리 × `collisionPushRatio` · 자신은 반대 방향으로 남은 거리 × `collisionSelfBounceRatio` · PD #1081) · 기병 = 유닛 무시 관통(적 피해 · 아군은 지나칠 거리가 안 나오면 **그 아군 직전 정지** `StopBeforeUnclearedAllies`) · 창병 = 첫 유닛을 **크게 밀치고 자신은 적게 밀림**(남은 거리 × `spearPushRatio` 1.5 · 반작용 = 그 × `spearRecoilRatio` 0.2 · 적이면 피해 · 아군은 밀기만 · PD #1098 · 2026-11-10) · **규칙 ⑤** = 밀린 같은 진영 유닛이 상대와 닿으면(`Combat.TouchingOpponent` · 간격 ≤ 0.12) 그 자리에서 근접 공격 1회(조작마다 · 자기 행동 소모 없음 · `ActionOutcome.Notes`). 적 AI 도 같은 코드(대칭). `UnitStats.attackRange` = 옛 창병 견제 반경(견제 제거로 미사용).
|
||||||
- **사거리 표시(PD 2026-11-04)**: 유닛 탭(아군·적군) → `RangeIndicator`(슬링샷 최대 거리 = 얇은 반투명 원 · 궁병 = 슬링샷 범위 ~ ×(1+비율) 채워진 붉은 도넛 · 범위 안 적이 없으면 회색).
|
- **사거리 표시(PD 2026-11-04)**: 유닛 탭(아군·적군) → `RangeIndicator`(슬링샷 최대 거리 = 얇은 반투명 원 · 궁병 = 슬링샷 범위 ~ ×(1+비율) 채워진 붉은 도넛 · 범위 안 적이 없으면 회색).
|
||||||
- 보고는 실측·추정·미확인을 분리한다(C23 · C5). 커밋은 의도 변경 파일만.
|
- 보고는 실측·추정·미확인을 분리한다(C23 · C5). 커밋은 의도 변경 파일만.
|
||||||
|
|
||||||
|
|
@ -84,7 +84,7 @@ UI 를 새로 만들거나 고칠 때 **반드시** 이 구조를 따른다(어
|
||||||
- **IB(쿠로네코) 이펙트 시험(현재 기본 공격 · PD 2026-11-10 「내가 원한 비주얼은 이런게 아니야(이노센트베인 영상) → 우선 IB 이펙트를 그대로 써서 확인 · 나중에 구조에 맞게 새로 그림(화풍 통일)」)**: IB 슬링샷 발사 `UnitVisualEffectControl.ShowBoosterFX()` 와 같은 구성 — `ef_booster`(앞 빛 물결 2장 + 가시 · 0.2초 반복 맥동 · 유닛 위 · 이동 끝에 바로 지움 = IB Breaking 때 Kill) · `ef_speed_tail`(꼬리 0.6초 + 화살표 입자 · 유닛 아래 · 끝나면 꼬리가 줄며 사라짐 — `SkillVfx.Stop` 이 트레일 길이만큼 기다림) · `ef_booster_dust`(출발 먼지 1번) · 타격 `ef_normal_hit`. 원본 = `Assets/VFX/IB/`(쿠로네코 `E:\NerdNavis\Kuroneko\KuronekoClient` 에서 `Tools/wl_vfx_import.py copy … --wl <쿠로네코> --dest Assets/VFX/IB --skip-scripts` · GUID 유지 · 무수정) · 사본 = `Resources/Fx/TKS_Fx_IB_*`(`Tools/make_ib_fx_copies.cs` — IB 전용 스크립트 부품 제거 · 정렬 레이어 Default · 방향 있는 것은 감싸는 루트 + 안쪽 Z −90°(IB 정면 +Y → 이 게임 +X) · 소리 이벤트 받는 곳 `IbFxAnimEvents`(아무것도 안 함 · 없으면 재생마다 콘솔 오류)). 셰이더 = `Fingar/Additive`(더하기 섞기 · 빌트인 그대로). 2D 돌진 3차 행은 표에 `#` 주석(되돌리기). **진영색(PD 「진영 색을 입히고」)** = `TKS_Fx_IB_Booster_<진영>`(스프라이트 색의 색상만 진영색 · 채도·밝기 유지) · `TKS_Fx_IB_SpeedTail_<진영>`(꼬리 그림 `Assets/VFX/IB/_TKS/Tail_Basic02_<진영>.png` = `Tools/make_ib_camp_textures.py` · 재질 같은 폴더 · 화살표 입자 색) — 둘 다 `make_ib_fx_copies.cs` 가 `Tools/dash2d_camps.csv` 로 만듦 · 원색 사본 `TKS_Fx_IB_Booster` · `SpeedTail` 은 비교용으로 남음(표에서 안 씀). **사라지는 시점(PD 「멈춘 이후 뒤늦게 이펙트가 사라짐 · 위치 안 맞음」)** = 속도 꼬리 `f_fade` 0.12 — 멈춘 자리에서 꼬리째 흐려짐(`SkillVfxFade` 가 트레일 색 그러데이션 불투명도도 낮춤 · 전에는 0.6초 꼬리 머리가 기병 돌격 끝자리에 남아 복귀 중에도 보였음). **타격** = 현재 없음(PD 2026-11-10 「적이 피격되었을 때 히트 이펙트는 제거 · 어떤 히트 이펙트를 쓸지 알려줄게」 · IB NormalHit · BounceHit 행은 표에 `#` 주석). **기준점 `e_anchor` = dash**(빛 물결 · 꼬리 · PD 「복귀할 때 이펙트가 캐릭터보다 위쪽에 생겨 안 보임 → 유닛 바닥에」) = 위·옆으로 갈 때 몸 가운데 · 아래로 갈수록 발 쪽(곧장 아래 = 발). **흙먼지** = WL 창 돌진 흙먼지 `TKS_Fx_SpearRushDust`(0.15초마다 발밑 · PD 「복귀할 때 흙먼지가 너무 세서 뭉개짐 → WL 흙먼지로」 · IB 출발 먼지 행은 `#` 주석).
|
- **IB(쿠로네코) 이펙트 시험(현재 기본 공격 · PD 2026-11-10 「내가 원한 비주얼은 이런게 아니야(이노센트베인 영상) → 우선 IB 이펙트를 그대로 써서 확인 · 나중에 구조에 맞게 새로 그림(화풍 통일)」)**: IB 슬링샷 발사 `UnitVisualEffectControl.ShowBoosterFX()` 와 같은 구성 — `ef_booster`(앞 빛 물결 2장 + 가시 · 0.2초 반복 맥동 · 유닛 위 · 이동 끝에 바로 지움 = IB Breaking 때 Kill) · `ef_speed_tail`(꼬리 0.6초 + 화살표 입자 · 유닛 아래 · 끝나면 꼬리가 줄며 사라짐 — `SkillVfx.Stop` 이 트레일 길이만큼 기다림) · `ef_booster_dust`(출발 먼지 1번) · 타격 `ef_normal_hit`. 원본 = `Assets/VFX/IB/`(쿠로네코 `E:\NerdNavis\Kuroneko\KuronekoClient` 에서 `Tools/wl_vfx_import.py copy … --wl <쿠로네코> --dest Assets/VFX/IB --skip-scripts` · GUID 유지 · 무수정) · 사본 = `Resources/Fx/TKS_Fx_IB_*`(`Tools/make_ib_fx_copies.cs` — IB 전용 스크립트 부품 제거 · 정렬 레이어 Default · 방향 있는 것은 감싸는 루트 + 안쪽 Z −90°(IB 정면 +Y → 이 게임 +X) · 소리 이벤트 받는 곳 `IbFxAnimEvents`(아무것도 안 함 · 없으면 재생마다 콘솔 오류)). 셰이더 = `Fingar/Additive`(더하기 섞기 · 빌트인 그대로). 2D 돌진 3차 행은 표에 `#` 주석(되돌리기). **진영색(PD 「진영 색을 입히고」)** = `TKS_Fx_IB_Booster_<진영>`(스프라이트 색의 색상만 진영색 · 채도·밝기 유지) · `TKS_Fx_IB_SpeedTail_<진영>`(꼬리 그림 `Assets/VFX/IB/_TKS/Tail_Basic02_<진영>.png` = `Tools/make_ib_camp_textures.py` · 재질 같은 폴더 · 화살표 입자 색) — 둘 다 `make_ib_fx_copies.cs` 가 `Tools/dash2d_camps.csv` 로 만듦 · 원색 사본 `TKS_Fx_IB_Booster` · `SpeedTail` 은 비교용으로 남음(표에서 안 씀). **사라지는 시점(PD 「멈춘 이후 뒤늦게 이펙트가 사라짐 · 위치 안 맞음」)** = 속도 꼬리 `f_fade` 0.12 — 멈춘 자리에서 꼬리째 흐려짐(`SkillVfxFade` 가 트레일 색 그러데이션 불투명도도 낮춤 · 전에는 0.6초 꼬리 머리가 기병 돌격 끝자리에 남아 복귀 중에도 보였음). **타격** = 현재 없음(PD 2026-11-10 「적이 피격되었을 때 히트 이펙트는 제거 · 어떤 히트 이펙트를 쓸지 알려줄게」 · IB NormalHit · BounceHit 행은 표에 `#` 주석). **기준점 `e_anchor` = dash**(빛 물결 · 꼬리 · PD 「복귀할 때 이펙트가 캐릭터보다 위쪽에 생겨 안 보임 → 유닛 바닥에」) = 위·옆으로 갈 때 몸 가운데 · 아래로 갈수록 발 쪽(곧장 아래 = 발). **흙먼지** = WL 창 돌진 흙먼지 `TKS_Fx_SpearRushDust`(0.15초마다 발밑 · PD 「복귀할 때 흙먼지가 너무 세서 뭉개짐 → WL 흙먼지로」 · IB 출발 먼지 행은 `#` 주석).
|
||||||
- **방향 결정 UI = IB 이식(PD 2026-11-10 「IB 와 같이 슬링샷 조작 시 방향 결정 UI 등을 그대로 이식」)**: `TKS.IbAimArrow`(IB `GameTouchArrow` 동작 · 프리팹 `Resources/Fx/TKS_Fx_IB_GameArrow` = 원본 `Assets/VFX/IB/AssetResources/Game/GameArrow` 사본) — 누르면 유닛 둘레 고리 + 퍼지는 고리 3개(3초 · 1초 간격) · 당기면 하늘색 화살표가 쏠 방향으로 돌고 당긴 세기만큼 길어짐(0.5~1.5 · 최소 당김 → 슬링샷 최대 거리) · 화살 끝이 전장 밖이면 안으로 · 놓으면 숨김. 전에 쓰던 조준선 · 당김선은 끔(`SlingshotInput.ShowPreview` · IbAimArrow 가 없을 때만 예전 선). 목표 고리 · 강조 · 궁병 화살 경로선 · 기믹 예상 궤적은 그대로. 화살표 색 = IB 공격 종류 3(하늘색) 하나.
|
- **방향 결정 UI = IB 이식(PD 2026-11-10 「IB 와 같이 슬링샷 조작 시 방향 결정 UI 등을 그대로 이식」)**: `TKS.IbAimArrow`(IB `GameTouchArrow` 동작 · 프리팹 `Resources/Fx/TKS_Fx_IB_GameArrow` = 원본 `Assets/VFX/IB/AssetResources/Game/GameArrow` 사본) — 누르면 유닛 둘레 고리 + 퍼지는 고리 3개(3초 · 1초 간격) · 당기면 하늘색 화살표가 쏠 방향으로 돌고 당긴 세기만큼 길어짐(0.5~1.5 · 최소 당김 → 슬링샷 최대 거리) · 화살 끝이 전장 밖이면 안으로 · 놓으면 숨김. 전에 쓰던 조준선 · 당김선은 끔(`SlingshotInput.ShowPreview` · IbAimArrow 가 없을 때만 예전 선). 목표 고리 · 강조 · 궁병 화살 경로선 · 기믹 예상 궤적은 그대로. 화살표 색 = IB 공격 종류 3(하늘색) 하나.
|
||||||
- **여포 크기(PD 2026-11-10 「왜 여포는 다른 기병보다 작지?」)**: 말 탄 유닛은 머리 끝 상한(BalanceConfig.mountedArtMaxTop 1.3 × 표준 키)에 맞춰 줄어드는데, `dz_boss_lvbu` 의 몸 높이(`meta.json` bodyHeightPx · `art_size.csv` body_px)가 머리 깃털 끝까지 819 px 라 몸 전체가 더 줄었음 → 왕관 끝 ~ 발 718 px(front_idle 실측 · 깃털 제외 · 다른 기병의 투구 끝 기준과 같은 규칙)로 고침 → 배율 0.2381 → 0.2716(+14 %). 원본 백업 = 세션 scratchpad. **다른 PC 에서 그림을 다시 export 하면 819 로 되돌아갈 수 있음**(export 규칙 = 불투명 폭 30 % 행 — 깃털이 넓어서 안 걸러짐).
|
- **여포 크기(PD 2026-11-10 「왜 여포는 다른 기병보다 작지?」)**: 말 탄 유닛은 머리 끝 상한(BalanceConfig.mountedArtMaxTop 1.3 × 표준 키)에 맞춰 줄어드는데, `dz_boss_lvbu` 의 몸 높이(`meta.json` bodyHeightPx · `art_size.csv` body_px)가 머리 깃털 끝까지 819 px 라 몸 전체가 더 줄었음 → 왕관 끝 ~ 발 718 px(front_idle 실측 · 깃털 제외 · 다른 기병의 투구 끝 기준과 같은 규칙)로 고침 → 배율 0.2381 → 0.2716(+14 %). 원본 백업 = 세션 scratchpad. **다른 PC 에서 그림을 다시 export 하면 819 로 되돌아갈 수 있음**(export 규칙 = 불투명 폭 30 % 행 — 깃털이 넓어서 안 걸러짐).
|
||||||
- **기병 복귀 영역(PD 2026-11-10 「기병도 궁수와 같이 … 복귀 발동 지점」 → 「궁병처럼 채워진 영역 형태로」)**: 아군 기병을 선택·조준하면 적마다 (유닛 반지름 + 최종 명중 허용 거리) 하늘색 채운 원 = 목표점을 이 안에 놓으면 공격 뒤 복귀(`ActionResolver.PreviewCavalryReturn` 이 참 · 슬링샷 거리 안인 적만 · 지금 노리는 원 진하게 · 유닛 위 정렬 11 — 바닥이면 적 그림에 가려 안 보임 · `SlingshotInput.ShowReturnZones`). 예약 액티브(위진소요 · 단기구주)는 발동 전이라 판정에 안 들어감.
|
- **근접 3병종 특징 = 한 줄 정의(PD 2026-11-10 직접 지시)**: **보병** = 적을 밀치고 튕겨나며 튕기면서 다른 적을 추가로 공격(알까기 + 연쇄 · #1098 그대로 · 목표점 명중 추가 이동 1회는 유지 = PD 확인 대상) · **창병** = 적을 크게 밀치지만 자신은 적게 밀림(`spearPushRatio` 1.5 > 보병 `collisionPushRatio` 1.0 · 반작용 남은 거리 × 0.3 < 보병 자기 튕김 0.5) · **창병 대기(견제) 제거**(「유저가 인지하기 어려움」 · 목표점 명중해도 `SetOverwatch` 안 함 · HUD 「견제 반경·대기 중」 문구 삭제) · **기병** = 적을 관통해 이동 · **목표점 명중 시 제자리 복귀 제거**(「유저가 인지하기 어려움」 · `PreviewCavalryReturn` · 조준 때 복귀 영역 `ShowReturnZones` 삭제 · AI 가중치 `cav_return`·`cav_stranded` 삭제). 견제·복귀 스킬(청룡언월 · 후계 · 구벌중원 · 기린아 · 교변 · 삼대 노신 · 유수의 복수 · 장사 · 백마의 창 · 하북 명장 · 남만의 여왕 · 장사 태수 · 단기구주 · 도주 본능 · 소패왕) = 발동 안 함 · **재설계 PD 결정 대기** → 그때까지 `BattleManager.ApplyOverwatch` · `Unit.Overwatch` · `SkillMods.Overwatch*` 는 남겨 둠(대기 유닛이 없어 실행 안 됨) · `SkillTests.Spear_OverwatchSkills_*` = [Ignore].
|
||||||
- **IB 크기 · 캐릭터 연출(PD 2026-11-10 「IB 와 화살표 크기도 다르고 선택된 캐릭터 주위 연출도 누락」)**: 값 표 `Resources/Skills/ib_ui.csv`(`TKS.IbUi`) — IB 유닛 반지름 0.496(ExportCharacter size 8 × 0.062) 기준 배율 = 이 게임 유닛 반지름 / 0.496 → 고리 지름 = 반지름 × 5.4 · 화살표 세기 s = IB 월드 당김 × 0.6(0.5~1.5) · IB 취소 거리 0.5. **`TKS.IbUnitMarks`** = 선택한 행동 가능 아군 발밑 도는 원(`TKS_Fx_IB_TargetCircle` · IB ShowUserTurnMark) + 몸 둘레 빛(`TKS_Fx_IB_AttackReady`) · 조준 중(누름 ~ 놓음) 모든 유닛 속성 방울(`TKS_Fx_IB_Attribute_<속성>` · IB SetActiveBarrierVisualEffect · 진영 → 속성 = 표 element_*: 위 물 · 촉 나무 · 오 불 · 군·진영 없음 어둠).
|
- **IB 크기 · 캐릭터 연출(PD 2026-11-10 「IB 와 화살표 크기도 다르고 선택된 캐릭터 주위 연출도 누락」)**: 값 표 `Resources/Skills/ib_ui.csv`(`TKS.IbUi`) — IB 유닛 반지름 0.496(ExportCharacter size 8 × 0.062) 기준 배율 = 이 게임 유닛 반지름 / 0.496 → 고리 지름 = 반지름 × 5.4 · 화살표 세기 s = IB 월드 당김 × 0.6(0.5~1.5) · IB 취소 거리 0.5. **`TKS.IbUnitMarks`** = 선택한 행동 가능 아군 발밑 도는 원(`TKS_Fx_IB_TargetCircle` · IB ShowUserTurnMark) + 몸 둘레 빛(`TKS_Fx_IB_AttackReady`) · 조준 중(누름 ~ 놓음) 모든 유닛 속성 방울(`TKS_Fx_IB_Attribute_<속성>` · IB SetActiveBarrierVisualEffect · 진영 → 속성 = 표 element_*: 위 물 · 촉 나무 · 오 불 · 군·진영 없음 어둠).
|
||||||
- **타격감(PD 2026-11-10 「화면 흔들림 등도 IB 를 참고해서」)**: `TKS.HitFeel` + 표 `Resources/Skills/hit_feel.csv`(행동 중 유닛이 상대에게 처음 피해를 준 순간 · `SkillSystem.Hit` · 흔들림 = `CameraController.Shake`(추적 위치는 그대로 두고 흔들림만 더함) · 히트 스톱 칸 있음(현재 0)). **IB 실측**: 흔들림 함수 6종은 스킬·궁극기·기믹 이펙트 애니메이션 이벤트로만 불리고 본문이 디컴파일에서 비어 있어 값 없음 · 일반 타격·벽 튕김엔 흔들림 없음 · 히트 스톱 없음 → 표 값(hit 0.15초 · 키 10 % / 기병 0.18초 · 13 %)은 추정.
|
- **타격감(PD 2026-11-10 「화면 흔들림 등도 IB 를 참고해서」)**: `TKS.HitFeel` + 표 `Resources/Skills/hit_feel.csv`(행동 중 유닛이 상대에게 처음 피해를 준 순간 · `SkillSystem.Hit` · 흔들림 = `CameraController.Shake`(추적 위치는 그대로 두고 흔들림만 더함) · 히트 스톱 칸 있음(현재 0)). **IB 실측**: 흔들림 함수 6종은 스킬·궁극기·기믹 이펙트 애니메이션 이벤트로만 불리고 본문이 디컴파일에서 비어 있어 값 없음 · 일반 타격·벽 튕김엔 흔들림 없음 · 히트 스톱 없음 → 표 값(hit 0.15초 · 키 10 % / 기병 0.18초 · 13 %)은 추정.
|
||||||
- **2D 돌진(PD 2026-11-10 「기본 공격(돌진) 이펙트가 너무 3D 느낌 → 2D 처럼」 · MSFX 혜성 행은 표에 `#` 주석으로 남김 = 되돌리기)**: **3차(현재 · PD 2026-11-10 「검기는 이전 형태가 더 나음 · 대쉬 이펙트는 캐릭터 몸을 벗어나지 않도록 · 첨부 같은 느낌으로 몸을 뒤덮는 연출을 스킬용 시험」)** = 초승달 `Head` = 1차 그림 `dash2d_crescent`(검정 외곽선 · 진영색 곱) · 대쉬 층(꼬리 0.04초 · 바람 줄기 · 알갱이 · 흙먼지) = 유닛을 따라 움직이는 Local 공간 · 몸 기준점 반경 0.45 키 안(실측 그림 경계 좌우 ±0.56 · 아래 −0.60 ~ 위 +0.59 키) · 바람 줄기 `Lines` · 알갱이 `Dots` = 유닛 위 반투명(표 `s_over` = `Head;Lines;Dots` · 유닛 아래면 몸에 다 가려 안 보임 · PD 「몸의 대쉬 효과가 왜 안 보이나」) · 꼬리 · 흙먼지 = 유닛 아래 · **기병 복귀 = 돌아서서 감**(`UnitView.TurnAround` · 복귀 방향으로 보기 + 곧장 위·아래면 좌우 반전 · 도착 뒤에도 마지막 이동 방향 PD #1082) · **몸 뒤덮는 기운(시험)** = `TKS_Fx_Aura2D_<진영>`(혜성 기운 `aura_comet_<진영>` 겹쳐 깜빡임 · 불꽃 혀 `aura_wisp` · 반짝임 = 유닛 아래 · 옅은 막 `Veil` · 앞 테 `Rim`(2차 검기 그림) = 유닛 위) · 표 키 `test_aura` · 테스트 씬 「기운 시험」 스위치(`SkillVfx.TestTravelKey` · 켜면 슬링샷 공격 돌진에 함께 · 실제 전투 = 늘 빈칸). 2차 = 외곽선 없음 · 그림 `Assets/VFX/TKS2D/`(만들기 = `python -I -X utf8 Tools/make_dash2d_textures.py` · 진영색 원본 = `Tools/dash2d_camps.csv`) → 프리팹 `Resources/Fx/TKS_Fx_Dash2D_<wei|shu|wu|other>`(검기 초승달 `Head` = 진영별 그림 `dash2d_slash_<진영>` · 유닛 위 · 반투명 꼬리 `Trail`(앞 0.7 → 0) · 흰 바람 줄기 `Lines` · 알갱이 `Dots` = 유닛 아래 · 수명 동안 투명해짐) + `TKS_Fx_Dust2D`(부드러운 반투명 흙먼지 · 모든 진영) · 다시 만들기 = `unity command … script eval --file Tools/build_dash2d.cs`(값을 바꾸면 이 파일과 프리팹을 함께). 1차(검정 외곽선 셀 그림)의 `dash2d_crescent.png` · `dash2d_highlight.png` 는 안 씀. 표 `b_flat` = 1 이면 화면 안에서 진행 방향으로만 돌림(기울기·팩 루트 회전 무시 · `SkillVfx.FlatRotation`) · 크기 단위 = 유닛 키 1(`f_scale` 1) · 끝 소멸 = `f_fade` 0.12(`SkillVfxFade` 가 스프라이트 불투명도도 낮춤).
|
- **2D 돌진(PD 2026-11-10 「기본 공격(돌진) 이펙트가 너무 3D 느낌 → 2D 처럼」 · MSFX 혜성 행은 표에 `#` 주석으로 남김 = 되돌리기)**: **3차(현재 · PD 2026-11-10 「검기는 이전 형태가 더 나음 · 대쉬 이펙트는 캐릭터 몸을 벗어나지 않도록 · 첨부 같은 느낌으로 몸을 뒤덮는 연출을 스킬용 시험」)** = 초승달 `Head` = 1차 그림 `dash2d_crescent`(검정 외곽선 · 진영색 곱) · 대쉬 층(꼬리 0.04초 · 바람 줄기 · 알갱이 · 흙먼지) = 유닛을 따라 움직이는 Local 공간 · 몸 기준점 반경 0.45 키 안(실측 그림 경계 좌우 ±0.56 · 아래 −0.60 ~ 위 +0.59 키) · 바람 줄기 `Lines` · 알갱이 `Dots` = 유닛 위 반투명(표 `s_over` = `Head;Lines;Dots` · 유닛 아래면 몸에 다 가려 안 보임 · PD 「몸의 대쉬 효과가 왜 안 보이나」) · 꼬리 · 흙먼지 = 유닛 아래 · **기병 복귀 = 돌아서서 감**(`UnitView.TurnAround` · 복귀 방향으로 보기 + 곧장 위·아래면 좌우 반전 · 도착 뒤에도 마지막 이동 방향 PD #1082) · **몸 뒤덮는 기운(시험)** = `TKS_Fx_Aura2D_<진영>`(혜성 기운 `aura_comet_<진영>` 겹쳐 깜빡임 · 불꽃 혀 `aura_wisp` · 반짝임 = 유닛 아래 · 옅은 막 `Veil` · 앞 테 `Rim`(2차 검기 그림) = 유닛 위) · 표 키 `test_aura` · 테스트 씬 「기운 시험」 스위치(`SkillVfx.TestTravelKey` · 켜면 슬링샷 공격 돌진에 함께 · 실제 전투 = 늘 빈칸). 2차 = 외곽선 없음 · 그림 `Assets/VFX/TKS2D/`(만들기 = `python -I -X utf8 Tools/make_dash2d_textures.py` · 진영색 원본 = `Tools/dash2d_camps.csv`) → 프리팹 `Resources/Fx/TKS_Fx_Dash2D_<wei|shu|wu|other>`(검기 초승달 `Head` = 진영별 그림 `dash2d_slash_<진영>` · 유닛 위 · 반투명 꼬리 `Trail`(앞 0.7 → 0) · 흰 바람 줄기 `Lines` · 알갱이 `Dots` = 유닛 아래 · 수명 동안 투명해짐) + `TKS_Fx_Dust2D`(부드러운 반투명 흙먼지 · 모든 진영) · 다시 만들기 = `unity command … script eval --file Tools/build_dash2d.cs`(값을 바꾸면 이 파일과 프리팹을 함께). 1차(검정 외곽선 셀 그림)의 `dash2d_crescent.png` · `dash2d_highlight.png` 는 안 씀. 표 `b_flat` = 1 이면 화면 안에서 진행 방향으로만 돌림(기울기·팩 루트 회전 무시 · `SkillVfx.FlatRotation`) · 크기 단위 = 유닛 키 1(`f_scale` 1) · 끝 소멸 = `f_fade` 0.12(`SkillVfxFade` 가 스프라이트 불투명도도 낮춤).
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue