강물 텍스처 교체: 수면 전용 이미지 + 한 방향 흐름

- 순수 수면 텍스처로 교체 (900px JPEG, 137KB)
- 컨텍스트를 강 방향으로 회전 후 미러 타일링 스크롤 → 이음매 없는 단방향 흐름
- 상류(북동) → 하류(남서) 22px/초

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
이 커밋은 다음에 포함됨:
kjs
2026-08-13 12:34:14 +09:00
co-authored by Claude Fable 5
부모 881673b975
커밋 318c7a36b1
2개의 변경된 파일과 27개의 추가작업 그리고 19개의 파일을 삭제
+27 -19
파일 보기
@@ -253,25 +253,33 @@ export default function VillageView() {
const water = waterImgRef.current;
if (water) {
// 물 영역 바운딩 박스를 덮도록 cover 배치 + 흐르는 방향으로 느린 드리프트
const left = isoX(COLS - 1, ROWS - 1) - TW / 2;
const right = isoX(COLS - 1, 0) + TW / 2;
const top = isoY(COLS - 1, 0);
const bottom = isoY(COLS - 1, ROWS - 1) + TH;
const boxW = right - left;
const boxH = bottom - top;
const scale = Math.max(boxW / water.width, boxH / water.height) * 1.15;
const w = water.width * scale;
const h = water.height * scale;
// 강 줄기 방향(좌하향)으로 왕복 드리프트
const drift = Math.sin(now / 4000) * 14;
ctx.drawImage(
water,
left + (boxW - w) / 2 - drift,
top + (boxH - h) / 2 + drift / 2,
w,
h,
);
// 강 줄기 방향(상류→하류, 좌하향)으로 한 방향 흐름:
// 컨텍스트를 강 방향으로 회전시킨 뒤 텍스처를 미러 타일링하며 스크롤
const sx = isoX(COLS - 1, 0);
const sy = isoY(COLS - 1, 0) + TH / 2;
const ex = isoX(COLS - 1, ROWS - 1);
const ey = isoY(COLS - 1, ROWS - 1) + TH / 2;
const angle = Math.atan2(ey - sy, ex - sx);
const riverLen = Math.hypot(ex - sx, ey - sy);
const h = 220; // 강 폭 방향 커버 (클리핑되므로 넉넉히)
const w = (water.width / water.height) * h;
const FLOW_SPEED = 22; // px/초
const offset = ((now / 1000) * FLOW_SPEED) % (2 * w);
ctx.save();
ctx.translate(sx, sy);
ctx.rotate(angle);
const copies = Math.ceil((riverLen + TW * 3) / w) + 2;
for (let k = -2; k < copies; k++) {
const xk = k * w - offset;
ctx.save();
ctx.translate(xk + w / 2, 0);
if (((k % 2) + 2) % 2 === 1) ctx.scale(-1, 1); // 미러 타일링으로 이음매 제거
ctx.drawImage(water, -w / 2, -h / 2, w, h);
ctx.restore();
}
ctx.restore();
} else {
ctx.fillStyle = "#60a5fa";
ctx.fillRect(0, 0, CANVAS_W, CANVAS_H);