성능상 개선 필요한 부분[ ]
적 ai도 리스트 이용하도록[ 완료 ]
클래스를 기반으로 한 객체 생성은 이미 하고 있습니다. 다만, 클래스 이름을 PlayerHero, EnemyHero로 나누지 않고, 하나의 Hero 클래스로 **여러 개의 '인스턴스(실체)'**를 만든 뒤, 그들이 누구인지 구분만 불리언으로 하고 있는 것입니다.
(손패도 하나로 합칠수있나 )
근데 그러면 현재 손패에 어떤 카드들이 있는지는 어디에다가 저장하는지
아 어차피, 해당 클래스안의 배열이나 그런곳(멤버변수에 담으면 되니깐
역할(AI vs Player)이 다르다는 점 때문에 나누어져 있습니다.
ai때문에
플레이어의 HandManager에는 아직 리스트(List<Card>)가 없습니다. 적처럼 플레이어도 리스트를 가지도록 수정해야 나중에 카드 효과(예: 내 손패의 카드 비용 감소)를 구현할 수 있습니다![ ]
금 당장 카드를 내고 게임을 진행하는 데는 아무런 문제가 없습니다. 왜냐하면 유니티의 **GameObject(화면에 보이는 카드 오브젝트)**가 각자 자기의 데이터(cardData)를 이미 들고 있기 때문입니다.
유저님이 카드를 드래그해서 필드에 놓을 때, CardDraggable 스크립트가 그 오브젝트 안에 들어있는 CardDisplay의 데이터를 읽어서 처리하죠? 그래서 "카드를 내는 행위" 자체는 아주 잘 작동하는 것입니다.
리스트가 없을 때 (현재 방식):
- PlayerHand라는 부모 오브젝트 아래에 자식이 몇 명인지 하나하나 다 뒤져야 합니다 (transform.childCount).
- 각 자식 오브젝트에서 CardDisplay 컴포넌트를 가져옵니다 (GetComponent).
- 그 안의 cardData를 확인해서 이게 주문인지 하수인지 체크합니다.
- 이 과정은 코드가 복잡하고 성능상으로도 좋지 않습니다.
리스트가 있을 때:
- HandManager에 있는 List<Card> cardsInHand를 한 바퀴 돕니다 (foreach).
- 조건에 맞는 데이터의 수치만 바로 바꿉니다. 끝!
뭔 차인거지
물리적인 실체(껍데기)"를 뒤지느냐, "논리적인 장부(데이터)"를 읽느냐의 차이
하이라키(자식 오브젝트)를 뒤지는 방식 (현재 방식):
- 문제점: 만약 어떤 사람이 책을 빌려 가려고 들고 있는 중(드래그 중)이거나, 직원이 책을 꽂으려고 이동 중(애니메이션 중)이라면 그 책은 책상 위에 없어서 검색에서 빠질 수 있습니다.
기술적인 차이 (왜 리스트가 좋은가?)
1) 속도와 비용 (Performance)
- 하이라키 순회: transform.GetChild(i)로 접근하고, 특히 GetComponent<CardDisplay>()를 호출하는 것은 컴퓨터 입장에서 상당히 무거운 작업입니다. (매 프레임 수십 번씩 하면 게임이 느려질 수 있습니다.)
- 리스트 순회: List<Card>는 메모리에 연속적으로 예쁘게 담겨있는 데이터 덩어리입니다. 그냥 숫자를 읽는 것만큼이나 빠릅니다.
2) 데이터의 정확성 (Sync)
- 카드 게임에서는 애니메이션이 많이 들어갑니다. 카드가 손패에서 필드로 날아가는 1.5초 동안, 이 카드는 '손패'인가요 '필드'인가요?
- 하이라키 기준이면 부모(Parent)가 바뀌는 순간 바로 손패에서 사라지지만, 리스트(장부) 기준이면 "애니메이션이 끝날 때 장부에서 지워라" 혹은 "시작할 때 지워라"라고 개발자가 로직을 완벽하게 통제할 수 있습니다.
3) 파괴된 오브젝트 문제
- 유니티에서 Destroy(obj)를 호출해도 실제 메모리에서 완전히 사라지기까지 아주 미세한 시간이 걸립니다. 하이라키를 뒤지다 보면 "이미 죽어가는(Missing)" 오브젝트를 건드려서 에러가 날 확률이 높습니다. 하지만 리스트는 우리가 직접 Remove()를 해주므로 그런 실수가 적습니다.
현재 적 AI 코드를 보면 EnemyHandManager에 리스트(cardsInHand)는 있지만, 정작 EnemyAI.cs에서는 foreach (Transform child in enemyHandParent)를 써서 하이라키를 뒤지고 있습니다. (즉, 리스트를 만들어만 놓고 활용은 안 하고 있는 상태입니다.)
이 검은색 부분 누르니깐, 갑자기 화면이 커지는거엿네
>습과어플 다시 고칠수있을지도

