튜토리얼: 촌장 냥이가 안내하는 10단계 온보딩
- 새 게임: 회관만 있는 빈 마을(고양이 0·자원 0)에서 시작 - 촌장 냥이(코숏 스프라이트) + 말풍선 가이드 오버레이 (진행도 n/10, 태스크 칩) - 라인: 인사 → 첫 입양 → 부두 건설(무료·강물 위)·배치 → 둘째 입양 → 벌목장 건설(생선 30)·배치 → 부엌 건설(목재 60) → 회관 Lv2 증축 → 작별 - 조건 충족 시 자동 진행, 인사·작별은 버튼. 완료 후 자유 운영(step 99) - 부두·벌목장을 상점 구매 건물로 이동 (기존 세이브는 "보유 중"+튜토리얼 생략) - 튜토리얼 입양 후보는 필요 적성(낚시→벌목) 보장 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
이 커밋은 다음에 포함됨:
+11
-1
@@ -149,7 +149,7 @@ export const APT_POINT_EVERY = 3; // 3레벨마다 적성 포인트 1
|
||||
|
||||
export interface BuildDef {
|
||||
id: string; // 건물 인스턴스 id (단일 건설)
|
||||
type: "quarry" | "kitchen" | "cat_house" | "sun_deck";
|
||||
type: "dock" | "lumberyard" | "quarry" | "kitchen" | "cat_house" | "sun_deck";
|
||||
kind?: "work" | "passive"; // passive: 고양이 배치 없음 (기본 work)
|
||||
name: string;
|
||||
emoji: string;
|
||||
@@ -163,6 +163,16 @@ export interface BuildDef {
|
||||
}
|
||||
|
||||
export const BUILD_DEFS: BuildDef[] = [
|
||||
{
|
||||
id: "dock_1", type: "dock", name: "부두", emoji: "⚓",
|
||||
aptitude: "fishing", resource: "fish", maxSlots: 3, cost: {}, reqHallLv: 1, buildTime: 15,
|
||||
desc: "낚시 적성 고양이가 생선을 잡습니다. 강물 위에 지어요. 첫 건물이라 무료!",
|
||||
},
|
||||
{
|
||||
id: "lumberyard_1", type: "lumberyard", name: "벌목장", emoji: "🪓",
|
||||
aptitude: "logging", resource: "wood", maxSlots: 3, cost: { fish: 30 }, reqHallLv: 1, buildTime: 30,
|
||||
desc: "벌목 적성 고양이가 목재를 모읍니다. 목재는 모든 건설의 기본!",
|
||||
},
|
||||
{
|
||||
id: "kitchen_1", type: "kitchen", name: "부엌", emoji: "🍳",
|
||||
aptitude: "cooking", maxSlots: 2, cost: { wood: 60 }, reqHallLv: 1, buildTime: 60,
|
||||
|
||||
+18
-24
@@ -106,30 +106,16 @@ function generateCat(forceAptitude?: AptitudeKey): Cat {
|
||||
|
||||
// ---------- 초기 상태 ----------
|
||||
|
||||
function createBuildings(): Building[] {
|
||||
return [
|
||||
{
|
||||
id: "dock_1", type: "dock", name: "부두", emoji: "⚓",
|
||||
pos: { ...DEFAULT_BUILDING_POS.dock_1 },
|
||||
level: 1, maxSlots: 3, aptitude: "fishing", resource: "fish",
|
||||
assigned: [], stored: 0,
|
||||
},
|
||||
{
|
||||
id: "lumberyard_1", type: "lumberyard", name: "벌목장", emoji: "🪓",
|
||||
pos: { ...DEFAULT_BUILDING_POS.lumberyard_1 },
|
||||
level: 1, maxSlots: 3, aptitude: "logging", resource: "wood",
|
||||
assigned: [], stored: 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function createInitialState(): GameState {
|
||||
// 시작 고양이 2마리는 각각 낚시·벌목이 가능하도록 보정 (초반 막힘 방지)
|
||||
// 새 게임: 회관만 있는 빈 마을에서 촌장 냥이 튜토리얼로 시작
|
||||
return {
|
||||
version: 1,
|
||||
boardVersion: BOARD_VERSION,
|
||||
tutorialStep: 0,
|
||||
resources: { fish: 0, wood: 0, stone: 0 },
|
||||
cats: [generateCat("fishing"), generateCat("logging")],
|
||||
buildings: createBuildings(),
|
||||
cats: [],
|
||||
buildings: [],
|
||||
decors: [],
|
||||
hall: { level: 1, capacity: 3 },
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
@@ -291,6 +277,7 @@ export const engine = {
|
||||
for (const b of state.buildings)
|
||||
b.pos ??= { ...(DEFAULT_BUILDING_POS[b.id] ?? { i: 4, j: 5 }) };
|
||||
state.decors ??= [];
|
||||
state.tutorialStep ??= 99; // 기존 마을은 튜토리얼 생략
|
||||
// 보드 개편(크기·풋프린트 변경) 시 건물 위치를 새 기본값으로 재배치
|
||||
if (state.boardVersion !== BOARD_VERSION) {
|
||||
for (const b of state.buildings)
|
||||
@@ -317,6 +304,12 @@ export const engine = {
|
||||
return r;
|
||||
},
|
||||
|
||||
/** 튜토리얼 진행 갱신 */
|
||||
setTutorialStep(step: number) {
|
||||
if (!state) return;
|
||||
state.tutorialStep = step;
|
||||
},
|
||||
|
||||
tick(dt: number) {
|
||||
if (!state) return;
|
||||
for (const building of state.buildings) {
|
||||
@@ -489,9 +482,9 @@ export const engine = {
|
||||
state.updatedAt = Date.now();
|
||||
},
|
||||
|
||||
/** 입양 후보 생성 (아직 마을에 합류하지 않음) */
|
||||
generateCandidates(count = 3): Cat[] {
|
||||
return Array.from({ length: count }, () => generateCat());
|
||||
/** 입양 후보 생성 (아직 마을에 합류하지 않음). forceApt로 특정 적성 보장 */
|
||||
generateCandidates(count = 3, forceApt?: AptitudeKey): Cat[] {
|
||||
return Array.from({ length: count }, () => generateCat(forceApt));
|
||||
},
|
||||
|
||||
/** 후보 중 한 마리 입양 확정 */
|
||||
@@ -505,7 +498,7 @@ export const engine = {
|
||||
|
||||
/** 공통 건설: 상점 구매 후 선택한 타일에 배치 (비용은 배치 확정 시 차감) */
|
||||
build(
|
||||
type: "quarry" | "kitchen" | "cat_house" | "sun_deck",
|
||||
type: "dock" | "lumberyard" | "quarry" | "kitchen" | "cat_house" | "sun_deck",
|
||||
pos: { i: number; j: number },
|
||||
): string | null {
|
||||
if (!state) return null;
|
||||
@@ -563,6 +556,7 @@ export const engine = {
|
||||
.join(", ")})`;
|
||||
consume(state, def.cost);
|
||||
state.decors ??= [];
|
||||
state.tutorialStep ??= 99; // 기존 마을은 튜토리얼 생략
|
||||
// 같은 타일의 기존 장식 제거 후 배치
|
||||
state.decors = state.decors.filter(
|
||||
(d) => !(d.pos.i === pos.i && d.pos.j === pos.j),
|
||||
|
||||
@@ -68,6 +68,7 @@ export interface DecoTile {
|
||||
export interface GameState {
|
||||
version: 1;
|
||||
boardVersion?: number; // 보드 개편 마이그레이션 마커
|
||||
tutorialStep?: number; // 튜토리얼 진행 (99 = 완료)
|
||||
decors?: DecoTile[]; // 꾸미기 바닥 타일
|
||||
resources: Record<ResourceKey, number>;
|
||||
cats: Cat[];
|
||||
|
||||
새 이슈에서 참조
사용자 차단