// ======================================== // RENDERER.JS - Изометрический рендерер с текстурами // ======================================== const Renderer = { canvas: null, ctx: null, TILE_WIDTH: 64, TILE_HEIGHT: 32, TILE_DEPTH: 20, // Кэшированные паттерны для текстур patterns: {}, // Палитра цветов для тайлов с текстурами TILE_COLORS: { grass: { top: '#4a7c3f', left: '#3d6834', right: '#2d5228', name: 'Трава', pattern: 'grass' }, water: { top: '#3d6b8c', left: '#2d5a78', right: '#1d4868', name: 'Вода', pattern: 'water', animated: true }, stone: { top: '#6a6a6a', left: '#5a5a5a', right: '#4a4a4a', name: 'Камень', pattern: 'stone' }, sand: { top: '#c4a86c', left: '#b4985c', right: '#a4884c', name: 'Песок', pattern: 'sand' }, wall: { top: '#5a4a3a', left: '#4a3a2a', right: '#3a2a1a', name: 'Стена', pattern: 'brick' }, wood: { top: '#8b6914', left: '#7b5904', right: '#6b4904', name: 'Дерево', pattern: 'wood' }, lava: { top: '#cc4400', left: '#bb3300', right: '#aa2200', name: 'Лава', pattern: 'lava', animated: true }, snow: { top: '#e8e8f0', left: '#d8d8e0', right: '#c8c8d0', name: 'Снег', pattern: 'snow' }, dirt: { top: '#8b5a2b', left: '#7b4a1b', right: '#6b3a0b', name: 'Земля', pattern: 'dirt' }, cobblestone: { top: '#707070', left: '#606060', right: '#505050', name: 'Булыжник', pattern: 'cobblestone' } }, // Инициализация init(canvasId) { this.canvas = document.getElementById(canvasId); this.ctx = this.canvas.getContext('2d'); this.ctx.imageSmoothingEnabled = false; // Создание текстурных паттернов this.createPatterns(); }, // Создание паттернов для текстур createPatterns() { // Трава this.patterns.grass = this.createGrassPattern(); // Камень this.patterns.stone = this.createStonePattern(); // Песок this.patterns.sand = this.createSandPattern(); // Кирпич this.patterns.brick = this.createBrickPattern(); // Дерево this.patterns.wood = this.createWoodPattern(); // Булыжник this.patterns.cobblestone = this.createCobblePattern(); // Земля this.patterns.dirt = this.createDirtPattern(); }, // Создание текстуры травы createGrassPattern() { const patternCanvas = document.createElement('canvas'); patternCanvas.width = 32; patternCanvas.height = 32; const pctx = patternCanvas.getContext('2d'); // Базовый цвет pctx.fillStyle = '#4a7c3f'; pctx.fillRect(0, 0, 32, 32); // Травинки pctx.strokeStyle = '#5a9c4f'; pctx.lineWidth = 1; for (let i = 0; i < 20; i++) { const x = Math.random() * 32; const y = Math.random() * 32; const h = 4 + Math.random() * 6; pctx.beginPath(); pctx.moveTo(x, y); pctx.lineTo(x + Math.random() * 4 - 2, y - h); pctx.stroke(); } // Тёмные пятна pctx.fillStyle = '#3d6834'; for (let i = 0; i < 5; i++) { pctx.beginPath(); pctx.arc(Math.random() * 32, Math.random() * 32, 2 + Math.random() * 3, 0, Math.PI * 2); pctx.fill(); } return this.ctx.createPattern(patternCanvas, 'repeat'); }, // Создание текстуры камня createStonePattern() { const patternCanvas = document.createElement('canvas'); patternCanvas.width = 32; patternCanvas.height = 32; const pctx = patternCanvas.getContext('2d'); pctx.fillStyle = '#6a6a6a'; pctx.fillRect(0, 0, 32, 32); // Плитки pctx.strokeStyle = '#5a5a5a'; pctx.lineWidth = 1; pctx.strokeRect(0, 0, 16, 16); pctx.strokeRect(16, 0, 16, 16); pctx.strokeRect(0, 16, 16, 16); pctx.strokeRect(16, 16, 16, 16); // Пятна pctx.fillStyle = '#5a5a5a'; pctx.fillRect(5, 5, 4, 3); pctx.fillRect(20, 10, 3, 4); pctx.fillRect(8, 22, 5, 3); return this.ctx.createPattern(patternCanvas, 'repeat'); }, // Создание текстуры песка createSandPattern() { const patternCanvas = document.createElement('canvas'); patternCanvas.width = 32; patternCanvas.height = 32; const pctx = patternCanvas.getContext('2d'); pctx.fillStyle = '#c4a86c'; pctx.fillRect(0, 0, 32, 32); // Песчинки pctx.fillStyle = '#d4b87c'; for (let i = 0; i < 30; i++) { pctx.fillRect(Math.random() * 32, Math.random() * 32, 1, 1); } pctx.fillStyle = '#b4985c'; for (let i = 0; i < 20; i++) { pctx.fillRect(Math.random() * 32, Math.random() * 32, 1, 1); } return this.ctx.createPattern(patternCanvas, 'repeat'); }, // Создание текстуры кирпича createBrickPattern() { const patternCanvas = document.createElement('canvas'); patternCanvas.width = 32; patternCanvas.height = 32; const pctx = patternCanvas.getContext('2d'); pctx.fillStyle = '#5a4a3a'; pctx.fillRect(0, 0, 32, 32); pctx.strokeStyle = '#3a2a1a'; pctx.lineWidth = 2; // Горизонтальные линии pctx.beginPath(); pctx.moveTo(0, 8); pctx.lineTo(32, 8); pctx.moveTo(0, 24); pctx.lineTo(32, 24); pctx.stroke(); // Вертикальные линии (со смещением) pctx.beginPath(); pctx.moveTo(16, 0); pctx.lineTo(16, 8); pctx.moveTo(8, 8); pctx.lineTo(8, 24); pctx.moveTo(24, 8); pctx.lineTo(24, 24); pctx.moveTo(16, 24); pctx.lineTo(16, 32); pctx.stroke(); // Оттенки кирпичей pctx.fillStyle = '#6a5a4a'; pctx.fillRect(2, 2, 12, 4); pctx.fillStyle = '#4a3a2a'; pctx.fillRect(18, 2, 12, 4); return this.ctx.createPattern(patternCanvas, 'repeat'); }, // Создание текстуры дерева createWoodPattern() { const patternCanvas = document.createElement('canvas'); patternCanvas.width = 32; patternCanvas.height = 32; const pctx = patternCanvas.getContext('2d'); pctx.fillStyle = '#8b6914'; pctx.fillRect(0, 0, 32, 32); // Волокна pctx.strokeStyle = '#7b5914'; pctx.lineWidth = 1; for (let i = 0; i < 8; i++) { pctx.beginPath(); pctx.moveTo(i * 4 + 2, 0); pctx.lineTo(i * 4 + 2, 32); pctx.stroke(); } // Годовые кольца pctx.strokeStyle = '#6b4914'; pctx.beginPath(); pctx.arc(16, 16, 8, 0, Math.PI * 2); pctx.stroke(); return this.ctx.createPattern(patternCanvas, 'repeat'); }, // Создание текстуры булыжника createCobblePattern() { const patternCanvas = document.createElement('canvas'); patternCanvas.width = 32; patternCanvas.height = 32; const pctx = patternCanvas.getContext('2d'); pctx.fillStyle = '#707070'; pctx.fillRect(0, 0, 32, 32); // Камни pctx.fillStyle = '#606060'; pctx.beginPath(); pctx.arc(8, 8, 7, 0, Math.PI * 2); pctx.fill(); pctx.beginPath(); pctx.arc(24, 8, 6, 0, Math.PI * 2); pctx.fill(); pctx.beginPath(); pctx.arc(8, 24, 6, 0, Math.PI * 2); pctx.fill(); pctx.beginPath(); pctx.arc(24, 24, 7, 0, Math.PI * 2); pctx.fill(); pctx.beginPath(); pctx.arc(16, 16, 5, 0, Math.PI * 2); pctx.fill(); return this.ctx.createPattern(patternCanvas, 'repeat'); }, // Создание текстуры земли createDirtPattern() { const patternCanvas = document.createElement('canvas'); patternCanvas.width = 32; patternCanvas.height = 32; const pctx = patternCanvas.getContext('2d'); pctx.fillStyle = '#8b5a2b'; pctx.fillRect(0, 0, 32, 32); // Комочки pctx.fillStyle = '#7b4a1b'; for (let i = 0; i < 15; i++) { pctx.beginPath(); pctx.arc(Math.random() * 32, Math.random() * 32, 2 + Math.random() * 2, 0, Math.PI * 2); pctx.fill(); } pctx.fillStyle = '#9b6a3b'; for (let i = 0; i < 10; i++) { pctx.fillRect(Math.random() * 32, Math.random() * 32, 1, 1); } return this.ctx.createPattern(patternCanvas, 'repeat'); }, // Конвертация изометрических координат в экранные toIso(x, y) { return { x: (x - y) * (this.TILE_WIDTH / 2) + this.canvas.width / 2, y: (x + y) * (this.TILE_HEIGHT / 2) + 80 }; }, // Конвертация экранных координат в изометрические fromIso(screenX, screenY) { const adjX = screenX - this.canvas.width / 2; const adjY = screenY - 80; const x = (adjX / (this.TILE_WIDTH / 2) + adjY / (this.TILE_HEIGHT / 2)) / 2; const y = (adjY / (this.TILE_HEIGHT / 2) - adjX / (this.TILE_WIDTH / 2)) / 2; return { x: Math.floor(x), y: Math.floor(y) }; }, // Отрисовка изометрического тайла с текстурой drawTile(x, y, tileType, highlight = false, hover = false, time = 0) { const pos = this.toIso(x, y); const colors = this.TILE_COLORS[tileType] || this.TILE_COLORS.grass; // Модификация цвета для анимации (вода, лава) let topColor = colors.top; if (colors.animated) { const wave = Math.sin(time / 500 + x + y) * 10; topColor = this.adjustBrightness(colors.top, wave); } // Верхняя грань с текстурой this.ctx.beginPath(); this.ctx.moveTo(pos.x, pos.y - this.TILE_HEIGHT / 2); this.ctx.lineTo(pos.x + this.TILE_WIDTH / 2, pos.y); this.ctx.lineTo(pos.x, pos.y + this.TILE_HEIGHT / 2); this.ctx.lineTo(pos.x - this.TILE_WIDTH / 2, pos.y); this.ctx.closePath(); // Используем паттерн или цвет if (this.patterns[colors.pattern]) { this.ctx.save(); this.ctx.translate(pos.x - this.TILE_WIDTH/2, pos.y - this.TILE_HEIGHT/2); this.ctx.scale(1, 0.5); this.ctx.rotate(Math.PI / 4); this.ctx.translate(-pos.x, -pos.y); this.ctx.fillStyle = this.patterns[colors.pattern]; this.ctx.fill(); this.ctx.restore(); } else { this.ctx.fillStyle = topColor; this.ctx.fill(); } this.ctx.strokeStyle = hover ? '#ffffff' : 'rgba(0,0,0,0.4)'; this.ctx.lineWidth = hover ? 2 : 1; this.ctx.stroke(); // Левая грань this.ctx.beginPath(); this.ctx.moveTo(pos.x - this.TILE_WIDTH / 2, pos.y); this.ctx.lineTo(pos.x, pos.y + this.TILE_HEIGHT / 2); this.ctx.lineTo(pos.x, pos.y + this.TILE_HEIGHT / 2 + this.TILE_DEPTH); this.ctx.lineTo(pos.x - this.TILE_WIDTH / 2, pos.y + this.TILE_DEPTH); this.ctx.closePath(); this.ctx.fillStyle = colors.left; this.ctx.fill(); this.ctx.strokeStyle = 'rgba(0,0,0,0.4)'; this.ctx.lineWidth = 1; this.ctx.stroke(); // Правая грань this.ctx.beginPath(); this.ctx.moveTo(pos.x + this.TILE_WIDTH / 2, pos.y); this.ctx.lineTo(pos.x, pos.y + this.TILE_HEIGHT / 2); this.ctx.lineTo(pos.x, pos.y + this.TILE_HEIGHT / 2 + this.TILE_DEPTH); this.ctx.lineTo(pos.x + this.TILE_WIDTH / 2, pos.y + this.TILE_DEPTH); this.ctx.closePath(); this.ctx.fillStyle = colors.right; this.ctx.fill(); this.ctx.stroke(); // Декорации на тайлах this.drawTileDecorations(x, y, tileType, pos, time); // Название тайла при наведении if (hover) { this.ctx.fillStyle = '#ffffff'; this.ctx.font = '12px Arial'; this.ctx.textAlign = 'center'; this.ctx.fillText(colors.name, pos.x, pos.y + 5); } }, // Отрисовка декораций на тайлах drawTileDecorations(x, y, tileType, pos, time) { // Деревья на траве if (tileType === 0) { // grass // Случайные декорации на некоторых клетках const seed = (x * 7 + y * 13) % 10; if (seed < 2) { this.drawTree(pos.x, pos.y, 0.6 + (seed * 0.1)); } else if (seed < 4) { this.drawFlower(pos.x, pos.y, seed); } } // Камни if (tileType === 2) { // stone const seed = (x * 5 + y * 11) % 7; if (seed < 2) { this.drawRock(pos.x, pos.y, seed); } } // Вода - рябь if (tileType === 1) { // water const wave = Math.sin(time / 300 + x * 0.5 + y * 0.5) * 2; this.ctx.fillStyle = 'rgba(255,255,255,0.2)'; this.ctx.beginPath(); this.ctx.ellipse(pos.x + wave, pos.y, 8, 3, 0, 0, Math.PI * 2); this.ctx.fill(); } }, // Отрисовка дерева drawTree(x, y, scale = 1) { const trunkHeight = 15 * scale; const crownRadius = 20 * scale; // Ствол this.ctx.fillStyle = '#5a3a1a'; this.ctx.fillRect(x - 3 * scale, y - trunkHeight, 6 * scale, trunkHeight + 5); // Крона this.ctx.fillStyle = '#2a5a2a'; this.ctx.beginPath(); this.ctx.moveTo(x, y - trunkHeight - crownRadius); this.ctx.lineTo(x - crownRadius * 0.8, y - trunkHeight * 0.5); this.ctx.lineTo(x + crownRadius * 0.8, y - trunkHeight * 0.5); this.ctx.closePath(); this.ctx.fill(); this.ctx.fillStyle = '#3a6a3a'; this.ctx.beginPath(); this.ctx.moveTo(x - crownRadius * 0.6, y - trunkHeight - crownRadius * 0.5); this.ctx.lineTo(x + crownRadius * 0.6, y - trunkHeight - crownRadius * 0.5); this.ctx.lineTo(x, y - trunkHeight - crownRadius); this.ctx.closePath(); this.ctx.fill(); }, // Отрисовка цветка drawFlower(x, y, seed) { const colors = ['#ff6b6b', '#ffff6b', '#ff6bff', '#ffffff']; const color = colors[seed % colors.length]; // Стебель this.ctx.strokeStyle = '#3a5a3a'; this.ctx.lineWidth = 1; this.ctx.beginPath(); this.ctx.moveTo(x, y); this.ctx.lineTo(x, y - 10); this.ctx.stroke(); // Лепестки this.ctx.fillStyle = color; for (let i = 0; i < 5; i++) { const angle = (i / 5) * Math.PI * 2; this.ctx.beginPath(); this.ctx.arc(x + Math.cos(angle) * 3, y - 10 + Math.sin(angle) * 3, 2, 0, Math.PI * 2); this.ctx.fill(); } // Центр this.ctx.fillStyle = '#ffff00'; this.ctx.beginPath(); this.ctx.arc(x, y - 10, 1.5, 0, Math.PI * 2); this.ctx.fill(); }, // Отрисовка камня drawRock(x, y, seed) { const size = 8 + seed * 2; this.ctx.fillStyle = '#5a5a5a'; this.ctx.beginPath(); this.ctx.ellipse(x, y - size/2, size, size/2, 0, 0, Math.PI * 2); this.ctx.fill(); this.ctx.fillStyle = '#6a6a6a'; this.ctx.beginPath(); this.ctx.ellipse(x - 2, y - size/2 - 2, size * 0.6, size * 0.3, 0, 0, Math.PI * 2); this.ctx.fill(); }, // Отрисовка игрока drawPlayer(player, time) { let drawX = player.x; let drawY = player.y; if (player.isMoving) { drawX = player.x + (player.targetX - player.x) * player.moveProgress; drawY = player.y + (player.targetY - player.y) * player.moveProgress; } const pos = this.toIso(drawX, drawY); const height = 50; const bob = Math.sin(time / 200) * 2; const walkBob = player.isMoving ? Math.sin(time / 100) * 3 : 0; // Тень this.ctx.beginPath(); this.ctx.ellipse(pos.x, pos.y + 10, 20, 10, 0, 0, Math.PI * 2); this.ctx.fillStyle = 'rgba(0,0,0,0.3)'; this.ctx.fill(); // Цвет в зависимости от класса const classColors = { warrior: { body: '#e74c3c', outline: '#c0392b' }, mage: { body: '#3498db', outline: '#2980b9' }, archer: { body: '#2ecc71', outline: '#27ae60' }, thief: { body: '#9b59b6', outline: '#8e44ad' } }; const classColor = classColors[player.class] || classColors.warrior; // Тело персонажа const bodyColor = player.gender === 'male' ? classColor.body : '#e91e63'; // Основное тело this.ctx.beginPath(); this.ctx.ellipse(pos.x, pos.y - 5 + walkBob, 15, 8, 0, 0, Math.PI * 2); this.ctx.fillStyle = bodyColor; this.ctx.fill(); this.ctx.strokeStyle = classColor.outline; this.ctx.lineWidth = 2; this.ctx.stroke(); // Верх тела this.ctx.beginPath(); this.ctx.ellipse(pos.x, pos.y - height + walkBob, 15, 8, 0, 0, Math.PI * 2); this.ctx.fillStyle = this.lightenColor(bodyColor, 20); this.ctx.fill(); this.ctx.stroke(); // Детали одежды this.ctx.fillStyle = '#ffd700'; // Золотая пряжка this.ctx.fillRect(pos.x - 3, pos.y - 15 + walkBob, 6, 4); // Голова this.ctx.beginPath(); this.ctx.arc(pos.x, pos.y - height - 15 + bob, 12, 0, Math.PI * 2); this.ctx.fillStyle = '#ffcc99'; this.ctx.fill(); this.ctx.strokeStyle = '#ddaa77'; this.ctx.lineWidth = 2; this.ctx.stroke(); // Волосы this.ctx.beginPath(); this.ctx.arc(pos.x, pos.y - height - 18 + bob, 10, Math.PI, 0); this.ctx.fillStyle = player.hairColor || '#4a3520'; this.ctx.fill(); // Глаза this.ctx.fillStyle = '#333'; this.ctx.beginPath(); this.ctx.arc(pos.x - 4, pos.y - height - 15 + bob, 2, 0, Math.PI * 2); this.ctx.arc(pos.x + 4, pos.y - height - 15 + bob, 2, 0, Math.PI * 2); this.ctx.fill(); // Брови this.ctx.strokeStyle = '#333'; this.ctx.lineWidth = 1; this.ctx.beginPath(); this.ctx.moveTo(pos.x - 6, pos.y - height - 18 + bob); this.ctx.lineTo(pos.x - 2, pos.y - height - 19 + bob); this.ctx.moveTo(pos.x + 2, pos.y - height - 19 + bob); this.ctx.lineTo(pos.x + 6, pos.y - height - 18 + bob); this.ctx.stroke(); // Уровень над головой this.ctx.fillStyle = '#ffd700'; this.ctx.font = 'bold 11px Arial'; this.ctx.textAlign = 'center'; this.ctx.fillText('Lv.' + player.level, pos.x, pos.y - height - 35); // Эффект свечения если есть мана if (player.class === 'mage') { this.ctx.strokeStyle = 'rgba(100, 100, 255, 0.5)'; this.ctx.lineWidth = 2; this.ctx.beginPath(); this.ctx.arc(pos.x, pos.y - height - 10 + bob, 18, 0, Math.PI * 2); this.ctx.stroke(); } }, // Отрисовка предмета drawItem(item, time) { const pos = this.toIso(item.x, item.y); const bounce = Math.sin(time / 300) * 5; const rotate = time / 1000; if (item.type === 'gold' || item.type === 'coin') { // Монетка this.ctx.save(); this.ctx.translate(pos.x, pos.y - 15 - bounce); // Блик this.ctx.beginPath(); this.ctx.arc(0, 0, 10, 0, Math.PI * 2); this.ctx.fillStyle = '#ffd700'; this.ctx.fill(); this.ctx.strokeStyle = '#cc9900'; this.ctx.lineWidth = 2; this.ctx.stroke(); this.ctx.fillStyle = '#cc9900'; this.ctx.font = 'bold 12px Arial'; this.ctx.textAlign = 'center'; this.ctx.fillText('$', 0, 4); // Искры this.ctx.fillStyle = '#ffffff'; for (let i = 0; i < 3; i++) { const sparkAngle = time / 200 + i * 2; const sx = Math.cos(sparkAngle) * 12; const sy = Math.sin(sparkAngle) * 12; this.ctx.fillRect(sx, sy, 2, 2); } this.ctx.restore(); } else if (item.type === 'potion' || item.type === 'health_potion') { // Зелье здоровья this._drawPotion(pos.x, pos.y - 15 - bounce, '#ff4444', '#cc2222'); } else if (item.type === 'mana_potion') { // Зелье маны this._drawPotion(pos.x, pos.y - 15 - bounce, '#4444ff', '#2222cc'); } else if (item.type === 'weapon') { // Оружие this._drawWeapon(pos.x, pos.y - 15 - bounce, item.subtype); } else if (item.type === 'armor') { // Броня this._drawArmor(pos.x, pos.y - 15 - bounce, item.subtype); } else if (item.type === 'quest') { // Квестовый предмет this.ctx.save(); this.ctx.translate(pos.x, pos.y - 20 - bounce); this.ctx.rotate(Math.sin(time / 500) * 0.2); this.ctx.beginPath(); this.ctx.moveTo(0, -15); this.ctx.lineTo(-10, 0); this.ctx.lineTo(10, 0); this.ctx.closePath(); this.ctx.fillStyle = '#ff00ff'; this.ctx.fill(); this.ctx.strokeStyle = '#aa00aa'; this.ctx.lineWidth = 2; this.ctx.stroke(); this.ctx.fillStyle = '#ffffff'; this.ctx.font = 'bold 14px Arial'; this.ctx.textAlign = 'center'; this.ctx.fillText('!', 0, 5); this.ctx.restore(); } }, _drawPotion(x, y, color, darkColor) { // Бутылка this.ctx.beginPath(); this.ctx.moveTo(x, y - 15); this.ctx.lineTo(x - 8, y + 5); this.ctx.lineTo(x + 8, y + 5); this.ctx.closePath(); this.ctx.fillStyle = color; this.ctx.fill(); this.ctx.strokeStyle = darkColor; this.ctx.lineWidth = 2; this.ctx.stroke(); // Жидкость внутри this.ctx.fillStyle = this.lightenColor(color, 30); this.ctx.beginPath(); this.ctx.moveTo(x - 4, y - 8); this.ctx.lineTo(x - 6, y + 3); this.ctx.lineTo(x + 6, y + 3); this.ctx.lineTo(x + 4, y - 8); this.ctx.closePath(); this.ctx.fill(); // Пробка this.ctx.fillStyle = '#8b4513'; this.ctx.fillRect(x - 4, y - 20, 8, 8); // Блик this.ctx.fillStyle = 'rgba(255,255,255,0.4)'; this.ctx.beginPath(); this.ctx.ellipse(x - 3, y - 5, 2, 4, 0, 0, Math.PI * 2); this.ctx.fill(); }, _drawWeapon(x, y, subtype) { this.ctx.save(); this.ctx.translate(x, y); if (subtype === 'sword') { // Клинок const gradient = this.ctx.createLinearGradient(0, -15, 0, 5); gradient.addColorStop(0, '#e0e0e0'); gradient.addColorStop(0.5, '#c0c0c0'); gradient.addColorStop(1, '#909090'); this.ctx.fillStyle = gradient; this.ctx.beginPath(); this.ctx.moveTo(0, -20); this.ctx.lineTo(-4, 0); this.ctx.lineTo(0, 2); this.ctx.lineTo(4, 0); this.ctx.closePath(); this.ctx.fill(); this.ctx.strokeStyle = '#707070'; this.ctx.lineWidth = 1; this.ctx.stroke(); // Рукоять this.ctx.fillStyle = '#8b4513'; this.ctx.fillRect(-2, 2, 4, 8); // Гарда this.ctx.fillStyle = '#ffd700'; this.ctx.fillRect(-6, 0, 12, 3); } this.ctx.restore(); }, _drawArmor(x, y, subtype) { this.ctx.save(); this.ctx.translate(x, y); // Броня this.ctx.fillStyle = '#4a4a6a'; this.ctx.beginPath(); this.ctx.arc(0, 0, 12, 0, Math.PI * 2); this.ctx.fill(); this.ctx.strokeStyle = '#3a3a5a'; this.ctx.lineWidth = 2; this.ctx.stroke(); // Узор this.ctx.strokeStyle = '#6a6a8a'; this.ctx.lineWidth = 1; this.ctx.beginPath(); this.ctx.arc(0, 0, 8, 0, Math.PI * 2); this.ctx.stroke(); // Блик this.ctx.fillStyle = 'rgba(255,255,255,0.2)'; this.ctx.beginPath(); this.ctx.arc(-3, -3, 4, 0, Math.PI * 2); this.ctx.fill(); this.ctx.restore(); }, // Отрисовка врага drawEnemy(enemy, time) { const pos = this.toIso(enemy.x, enemy.y); const height = enemy.height || 45; const bob = Math.sin(time / 200) * 3; const attackBob = enemy.isAttacking ? Math.sin(time / 50) * 5 : 0; // Тень this.ctx.beginPath(); this.ctx.ellipse(pos.x, pos.y + 10, 18, 9, 0, 0, Math.PI * 2); this.ctx.fillStyle = 'rgba(0,0,0,0.3)'; this.ctx.fill(); // Цвет в зависимости от типа врага let bodyColor, headColor, eyeColor, nameColor; switch(enemy.type) { case 'goblin': bodyColor = '#4a6a3a'; headColor = '#5a7a4a'; eyeColor = '#ff0000'; nameColor = '#ff6b6b'; break; case 'orc': bodyColor = '#4a5a3a'; headColor = '#5a6a4a'; eyeColor = '#ff4400'; nameColor = '#ff8844'; break; case 'skeleton': bodyColor = '#d0d0c0'; headColor = '#e0e0d0'; eyeColor = '#00ff00'; nameColor = '#cccccc'; break; case 'dragon': bodyColor = '#8b0000'; headColor = '#a00000'; eyeColor = '#ffff00'; nameColor = '#ff0000'; break; case 'slime': bodyColor = '#00aa00'; headColor = '#00cc00'; eyeColor = '#ffffff'; nameColor = '#44ff44'; break; case 'bandit': bodyColor = '#6a5a4a'; headColor = '#7a6a5a'; eyeColor = '#ff6600'; nameColor = '#cc9944'; break; case 'mage': bodyColor = '#4a3a6a'; headColor = '#5a4a7a'; eyeColor = '#ff00ff'; nameColor = '#aa44ff'; break; default: bodyColor = '#6b4a3a'; headColor = '#8a6a5a'; eyeColor = '#ff0000'; nameColor = '#ffffff'; } // Анимация атаки const drawY = pos.y + attackBob; // Тело this.ctx.beginPath(); this.ctx.ellipse(pos.x, drawY - 5 + bob, 14, 7, 0, 0, Math.PI * 2); this.ctx.fillStyle = bodyColor; this.ctx.fill(); this.ctx.strokeStyle = '#000000'; this.ctx.lineWidth = 2; this.ctx.stroke(); // Детали тела if (enemy.type === 'bandit' || enemy.type === 'orc') { // Ремень this.ctx.fillStyle = '#3a2a1a'; this.ctx.fillRect(pos.x - 10, drawY - 7 + bob, 20, 3); // Пряжка this.ctx.fillStyle = '#ffd700'; this.ctx.fillRect(pos.x - 2, drawY - 8 + bob, 4, 5); } // Голова this.ctx.beginPath(); this.ctx.arc(pos.x, drawY - height + bob, 11, 0, Math.PI * 2); this.ctx.fillStyle = headColor; this.ctx.fill(); this.ctx.stroke(); // Глаза this.ctx.fillStyle = eyeColor; this.ctx.beginPath(); this.ctx.arc(pos.x - 3, drawY - height - 2 + bob, 2, 0, Math.PI * 2); this.ctx.arc(pos.x + 3, drawY - height - 2 + bob, 2, 0, Math.PI * 2); this.ctx.fill(); // Рот (для некоторых врагов) if (enemy.type === 'orc' || enemy.type === 'goblin') { this.ctx.fillStyle = '#2a1a0a'; this.ctx.beginPath(); this.ctx.arc(pos.x, drawY - height + 5 + bob, 4, 0, Math.PI); this.ctx.fill(); // Клыки this.ctx.fillStyle = '#ffffff'; this.ctx.beginPath(); this.ctx.moveTo(pos.x - 3, drawY - height + 5 + bob); this.ctx.lineTo(pos.x - 1, drawY - height + 8 + bob); this.ctx.lineTo(pos.x + 1, drawY - height + 5 + bob); this.ctx.fill(); this.ctx.beginPath(); this.ctx.moveTo(pos.x + 1, drawY - height + 5 + bob); this.ctx.lineTo(pos.x + 3, drawY - height + 8 + bob); this.ctx.lineTo(pos.x + 5, drawY - height + 5 + bob); this.ctx.fill(); } // HP бар const barWidth = 30; const barHeight = 5; const hpPercent = enemy.hp / enemy.maxHp; this.ctx.fillStyle = '#333333'; this.ctx.fillRect(pos.x - barWidth/2, drawY - height - 15 + bob, barWidth, barHeight); const hpColor = hpPercent > 0.5 ? '#44ff44' : hpPercent > 0.25 ? '#ffff44' : '#ff4444'; this.ctx.fillStyle = hpColor; this.ctx.fillRect(pos.x - barWidth/2, drawY - height - 15 + bob, barWidth * hpPercent, barHeight); this.ctx.strokeStyle = '#000000'; this.ctx.lineWidth = 1; this.ctx.strokeRect(pos.x - barWidth/2, drawY - height - 15 + bob, barWidth, barHeight); // MP бар если есть if (enemy.mp !== undefined) { const mpPercent = enemy.mp / enemy.maxMp; this.ctx.fillStyle = '#333333'; this.ctx.fillRect(pos.x - barWidth/2, drawY - height - 9 + bob, barWidth, 3); this.ctx.fillStyle = '#4444ff'; this.ctx.fillRect(pos.x - barWidth/2, drawY - height - 9 + bob, barWidth * mpPercent, 3); } // Имя врага с цветом this.ctx.fillStyle = nameColor; this.ctx.font = 'bold 10px Arial'; this.ctx.textAlign = 'center'; this.ctx.fillText(enemy.name, pos.x, drawY - height - 22 + bob); // Уровень this.ctx.fillStyle = '#888888'; this.ctx.font = '9px Arial'; this.ctx.fillText('Lv.' + enemy.level, pos.x, drawY - height - 32 + bob); // Угрожающая аура для боссов if (enemy.isBoss) { this.ctx.strokeStyle = `rgba(255, 0, 0, ${0.3 + Math.sin(time / 200) * 0.2})`; this.ctx.lineWidth = 2; this.ctx.beginPath(); this.ctx.arc(pos.x, drawY - height/2, 25, 0, Math.PI * 2); this.ctx.stroke(); } }, // Отрисовка NPC drawNPC(npc, time) { const pos = this.toIso(npc.x, npc.y); const height = 45; const bob = Math.sin(time / 300) * 2; // Тень this.ctx.beginPath(); this.ctx.ellipse(pos.x, pos.y + 10, 18, 9, 0, 0, Math.PI * 2); this.ctx.fillStyle = 'rgba(0,0,0,0.3)'; this.ctx.fill(); // Одежда NPC const clothColor = npc.color || '#8b6914'; this.ctx.beginPath(); this.ctx.ellipse(pos.x, pos.y - 5 + bob, 14, 7, 0, 0, Math.PI * 2); this.ctx.fillStyle = clothColor; this.ctx.fill(); this.ctx.strokeStyle = '#000000'; this.ctx.lineWidth = 2; this.ctx.stroke(); // Детали одежды this.ctx.fillStyle = this.lightenColor(clothColor, 30); this.ctx.beginPath(); this.ctx.ellipse(pos.x, pos.y - height + bob, 12, 6, 0, 0, Math.PI * 2); this.ctx.fill(); this.ctx.stroke(); // Голова this.ctx.beginPath(); this.ctx.arc(pos.x, pos.y - height - 12 + bob, 10, 0, Math.PI * 2); this.ctx.fillStyle = '#ffcc99'; this.ctx.fill(); this.ctx.strokeStyle = '#ddaa77'; this.ctx.lineWidth = 2; this.ctx.stroke(); // Индиктор диалога const bounce = Math.sin(time / 200) * 5; this.ctx.fillStyle = '#ffff00'; this.ctx.font = 'bold 16px Arial'; this.ctx.textAlign = 'center'; this.ctx.fillText('...', pos.x, pos.y - height - 30 + bounce); // Имя NPC this.ctx.fillStyle = '#88ff88'; this.ctx.font = 'bold 11px Arial'; this.ctx.fillText(npc.name, pos.x, pos.y - height - 45); }, // Отрисовка всей карты drawMap(gameMap, highlightTile, hoverTile, time = 0) { const tiles = []; for (let y = 0; y < gameMap.length; y++) { for (let x = 0; x < gameMap[y].length; x++) { tiles.push({ x, y, type: gameMap[y][x] }); } } tiles.sort((a, b) => (a.x + a.y) - (b.x + b.y)); tiles.forEach(tile => { const isHighlight = highlightTile && highlightTile.x === tile.x && highlightTile.y === tile.y; const isHover = hoverTile && hoverTile.x === tile.x && hoverTile.y === tile.y; this.drawTile(tile.x, tile.y, tile.type, isHighlight, isHover, time); }); return tiles; }, // Очистка canvas clear() { this.ctx.fillStyle = '#0a0a15'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); // Градиентный фон const gradient = this.ctx.createRadialGradient( this.canvas.width / 2, this.canvas.height / 2, 0, this.canvas.width / 2, this.canvas.height / 2, this.canvas.width ); gradient.addColorStop(0, '#1a1a2e'); gradient.addColorStop(1, '#0a0a15'); this.ctx.fillStyle = gradient; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); }, // Осветление цвета adjustBrightness(color, amount) { const num = parseInt(color.replace('#', ''), 16); const R = Math.max(0, Math.min(255, (num >> 16) + amount)); const G = Math.max(0, Math.min(255, ((num >> 8) & 0x00FF) + amount)); const B = Math.max(0, Math.min(255, (num & 0x0000FF) + amount)); return '#' + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1); }, lightenColor(color, percent) { return this.adjustBrightness(color, percent); }, // Отрисовка текста с тенью drawText(text, x, y, color, size = '14px', align = 'left') { this.ctx.font = `${size} Arial`; this.ctx.textAlign = align; this.ctx.fillStyle = '#000000'; this.ctx.fillText(text, x + 1, y + 1); this.ctx.fillStyle = color; this.ctx.fillText(text, x, y); } };