목록으로
ARCANE BREW 개발기

ARCANE BREW 개발기 (4) - 재료 시스템과 데이터 아키텍처

2026년 01월 22일9 min read
#Vue#Pinia#데이터 설계#AI#ARCANE BREW

목차 보기
시리즈 목차 보기4 / 7

🧪 33개 재료라는 도전

ARCANE BREW의 핵심 콘텐츠는 포션 재료다. 5원소(불, 물, 흙, 바람, 정령) 체계 아래 33개 재료가 존재하고, 각각이 이름, 한글명, 설명, 효과, 원산지, 세계관 설정을 갖는다. 단순 목 데이터가 아니라 실제 세계관이 느껴지는 데이터를 만들고 싶었다.

📐 Ingredient 인터페이스 설계

데이터 구조부터 확정했다. 모든 재료가 따르는 계약(contract)이다.

ts
interface Ingredient {
  id: string
  name: string
  nameKo: string
  element: 'fire' | 'water' | 'earth' | 'air' | 'spirit'
  rarity: 'common' | 'rare' | 'legendary'
  color: string
  glowColor: string
  description: string
  effect: string
  icon: string
  gatherDifficulty: number
  origin: string
}

설계 시 고려한 포인트가 몇 가지 있다.

  • colorglowColor 분리 — 카드 배경색과 호버 시 글로우 색을 다르게 적용하기 위해서다. 글로우는 원색보다 밝고 채도가 높은 톤을 쓴다.
  • gatherDifficulty — 1~5 스케일. 레어리티와 별개로 채집 난이도를 부여해서 게임적 깊이를 더했다. 일반 등급이어도 채집이 어려운 재료가 있다.
  • icon — 이모지를 사용했다. 커스텀 아이콘 에셋 대신 유니코드 이모지로 통일해서 번들 부담을 없앴다.

🐉 재료 데이터 예시

33개 중 하나를 보면 데이터의 결을 느낄 수 있다.

ts
{
  id: 'dragons-breath-ember',
  name: "Dragon's Breath Ember",
  nameKo: '용의 숨결 잔불',
  element: 'fire',
  rarity: 'rare',
  color: '#e25822',
  glowColor: '#ff6b35',
  description: '고대 화룡이 마지막 숨을 내쉴 때 떨어지는 불씨...',
  effect: '포션의 지속 시간을 극적으로 연장하고 불꽃 속성을 부여한다.',
  icon: '🐉',
  gatherDifficulty: 4,
  origin: '적염 산맥 최심부의 용의 묘지',
}

각 재료의 descriptionorigin이 세계관 안에서 자연스럽게 연결되어야 했다. "용의 묘지"라는 장소가 불 원소 재료의 원산지로 등장하면, 다른 불 원소 재료의 원산지와도 지리적 일관성이 있어야 한다.

레어리티 분포는 의도적으로 비대칭하게 잡았다.

등급개수비율
일반(common)1855%
희귀(rare)1030%
전설(legendary)515%

전설 등급은 원소당 1개씩 배치했다. 각 원소의 정점에 해당하는 재료다.

🤖 AI 서브 에이전트로 데이터 대량 생성

33개 재료 각각에 이름, 설명, 효과, 원산지를 일관된 톤으로 작성하는 건 수작업으로는 상당히 고된 작업이다. 여기서 Claude Code의 서브 에이전트를 활용했다.

접근 방식은 이랬다.

  1. 톤 가이드 작성 — 세계관 룰("5원소는 상생/상극 관계"), 이름 짓기 규칙("영문명은 2~3단어, 한글명은 자연스러운 판타지 네이밍"), 레어리티 기준("전설 등급은 신화적 존재와 관련")을 명시했다.
  2. 원소별 병렬 생성 — 5원소를 각각 별도 서브 에이전트 태스크로 분리했다. "불 원소 재료 7개를 생성해줘. 일반 4, 희귀 2, 전설 1"처럼 원소 단위로 요청을 나눴다.
  3. 교차 검증 — 생성된 데이터를 합친 뒤, 중복된 컨셉이 없는지, 색상 코드가 원소별로 구분되는지 확인했다.

핵심은 톤 가이드의 구체성이었다. "판타지 느낌으로"라는 모호한 지시 대신, "description은 2문장 이내, 첫 문장은 재료의 물리적 특성, 두 번째 문장은 세계관 내 전설이나 용도"처럼 명확한 포맷을 제시했더니 일관성이 크게 올라갔다.

