[2026.04] ToDoList - 최소 마일스톤

 

[2026.04] ToDoList - 최소 마일스톤

To Do List 레벨난이도에 따른 맵 스테이지 선택 입장 ㄴeasy, normal, hard는 각 고유의 스테이지 ㄴ맵 노드 갯수 설정도 다르게 사용할수 있게 수정 보스 노드 완료시 , 결과 정산창 로직 및 연출 필요

01149.tistory.com

◎ 해당 글의 작업 진행도 확인 체크리스트

To Do List 진행도 (완료 : O , 작업 중 or 수정 가능성 높음: △ , 미구현 : X)
레벨난이도에 따른 맵 스테이지 선택 입장 O
easy, normal, hard는 각 고유의 스테이지 O
맵 노드 갯수 설정도 다르게 사용할수 있게 수정 O
보스 노드 완료시 , 결과 정산창 로직 및 연출 필요  O
전투 노드 완료시 , 요정 스킬 선택지 제공 + 마나결정(경험치 구슬)이 플레이어에게 흡수 되는 연출 O
요정 레벨업 시 스텟 상승 할수 있게 수정 
보스 디자인 설계하기
보스 노드 설계하기 X

 

현재까지 개발 완료 체크리스트 (~5월 18일)

To Do List 진행도 (완료 : O , 작업 중 or 수정 가능성 높음: △ , 미구현 : X)
기본적인 전투 시스템 기반 구축 O
스테이지 입장하여 맵 노드 선택 입장 및 전투 O
요정 스킬카드 선택지 기능 O
요정 스킬 프로토타입 12종류 구현 완료 O
대화 시스템 기반 구축 O
기획 아트팀이 / 스토리 및 아트리소스 테스트를 위한 대화씬 전용 빌드 구현 완 료 O
현재 스테이지 완료시 결과 정산창의 데이터 연동 및 Tween 연출 완료 O
스킬 레어도 구조 설계 (Common,Advanced,Rare,Unique,Legendary) O
스킬 카드 데이터 UI 로직 연동 및 Json 역직렬화 파라미터 설계 O
특정 카드 조합 선택시 보상을 받는 'ComboSystem'  △(프로토 타입으로 스킬카드 보상만 구현된상태)
스킬 카드를 업그레이드 및 분기점을 생성 'SkillUpgareTreeSystem' O
스테이지1 보스 기획 및 보스 시스템 설계

 

To Do List 

레벨난이도에 따른 맵 스테이지 선택 입장
ㄴeasy, normal, hard는 각 고유의 스테이지
ㄴ맵 노드 갯수 설정도 다르게 사용할수 있게 수정

보스 노드 완료시 , 결과 정산창 로직 및 연출 필요 

전투 노드 완료시 , 요정 스킬 선택지 제공 + 마나결정(경험치 구슬)이 플레이어에게 흡수 되는 연출 필요
ㄴ 로직 순서 : 
플레이어의 이동 정지 -> 
필드에 있는 마나 결정이 플레이어 에게 흡수 ->
흡수 완료시 화면 살짝 암전되고 요정 스킬 선택지 제공 -> 
요정 스킬 선택후 맵 노드 선택 화면 전환

ㄴ전투노드 종료(완료) 호출 순서 수정할것 

 

기존 전투 노드 완료(종료) 로직

더보기

1. if( WaveManager에서 모든 Wave 완료 && 현재 몬스터 갯수 0 )
{
 WaveClearEvent?.Invoke(); // 
}

2. WaveClearEvent 에 ChoiceCardManager의 ChoiceCard 메서드 구독
WaveClearEvent 실행하면 랜덤 카드를 생성하고
생성된 랜덤카드들의 Button 컴포넌트 onClice 이벤트에 

() => cardObj.SelectBtn.enabled = false);//다중 클릭을 막기위한 선택시 버튼 비활성화
() => ApplyChoiceSkillToUnit(skillCtx)
() => choiceWindowUI.Close()
() => BattleSceneManager.Instance.BattleEnd()

4종류의 람다식을 구독 후

카드 선택시 이벤트 동작

변경 전투 노드 완료(종료) 로직

더보기

WaveClearEvent에 ChoiceCardManager 메서드를 구독 X , 
BattleScene 전체를 컨트롤하는 BattleSceneManager의 BattleEnd를 메서드를 구독
BattleEnd 메서드 내부에 ChoiceCardManager를 호출해서 사용할 예정

이렇게되면 전투 종료후 나오는 연출 추가가 좀더 자연스러워지게 구성이 가능해짐 

 

 ChatGpt 평가

