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/)