diff --git a/assets-src/build2.png b/assets-src/build2.png new file mode 100644 index 0000000..da84422 Binary files /dev/null and b/assets-src/build2.png differ diff --git a/assets-src/icons.png b/assets-src/icons.png new file mode 100644 index 0000000..ed3cfe4 Binary files /dev/null and b/assets-src/icons.png differ diff --git a/assets-src/nature.png b/assets-src/nature.png new file mode 100644 index 0000000..a34d4e0 Binary files /dev/null and b/assets-src/nature.png differ diff --git a/public/assets/buildings/cat_house.png b/public/assets/buildings/cat_house.png new file mode 100644 index 0000000..48c5441 Binary files /dev/null and b/public/assets/buildings/cat_house.png differ diff --git a/public/assets/buildings/market.png b/public/assets/buildings/market.png new file mode 100644 index 0000000..4f7717e Binary files /dev/null and b/public/assets/buildings/market.png differ diff --git a/public/assets/buildings/sun_deck.png b/public/assets/buildings/sun_deck.png new file mode 100644 index 0000000..2ead90d Binary files /dev/null and b/public/assets/buildings/sun_deck.png differ diff --git a/public/assets/buildings/warehouse.png b/public/assets/buildings/warehouse.png new file mode 100644 index 0000000..2350f3b Binary files /dev/null and b/public/assets/buildings/warehouse.png differ diff --git a/public/assets/icons/anger.png b/public/assets/icons/anger.png new file mode 100644 index 0000000..3090c68 Binary files /dev/null and b/public/assets/icons/anger.png differ diff --git a/public/assets/icons/coin.png b/public/assets/icons/coin.png new file mode 100644 index 0000000..cd8d1bb Binary files /dev/null and b/public/assets/icons/coin.png differ diff --git a/public/assets/icons/fish.png b/public/assets/icons/fish.png new file mode 100644 index 0000000..458cdad Binary files /dev/null and b/public/assets/icons/fish.png differ diff --git a/public/assets/icons/hammer.png b/public/assets/icons/hammer.png new file mode 100644 index 0000000..8311466 Binary files /dev/null and b/public/assets/icons/hammer.png differ diff --git a/public/assets/icons/heart.png b/public/assets/icons/heart.png new file mode 100644 index 0000000..bb6206a Binary files /dev/null and b/public/assets/icons/heart.png differ diff --git a/public/assets/icons/meal.png b/public/assets/icons/meal.png new file mode 100644 index 0000000..988628f Binary files /dev/null and b/public/assets/icons/meal.png differ diff --git a/public/assets/icons/pan.png b/public/assets/icons/pan.png new file mode 100644 index 0000000..3d2e44f Binary files /dev/null and b/public/assets/icons/pan.png differ diff --git a/public/assets/icons/plate.png b/public/assets/icons/plate.png new file mode 100644 index 0000000..1629083 Binary files /dev/null and b/public/assets/icons/plate.png differ diff --git a/public/assets/icons/sleep.png b/public/assets/icons/sleep.png new file mode 100644 index 0000000..69d3e78 Binary files /dev/null and b/public/assets/icons/sleep.png differ diff --git a/public/assets/icons/stone.png b/public/assets/icons/stone.png new file mode 100644 index 0000000..8d447b5 Binary files /dev/null and b/public/assets/icons/stone.png differ diff --git a/public/assets/icons/sweat.png b/public/assets/icons/sweat.png new file mode 100644 index 0000000..47cb288 Binary files /dev/null and b/public/assets/icons/sweat.png differ diff --git a/public/assets/icons/wood.png b/public/assets/icons/wood.png new file mode 100644 index 0000000..76fe012 Binary files /dev/null and b/public/assets/icons/wood.png differ diff --git a/public/assets/nature/bush.png b/public/assets/nature/bush.png new file mode 100644 index 0000000..8c5dd9b Binary files /dev/null and b/public/assets/nature/bush.png differ diff --git a/public/assets/nature/daisy.png b/public/assets/nature/daisy.png new file mode 100644 index 0000000..f18987d Binary files /dev/null and b/public/assets/nature/daisy.png differ diff --git a/public/assets/nature/leafy.png b/public/assets/nature/leafy.png new file mode 100644 index 0000000..9b1ea15 Binary files /dev/null and b/public/assets/nature/leafy.png differ diff --git a/public/assets/nature/mushroom.png b/public/assets/nature/mushroom.png new file mode 100644 index 0000000..b0b7242 Binary files /dev/null and b/public/assets/nature/mushroom.png differ diff --git a/public/assets/nature/pine.png b/public/assets/nature/pine.png new file mode 100644 index 0000000..862bc3d Binary files /dev/null and b/public/assets/nature/pine.png differ diff --git a/public/assets/nature/pine_small.png b/public/assets/nature/pine_small.png new file mode 100644 index 0000000..1284045 Binary files /dev/null and b/public/assets/nature/pine_small.png differ diff --git a/public/assets/nature/rock.png b/public/assets/nature/rock.png new file mode 100644 index 0000000..10a8cdd Binary files /dev/null and b/public/assets/nature/rock.png differ diff --git a/public/assets/nature/tulip.png b/public/assets/nature/tulip.png new file mode 100644 index 0000000..1e232fc Binary files /dev/null and b/public/assets/nature/tulip.png differ diff --git a/public/assets/nature/wheat.png b/public/assets/nature/wheat.png new file mode 100644 index 0000000..6bfd474 Binary files /dev/null and b/public/assets/nature/wheat.png differ diff --git a/src/components/game/Game.tsx b/src/components/game/Game.tsx index 3321c3a..a17712a 100644 --- a/src/components/game/Game.tsx +++ b/src/components/game/Game.tsx @@ -21,6 +21,7 @@ import { catSprite, DECO_DEFS, HALL_LEVELS, + ICON_IMG, KITCHEN_UPGRADE_COST, MEAL_PRICES, mealCap, @@ -263,20 +264,27 @@ export default function Game() { {/* μžμ› pill */}
- {[ - ["🐱", `${state.cats.length}/${state.hall.capacity}`], - ["🐟", Math.floor(state.resources.fish)], - ["πŸͺ΅", Math.floor(state.resources.wood)], - ["πŸͺ¨", Math.floor(state.resources.stone)], - ["πŸ–", meals], - ["πŸͺ™", Math.floor(state.coins ?? 0)], - ].map(([icon, value]) => ( + {( + [ + ["🐱", null, `${state.cats.length}/${state.hall.capacity}`], + ["🐟", ICON_IMG.fish, Math.floor(state.resources.fish)], + ["πŸͺ΅", ICON_IMG.wood, Math.floor(state.resources.wood)], + ["πŸͺ¨", ICON_IMG.stone, Math.floor(state.resources.stone)], + ["πŸ–", ICON_IMG.meal, meals], + ["πŸͺ™", ICON_IMG.coin, Math.floor(state.coins ?? 0)], + ] as Array<[string, string | null, string | number]> + ).map(([emoji, img, value]) => ( - {icon} + {img ? ( + /* eslint-disable-next-line @next/next/no-img-element */ + + ) : ( + emoji + )} {value} diff --git a/src/components/game/VillageView.tsx b/src/components/game/VillageView.tsx index 38fd90e..bd46a37 100644 --- a/src/components/game/VillageView.tsx +++ b/src/components/game/VillageView.tsx @@ -8,6 +8,8 @@ import { BUILDING_IMG, catSprite, DECO_DEFS, + ICON_IMG, + NATURE_IMG, footprintOf, HALL_TILE, TOOL_IMG, @@ -23,6 +25,10 @@ import type { } from "@/lib/game/types"; const RES_EMOJI: Record = { fish: "🐟", wood: "πŸͺ΅", stone: "πŸͺ¨" }; +const RES_ICON: Record = { fish: "fish", wood: "wood", stone: "stone" }; +const EMOJI_TO_NATURE: Record = { + "🌼": "daisy", "🌷": "tulip", "🌾": "wheat", "πŸ„": "mushroom", "πŸͺ¨": "rock", +}; export interface PlacingState { type?: BuildingType; // 건물 λ°°μΉ˜Β·μ΄λ™ @@ -100,7 +106,8 @@ interface Bubble { x: number; y: number; born: number; - text: string; + text?: string; // 이λͺ¨μ§€Β·ν…μŠ€νŠΈ + icon?: string; // ICON_IMG ν‚€ (있으면 μ΄λ―Έμ§€λ‘œ) } const WALK_SPEED = 42; // px/초 @@ -260,10 +267,17 @@ export default function VillageView({ .getPropertyValue("--font-jua") .trim() || "sans-serif"; - // λ‚˜λ¬΄ 판자 λ°°μ§€ 그리기 (μžμ› 카운트) - const drawPlankBadge = (cx: number, cy: number, label: string) => { + // λ‚˜λ¬΄ 판자 λ°°μ§€ 그리기 (μ•„μ΄μ½˜ + ν…μŠ€νŠΈ) + const drawPlankBadge = ( + cx: number, + cy: number, + label: string, + iconKey?: string, + ) => { + const iconImg = iconKey ? getImage(ICON_IMG[iconKey]) : undefined; + const iconW = iconImg ? 16 : 0; ctx.font = `bold 13px ${juaFamily}`; - const w = ctx.measureText(label).width + 18; + const w = ctx.measureText(label).width + 18 + iconW; const h = 22; const x0 = cx - w / 2; const y0 = cy - h; @@ -294,10 +308,15 @@ export default function VillageView({ ctx.lineWidth = 2; ctx.stroke(); ctx.lineWidth = 1; - // ν…μŠ€νŠΈ + // μ•„μ΄μ½˜ + ν…μŠ€νŠΈ + if (iconImg) { + const ih = 15; + const iw = (iconImg.width / iconImg.height) * ih; + ctx.drawImage(iconImg, x0 + 8, y0 + (h - ih) / 2, iw, ih); + } ctx.fillStyle = "#fbf1d9"; ctx.textAlign = "center"; - ctx.fillText(label, cx, y0 + 16); + ctx.fillText(label, cx + iconW / 2, y0 + 16); }; const draw = (now: number) => { @@ -327,7 +346,7 @@ export default function VillageView({ cat.status.fullness > anim.lastFullness + 15 ) { bubblesRef.current.push({ - x: anim.x, y: anim.y - 40, born: now, text: "🍽️", + x: anim.x, y: anim.y - 40, born: now, icon: "plate", }); } anim.lastFullness = cat.status.fullness; @@ -352,7 +371,7 @@ export default function VillageView({ anim.workBuildingId = null; // πŸ’€ 버블 if (Math.random() < dt / 4) { - bubblesRef.current.push({ x: anim.x + 10, y: anim.y - 36, born: now, text: "πŸ’€" }); + bubblesRef.current.push({ x: anim.x + 10, y: anim.y - 36, born: now, icon: "sleep" }); } } else if (cat.state === "striking") { anim.workBuildingId = null; @@ -363,7 +382,7 @@ export default function VillageView({ anim.ty = t.y; } if (Math.random() < dt / 5) { - bubblesRef.current.push({ x: anim.x + 10, y: anim.y - 36, born: now, text: "πŸ’’" }); + bubblesRef.current.push({ x: anim.x + 10, y: anim.y - 36, born: now, icon: "anger" }); } } else if (cat.assignedTo) { const building = state.buildings.find((b) => b.id === cat.assignedTo); @@ -407,8 +426,7 @@ export default function VillageView({ if (b && catRate(cat, b.aptitude) > 0) { bubblesRef.current.push({ x: anim.x, y: anim.y - 34, born: now, - text: - b.type === "kitchen" ? "🍳" : RES_EMOJI[b.resource ?? "fish"], + icon: b.type === "kitchen" ? "pan" : RES_ICON[b.resource ?? "fish"], }); } } @@ -569,23 +587,73 @@ export default function VillageView({ for (const t of TREES) { const x = isoX(t.i, t.j); const y = isoY(t.i, t.j) + TH / 2; - sprites.push({ y, draw: () => { shadow(x, y + 4, 14); drawEmoji("🌲", x, y + 6, 34); } }); + sprites.push({ + y, + draw: () => { + const img = getImage(NATURE_IMG.pine); + if (img) { + const h = 52; + const w = (img.width / img.height) * h; + shadow(x, y + 4, 12); + ctx.drawImage(img, x - w / 2, y + 8 - h, w, h); + } else { + shadow(x, y + 4, 14); + drawEmoji("🌲", x, y + 6, 34); + } + }, + }); } for (const d of DECOR) { const x = isoX(d.i, d.j); const y = isoY(d.i, d.j) + TH / 2; - sprites.push({ y: y - 6, draw: () => drawEmoji(d.emoji, x, y + 4, 18) }); + sprites.push({ + y: y - 6, + draw: () => { + const img = getImage(NATURE_IMG[EMOJI_TO_NATURE[d.emoji] ?? ""]); + if (img) { + const h = 22; + const w = (img.width / img.height) * h; + ctx.drawImage(img, x - w / 2, y + 6 - h, w, h); + } else drawEmoji(d.emoji, x, y + 4, 18); + }, + }); } - // λ³΄λ“œ λ°– λ°°κ²½ μž₯식 (λ§ˆμ„μ„ λ‘˜λŸ¬μ‹Ό 숲) - for (const t of OUTER_TREES) { + // λ³΄λ“œ λ°– λ°°κ²½ μž₯식 (λ§ˆμ„μ„ λ‘˜λŸ¬μ‹Ό 숲 β€” λ‚˜λ¬΄ 3μ’… μˆœν™˜) + const OUTER_KINDS = ["pine", "leafy", "pine_small"]; + OUTER_TREES.forEach((t, idx) => { const x = isoX(t.i, t.j); const y = isoY(t.i, t.j) + TH / 2; - sprites.push({ y, draw: () => { shadow(x, y + 4, 15); drawEmoji("🌲", x, y + 6, 38); } }); - } + const kind = OUTER_KINDS[idx % 3]; + sprites.push({ + y, + draw: () => { + const img = getImage(NATURE_IMG[kind]); + if (img) { + const h = kind === "pine_small" ? 42 : 58; + const w = (img.width / img.height) * h; + shadow(x, y + 4, 13); + ctx.drawImage(img, x - w / 2, y + 8 - h, w, h); + } else { + shadow(x, y + 4, 15); + drawEmoji("🌲", x, y + 6, 38); + } + }, + }); + }); for (const d of OUTER_DECOR) { const x = isoX(d.i, d.j); const y = isoY(d.i, d.j) + TH / 2; - sprites.push({ y: y - 6, draw: () => drawEmoji(d.emoji, x, y + 4, 18) }); + sprites.push({ + y: y - 6, + draw: () => { + const img = getImage(NATURE_IMG[EMOJI_TO_NATURE[d.emoji] ?? "bush"]); + if (img) { + const h = 24; + const w = (img.width / img.height) * h; + ctx.drawImage(img, x - w / 2, y + 6 - h, w, h); + } else drawEmoji(d.emoji, x, y + 4, 18); + }, + }); } hitRectsRef.current = []; @@ -658,7 +726,7 @@ export default function VillageView({ const remain = Math.max(0, Math.ceil(((b.constructionUntil ?? 0) - Date.now()) / 1000)); const mm = Math.floor(remain / 60); const ss = remain % 60; - drawPlankBadge(x, y - imgW * 0.5, `πŸ”¨ ${mm}:${String(ss).padStart(2, "0")}`); + drawPlankBadge(x, y - imgW * 0.5, `${mm}:${String(ss).padStart(2, "0")}`, "hammer"); ctx.font = `13px ${juaFamily}`; ctx.textAlign = "center"; ctx.strokeStyle = "rgba(255,252,238,0.75)"; @@ -689,8 +757,10 @@ export default function VillageView({ ctx.fillStyle = "#4a3418"; ctx.fillText(b.name, x, baseY + 17); if (stored > 0) { - const label = `${isKitchen ? "πŸ–" : RES_EMOJI[b.resource ?? "fish"]} ${stored}`; - drawPlankBadge(x, y - imgW * 0.55, label); + drawPlankBadge( + x, y - imgW * 0.55, `${stored}`, + isKitchen ? "meal" : RES_ICON[b.resource ?? "fish"], + ); } }, }); @@ -750,7 +820,13 @@ export default function VillageView({ ctx.lineWidth = 1; ctx.fillStyle = "rgba(74,52,24,0.9)"; ctx.fillText(cat.name, anim.x, anim.y + 14); - if (working && !cat.state) drawEmoji("πŸ’¦", anim.x + 14, anim.y - 30 + bob, 11); + if (working && !cat.state) { + const sw = getImage(ICON_IMG.sweat); + if (sw) { + const ih = 13; + ctx.drawImage(sw, anim.x + 10, anim.y - 40 + bob, (sw.width / sw.height) * ih, ih); + } else drawEmoji("πŸ’¦", anim.x + 14, anim.y - 30 + bob, 11); + } }, }); } @@ -863,7 +939,29 @@ export default function VillageView({ continue; } ctx.globalAlpha = 1 - age; - drawEmoji(bub.text, bub.x, bub.y - age * 22, 14); + const by = bub.y - age * 22; + const iconImg = bub.icon ? getImage(ICON_IMG[bub.icon]) : undefined; + if (iconImg) { + const ih = 16; + const iw = (iconImg.width / iconImg.height) * ih; + const textW = bub.text + ? (ctx.font = `bold 12px ${juaFamily}`, ctx.measureText(bub.text).width) + : 0; + const total = iw + (textW ? textW + 4 : 0); + ctx.drawImage(iconImg, bub.x - total / 2, by - ih + 4, iw, ih); + if (bub.text) { + ctx.fillStyle = "#4a3418"; + ctx.strokeStyle = "rgba(255,252,238,0.8)"; + ctx.lineWidth = 3; + ctx.textAlign = "left"; + ctx.strokeText(bub.text, bub.x - total / 2 + iw + 4, by); + ctx.lineWidth = 1; + ctx.fillText(bub.text, bub.x - total / 2 + iw + 4, by); + ctx.textAlign = "center"; + } + } else if (bub.text) { + drawEmoji(bub.text, bub.x, by, 14); + } ctx.globalAlpha = 1; } @@ -915,9 +1013,9 @@ export default function VillageView({ const gained = Math.floor(s.resources[b.resource]) - before; bubblesRef.current.push({ x: r.x + r.w / 2, y: r.y, born: performance.now(), - text: err - ? "πŸ“¦ μ°½κ³  가득!" - : `+${gained} ${RES_EMOJI[b.resource]}`, + ...(err + ? { text: "πŸ“¦ μ°½κ³  가득!" } + : { icon: RES_ICON[b.resource], text: `+${gained}` }), }); } return; diff --git a/src/lib/game/data.ts b/src/lib/game/data.ts index ee03029..d565f49 100644 --- a/src/lib/game/data.ts +++ b/src/lib/game/data.ts @@ -31,6 +31,39 @@ export const BUILDING_IMG: Record = { lumberyard: "/assets/buildings/lumberyard.png", quarry: "/assets/buildings/quarry.png", kitchen: "/assets/buildings/kitchen.png", + cat_house: "/assets/buildings/cat_house.png", + sun_deck: "/assets/buildings/sun_deck.png", + market: "/assets/buildings/market.png", + warehouse: "/assets/buildings/warehouse.png", +}; + +// μžμ—° μ†Œν’ˆ 일러슀트 (public/assets/nature/) +export const NATURE_IMG: Record = { + pine: "/assets/nature/pine.png", + leafy: "/assets/nature/leafy.png", + pine_small: "/assets/nature/pine_small.png", + daisy: "/assets/nature/daisy.png", + tulip: "/assets/nature/tulip.png", + wheat: "/assets/nature/wheat.png", + mushroom: "/assets/nature/mushroom.png", + rock: "/assets/nature/rock.png", + bush: "/assets/nature/bush.png", +}; + +// μ•„μ΄μ½˜ 일러슀트 β€” 배지·버블·HUD (public/assets/icons/) +export const ICON_IMG: Record = { + fish: "/assets/icons/fish.png", + wood: "/assets/icons/wood.png", + stone: "/assets/icons/stone.png", + meal: "/assets/icons/meal.png", + coin: "/assets/icons/coin.png", + hammer: "/assets/icons/hammer.png", + sweat: "/assets/icons/sweat.png", + plate: "/assets/icons/plate.png", + sleep: "/assets/icons/sleep.png", + anger: "/assets/icons/anger.png", + pan: "/assets/icons/pan.png", + heart: "/assets/icons/heart.png", }; // 적성별 도ꡬ 일러슀트 (public/assets/tools/)