ARCANE BREW 개발기 (4) - 재료 시스템과 데이터 아키텍처
목차 보기
시리즈 목차 보기4 / 7
ARCANE BREW 개발기
4 / 7
33개 재료라는 도전
ARCANE BREW의 핵심 콘텐츠는 포션 재료다. 5원소(불, 물, 흙, 바람, 정령) 체계 아래 33개 재료가 존재하고, 각각이 이름, 한글명, 설명, 효과, 원산지, 세계관 설정을 갖는다. 단순 목 데이터가 아니라 실제 세계관이 느껴지는 데이터를 만들고 싶었다.
Ingredient 인터페이스 설계
데이터 구조부터 확정했다. 모든 재료가 따르는 계약(contract)이다.
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
}설계 시 고려한 포인트가 몇 가지 있다.
color와glowColor분리 — 카드 배경색과 호버 시 글로우 색을 다르게 적용하기 위해서다. 글로우는 원색보다 밝고 채도가 높은 톤을 쓴다.gatherDifficulty— 1~5 스케일. 레어리티와 별개로 채집 난이도를 부여해서 게임적 깊이를 더했다. 일반 등급이어도 채집이 어려운 재료가 있다.icon— 이모지를 사용했다. 커스텀 아이콘 에셋 대신 유니코드 이모지로 통일해서 번들 부담을 없앴다.
재료 데이터 예시
33개 중 하나를 보면 데이터의 결을 느낄 수 있다.
{
id: 'dragons-breath-ember',
name: "Dragon's Breath Ember",
nameKo: '용의 숨결 잔불',
element: 'fire',
rarity: 'rare',
color: '#e25822',
glowColor: '#ff6b35',
description: '고대 화룡이 마지막 숨을 내쉴 때 떨어지는 불씨...',
effect: '포션의 지속 시간을 극적으로 연장하고 불꽃 속성을 부여한다.',
icon: '🐉',
gatherDifficulty: 4,
origin: '적염 산맥 최심부의 용의 묘지',
}각 재료의 description과 origin이 세계관 안에서 자연스럽게 연결되어야 했다. "용의 묘지"라는 장소가 불 원소 재료의 원산지로 등장하면, 다른 불 원소 재료의 원산지와도 지리적 일관성이 있어야 한다.
레어리티 분포는 의도적으로 비대칭하게 잡았다.
| 등급 | 개수 | 비율 |
|---|---|---|
| 일반(common) | 18 | 55% |
| 희귀(rare) | 10 | 30% |
| 전설(legendary) | 5 | 15% |
전설 등급은 원소당 1개씩 배치했다. 각 원소의 정점에 해당하는 재료다.
AI 서브 에이전트로 데이터 대량 생성
33개 재료 각각에 이름, 설명, 효과, 원산지를 일관된 톤으로 작성하는 건 수작업으로는 상당히 고된 작업이다. 여기서 Claude Code의 서브 에이전트를 활용했다.
접근 방식은 이랬다.
- 톤 가이드 작성 — 세계관 룰("5원소는 상생/상극 관계"), 이름 짓기 규칙("영문명은 2~3단어, 한글명은 자연스러운 판타지 네이밍"), 레어리티 기준("전설 등급은 신화적 존재와 관련")을 명시했다.
- 원소별 병렬 생성 — 5원소를 각각 별도 서브 에이전트 태스크로 분리했다. "불 원소 재료 7개를 생성해줘. 일반 4, 희귀 2, 전설 1"처럼 원소 단위로 요청을 나눴다.
- 교차 검증 — 생성된 데이터를 합친 뒤, 중복된 컨셉이 없는지, 색상 코드가 원소별로 구분되는지 확인했다.
핵심은 톤 가이드의 구체성이었다. "판타지 느낌으로"라는 모호한 지시 대신, "description은 2문장 이내, 첫 문장은 재료의 물리적 특성, 두 번째 문장은 세계관 내 전설이나 용도"처럼 명확한 포맷을 제시했더니 일관성이 크게 올라갔다.
Pinia 스토어의 Composition API setup 패턴
재료 데이터를 관리하는 Pinia 스토어는 Composition API의 setup 패턴으로 작성했다.
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 구분 대신, ref와 computed로 자연스럽게 반응형 데이터를 구성한다. 이 패턴이 좋은 이유는 Composition API의 composable과 동일한 멘탈 모델을 유지할 수 있다는 점이다.
필터는 selectedElement와 selectedRarity를 null로 초기화해서 "전체 보기" 상태를 기본값으로 잡았다. 검색은 한글명(nameKo)과 영문명(name) 모두에 대응한다.
카드 컴포넌트와 원소 글로우
재료 카드는 원소별 color와 glowColor를 활용한 동적 스타일링이 핵심이다.
<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 서브 에이전트를 활용한 대량 데이터 생성도 "톤 가이드"라는 제약 조건을 명확히 제시하면 충분히 실용적이라는 걸 체감했다.
다음 편에서는 이 재료들을 실제로 조합하는 가마솥과 포션 조제 시스템을 다룬다. 드래그 앤 드롭으로 재료를 가마솥에 넣고, 원소 궁합에 따라 포션이 만들어지는 과정을 구현한 이야기다.