본문 바로가기
AI 게임 제작/AI 코딩

경험치 바 구현

by Yuriring 2025. 9. 27.

유니티 도형, 컬러로 경험치바 제작

Canvas
 └─ XPBar (RectTransform, **RectMask2D**)
     ├─ Bg (RawImage)        ← 전체 바 배경 색
     └─ Fill (RawImage)      ← 채워지는 부분 색 (왼쪽 정렬)
        └─ Shine (옵션, RawImage 반투명 흰색)

 

  • Canvas
    • Canvas Scaler: Scale With Screen Size, Reference 1920×1080, Match 0.5.
  • XPBar (자식 빈 오브젝트)
    • 원하는 크기로 배치(예: W=600, H=20).
    • RectMask2D 추가 → Fill이 항상 박스 안에서만 보이게 클리핑.
    • (선택) Outline 컴포넌트(Effect) 추가해서 외곽선 색/두께 살짝 주면 테두리 느낌.
  • Bg
    • Component: Raw Image (스프라이트 필요 없음)
    • Color: 어두운 회색/검정(예: #2B2B2B, a=255)
    • Anchors: Stretch(Left=0, Right=0, Top=0, Bottom=0) → XPBar에 딱 맞춤
  • Fill
    • Component: Raw Image
    • Anchors: Left 고정 (Left=0, Top=0, Bottom=0, Right=0), Pivot= (0,0.5)
    • Size: Height는 XPBar와 동일, Width는 코드로 제어
    • Color: 강조 색(예: #FFCC33)
    • (옵션) 살짝 둥근 느낌 내고 싶으면 Fill에 Shadow 효과로 부드러운 가장자리 연출
  • Shine
    • Fill의 자식으로 RawImage 하나 더 두고 반투명 흰색(#FFFFFF, a≈30)
    • Height를 Fill보다 조금 작게, Y= -2 정도로 살짝 아래두면 광택 느낌

프롬프트

작업 제목
“경험치(Exp) 시스템과 XPBar UI 연동 (이벤트 기반, 부드러운 채움, 프리팹 구조 고정)”

환경
Unity 2D (URP 유무 무관), C#
이미 “경험치 오브(orb)”를 먹는 로직은 있음 → UI가 채워지는 동작만 확실하게 구현/연동해줘.

1) 데이터/이벤트 레이어 (필수)
아래 단일 매니저를 구현하고, 프로젝트 어디서든 접근 가능하게 해줘(싱글턴 또는 씬 내 1개 보장):
ExperienceManager

공개 속성
int CurrentXP { get; }
int XPForNextLevel { get; set; } (기본값 100, 인스펙터에서 조정 가능)
float Normalized => Mathf.Clamp01((float)CurrentXP / XPForNextLevel)

공개 메서드
void ResetXP(int total = 0, int target = 100)
void AddXP(int amount)

누적 증가, 0 이하 입력 방어
값 변경 시 이벤트 발행

이벤트
event Action<int,int,float> OnXPChanged
파라미터: (current, target, normalized)
디버그 로그(1줄)
AddXP 시: [XP] +{amount} => {CurrentXP}/{XPForNextLevel} ({Normalized:P0})
주의: Update 루프 금지. 오직 AddXP, ResetXP 등에서만 이벤트 발행.

2) UI 프리팹 구조 고정 (이미 존재)

프리팹 이름: XPBar
자식 구조(고정 이름):
XPBar
 ├─ Bg         (배경)
 └─ FILL       (채워지는 바, Image)
     └─ Shine  (하이라이트, FILL에 종속)
RectTransform/Anchor 지시
FILL
Pivot = (0, 0.5) ← 왼쪽 기준
Anchor Min/Max = (0, 0.5) ← 좌측 고정
SizeDelta.x = “가득 찬 폭”(스크립트가 초기값으로 읽어 저장)
Raycast Target 꺼두기
Image Type은 Simple(Filled 타입 사용하지 않음)
Shine
부모(FILL)에 Stretch(Min=(0,0) Max=(1,1))로 붙여서 항상 FILL 영역을 따라가게
추가 스케일/오프셋 없음

3) UI 제어 스크립트
XPBarController (XPBar 프리팹에 붙이기)
공개 필드
RectTransform bgRect
RectTransform fillRect
RectTransform shineRect
float lerpSpeed = 8f (0이면 즉시 반영)