핵심은 전투 종료 판정전투 종료 이후 처리(보상, 연출, UI, 씬 상태 전환)를 분리하는 쪽으로 가고 있다는 점인데, 이게 구조적으로 더 안정적이다.

지금 변경안의 장점은 명확하다.

왜 더 좋아졌는가

기존 구조에서는 WaveClearEvent가 사실상

  • 웨이브 종료 판정
  • 카드 선택 UI 생성
  • 카드 버튼 동작 연결
  • 전투 종료 처리

까지 직접 이어지는 흐름이었다.
이 경우 이벤트 이름은 WaveClearEvent인데 실제 책임은 전투 결과 처리 전체의 시작점이 되어버린다.

반면 변경안은

  • WaveManager는 전투가 끝날 조건이 충족되었는지 알림
  • BattleSceneManager는 전투 종료 시퀀스 전체를 통제
  • ChoiceCardManager는 카드 선택 UI와 선택 결과 처리 담당

으로 책임이 더 자연스럽게 나뉜다.

즉, 지금 변경은 단순히 위치를 옮기는 게 아니라
“판정” → “연출/흐름 제어” → “보상 UI” 로 계층을 정리하는 리팩토링에 가깝다.


특히 좋아지는 부분

1. 연출 추가가 쉬워짐

말한 것처럼 전투 종료 후에

  • 슬로우모션
  • 클리어 연출
  • 결과 텍스트
  • 카메라 이동
  • 사운드 재생
  • 약간의 딜레이 후 카드 노출

같은 흐름을 BattleSceneManager.BattleEnd() 안에서 순차적으로 넣기 쉬워진다.

기존처럼 WaveClearEvent에 바로 ChoiceCardManager가 붙어 있으면
카드 UI가 너무 일찍 튀어나오거나, 중간 연출을 끼워 넣을 때 의존성이 꼬이기 쉽다.


2. 이벤트 의미가 명확해짐

WaveClearEvent는 이름 그대로
“웨이브/전투 종료 조건이 충족되었다”는 신호만 주는 게 맞다.

그 이벤트에 카드 UI 생성까지 직접 물려 있으면
이벤트 의미가 점점 무거워진다.

이벤트는 가볍게 유지하고,
실제 후속 흐름은 상위 컨트롤러가 잡는 게 유지보수에 좋다.


3. 확장성이 좋아짐

나중에 노드 종류가 늘어나면 더 차이가 난다.

예를 들어 어떤 노드는 전투 종료 후

  • 카드 선택이 나오고
  • 어떤 노드는 보물 상자 UI가 나오고
  • 어떤 노드는 컷신으로 넘어가고
  • 어떤 노드는 바로 맵 복귀

가 될 수 있다.

이때 WaveClearEvent -> ChoiceCardManager 직결 구조면 분기가 여기저기 생긴다.
반대로 BattleSceneManager가 끝 흐름을 잡고 있으면 노드 타입별 처리 분기가 훨씬 자연스럽다.

1. BattleEnd()가 너무 많은 일을 하지 않게 하기

방향은 좋지만 BattleEnd() 하나에 전부 몰아넣으면 나중에 비대해질 수 있다.

2. 중복 호출 방지

이건 꼭 넣는 걸 추천한다.

전투 종료 조건은 프레임 단위로 계속 체크될 가능성이 있으니
WaveClearEvent나 BattleEnd()가 여러 번 호출되지 않도록 해야 한다.

 

3. 카드 선택 완료와 전투 종료 시점 구분

기존 람다에서는 카드 버튼 클릭 시

  • 버튼 비활성화
  • 스킬 적용
  • UI 닫기
  • BattleSceneManager.Instance.BattleEnd()

이 들어가 있었는데,
이제 BattleEnd()가 카드 UI를 띄우는 쪽으로 옮겨가면
이 이름이 약간 안 맞을 수 있다.

즉 현재는 의미가 둘 중 하나일 수 있다.

  • 전투가 끝났을 때 호출되는 함수
  • 카드 선택까지 끝나고 최종 퇴장하는 함수

이 둘은 분리하는 편이 더 좋다.

예를 들면:

  • HandleBattleClear()
  • StartBattleEndSequence()
  • CompleteRewardSelection()
  • ExitBattleNode()

이런 식으로 나누면 흐름이 더 읽기 좋아진다.


요정 레벨업 시 스텟 상승 할수 있게 수정 
ㄴ수치는 아직 밸런스 기획단계아니니 임시로 , 또한 다른 스텟이 같이 오를수 있음
마나결정(경험치구슬)은 일반적으로 플레이어에게 흡입되게 수정