CardDraggable.cs 분기점 질문 답변
질문: "여기(CardDraggable)는 적인지 아군인지 분기점 필요 없나요?"
답변: "현재 구조에서는 필요 없습니다."
그 이유는 다음과 같습니다:
- 드래그는 플레이어만 합니다: CardDraggable 스크립트는 마우스로 끌어서 카드를 내는 로직입니다. 적(AI)은 마우스를 쓰지 않고 코드로 카드를 바로 필드로 이동시킵니다.
- 이름 체크 로직: 코드 상단에 if (parentToReturnTo.name == "PlayerField")라고 이미 아군 필드인지 확인하는 조건이 있습니다.
- 드래그 금지 로직: 이미 OnBeginDrag에서 if (transform.parent.name == "EnemyHand")일 경우 드래그를 막아두셨습니다.
방식 A: List<Card> (현재 유저님의 방식 - 장부 방식)
- 개념: "내 손에 '불꽃구슬' 카드가 한 장 있다"라는 정보만 적어둔 장부입니다.
- 단점: AI가 "이제 '불꽃구슬' 카드를 내야지!"라고 결정했을 때, 장부에는 이름만 써있지 화면 어디에 그 카드 오브젝트가 있는지는 모릅니다. 그래서 화면(Hierarchy)을 뒤져서 "너 이름이 불꽃구슬이니?"라고 물어보는 과정(GetComponent)이 필요합니다.
방식 B: List<CardDisplay> (완전한 리스트 방식 - 바구니 방식)
- 개념: "내 손에 이 **실제 카드 뭉치(오브젝트)**들이 있다"라고 아예 물건 자체를 바구니에 담아둔 것입니다.
- 장점: AI가 "이 두 번째 카드를 내야지!"라고 결정하면, 그 즉시 그 물건을 집어서 필드로 옮기면 됩니다. 장부 뒤지고, 화면 뒤지고 할 필요가 없습니다.
플레이어와 AI의 현재 방식 차이
플레이어는 어떤 방식인가?
- **"마우스 중심 방식"**입니다.
- 플레이어는 리스트를 볼 필요가 없습니다. 내 눈에 보이는 카드를 마우스로 직접 찍기 때문입니다. 마우스가 찍은 그 놈이 바로 GameObject이고, 그 놈한테 CardDisplay가 붙어있죠.
- 그래서 플레이어에게 List<Card>는 나중에 효과(코스트 감소 등)를 줄 때나 필요한 보조 장부일 뿐입니다.
AI는 어떤 방식인가? (현재 유저님 코드 기준)
- **"장부 보고 화면 뒤지기 방식"**입니다.
- AI는 마우스가 없습니다. 그래서 코드로 List<Card>(장부)를 먼저 훑어봅니다.
- "오, 1코스트짜리 하수인이 있네? 이걸 내야지!"라고 결정한 뒤에, 실제 화면(EnemyHand)의 자식들을 하나하나 뒤져서(foreach Transform child) 그 데이터와 일치하는 물건을 찾아냅니다.
GetComponent 삭제: AI가 카드를 낼 때마다 모든 자식 오브젝트의 컴포넌트를 뒤지던 무거운 작업이 사라졌습니다.
GetComponent를 "매번(Loop)" 하는 것을 막는 것이 목표이지, "태어날 때 딱 한 번" 하는 것은 필수입니다.
- DeckManager.cs를 보면 public List<Card> deckList;가 있습니다.
- 유니티 에디터에서 DeckManager가 붙은 오브젝트를 클릭하면 인스펙터 창에 리스트가 뜹니다. 거기에 위에서 만든 카드 파일들을 드래그해서 넣어두셨을 겁니다. 이게 바로 **"이 덱에는 이 카드들이 들어있다"**라고 유니티에게 알려주는 과정입니다.
- CardDrawer.cs의 13번 줄(Card data = deckManager.PopRandomCard();)을 보면, 드로워가 매니저에게 "카드 한 장 줘!"라고 요청합니다.
- 매니저는 자기가 가진 deckList에서 랜덤하게 하나를 골라 Card data라는 변수에 담아 넘겨줍니다.