내부
시작 시 fillRect의 원래 너비(fullWidth)를 캐싱
Set01(float normalized) 메서드
목표 너비 = fullWidth * normalized
fillRect.sizeDelta = new Vector2(목표너비, 기존높이)
lerpSpeed > 0 이면 코루틴/Update로 부드럽게 보간, 아니면 즉시 설정

안정성
누락된 참조가 있으면 경고:
[XPBar] Missing fillRect/bgRect/shineRect

디버그(옵션)
매 프레임 로그 금지. 변화 시작/끝에만:
[XPBar] target={normalized:0.00}, width={current}/{full}

4) UI-데이터 연결 부트스트랩
XPBarUIConnector (씬 내 아무 오브젝트 or XPBar 루트에 붙여도 됨)

공개 필드
ExperienceManager manager (비어있으면 FindObjectOfType로 찾아 연결)
XPBarController bar (비어있으면 GetComponentInChildren/자기 자신에서 자동 탐색)

동작
OnEnable에서 manager.OnXPChanged += UpdateBar
OnDisable에서 구독 해제
Start()에서 현재값 즉시 반영
void UpdateBar(int current, int target, float t) → bar.Set01(t)

경고/로그
매니저/바를 못 찾으면 [XPBar] Connector missing refs 경고 1회

5) 에디터 세팅 지시 (반드시 이렇게 세팅되도록 코드/가드)

Canvas(스크린 스페이스) 안에 XPBar 프리팹 배치
원하는 위치(상단 중앙 등)로 앵커/오프셋 조정
XPBar 루트 선택 → XPBarController가 달려 있고,
bgRect = Bg,
fillRect = FILL,
shineRect = Shine 할당
씬 어딘가에 ExperienceManager 1개 배치 (초기 목표치 세팅 가능)
씬 어딘가에 XPBarUIConnector 배치
manager, bar 필드 비어 있어도 자동 연결되게 구현
경험치 오브 흡수 시 ExperienceManager.AddXP(value) 가 반드시 호출되도록(이미 되어 있으면 유지)

6) 검증 체크리스트 (코드에 간단 로그 포함)

게임 시작 직후, XPBar가 0% 상태로 표시 (FILL 너비 = 0)
오브 1개 흡수 시: 콘솔
[XP] +N => a/b (c%)
이어서 XPBar가 부드럽게 채워짐
Shine이 FILL 내부 전체를 맞춰 따라옴(찢어짐/어긋남 없음)
목표치 도달 전까지 누적해서 채워짐 (정규화 값 정확)
누락/실패 시 경고 메시지로 원인 표시

7) 지켜야 할 것 / 하지 말 것

이벤트 기반 업데이트(폴링 X)
FILL의 Image Type = Simple 유지 (Filled 타입/Mask 사용하지 말 것)
프리팹 구조/이름(Bg, FILL, Shine) 하드코딩으로 안전하게 찾기도 지원
인스펙터 미할당이면 이름으로 탐색 시도 후 연결
UI 업데이트는 메인 스레드/메인 카메라 종속 X

8) 실패 원인 가드(코드에서 감지해 경고)

fillRect.pivot.x != 0f 이면: [XPBar] fillRect pivot must be (0,0.5)
fillRect.anchorMin != (0,0.5) 또는 anchorMax != (0,0.5) 이면 경고
Shine가 FILL 자식이 아니면 경고

9) 마무리

기존 흡수 로직은 절대 수정하지 말고, UI만 이벤트로 구독해 작동시켜.
최종적으로 오브 흡수 → ExperienceManager.AddXP → OnXPChanged → XPBarController.Set01 흐름이 보장되게.

 

해결이 안 됨... 이 중 어디가 문제인지 모르겠음

1. 경험치 구슬을 먹어도 경험치가 안 찬다?

2. 경험치는 찼지만 경험치 바에 반영이 안 된다?

실패 진단용 프롬프트

아래 지시대로 기존 스크립트 수정 없이 동작 확인용 진단을 추가하고, 발견된 원인을 자동으로 보정해줘.

