Files

1091 lines
38 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ========================================
// 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);
}
};