보스 디자인 설계하기

 

보스 노드 설계하기

 

* 대화 설계 구조 작업

- Excel에 사용할 Asset 및 대본을 작성후 해당 폴더에 넣으면 Json으로 변환 후 파싱하여

각 Command에 따라 기능이 동작하게 설계

 

- 기획에서 리소스 테스트를 위해 Dialog_Demo를 따로 제작하여 전달

ㄴ Dialog_Demo는 StreamAsset 폴더내에 외부 리소스(Spine,Sound,Image)를 넣어 빌드 버전에서 바로 사용하게 작업

ㄴ Build버전에서 사용할 [ Excel -> Json 변환 Tool ] 을 만들어 기획에게 전달

 

- 메인화면 에서 대화씬 진입시 로딩화면 연출 및 제작 

 

미구현 된 기능

- 채팅내역 보기

- 대화씬에서 특수연출(파티클,쉐이더)을 Excel을 통해 사용 할 수 있게 구현 

- 해당 진행도에 따라 스토리 해금 조건 및 보상 설계 구조 

이슈 상황 :

대화 스크립트 엑셀 파일에서 Command란에

 

$TokenType : Value $

를 작성하고 역직렬화 할때 , 파싱해서 사용을 하고 있다.

 

하지만 해당 문자열을 파싱할때 문제가 발생했다.

 

"$fade:true$ $duration:0.5$ $easygraph:4$"

 

fadeeasygraph는 토큰으로 인지하여 값을 분리했는데

duration value인 0.5가 값으로 안 읽히고 text 토큰 형태로 읽히는 문제가 발생했다.

 

원인 :

   private static readonly Regex TagRegex =
        new Regex(@"\$(\w+):(\w*)\$", RegexOptions.Compiled);

Command 문자열을 특정패턴으로 분리 할수있게 Regax를 사용하고 있다.

 

(\w*)

원인은 이 정규식(Regax) 때문이였다.

\w는 보통 영문자,숫자,밑줄(_)허용함,

예시 ) abc,A1,test_01 이런것만 정규식으로 인정이 된다는 것

0.5에는 점(.)이 있으므로 매칭이 되지 않는다. 

 

그렇기때문에 앞에 duration 토큰 탐색후 value값 탐색시에 조건이 안맞으므로

예외 처리로 text 토큰 처리를 하게 된것이다. 

 

해결 방법 : 

   private static readonly Regex TagRegex =
      new Regex(@"\$(\w+):([^$]*)\$", RegexOptions.Compiled);

 

(\w*) => ([^$]*)

로 정규식을 변경하였다.

 

[^$]* 해당 정규식을 사용한 의미

1. 값 부분은 $ 아니면 전부 허용

2. * 이므로 빈 문자열(공백)도 허용

 

예시 : 

$tag:0.5$
$tag:-1.25$
$tag:hello world$
$tag:50%$
$tag:test_01$
$tag:abc-def$
$tag:$

 

Value 부분에 어떤 값이 들어와도 해당 토큰에 따라 변경을 해놓게 해결하였다.

camelCase 소문자로 시작, 띄어쓰기 생략, 대문자로 구분
PascalCase 대문자로 시작, 띄어쓰기 생략, 대문자로 구분
snake_Case 소문자만 사용 ,띄어쓰기 대신 _ 사용
kebab-case 소문자만 사용 ,띄어쓰기 대신 - 사용

5-1. [namespace], [class] , [struct] 는 파스칼을 사용
5-2. [함수]는 파스칼을 사용 , 함수 내부는 카멜을 사용
5-3. [Enum]는 파스칼을 사용
5-4. public 변수는 파스칼을 사용 ex) public int Num;
5-5. 나머지 변수는 _ + 카멜을 사용 ex) private int _num;

1.컴퓨터 
저장 장치
하드디스크 -  컴퓨터에 물리적으로 파일을 저장
메모리 - 프로그램/어플리케이션이 실행하는데 필요한 데이터를 저장 
ex) 게임 - 레벨,소유골드 등등...

메모리는 배열처럼 생겼다 (배열은 아님!!!!)
Code : 프로그래머가 작성한 코드를 보관
Data : static,const 어플리케이션 전반에 필요한 데이터 저장
Heap : 참조 데이터 (객체) 저장
Stack : 로컬변수 ,매개변수 저장
어플리케이션 실행 순서에 필요한 데이터 보관

