- Pathfinder.setBlocked: remove blocked.clear() to preserve wall tiles set by setWalls() - Pathfinder.findPath: fix dead-code goal exception — remove redundant isBlocked check, keep goal-tile exception in single guard - OfficeRenderer: track mouseDownPos/_wasDragging; expose wasDragging() method for click-after-drag suppression - OfficeRenderer._render: track _lastDpr to detect monitor DPR changes; use setTransform instead of scale to avoid accumulation - TileMap.render: use clientWidth/clientHeight for viewport culling (CSS space, not buffer pixels) - TaskTab: wrap JSON.parse in try/catch to prevent crash on malformed result_data Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
81 lines
2.5 KiB
JavaScript
81 lines
2.5 KiB
JavaScript
// src/pages/agent-office/canvas/TileMap.js
|
|
|
|
/**
|
|
* 타일맵 렌더러 — 바닥, 벽, 그리드를 테마 팔레트로 렌더링
|
|
* 가구는 FurnitureRenderer가 별도 처리
|
|
*/
|
|
export class TileMap {
|
|
constructor(mapData) {
|
|
this.cols = mapData.cols;
|
|
this.rows = mapData.rows;
|
|
this.tileSize = mapData.tileSize;
|
|
this.floor = mapData.floor;
|
|
this.tileTypes = mapData.tileTypes;
|
|
}
|
|
|
|
/**
|
|
* 바닥 + 벽 렌더링
|
|
* @param {CanvasRenderingContext2D} ctx
|
|
* @param {object} theme - themes.js 에서 가져온 테마 객체
|
|
* @param {number} scale - 줌 레벨
|
|
* @param {number} offsetX - 패닝 X 오프셋
|
|
* @param {number} offsetY - 패닝 Y 오프셋
|
|
*/
|
|
render(ctx, theme, scale, offsetX, offsetY) {
|
|
const ts = this.tileSize * scale;
|
|
|
|
for (let r = 0; r < this.rows; r++) {
|
|
for (let c = 0; c < this.cols; c++) {
|
|
const tileType = this.floor[r][c];
|
|
const x = c * ts + offsetX;
|
|
const y = r * ts + offsetY;
|
|
|
|
// 화면 밖이면 스킵 (CSS 공간 기준 — DPR 변환 적용된 좌표계)
|
|
if (x + ts < 0 || y + ts < 0 || x > ctx.canvas.clientWidth || y > ctx.canvas.clientHeight) continue;
|
|
|
|
if (tileType === 0) {
|
|
// 벽
|
|
ctx.fillStyle = theme.wall.color;
|
|
ctx.fillRect(x, y, ts, ts);
|
|
// 벽 하단 경계선
|
|
ctx.fillStyle = theme.wall.border;
|
|
ctx.fillRect(x, y + ts - scale, ts, scale);
|
|
} else {
|
|
// 바닥
|
|
const isBreak = this.tileTypes[String(tileType)] === 'floor_break';
|
|
ctx.fillStyle = isBreak ? theme.floor.color2 : theme.floor.color1;
|
|
ctx.fillRect(x, y, ts, ts);
|
|
|
|
// 체커보드 패턴
|
|
if ((r + c) % 2 === 0) {
|
|
ctx.fillStyle = theme.floor.grid;
|
|
ctx.fillRect(x, y, ts, ts);
|
|
}
|
|
|
|
// 그리드 선
|
|
ctx.strokeStyle = theme.floor.grid;
|
|
ctx.lineWidth = scale * 0.5;
|
|
ctx.strokeRect(x, y, ts, ts);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 화면 좌표 → 타일 좌표 변환 */
|
|
screenToTile(screenX, screenY, scale, offsetX, offsetY) {
|
|
const ts = this.tileSize * scale;
|
|
const col = Math.floor((screenX - offsetX) / ts);
|
|
const row = Math.floor((screenY - offsetY) / ts);
|
|
return { col, row };
|
|
}
|
|
|
|
/** 타일 좌표 → 화면 좌표 (타일 중앙) */
|
|
tileToScreen(col, row, scale, offsetX, offsetY) {
|
|
const ts = this.tileSize * scale;
|
|
return {
|
|
x: col * ts + offsetX + ts / 2,
|
|
y: row * ts + offsetY + ts / 2
|
|
};
|
|
}
|
|
}
|