StageManager 타이머/리젠 루프, HunterRule(AnimationCurve) 간격 제어
프리팹 능력치 주입(Stat Injection), NRE 방어
✅ 오늘 학습 한 내용을 나만의 언어로 정리하기
데이터 책임 분리개체 능력치(HP/공격력/이동속도/공속)는 StageStatsSet이 맡는다.
StageConfig.stageTier로 현재 스테이지(1/2/3)를 명시하고, statsSet.Get(kind, tier)로 필요한 스탯을 읽기 전용으로 가져와 프리팹에 주입한다.
스폰 수치·시간(Init/Max/Cooldown/목표 등)은 StageConfig가,
유연한 밸런싱 파이프라인
모든 스테이지가 한 개의 StatsSet을 공유하므로, 밸런서는 StatsSet만 수정해도 전 스테이지가 반영된다. 반대로 특수 스테이지는 별도 StatsSet 에셋을 가리키면 된다.
헌터 스폰의 난이도 곡선화
HunterRule.intervalByInfection 커브로 감염률→스폰간격을 매핑하니 난이도 체감이 부드럽다. 하드 스테이지는 커브 기울기만 바꿔도 된다.
운영 루프 간결화
CoTimer / CoHumanRegen / CoVipRegen / CoHunterControl로 역할이 분리돼 가독성이 좋아졌다. 스테이지 전환 시 StopAllCoroutines()로 누수 방지.
🧩 학습하며 겪었던 문제점 & 에러
1. StageStatsSet 미지정 시 NRE 위험
문제정의: StageConfig.statsSet이 비어 있으면 스탯 주입 코드에서 NRE 가능.
시도: 사용부에서만 null 체크.
해결 방법: 제공자와 사용자 모두 방어. StageConfig.GetStats()에서 null 대응, 사용부도 이중 가드.
// StageConfig.cs
public UnitStats GetStats(ActorKind kind) {
if (statsSet == null) return null;
return statsSet.Get(kind, stageTier);
}
// 사용부
var stat = cfg.GetStats(ActorKind.Human);
if (stat == null) return; // 안전 가드
새롭게 알게 된 점: SO 의존성은 “제공자 가드 + 소비자 가드”가 심리적 안정감을 준다.
다시 만나면: 에디터 Validate(예: OnValidate)에서 미지정 시 콘솔 경고 띄우기.
2. 스테이지별 스탯 매핑 실수(잘못된 Tier 선택)
문제정의: Tier 매핑 스위치문에서 default 처리를 안 하면 잘못된 값 사용 가능.
시도: 단순 스위치 반환.
해결 방법: 명시적 default와 에디터 노출 순서 일치.
// StatsByTier.cs
public UnitStats Get(StageTier tier) {
switch (tier) {
case StageTier.Stage1: return stage1;
case StageTier.Stage2: return stage2;
case StageTier.Stage3: return stage3;
default: return stage1; // 안전 기본값
}
}
새롭게 알게 된 점: Enum 확장 시 누락 위험이 있다.
다시 만나면: Dictionary로 전환하거나 switch에 _ 패턴 사용(C# 버전에 따라).
📝 메모
생각보다 데이터 책임 분리가 많은 걸 바꿔준다. “스테이지는 규칙과 수량”, “능력치는 별도 세트”로 나누니 머리가 한결 가벼워졌다. 오늘 스테이지 1·2·3을 끝까지 밀어 붙인 게 큰 수확! 모르는 걸 부끄러워하지 말고, 내일은 UI에서 현재 Tier/목표/남은 시간을 직관적으로 보여주는 패널까지 연결하자.
본격적으로 팀 프로젝트 시작하는 1일차이다 이번이 5번째인가.. 다음 마지막 프로젝트가 최종 프로젝트인데 그 전 마지막 프로젝트이다 이번 팀원들과도 마지막까지 힘내보자 화이티잉!!!!!
// 스폰 시
var rot = Quaternion.LookRotation(Vector3.up);
var go = Instantiate(vfxPrefab, pos, rot);
var ps = go.GetComponent<ParticleSystem>();
var main = ps.main;
main.simulationSpace = ParticleSystemSimulationSpace.World;
ps.Play();
다시 만나면: 프리팹의 분사축(Z+)를 기준으로 생각하자.
4) 스테이지가 올라가도 적 능력치가 안 변함(퍼센트가 아니라 가산형으로)
문제정의: 기존 구조는 피해량 배율/표기 불일치로 체감이 약함.
해결방법: 스폰 시 기준 MaxHealth/Speed를 캐시하고, 스테이지마다 절대값 재설정.
// 체력
float baseMax; // 스폰 시 1회 저장
void ApplyStageHealth(int stage, float addPerStage){
int n = Mathf.Max(0, stage-1);
SetMaxHealthAbsolute(baseMax + addPerStage * n, keepRatio:true);
}
// 스피드
float baseSpeed; // 스폰 시 1회 저장
void ApplyStageSpeed(int stage, float addPerStage){
int n = Mathf.Max(0, stage-1);
agent.speed = baseSpeed + addPerStage * n;
}
체크포인트: buffAliveOnStageChange 켜서 살아있는 적도 즉시 갱신.
5) 에디터 ArgumentOutOfRangeException (Hierarchy)
문제정의: Hierarchy 트리뷰가 그리는 동안 목록이 변해 인덱스 오류.
해결방법: 검색 필터 해제 → 레이아웃 리셋 → Hierarchy 관련 플러그인 OFF → Library/ 재생성 순으로 정리.
오늘은 기초를 다시 튼튼히 다진 날이었다. 특히 초기화 시점과 절대값 스케일링의 중요성을 제대로 체감했다. 퍼센트보다 가산형이 내 프로젝트 흐름에 훨씬 명확했다. “보이는 것(파티클/사운드)”을 먼저 단단히 만들고, 구조를 그 위에 쌓아가니 마음이 한결 편해졌다. 내일은 업그레이드 밸런스 숫자를 손보고, 스테이지 전환 연출(페이드/사운드 큐)까지 깔끔히 묶어보자. 🙂
업그레이드는 데이터(현재 단계/최대 단계/비용)–로직(검증/적용)–UI(표시/버튼) 를 분리해야 안정적이다. UI는 “눌렀다”를 알리고, 실제 적용/검증은 시스템이 한다.
매니저가 늘어날수록 초기화 순서/중복 인스턴스 이슈가 커진다. Generic Singleton으로 공통 규약을 두니, 생성·파괴·DontDestroyOnLoad 정책을 한 곳에서 통일할 수 있었다.
전투 피드백(히트 파티클, 사운드)은 정답이 아니라 체감이다. 먼저 “보인다/느껴진다”를 완성하고, 이후 과하게 눈에 띄는 지점을 줄이자는 흐름이 효율적이다.
에셋 작업(배경 제거/정사각)은 귀찮아 보여도 UI 일관성과 가독성에 바로 영향을 준다. 결과적으로 업그레이드 화면 완성도가 한 단계 올라갔다.
🧩 학습하며 겪었던 문제점 & 에러
1) 업그레이드 누적 적용 버그 (연타 시 단계/비용 불일치)
문제정의: 버튼을 빠르게 연타하면 단계와 골드가 어긋남.
시도: 버튼 인터랙션 잠금, 로직 내 2중 체크.
해결 코드:
public bool TryUpgrade(ref int level, int max, ref int gold, int cost, Action apply)
{
if (level >= max) return false;
if (gold < cost) return false;
// 원자적 처리
gold -= cost;
level++;
apply?.Invoke();
return true;
}
새롭게 알게 된 점: UI 잠금만 믿지 말고 도메인 로직에서 최종 검증이 필수.
2) Singleton 중복 생성(NRE, 초기화 순서 꼬임)
문제정의: 여러 씬에서 매니저 프리팹이 중복으로 살아나 예기치 않은 참조 에러.
해결 코드 (Generic Singleton):
public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
static T _instance;
public static T I => _instance;
protected virtual void Awake()
{
if (_instance != null && _instance != this as T)
{
Destroy(gameObject);
return;
}
_instance = this as T;
DontDestroyOnLoad(gameObject);
}
}
다시 만나면: 매니저는 하나의 부트스트랩 씬에서만 생성하고, 다른 씬엔 프리팹을 두지 않는다.
3) 히트 파티클 과다 생성(프레임 드랍)
문제정의: 다수 적 동시 타격 시 파티클 Instantiate 남발.
해결 코드(간단 풀링):
Queue<ParticleSystem> pool = new Queue<ParticleSystem>();
public ParticleSystem Get()
{
if (pool.Count > 0) return pool.Dequeue();
return Instantiate(hitParticlePrefab);
}
public void PlayAt(Vector3 pos)
{
var ps = Get();
ps.transform.position = pos;
ps.gameObject.SetActive(true);
ps.Play();
StartCoroutine(ReturnWhenDone(ps));
}
IEnumerator ReturnWhenDone(ParticleSystem ps)
{
yield return new WaitWhile(() => ps.isPlaying);
ps.gameObject.SetActive(false);
pool.Enqueue(ps);
}
새롭게 알게 된 점: “보이는가?” 다음엔 “몇 개까지 버티는가?”.
4) GoldUI 숫자 갱신 타이밍 지연
문제정의: 골드 차감은 되었는데 UI 반영이 한 프레임 늦게 보임.
해결 방법: 골드 변경을 이벤트로 발행하고, UI가 즉시 구독하여 적용.
public event Action<int> OnGoldChanged;
public void AddGold(int amount) { gold += amount; OnGoldChanged?.Invoke(gold); }
public bool SpendGold(int cost)
{
if (gold < cost) return false;
gold -= cost; OnGoldChanged?.Invoke(gold);
return true;
}
📝 메모
어제의 하이라이트는 캐논 업그레이드였다. 숫자가 오르는 손맛, 파티클이 터지는 시각 피드백, 그리고 골드가 딱 맞게 차감되는 느낌까지 하나하나 맞물리니 화면이 살아났다. 동시에 매니저 구조를 정리하니 머릿속도 정리된 느낌. 에셋 손질까지 마무리하니 업그레이드 화면이 확실히 또렷해졌다. 욕심내지 말고, 오**균형(밸런스/비용/단계캡)**부터 다듬자. 🙂
UI는 데이터→이벤트→바인딩 루프가 핵심. 구독 직후 강제 동기화를 넣으면 초기 표시가 안정적이다.
CarAI의 “도착”은 기즈모 크기가 아니라 거리 임계값으로 판정된다. 고속에선 반경을 키우고 Y를 무시(XZ) 하면 안정적.
전투 이펙트는 **상태 루프(스핀 동안)**와 히트 원샷을 분리하면 제어가 간단해진다.
대형 파일은 처음부터 LFS로 관리해야 push 이슈가 없다.
Addressables는 AssetBundle을 래핑해 주소/라벨 기반 로딩으로 운영을 단순화한다.
🧩 학습하며 겪었던 문제점 & 에러
1) 슬라이더가 값 반영 안 됨
정의: 씬 시작 시 HP/MP/EXP가 0으로 보임.
시도: 이벤트만 연결.
해결 방법: 구독 직후 ForceNotify(초기 브로드캐스트) 호출.
새롭게 알게 된 점: UI는 “언제 구독했는가”가 중요.
다시 만나면: 바인더 템플릿에 초기 동기화 호출을 기본 포함.
2) CarAI가 고속에서 노드 놓침/미끄러짐
정의: Max RPM↑ 시 코너에서 지나침.
시도: 다양한 보간/룩어헤드 실험.
해결 방법: nodeReachDistance 확대 + XZ 거리 판정, 기즈모 반경 동기화.
새롭게 알게 된 점: 단순한 해법이 체감상 더 낫다.
다시 만나면: 속도 구간별 반경 가변만 소폭 추가.
3) 스핀 파티클이 매번 안 나옴
정의: 적을 맞출 때만 나오거나 1회만 재생.
시도: Instantiate 위치/부모 지정, 변수명 충돌 수정.
해결 방법: 스핀 시작/끝 이벤트에서 루프 파티클 ON/OFF, 필요 시 히트 스파크는 원샷으로 별도 처리.
새롭게 알게 된 점: 파티클은 루프와 원샷 역할 분리.
다시 만나면: Object Pool로 전환해 성능·GC 최적화.
4) 100MB 파일 Push 실패
정의: GitHub 용량 제한 경고.
시도: 일반 커밋/푸시.
해결 방법: 해당 경로를 Git LFS로 트래킹, 필요 시 과거 이력 정리.
새롭게 알게 된 점: 리소스 경로가 바뀌면 다시 LFS에 등록 필요.
다시 만나면: .gitattributes에 패턴 등록(예: .png, 특정 경로).
📝 메모
오늘은 Craft UI를 연결하면서 NRE를 널 가드로 정리했다. CarAI는 도착 반경을 키워 고속 미끄러짐을 완화했고, 초기 UI 값은 구독 직후 강제 동기화가 확실하다는 걸 체감했다. 구현을 복잡하게 하기보다 단순한 해법이 체감 품질을 올린다는 점을 다시 확인. 내일은 Gun 시스템 설계를 시작해 최소 발사 루프를 띄울 예정이다.
URP로 갈아타면 HDRP 전용 셰이더는 핑크가 되니, 재질을 URP/Lit 로 교체하고 맵을 재배치해야 한다. 네비는 구(舊) Navigation 창 대신 AI Navigation 패키지를 써야 하며, NavMesh는 **선(Line)**이 아니라 면(폭 있는 메시/콜라이더) 위에만 베이크된다. CarAI는 무작위 경로보다 노드 기반이 안정적이고, 노드 간 직선(Lerp) 대신 Catmull-Rom 스플라인 으로 샘플링하면 코너가 부드럽다. 우측 바퀴가 180° 뒤집히는 건 메쉬 미러링 축 문제라
rot * Quaternion.Euler(0,180,0)
로 보정했다. 공통 상태 관리는
BaseCondition
에 넣고,
ValueChanged(int)
를 virtual로 둬 파생에서 선택적으로 오버라이드할 수 있게 했다.
🧩 학습하며 겪었던 문제점 & 에러
1) URP 전환 후 자동차가 핑크
정의: HDRP 전용 셰이더라 URP에서 렌더 실패.
시도: 프로젝트 전체 업그레이드, 머티리얼 개별 교체.
해결: URP/Lit로 변경 후 맵 재할당(Albedo/Metallic/Normal/AO/Emission), 유리는 Transparent.
새롭게 앎: URP 12+의 Clear Coat로 자동차 페인트 질감 일부 대체 가능.
다시 만나면: 에셋 임포트 시 URP 변환 프로파일부터 적용, 자동 변환 안 되는 커스텀 셰이더는 Shader Graph로 포팅.
해결: Catmull-Rom 스플라인으로 세그먼트당 8~16 포인트 샘플링, NavMesh.SamplePosition으로 트랙 중앙에 스냅.
새롭게 앎: 스플라인은 노드를 통과(C¹ 연속)해서 차량 움직임이 자연스러움.
다시 만나면: 속도에 따라 샘플 수를 가변 조절(속도↑ → 샘플↑), nodeReachDistance 튜닝.
📝 메모
TrackPath()가 웨이포인트를 계속 붙이므로 리스트 길이 관리(슬라이딩 윈도우) 필수
NavMesh 베이크: 트랙 레이어만 포함, Agent Radius ≤ 도로 반폭
내일: 스플라인 최종 적용 + 스포너 연결(MVP 루프 완성) + HUD(HP/골드/스테이지) 초안
오늘 새로운 팀 배정이 됐는데 11조로 배정을 받았고 29조 기획분들과 협업을 하게 되었다. 새로운 우리팀원분들도 성격이 너무 좋아보여서 다행이다 ㅎㅎ.. 기획분들중에서 저저번에 같이 팀플을 진행했던분이 계셔서 기획과 협업도 문제없을거라 생각한다 오늘 첫날이지만 회의를 꽤 많이 진행했고 서로의 의견을 나누며 하다보니 생각보다 기획분들과 협업이 쉽지않다고는 생각했다 하지만 다행히 진행이 잘 됐고 중간을 맞추는게 쉽지않았지만 잘 해결됐다 기획분들도 너무 좋으신분들이라 다행이다ㅎㅎ 앞으로 이번주에 팀 프로젝트를 진행하며 개발팀은 개인과제진행하고 기획분들은 다음주에 같이 협업해서 진행할 팀프로젝트 기획을 작성하게 된다. 벌써 다음주에 어떤 게임을 팀 프로젝트로 진행할지 너무 기대된다!! 내일도 화이팅!!!
오늘 오전에는 Craft UI와 NPC 대화 관련 세부 버그를 수정하며 UI 안정성을 다졌다. 오후에는 팀 프로젝트 발표를 통해 그동안 준비한 결과물을 공유했고, 팀원들과 발표 후 회고를 진행하며 서로의 개선점과 성과를 나눴다. 저녁에는 단원평가를 치르면서 학습한 내용을 점검했고, 이후 다시 팀원들과 의견을 나누며 하루를 마무리했다.
🧩 학습하며 겪었던 문제점 & 에러
문제정의: Craft UI 프리뷰 슬롯에서 NRE 발생
시도: countText 접근 시 널 가드 추가, 버튼 및 아이콘 참조 재확인
해결 방법: 널 체크 조건을 추가하고 인스펙터에서 잘못된 연결 수정
새롭게 알게 된 점: UI 연결 시 참조가 빠진 경우 런타임에서 예상치 못한 예외가 발생할 수 있음
다시 만나게 된다면: UI 초기화 시점에서 참조 유효성 검증 코드를 넣어 예방할 것
📝 메모
오늘 발표를 하면서 팀원들과 함께 준비한 것들을 공유할 수 있어 뿌듯했다. 긴장도 했지만, 서로 피드백을 주고받으면서 더 성장할 수 있다는 점이 좋았다. 단원평가로 공부한 내용을 다시 확인할 수 있어서 정리하는 시간이 된 것도 의미 있었다. 내일은 오늘 발표에서 받은 피드백을 토대로 개선 작업을 이어가고 싶다.
팀프로젝트가 또 끝났다 이번 팀원들과 또 헤어지게되어서 좀 아쉽지만 덕분에 많이 즐거웠다!