메모리는 2진수로된 주소값이 있다.
해당 메모리에 접근하기 위해 주소값을 가지고 있으면 된다 -> C/C++의 포인터 기능

int x = 10; 
해당 데이터(10)는 stack영역 메모리 첫번째에 저장된다.
메모리 첫번쨰는 0000 0000주소값을 가지고있는데

해당 데이터에 접근하기 위해 0000 0000 주소값을 가져오는게 아닌 x로 변경하여 사용하게 되는것이다.
x도 stack 영역에 저장됨

= : Rvalue를 Lvalue로 옮겨주는것이다.

class는 참조값이기에 heap 영역에 추가됨
heap은 어디에 저장되는지 모름, 그리고 알 필요도 없기에 빈 어딘가에 저장된다고 생각하면 됨

Class Person 
Class Data는 heap영역에
Calss로 만들어진 객체는 Stack 영역에 저장됨

사용할떄 주의할점
Stack 영역
변수할당시 생성
함수가 끝날때 삭제

참조가 한번 끊어지면 다시 heap영역의 data에 접근이 되지 안흔ㄴ다 -> heap영역의 과잉생산 -> 메모리 누수

데이터가 있지만 활용하지 못하는것 -> 쓰레기(가비지)
데이터가 언제 삭제되는지 모름, 단 한 조건은 있음
데이터가 사용되지 않을때 (참조가 끊겼을때)

GC가 언젠가 쓰지않는 데이터를 호출하여 처리해줌

가비지가 생긴다고 했을때
가비지를 1개만 가져가면 괜찮지만
가비지를 100개 1000개씩 한번에 가져갈때
컴퓨터가 과부하가 걸린다 -> 게임시 렉이 걸리는 이유

효율적으로 쓴다 = 가비지를 최대한 적게 생성시킨다.

Heap영역
new를 통해 생성 GC로 인해 삭제
Unity에서는 Instanitate() , Destroy()
하지만 Destroy()에서는 오브젝트가 삭제되는거지 Data는 남아있어 가비지로 남아있음 

유니티 inspector에 들어간 컴포넌트의 상위는 Class ->Monobehaviour 이다. 즉 ,  Instanitate() 를하면 new를 통해 생성하는것과 똑같은 의미이다.

최적화 -> 반복적으로 new 하는것을 줄인다
(Update,While 등등...)

최적화 1.
데이터를 한번 준비하고 **재활용**
-> 변수들을 미리 선언하여 호출하여 쓰기 

Unity에서 Vector는 구조체이기 때문
C/C++ 에서는 구조체나 class는 비슷하다.
(왜냐하면 프로그래머가 직접 메모리 할당 및 해지 , 포인터를 이용한 주소값 접근을  담당하기 때문)

하지만 C#은 다르다.
구조체는 데이터 자체가 Stack영역에 생성됨
즉, 구조체는 가비지가 남지 않게된다.
Stack 메모리가 heap 속도보다 더 빠르다
즉, Vector는 자주 사용되기 때문에 구조체로 Unity에서 만든것이다.

string str = "유저 레벨"

유 저  레 벨 이라는 5가지 저장됨

문자열은 불변객체 = 변하지 않는다는 뜻

str += "chad" 가 실행되면

내부적으로 간단하게
유저 레벨 뒤에 chad가 붙는것이 아니다.
즉, 유저 레벨 뒤 heap영역 메모리 데이터에 다른 데이터들도 있기에 붙일수가 없기 때문이다. 

유저레벨 + chad를 합친 문자열을 다른 heap영역에 새로 만들어진다. 

즉, heap에는 유저 레벨 , 유저레벨 chad 데이터가 2개가 동시에 존재하는 것이다
즉 string의 += 연산자를 계속 쓸때마다 heap영역을 쓰는 것이고 추가가 되어진다. 

이런 현상을 "파편화"라고 한다.

문자열 대처법
1.string을 한번에 제작 [ 보간문자열,스트링포맷,+연산자]
(추천)1-1.보간 문자열 : 문자열 앞에 $ 표시를 붙여 사용한다.
1-2.string.Format
1-3.+연산자를 한줄에 적어버리기

문자열을 한번에 제작하지 못하는경우
2.StringBuilder 사용하기

근데 왜 StringBuilder는 왜 이렇게 사용해도 괜찮은가??
ex)
유저 레벨 이 5글자이면 , StringBuilder는 추가로 공간을 확보해놓고 해당 확보한 공간을 넘어서는경우 새로 ㅏㄴ들게 된다. -> C#의 List와 유사하다.

stringBuilder는 처음에 16글자
넘어가면 2배인 32글자를 새로운 heap영역에 할당 이런식으로 된다.