목표

XPBar가 안 움직이는 원인을 런타임에 콘솔로 정확히 찍고, 자동 보정(피벗·앵커·폭 캐싱)까지 수행.
UI/매니저 연결 누락·중복을 감지하고 1회 경고.

요구사항
ExperienceManager
OnXPChanged 발생 시 현재 구독자 수 로그 1회:
[XP][Evt] listeners={N}
Awake에서 싱글턴 보장(중복이면 새 인스턴스 파괴 + 경고).
XPBarController (XPBar에 부착)
Start()에서 다음을 자동 보정 & 경고
fillRect의 Pivot/Anchors가 (0,0.5)/(0,0.5) 아니면 즉시 수정하고:
[XPBar][Fix] adjusted pivot/anchors to left
기준 폭은 fillRect.rect.width가 0이면 bgRect.rect.width로 대체. 0이면 경고:
[XPBar][Warn] fullWidth=0; check Bg/FILL size

내부 테스트 노브 추가(에디터 전용):
bool useTestValue, [Range(0,1)] float testValue
useTestValue==true면 매 프레임 Set01(testValue)로 매니저 없이도 FILL이 늘어나는지 단독 검증 가능.
Set01(t)에서는 sizeDelta.x를 바꾸되, 최종 적용 후 fillRect.rect.width를 읽어 반영 결과를 1회 로그:
[XPBar] set t=0.37 ⇒ width=148/400
XPBarUIConnector

OnEnable에서 명시적 연결 순서:
manager ??= FindObjectOfType<ExperienceManager>(true)
bar ??= GetComponentInChildren<XPBarController>(true) 혹은 지정된 참조 사용

둘 중 하나라도 없으면 경고: [XPBar][Err] missing manager/bar

구독 직후, 현재 값으로 즉시 1회 업데이트(Start/LateEnable에서):
bar.Set01(manager.Normalized)

구독/해제 시 로그 1회:
[XPBar][Evt] subscribe ok / [XPBar][Evt] unsubscribe

장식(Shine)
Shine이 FILL의 자식이 아니면 자동 재부모화 + 경고 1회.

콘솔 필터링
매 프레임 스팸 금지. 상태 변할 때/보정할 때만 로그.
검증 시나리오(수동 테스트 지원)

플레이 모드에서 XPBarController.useTestValue = true, testValue 슬라이더를 움직이면 FILL 너비가 즉시 변해야 함.
⇒ 이게 안 되면 UI 계층/앵커/폭 문제.

오브 흡수 시 [XP] +N …와 함께
[XPBar] set t=… 로그가 이어서 떠야 함.
안 뜨면 Connector/이벤트 연결 문제.

 

이제는 경험치를 먹으면 먹을 수록 경험치 바가 오히려 줄어드는 현상 발생 ㅜ-ㅜ

새 프롬프트

작업 목표

  • 게임 시작 시 ExperienceManager의 초기 XP가 50이면, 바가 50%로 채워진 상태로 보인다.
  • 경험치 오브를 먹을수록 바가 늘어나고(회색 영역이 줄어든다), 줄어들지 않는다.
  • XP가 0~Max 사이를 벗어나도 바가 깨지지 않고 0~1로 클램프된다.
  • 프레임 종속/해상도 변경에도 시각 결과가 안정적이다.

현재 증상

  • 초기 XP 50일 때 일부 채워진 상태로 시작하나, 오브를 먹을수록 바가 줄어들고 회색 영역이 길어짐.
  • 콘솔에 [XPBar][Warn] fullWidth=0; check Bg/FILL size 또는 missing fillRect/bgRect/shineRect 류의 로그가 간헐적으로 나타남.

에디터 구조 (바인딩 전제)

  • 씬에 XPBar 프리팹 인스턴스가 있음.
    • 자식: Bg(배경), FILL(채워지는 부분), Shine(FILL의 자식, 하이라이트).
  • ExperienceManager가 씬에 1개 존재하며, 현재 XP/필요 XP를 보관하고 XP 변화 시 이벤트를 발행한다.
  • XPBarUIConnector 또는 XPBarController가 ExperienceManager의 이벤트를 구독해 UI를 갱신한다.

