2026-11-05 12:11:10 +00:00
using System.Collections.Generic ;
using UnityEngine ;
using UnityEngine.EventSystems ;
using UnityEngine.UI ;
namespace TKS.OutGame
{
/// <summary>
/// 편성(img08 · 기획 S12 v3) — 프리셋 3 · 전열 3(윗줄)·후열 2(아랫줄) 원형 자리 · 하단 보유 목록(장수|병사 · 2줄 카드 · 출전 중) ·
/// 탭(자리 고르기 → 카드 탭 = 넣기 · 같은 자리 두 번 = 빼기 · 다른 자리 = 맞바꾸기) · 끌기(자리 ↔ 자리 · 카드 → 자리) · 저장 = 그 프리셋을 출전 편성으로.
/// 빈 칸 허용 · 최소 1 · 편성 제약 없음(D4).
/// </summary>
public class FormationScreen : OutGameScreen
{
int preset ;
readonly List < string > work = new List < string > ( ) ;
bool dirty ;
int selected = - 1 ;
bool soldiers ;
readonly List < Button > presetBtns = new List < Button > ( ) ;
readonly List < RectTransform > slots = new List < RectTransform > ( ) ;
Button tabHero ;
Button tabSoldier ;
RectTransform list ;
ScrollRect scroll ;
RectTransform ghost ;
public IList < string > Working
{
get { return work ; }
}
public int Preset
{
get { return preset ; }
}
public bool Dirty
{
get { return dirty ; }
}
public override void Build ( )
{
int n = Root . Battle . Config . playerSpawnPoints . Length ;
int front = Mathf . Clamp ( S . Db . Economy . FormationFront , 0 , n ) ;
Header ( "편성" , "출정 좌측 · 출전 " + n + "칸" , true ) ;
var body = Body ( ) ;
for ( int i = 0 ; i < S . Save . presets . Count ; i + + )
{
int k = i ;
presetBtns . Add ( UiKit . Btn ( "Preset" + i , body , "프리셋 " + ( i + 1 ) , 34 , UiKit . Paper , UiKit . Ink , new Vector2 ( 0f , 1f ) , new Vector2 ( 0f , 1f ) , new Vector2 ( 24f + i * 226f , - 18f ) , new Vector2 ( 210f , 84f ) , ( ) = > LoadPreset ( k ) ) ) ;
}
var chip = UiKit . Box ( "Free" , body , new Vector2 ( 1f , 1f ) , new Vector2 ( 1f , 1f ) , new Vector2 ( - 24f , - 24f ) , new Vector2 ( 270f , 72f ) , UiKit . Dark ) ;
UiKit . FillLabel ( chip . transform , "편성 제약 없음" , 30 , UiKit . White ) ;
var board = UiKit . TopBand ( "Board" , body , 122f , 600f , 24f , 24f ) ;
var bimg = UiKit . Paint ( board , UiKit . Paper ) ;
UiKit . Border ( bimg , UiKit . Line , 3f ) ;
Pill ( board , "전열" , UiKit . Red , - 116f ) ;
Pill ( board , "후열" , UiKit . Dark , - 380f ) ;
UiKit . Label ( board , "탭·드래그 = 자리 교체 · 같은 자리 두 번 탭 = 빼기" , 24 , UiKit . InkSoft , TextAnchor . MiddleLeft , new Vector2 ( 0f , 0f ) , new Vector2 ( 0f , 0f ) , new Vector2 ( 24f , 8f ) , new Vector2 ( 900f , 40f ) , false ) ;
for ( int i = 0 ; i < n ; i + + )
{
bool f = i < front ;
int col = f ? i : i - front ;
int cnt = f ? front : n - front ;
float gap = 264f ;
float x = 70f + ( col - ( cnt - 1 ) / 2f ) * gap ;
float y = f ? - 30f : - 290f ;
var s = UiKit . Rect ( "Slot" + i , board , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0.5f , 1f ) , new Vector2 ( x , y ) , new Vector2 ( 230f , 250f ) ) ;
var hit = s . gameObject . AddComponent < Image > ( ) ;
hit . color = new Color ( 1f , 1f , 1f , 0f ) ;
hit . raycastTarget = true ;
var td = s . gameObject . AddComponent < TapDrag > ( ) ;
td . AnyDirection = true ;
int k = i ;
td . OnTap = ( ) = > TapSlot ( k ) ;
td . OnDragStart = e = > StartGhost ( work [ k ] , e ) ;
td . OnDragMove = MoveGhost ;
td . OnDragEnd = e = > DropFromSlot ( k , e ) ;
slots . Add ( s ) ;
}
var row = UiKit . TopBand ( "ListHead" , body , 738f , 84f , 24f , 24f ) ;
UiKit . Label ( row , "보유 영웅" , 42 , UiKit . Ink , TextAnchor . MiddleLeft , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 0f , 0.5f ) , Vector2 . zero , new Vector2 ( 300f , 70f ) ) ;
tabHero = UiKit . Btn ( "TabHero" , row , "장수" , 32 , UiKit . Red , UiKit . White , new Vector2 ( 1f , 0.5f ) , new Vector2 ( 1f , 0.5f ) , new Vector2 ( - 440f , 0f ) , new Vector2 ( 190f , 76f ) , ( ) = > { soldiers = false ; Refresh ( ) ; } ) ;
tabSoldier = UiKit . Btn ( "TabSoldier" , row , "병사" , 32 , UiKit . Paper , UiKit . Ink , new Vector2 ( 1f , 0.5f ) , new Vector2 ( 1f , 0.5f ) , new Vector2 ( - 240f , 0f ) , new Vector2 ( 190f , 76f ) , ( ) = > { soldiers = true ; Refresh ( ) ; } ) ;
UiKit . Btn ( "Save" , row , "저장" , 34 , UiKit . Red , UiKit . White , new Vector2 ( 1f , 0.5f ) , new Vector2 ( 1f , 0.5f ) , Vector2 . zero , new Vector2 ( 220f , 76f ) , SaveNow ) ;
list = UiKit . ScrollGrid ( "List" , body , 24f , 24f , 836f , 12f , new Vector2 ( 506f , 150f ) , new Vector2 ( 20f , 18f ) , 2 , out scroll ) ;
}
static void Pill ( RectTransform board , string label , Color c , float y )
{
var p = UiKit . Box ( "Row_" + label , board , new Vector2 ( 0f , 1f ) , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 20f , y ) , new Vector2 ( 140f , 62f ) , c ) ;
UiKit . FillLabel ( p . transform , label , 32 , UiKit . White ) ;
}
public override void OnShow ( )
{
LoadPreset ( S . Save . presetIdx , true ) ;
}
public override bool OnBack ( )
{
if ( dirty ) Root . Toast ( "저장하지 않은 편성 변경은 취소됩니다" ) ;
dirty = false ;
return false ;
}
public void LoadPreset ( int idx , bool quiet = false )
{
if ( dirty & & ! quiet ) Root . Toast ( "저장하지 않은 변경 취소" ) ;
preset = Mathf . Clamp ( idx , 0 , S . Save . presets . Count - 1 ) ;
work . Clear ( ) ;
work . AddRange ( S . Save . presets [ preset ] . slots ) ;
dirty = false ;
selected = - 1 ;
Refresh ( ) ;
}
int Filled ( )
{
int n = 0 ;
foreach ( var id in work )
if ( ! string . IsNullOrEmpty ( id ) ) n + + ;
return n ;
}
public void TapSlot ( int i )
{
if ( selected < 0 )
{
selected = i ;
}
else if ( selected = = i )
{
if ( ! string . IsNullOrEmpty ( work [ i ] ) )
{
if ( Filled ( ) > 1 )
{
work [ i ] = "" ;
dirty = true ;
}
else Root . Toast ( "최소 1명은 출전해야 합니다" ) ;
}
selected = - 1 ;
}
else
{
Swap ( selected , i ) ;
selected = - 1 ;
}
Refresh ( ) ;
}
public void TapCard ( string id )
{
int at = work . IndexOf ( id ) ;
if ( at > = 0 )
{
if ( Filled ( ) > 1 )
{
work [ at ] = "" ;
dirty = true ;
}
else Root . Toast ( "최소 1명은 출전해야 합니다" ) ;
}
else
{
int target = selected > = 0 ? selected : work . IndexOf ( "" ) ;
if ( target < 0 ) Root . Toast ( "빈 자리가 없습니다 — 자리를 먼저 탭해 고른 뒤 카드를 탭(교체)" ) ;
else
{
work [ target ] = id ;
dirty = true ;
}
}
selected = - 1 ;
Refresh ( ) ;
}
/// <summary>id 를 자리 slot 에 넣는다(이미 다른 자리에 있으면 맞바꿈).</summary>
public void Place ( string id , int slot )
{
if ( slot < 0 | | slot > = work . Count | | string . IsNullOrEmpty ( id ) ) return ;
int at = work . IndexOf ( id ) ;
if ( at > = 0 ) Swap ( at , slot ) ;
else
{
work [ slot ] = id ;
dirty = true ;
}
Refresh ( ) ;
}
public void Swap ( int a , int b )
{
if ( a = = b ) return ;
var t = work [ a ] ;
work [ a ] = work [ b ] ;
work [ b ] = t ;
dirty = true ;
}
public void SaveNow ( )
{
var why = S . SavePreset ( preset , work ) ;
if ( why ! = null )
{
Root . Toast ( why ) ;
return ;
}
dirty = false ;
Root . Toast ( "프리셋 " + ( preset + 1 ) + " 저장 · 출전 편성으로 지정" ) ;
Refresh ( ) ;
}
// ---------- 끌기 ----------
void StartGhost ( string id , PointerEventData e )
{
if ( string . IsNullOrEmpty ( id ) ) return ;
if ( ghost ! = null ) Object . Destroy ( ghost . gameObject ) ;
ghost = UiKit . Portrait ( "Ghost" , Root . TopRoot , S . Def ( id ) , new Vector2 ( 0.5f , 0.5f ) , new Vector2 ( 0.5f , 0.5f ) , Vector2 . zero , new Vector2 ( 170f , 170f ) , true , 2.1f , UiKit . Red ) ;
ghost . gameObject . name = "Ghost_" + id ;
MoveGhost ( e ) ;
}
void MoveGhost ( PointerEventData e )
{
if ( ghost = = null ) return ;
Vector2 lp ;
if ( RectTransformUtility . ScreenPointToLocalPointInRectangle ( Root . TopRoot , e . position , Root . Cam , out lp ) ) ghost . anchoredPosition = lp ;
}
int SlotAt ( Vector2 screen )
{
for ( int i = 0 ; i < slots . Count ; i + + )
if ( RectTransformUtility . RectangleContainsScreenPoint ( slots [ i ] , screen , Root . Cam ) ) return i ;
return - 1 ;
}
void DropFromSlot ( int from , PointerEventData e )
{
EndGhost ( ) ;
int to = SlotAt ( e . position ) ;
if ( to < 0 | | to = = from ) return ;
Swap ( from , to ) ;
selected = - 1 ;
Refresh ( ) ;
}
void DropCard ( string id , PointerEventData e )
{
EndGhost ( ) ;
int to = SlotAt ( e . position ) ;
if ( to > = 0 ) Place ( id , to ) ;
}
void EndGhost ( )
{
if ( ghost = = null ) return ;
ghost . gameObject . SetActive ( false ) ;
Object . Destroy ( ghost . gameObject ) ;
ghost = null ;
}
// ---------- 그리기 ----------
public override void Refresh ( )
{
for ( int i = 0 ; i < presetBtns . Count ; i + + )
{
bool on = i = = preset ;
presetBtns [ i ] . GetComponent < Image > ( ) . color = on ? UiKit . Red : UiKit . Paper ;
var t = UiKit . LabelOf ( presetBtns [ i ] ) ;
t . color = on ? UiKit . White : UiKit . Ink ;
t . text = "프리셋 " + ( i + 1 ) + ( i = = S . Save . presetIdx ? " <size=22>출전</size>" : "" ) + ( on & & dirty ? "*" : "" ) ;
}
int front = S . Db . Economy . FormationFront ;
for ( int i = 0 ; i < slots . Count ; i + + )
{
var s = slots [ i ] ;
UiKit . Clear ( s ) ;
var id = i < work . Count ? work [ i ] : "" ;
var h = S . Def ( id ) ;
if ( i = = selected ) UiKit . Dot ( "Select" , s , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0f , 12f ) , 236f , UiKit . Star ) ;
Color ring = i < front ? UiKit . Red : UiKit . Dark ;
UiKit . Dot ( "Ring" , s , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0f , 0f ) , 212f , ring ) ;
if ( h ! = null )
{
UiKit . Portrait ( "Face" , s , h , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0f , - 10f ) , new Vector2 ( 192f , 192f ) , true , 2.1f , new Color ( 0.94f , 0.91f , 0.85f ) ) ;
var tag = UiKit . Box ( "Name" , s , new Vector2 ( 0.5f , 0f ) , new Vector2 ( 0.5f , 0f ) , new Vector2 ( 0f , 4f ) , new Vector2 ( 170f , 50f ) , UiKit . Dark ) ;
UiKit . FillLabel ( tag . transform , h . nameKo , 28 , UiKit . White ) ;
}
else
{
2026-11-09 03:20:32 +00:00
UiKit . Dot ( "Empty" , s , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0f , - 10f ) , 192f , UiKit . Slot ) ;
2026-11-05 12:11:10 +00:00
UiKit . Label ( s , "+\n<size=26>빈 칸</size>" , 60 , UiKit . InkSoft , TextAnchor . MiddleCenter , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0f , - 40f ) , new Vector2 ( 200f , 140f ) ) ;
}
}
tabHero . GetComponent < Image > ( ) . color = soldiers ? UiKit . Paper : UiKit . Red ;
UiKit . LabelOf ( tabHero ) . color = soldiers ? UiKit . Ink : UiKit . White ;
tabSoldier . GetComponent < Image > ( ) . color = soldiers ? UiKit . Red : UiKit . Paper ;
UiKit . LabelOf ( tabSoldier ) . color = soldiers ? UiKit . White : UiKit . Ink ;
UiKit . Clear ( list ) ;
foreach ( var h in S . Owned ( soldiers ) )
{
var id = h . id ;
var card = HeroCards . Card ( list , S , h , work . Contains ( id ) , true , null ) ;
var td = card . gameObject . AddComponent < TapDrag > ( ) ;
td . OnTap = ( ) = > TapCard ( id ) ;
td . OnDragStart = e = > StartGhost ( id , e ) ;
td . OnDragMove = MoveGhost ;
td . OnDragEnd = e = > DropCard ( id , e ) ;
}
}
}
}