도구 일러스트 에셋 7종 추가 및 건물 아이콘 적용

- public/assets/tools/: 낚싯대·도끼·곡괭이·약초바구니·냄비·톱·빗 (적성 키 이름)
- 원본 파일명 axe/pickaxe가 서로 뒤바뀌어 있어 교정
- VillageView 건물 스프라이트를 일러스트로 교체 (로드 전 이모지 폴백)
- BuildingCard 헤더 아이콘도 일러스트 사용

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
이 커밋은 다음에 포함됨:
kjs
2026-08-13 12:20:38 +09:00
co-authored by Claude Fable 5
부모 348ca5ded7
커밋 b50a22c6b9
11개의 변경된 파일과 41개의 추가작업 그리고 6개의 파일을 삭제
+1 -1
파일 보기
@@ -43,5 +43,5 @@ next-env.d.ts
# 로컬 테스트 산출물 # 로컬 테스트 산출물
.playwright-mcp/ .playwright-mcp/
*.png *.png
!public/*.png !public/**/*.png
!src/**/*.png !src/**/*.png
바이너리 파일은 표시되지 않음.

이후

너비:  |  높이:  |  크기: 96 KiB

바이너리 파일은 표시되지 않음.

이후

너비:  |  높이:  |  크기: 121 KiB

바이너리 파일은 표시되지 않음.

이후

너비:  |  높이:  |  크기: 66 KiB

바이너리 파일은 표시되지 않음.

이후

너비:  |  높이:  |  크기: 166 KiB

바이너리 파일은 표시되지 않음.

이후

너비:  |  높이:  |  크기: 73 KiB

바이너리 파일은 표시되지 않음.

이후

너비:  |  높이:  |  크기: 72 KiB

바이너리 파일은 표시되지 않음.

이후

너비:  |  높이:  |  크기: 68 KiB

+10 -4
파일 보기
@@ -4,7 +4,7 @@ import { useState } from "react";
import { useGame } from "@/hooks/useGame"; import { useGame } from "@/hooks/useGame";
import VillageView from "@/components/game/VillageView"; import VillageView from "@/components/game/VillageView";
import { engine, catRate } from "@/lib/game/engine"; import { engine, catRate } from "@/lib/game/engine";
import { APTITUDE_LABEL, PASSIVES } from "@/lib/game/data"; import { APTITUDE_LABEL, PASSIVES, TOOL_IMG } from "@/lib/game/data";
import type { Building, Cat, GameState } from "@/lib/game/types"; import type { Building, Cat, GameState } from "@/lib/game/types";
export default function Game() { export default function Game() {
@@ -129,9 +129,15 @@ function BuildingCard({
return ( return (
<div className="rounded-xl border border-gray-200 p-5 dark:border-gray-800"> <div className="rounded-xl border border-gray-200 p-5 dark:border-gray-800">
<div className="mb-1 flex items-baseline justify-between"> <div className="mb-1 flex items-center justify-between">
<h3 className="font-semibold"> <h3 className="flex items-center gap-2 font-semibold">
{building.emoji} {building.name} Lv{building.level} {/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={TOOL_IMG[building.aptitude]}
alt={building.name}
className="h-8 w-8 object-contain"
/>
{building.name} Lv{building.level}
</h3> </h3>
<span className="text-xs text-gray-400"> <span className="text-xs text-gray-400">
필요 적성: {APTITUDE_LABEL[building.aptitude]} 필요 적성: {APTITUDE_LABEL[building.aptitude]}
+19 -1
파일 보기
@@ -2,6 +2,8 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { engine, catRate } from "@/lib/game/engine"; import { engine, catRate } from "@/lib/game/engine";
import { TOOL_IMG } from "@/lib/game/data";
import type { AptitudeKey } from "@/lib/game/types";
// ---------- 아이소메트릭 그리드 ---------- // ---------- 아이소메트릭 그리드 ----------
const COLS = 11; const COLS = 11;
@@ -85,6 +87,7 @@ export default function VillageView() {
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
const animsRef = useRef<Map<string, CatAnim>>(new Map()); const animsRef = useRef<Map<string, CatAnim>>(new Map());
const bubblesRef = useRef<Bubble[]>([]); const bubblesRef = useRef<Bubble[]>([]);
const toolImagesRef = useRef<Map<AptitudeKey, HTMLImageElement>>(new Map());
const hitRectsRef = useRef< const hitRectsRef = useRef<
Array<{ id: string; x: number; y: number; w: number; h: number }> Array<{ id: string; x: number; y: number; w: number; h: number }>
>([]); >([]);
@@ -100,6 +103,13 @@ export default function VillageView() {
canvas.height = CANVAS_H * dpr; canvas.height = CANVAS_H * dpr;
ctx.scale(dpr, dpr); ctx.scale(dpr, dpr);
// 도구 일러스트 프리로드 (로드 전에는 이모지로 폴백)
for (const [key, src] of Object.entries(TOOL_IMG)) {
const img = new Image();
img.src = src;
img.onload = () => toolImagesRef.current.set(key as AptitudeKey, img);
}
let raf = 0; let raf = 0;
let last = performance.now(); let last = performance.now();
@@ -265,7 +275,15 @@ export default function VillageView() {
y, y,
draw: () => { draw: () => {
shadow(x, y + 6, 24); shadow(x, y + 6, 24);
drawEmoji(b.emoji, x, y + 8, 42); const img = toolImagesRef.current.get(b.aptitude);
if (img) {
// 세로가 긴 일러스트 — 높이 56px 기준으로 비율 유지
const h = 56;
const w = (img.width / img.height) * h;
ctx.drawImage(img, x - w / 2, y + 8 - h, w, h);
} else {
drawEmoji(b.emoji, x, y + 8, 42);
}
ctx.font = "bold 12px sans-serif"; ctx.font = "bold 12px sans-serif";
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.fillStyle = "#374151"; ctx.fillStyle = "#374151";
+11
파일 보기
@@ -4,6 +4,17 @@ import type { AptitudeKey } from "./types";
export const APTITUDE_RATE = [0, 1.0, 2.2, 4.8, 10.0, 21.0, 45.0]; export const APTITUDE_RATE = [0, 1.0, 2.2, 4.8, 10.0, 21.0, 45.0];
export const BASE_RATE = 0.2; export const BASE_RATE = 0.2;
// 적성별 도구 일러스트 (public/assets/tools/)
export const TOOL_IMG: Record<AptitudeKey, string> = {
fishing: "/assets/tools/fishing.png",
logging: "/assets/tools/logging.png",
mining: "/assets/tools/mining.png",
foraging: "/assets/tools/foraging.png",
cooking: "/assets/tools/cooking.png",
crafting: "/assets/tools/crafting.png",
grooming: "/assets/tools/grooming.png",
};
export const APTITUDE_LABEL: Record<AptitudeKey, string> = { export const APTITUDE_LABEL: Record<AptitudeKey, string> = {
fishing: "낚시", fishing: "낚시",
logging: "벌목", logging: "벌목",