수정/점검 요구사항

  1. 값 계산(핵심 버그 가능성)
    • fillPercent = CurrentXP / RequiredXPForLevel 로 계산하고, Mathf.Clamp01(fillPercent) 적용.
    • 역산/역수, 1 - fillPercent 같은 반전 로직이 들어가 있지 않은지 정리.
    • 델타 기반(증가분/감소분)으로 누적해서 음수 방향으로 가는 코드가 없는지 제거.
  2. UI 적용 방식(둘 중 하나만 사용)
    • Image 방식: FILL은 Image 컴포넌트이며 Type=Filled, Fill Method=Horizontal, Origin=Left, Fill Amount = fillPercent.
      • 이때 RectMask2D/마스크 유무와 관계 없이 결과가 동일해야 한다.
    • RectTransform 폭 방식: FILL.rectTransform.sizeDelta.x = fullWidth * fillPercent
      • fullWidth는 시작 시 FILL의 원래 폭을 캐시하고 사용(0이 아니어야 함).
      • FILL의 Anchors는 왼쪽 고정(최소/최대 X 동일), Pivot=(0, 0.5), anchoredPosition.x=0.
      • sizeDelta.x에 음수가 들어가거나, anchoredPosition.x를 반대로 이동시키는 코드가 없어야 한다.
  3. 참조 누락 방지
    • XPBarController(또는 XPBarUIConnector)에 Bg, FILL, Shine의 RectTransform/Image 레퍼런스가 필수로 연결되어야 함.
    • Awake/OnValidate에서 누락 시 명확한 에러 로그를 내고 조기 종료.
      • 예: [XPBar][Err] missing FILL reference
  4. 구독/해제 일관성
    • ExperienceManager.OnXPChanged(이벤트/Action 등) → XPBarController.OnXPChanged(float current, float required)로 한 번만 연결.
    • 씬 시작 시 콘솔에 구독자 수 로그를 남겨 listeners ≥ 1 확인. 중복 구독 방지.
  5. 초기 상태 동기화
    • 씬 시작 시(구독 직후) XPBarController가 즉시 한 번 현재 값을 읽어 초기 Fill을 세팅.
    • fullWidth=0 경고가 다시는 나오지 않게, 초기화 순서에서 LayoutRebuilder.ForceRebuildLayoutImmediate 등으로 한 번 레이아웃 강제 갱신해도 좋음.
  6. 디버그 로그(임시)
    • XP 갱신 시: [XPBar] cur=XXX req=YYY pct=ZZZ fillAmount=... sizeX=...
    • 계산된 pct가 0→1로 증가하는지, UI 적용값이 같은 방향으로 움직이는지 확인 가능하게.

수용 기준(반드시 통과)

  • 초기 CurrentXP=50, Required=100일 때 정확히 0.5로 표시.
  • 오브 획득 시 CurrentXP 증가에 따라 fillPercent가 증가하고, FILL이 오른쪽으로 확장된다.
  • 해상도/캔버스 스케일 변경 후에도 값과 시각이 일치.
  • 경고 로그: fullWidth=0 / missing fillRect... 더 이상 출력 금지.

에디터 가이드(설정 고정)

  • FILL은 반드시 Image(Type=Filled / Horizontal / Origin=Left) 또는 폭-조절 방식 중 하나만 사용. 혼용 금지.
  • 폭-조절 방식을 쓴다면:
    • XPBar(부모) Anchors 자유, FILL Anchors Left/Left, Pivot (0,0.5), 시작 sizeDelta.x = fullWidth(양수).
    • Bg의 폭이 기준이 되지 않게 하고, FILL의 원래 폭을 기준으로 계산.

이제는 경험치 바는 제대로 채워지고 있지만 경험치 바 UI를 끝까지 쓰지 못하는 문제가 발생한다...

문제 요약

  • 현재 BG는 800px로 의도대로지만, FILL이 800 전부를 쓰지 못해 최대치를 채워도 뒤쪽 회색(BG)이 더 남아 보임.
  • 초기화 타이밍에 rect.width가 0으로 읽히거나, FILL의 RectTransform 세팅(Anchors/Pivot/sizeDelta)이 잘못되어 있을 가능성이 높음.