stringBuilder는 일반 문자열보다 느리지만 파편화를 덜 방지 할 수 있기에 사용이 추천된다.

StringBuilder도 처음 초기화시 크기를 선언하고 사용 할 수 있다.

웬만한 문자열은 보간문자열을 사용할 수 ㅣㅆ고
중간중간 문자열이나 Data를 실시간으로 조합할때는 StringBuilder를 추천한다.
(개별 입력으로 받아들여서 합치는 경우에는 StringBuilder, 개발자가 처음 부터 값을 넣어주는 경우에는 $""를 쓰는 게 좋겠군요.)

Stack 값 형식 =  value
C#의 기본 타입(int,float,bool,char) , 구조체 , enum
Heap 참조형식 = referance
string class 배열 interface delegate

구조체는 Stack에 할당
메모리는 Stack에만 저장
큰 데이터나 외부 데이터를 가져와서 사용할떄 문제 발생

Calss는 Heap에 할당
Class의 객체(변수)는 stack에
Class 객체의 data는 heap영역에
데이터 접근은 Stack에 접근후 데이터의 주소를 파악후 heap영역에 접근한다. 

C#에서는 Class를 만들떄 new 어트리뷰트를 사용하여 생성한다. 유니티는 Instaiate는 new키워드와 유사한다

Static 키워드가 변수 ,메서드가 있으면
외부의 클래스의 이름을 바로 호출하여 바로 사용 할 수 있다.

Static을 메모리 관점으로 봤을때
Data영역에 배치 되어있음 
Data 영역은 프로그램 시작시 생성
프로그램 종료시 삭제

즉 , Static 키워드는 Data영역에 접근하여 언제든지 호출이 가능해진다.

Static 키워드 데이터는 Scene 사이의 데이터가 유지되어있다.

Static Class는 데이터를 세팅해줘야 됨
나머지 int,float은 언제든지 쓸 수 있음 
객체도 Class라서 초기값은 null이다.

Static 키워드들이 heap,stack영역 데이터에 접근해서 사용 할수는 없다.

'C#' 카테고리의 다른 글

Dirty Flag Pattern  (0) 2026.02.03
C# :: 메모리 구조  (0) 2024.10.14

* 타일맵의 정적 오브젝트 충돌 처리시 사용 방법 
composite collider : 타일 하나당 collider 적용되는것을 막기위해(연산 초과)  및 이상한 충돌처리를 막기위해 컴포넌트 추가
=> 리지드바디2D가 자동으로 추가 됨, body type을 static으로 변경 -> 정적으로 변경해서 고정된 얘들끼리는 충돌연산을 하지 않기에 비용이 매우 쌈

* Scene에서 리소스가 꺠져보이는 경우 발생
 원인 :  카메라의 해상도가 맞지 않기에 생기는 문제점 
대처법 : 스프라이트 아틀라스 사용하고 패킹 후 필터모드를 포인트로 변경 하면 부드럽고 리소스가 깨지는것을 방지 가능 

* 게임 중단 및 종료, 오브젝트 파괴시 사용되는 함수 

OnApplocationPause()
:모바일 개발시 많이 사용
home버튼을 누를떄 사용됨(검색할것)
OnApplicationQuit() 
:종료되는 시점을 알려주는 함수
Ondestroy()
:해당 오브젝트를 컴포넌트로 가지고있는경우 파괴 될때 알려줌

* Animator State Tip
Any State에서는 Settings에 Can Transition To Self 체크옵션을 빼주면 자기 자신한테 상태이전은 하지 않는다. 이로서 위처럼 깔끔하게 처리를 할수 있었다.

can Transsition self : 해당 컨디션에서 또 조건이 되면 애니메이션을 재생 

 

* 콜라이더 충돌 오브젝트 통과 판정 만들기
Collider 컴포넌트의 use-effect 체크 -> Surface Arc 사용가능
Surface Arc (표면각도)  : 이 각도 수치가 넘으면  이 오브젝트를 뚫고 지나가는지를 판단 

랜덤 맵 생성 기능
ㄴ하나의 스테이지 안에 맵을 랜덤으로 생성합니다.
ㄴ참고 키워드) 절차적 맵 생성 (Procedural Map Generation)
BigInteger 기능
ㄴ매우 큰 숫자를 처리하기 위한 기능입니다.
ㄴ방치형 게임에서 나오는 1a, 4d 등의 숫자를 구현합니다.
ㄴ참고) https://github.com/keiwando/biginteger

+ Recent posts