유니티가 왜 굳이 스크립트를 게임 오브젝트에 붙여서(Component 방식) 사용하는지
1. "심장 박동(Lifecycle)"이 필요하기 때문입니다
자바 프로그램은 main 함수부터 시작해서 코드가 순차적으로 흐르지만, 유니티는 매 초마다 수십 번씩(Frame) 화면을 다시 그려야 하는 엔진입니다.
- Start(): 게임 시작할 때 한 번 실행
- Update(): 매 프레임마다 실행
- OnCollisionEnter(): 물리적으로 부딪혔을 때 실행
이런 함수들은 **"게임 세상 속에 존재하는 오브젝트"**에 붙어 있어야 유니티 엔진이 "아, 이 녀석은 지금 살아있으니까 내가 매 프레임 체크해줘야겠구나!"라고 인식합니다. 만약 순수 C# 클래스로만 만들면 유니티의 이런 '프레임 시스템' 도움을 받기가 매우 까다로워집니다.
2. 시각적인 의존성 주입 (Visual Dependency Injection)
지금 올려주신 스크린샷이 정답을 보여주고 있습니다.
- 자바 방식: this.deckManager = GameObject.Find("EnemyDeck").getComponent(DeckManager.class); (코드가 길고, 이름 하나만 틀려도 에러 남)
- 유니티 방식: 그냥 인스펙터 창에서 눈으로 보면서 마우스로 끌어다 놓기.
이게 단순한 편의성을 넘어 엄청난 장점이 됩니다. 예를 들어 '보스전용 덱 매니저'를 따로 만들었다면, 코드 한 줄 안 고치고 인스펙터에서 보스용으로 슥 갈아 끼우기만 하면 됩니다. 기획자나 디자이너가 프로그래머의 도움 없이 게임 수치를 조절할 수 있게 하려는 유니티의 설계 철학입니다.
3. "직렬화(Serialization)"와 보존
유니티 에디터는 일종의 **'데이터 저장 도구'**이기도 합니다.
오브젝트에 스크립트를 붙이고 수치(Max Hand Size: 7 등)를 적어두면, 유니티는 이를 파일로 저장해둡니다. 게임을 껐다 켜도 그 수치가 유지되죠. 만약 자바처럼 코드로만 관리한다면, 모든 설정값을 하드코딩하거나 별도의 JSON/DB 파일을 만들어서 읽어오는 로직을 직접 짜야 합니다.
'개발 > 하스스톤+전장' 카테고리의 다른 글
| 하스+전장) 인터페이스,제네틱 쓰는 이유 (0) | 2026.01.26 |
|---|---|
| 하스+전장) 하수인 공격기능 추가 (0) | 2026.01.25 |
| 하스스톤+전장)하수인 공격할수잇게 (1) | 2026.01.14 |
| 하스스톤+전장) 코드, 메커니즘 이해2/ 영웅추가 (0) | 2026.01.14 |
| 하스스톤+전장) 코드, 메커니즘 이해 (2) | 2026.01.13 |