씬/프리팹 구조(변경 금지)

 
XPBar (루트, RectTransform) ├─ Bg (RectTransform, Image) ├─ FILL (RectTransform, Image) ← 여기 길이를 조절하거나 fillAmount를 조절 └─ Shine (선택) ← FILL과 함께 값에 맞춰 보이도록

목표 동작

  • 기준 폭(fullWidth) = XPBar(또는 Bg) 의 rect.width. 현재는 800px.
  • currentRatio = currentXP / xpForNextLevel (0~1).
  • FILL은 왼쪽에서 오른쪽으로 정확히 fullWidth * currentRatio만큼 채워짐.
  • Shine이 FILL과 함께 이동/클리핑되어 튀어나오지 않음.
  • 플레이 중 Bar/Bg/FILL 폭 디버그 시 세 값이 모두 800으로 일치.

구현 지침 (둘 중 하나로 해결, 둘 다 지원해도 됨)

A안: “크기 조절식” (sizeDelta.x 조절)

  • FILL RectTransform 세팅을 보장:
    • Anchors: Left 고정 (Min=(0,0.5), Max=(0,0.5))
    • Pivot: (0,0.5) (왼쪽 기준으로 자람)
    • anchoredPosition.x = 0, localScale = (1,1,1)
    • sizeDelta.x = fullWidth (800) 를 초기화 시에 세팅
  • 런타임에서 fill.sizeDelta = new Vector2(fullWidth * currentRatio, fill.sizeDelta.y);

B안: “Filled Image” (fillAmount 사용)

  • FILL의 Image를 Filled / Horizontal / Origin=Left 로 설정.
  • RectTransform.width는 fullWidth(800)로 고정, image.fillAmount = currentRatio;

초기화 타이밍(매우 중요)

  • Awake/Start 직후엔 rect.width가 0일 수 있음.
  • Start()에서 yield return new WaitForEndOfFrame() 후 또는 Canvas.ForceUpdateCanvases() / LayoutRebuilder.ForceRebuildLayoutImmediate(barRect) 호출 후 fullWidth를 읽어 캐시해.
  • fullWidth가 0이면 경고 로그를 띄우고 다음 프레임에 재시도하도록.

마스크/이펙트 주의

  • 마스크가 필요하면 RectMask2D는 XPBar(부모) 에 적용. (BG나 FILL에 붙지 않게)
  • Outline/Shadow 등 이펙트가 시각 길이를 왜곡하지 않도록 FILL/Bg에서는 비활성 또는 아주 작은 값으로.
  • XPBar/Bg/FILL/ Shine의 localScale은 (1,1,1) 로 강제.

디버그/검증(필수)

  • 초기화 시 1회:
    • Debug.Log($"[XPBAR] widths — Bar:{bar.rect.width:F1} Bg:{bg.rect.width:F1} Fill:{fill.rect.width:F1} full:{fullWidth:F1}")
    • 800이 아닌 값이 있으면 경고 로그 출력.
  • XP 갱신 시:
    • Debug.Log($"[XPBAR] ratio:{currentRatio:F2} fillW:{(fullWidth*currentRatio):F1}")
  • 에디터에서 CurrentXP=50, Next=100로 시작하면 정확히 절반 차야 함.
  • 100%일 때 회색 BG가 추가로 남아 보이지 않음(FILL 폭=fullWidth로 맞는지 확인).

수용 조건

  • CurrentXP를 0→50→100으로 바꿔가며 Play 시, FILL이 0%, 50%, 100%로 정확히 보임.
  • 경험치 구슬 획득 시 증가 방향으로 부드럽게 채워짐(선형/lerp는 유지 가능).
  • 모든 해상도/캔버스 스케일에서 Bar/Bg/Fill 폭이 동일하게 계산됨.
  • 기존 프리팹/오브젝트 이름(XPBar/Bg/FILL/Shine) 유지.

위 조건을 만족하도록 컨트롤러/세팅을 수정하고, fullWidth=0 문제를 피하기 위한 초기화 순서/강제 레이아웃 재빌드를 반드시 반영해줘.

결과물

해결했다!!!!