🏪 Pinia 스토어 — Composition API setup 패턴

재료 데이터를 관리하는 Pinia 스토어는 Composition API의 setup 패턴으로 작성했다.

ts
export const useIngredientStore = defineStore('ingredients', () => {
  // 상태
  const ingredients = ref<Ingredient[]>(allIngredients)
  const selectedElement = ref<ElementType | null>(null)
  const selectedRarity = ref<RarityType | null>(null)
  const searchQuery = ref('')

  // 필터링된 목록
  const filtered = computed(() => {
    return ingredients.value.filter((item) => {
      const matchElement = !selectedElement.value
        || item.element === selectedElement.value
      const matchRarity = !selectedRarity.value
        || item.rarity === selectedRarity.value
      const matchSearch = !searchQuery.value
        || item.nameKo.includes(searchQuery.value)
        || item.name.toLowerCase().includes(searchQuery.value.toLowerCase())
      return matchElement && matchRarity && matchSearch
    })
  })

  // 원소별 카운트
  const countByElement = computed(() => {
    const counts: Record<string, number> = {}
    for (const item of ingredients.value) {
      counts[item.element] = (counts[item.element] || 0) + 1
    }
    return counts
  })

  return {
    ingredients, selectedElement, selectedRarity,
    searchQuery, filtered, countByElement,
  }
})

Options API의 state/getters/actions 구분 대신, refcomputed로 자연스럽게 반응형 데이터를 구성한다. 이 패턴이 좋은 이유는 Composition API의 composable과 동일한 멘탈 모델을 유지할 수 있다는 점이다.

필터는 selectedElementselectedRaritynull로 초기화해서 "전체 보기" 상태를 기본값으로 잡았다. 검색은 한글명(nameKo)과 영문명(name) 모두에 대응한다.

🃏 카드 컴포넌트 — 원소 글로우

재료 카드는 원소별 colorglowColor를 활용한 동적 스타일링이 핵심이다.

vue
<template>
  <div
    class="ingredient-card"
    :style="{
      '--glow': ingredient.glowColor,
      '--base': ingredient.color,
    }"
  >
    <span class="icon">{{ ingredient.icon }}</span>
    <h3>{{ ingredient.nameKo }}</h3>
    <span :class="['rarity-badge', ingredient.rarity]">
      {{ rarityLabel[ingredient.rarity] }}
    </span>
  </div>
</template>

CSS 커스텀 프로퍼티(--glow, --base)를 인라인으로 주입하고, 호버 시 box-shadow: 0 0 20px var(--glow)를 적용하면 각 카드가 자기 원소 색으로 빛난다. 불 원소는 주황빛, 물 원소는 청색광, 정령 원소는 보라빛 — 33개 카드가 나열되면 원소 분포가 시각적으로 한눈에 읽힌다.

호버 애니메이션은 transform: translateY(-4px)box-shadow 전환을 transition으로 처리했다. GSAP 없이 CSS만으로 충분한 수준이라 성능 부담이 없다.

📋 상세 모달과 FilterBar

카드를 클릭하면 상세 모달이 열린다. description, effect, origin, gatherDifficulty를 보여주는 심플한 구성이다. 모달 배경에 해당 원소의 파티클을 띄워서 몰입감을 더했다 — 3편에서 만든 useParticles가 여기서 재활용된다.

FilterBar는 원소 5종 + 레어리티 3종의 토글 버튼으로 구성했다. 선택된 필터는 Pinia 스토어의 selectedElement/selectedRarity를 갱신하고, filtered computed가 즉시 반응한다. 필터 해제 시 null로 되돌리면 전체 목록이 복원된다.

📝 정리

33개 재료 데이터를 설계하면서 느낀 점은, 데이터 구조가 곧 UI를 결정한다는 것이다. color/glowColor를 인터페이스에 넣어둔 덕분에 카드 컴포넌트가 깔끔해졌고, gatherDifficulty 필드 하나가 게임적 재미를 만들어냈다. AI 서브 에이전트를 활용한 대량 데이터 생성도 "톤 가이드"라는 제약 조건을 명확히 제시하면 충분히 실용적이라는 걸 체감했다.

다음 편에서는 이 재료들을 실제로 조합하는 가마솥과 포션 조제 시스템을 다룬다. 드래그 앤 드롭으로 재료를 가마솥에 넣고, 원소 궁합에 따라 포션이 만들어지는 과정을 구현한 이야기다.

검색...

검색...