const CONFIG = { player: { size: 30, speed: 5, sprintMultiplier: 1.5, color: '#111' }, sword: { length: 60, swingSpeed: 0.6, colors: { primary: '#4169E1', secondary: '#1E90FF', tertiary: '#0000CD', glow: 'rgba(30, 144, 255, 0.3)' } }, defense: { numLayers: 6, maxRadiusMultiplier: 2, baseAlpha: 0.15, particleCount: 12, orbitRadiusMultiplier: 0.8, rotationSpeed: 1.5 }, particles: { max: 100, lifetime: 1.0, speed: 1.5 }, footprints: { lifetime: 1000, spacing: 300, size: 5 }, camera: { deadzoneMultiplierX: 0.6, deadzoneMultiplierY: 0.6, ease: 0.08 }, grid: { size: 100, color: '#ddd' }, fps: 60 }; let GAME_WIDTH = window.innerWidth; let GAME_HEIGHT = window.innerHeight; let lastFrameTime = 0; let animationTime = 0; const FRAME_TIME = 1000 / CONFIG.fps; const CAMERA_DEADZONE_X = GAME_WIDTH * CONFIG.camera.deadzoneMultiplierX; const CAMERA_DEADZONE_Y = GAME_HEIGHT * CONFIG.camera.deadzoneMultiplierY; const state = { player: { x: GAME_WIDTH / 2, y: GAME_HEIGHT / 2, isDefending: false, direction: { x: 0, y: -1 }, swordAngle: 0, isSwinging: false }, particles: [], footprints: [], lastFootprintTime: 0, camera: { x: 0, y: 0, targetX: 0, targetY: 0 } }; const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const keys = new Set(); const handleKeyDown = (e) => { keys.add(e.key); if (e.key === 'z' && !state.player.isSwinging && !state.player.isDefending) { state.player.isSwinging = true; state.player.swordAngle = Math.atan2(state.player.direction.y, state.player.direction.x) - Math.PI / 2; } if (e.key === 'x') { state.player.isDefending = true; } }; const handleKeyUp = (e) => { keys.delete(e.key); if (e.key === 'x') { state.player.isDefending = false; } }; const updatePlayer = () => { if (state.player.isDefending) { return; } let dx = 0; let dy = 0; if (keys.has('ArrowLeft')) dx -= 1; if (keys.has('ArrowRight')) dx += 1; if (keys.has('ArrowUp')) dy -= 1; if (keys.has('ArrowDown')) dy += 1; if (dx !== 0 || dy !== 0) { const length = Math.sqrt(dx * dx + dy * dy); state.player.direction = { x: dx / length, y: dy / length }; const currentSpeed = keys.has('Shift') ? CONFIG.player.speed * CONFIG.player.sprintMultiplier : CONFIG.player.speed; state.player.x += (dx / length) * currentSpeed; state.player.y += (dy / length) * currentSpeed; if (!state.player.isDefending) { const timeSinceLastFootprint = animationTime - state.lastFootprintTime; const currentSpacing = keys.has('Shift') ? CONFIG.footprints.spacing * CONFIG.player.sprintMultiplier : CONFIG.footprints.spacing; if (timeSinceLastFootprint > currentSpacing / currentSpeed) { const offset = (Math.random() - 0.5) * 6; const perpX = -state.player.direction.y * offset; const perpY = state.player.direction.x * offset; state.footprints.push(createFootprint( state.player.x + perpX, state.player.y + perpY, Math.atan2(dy, dx), currentSpeed )); state.lastFootprintTime = animationTime; } } } state.footprints = state.footprints.filter(footprint => { return (animationTime - footprint.createdAt) < CONFIG.footprints.lifetime; }); if (state.player.isSwinging) { state.player.swordAngle += CONFIG.sword.swingSpeed; if (Math.random() < 0.3) { const tipX = state.player.x + Math.cos(state.player.swordAngle) * CONFIG.sword.length; const tipY = state.player.y + Math.sin(state.player.swordAngle) * CONFIG.sword.length; state.particles.push(createParticle(tipX, tipY, state.player.swordAngle)); } if (state.player.swordAngle > Math.atan2(state.player.direction.y, state.player.direction.x) + Math.PI / 2) { state.player.isSwinging = false; } } state.particles = state.particles.filter(particle => { particle.lifetime -= 1/60; if (particle.lifetime <= 0) return false; particle.x += Math.cos(particle.angle) * particle.speed; particle.y += Math.sin(particle.angle) * particle.speed; return true; }); if (state.particles.length > CONFIG.particles.max) { state.particles.splice(0, state.particles.length - CONFIG.particles.max); } }; const createParticle = (x, y, angle) => ({ x, y, angle, lifetime: CONFIG.particles.lifetime, speed: CONFIG.particles.speed * (0.5 + Math.random() * 0.5), size: 2 + Math.random() * 2 }); const createFootprint = (x, y, direction, speed) => ({ x, y, direction, createdAt: animationTime, size: CONFIG.footprints.size * (0.8 + Math.random() * 0.4), offset: (Math.random() - 0.5) * 5 }); const renderPlayer = () => { ctx.save(); if (state.player.isSwinging) { const blurSteps = 12; const blurSpread = 0.2; for (let i = 0; i < blurSteps; i++) { const alpha = 0.35 - (i * 0.02); const angleOffset = -blurSpread * i; ctx.strokeStyle = `rgba(30, 144, 255, ${alpha})`; ctx.lineWidth = 4 + (blurSteps - i); ctx.beginPath(); ctx.moveTo(state.player.x, state.player.y); ctx.lineTo( state.player.x + Math.cos(state.player.swordAngle + angleOffset) * CONFIG.sword.length, state.player.y + Math.sin(state.player.swordAngle + angleOffset) * CONFIG.sword.length ); ctx.stroke(); } state.particles.forEach(particle => { const alpha = (particle.lifetime / CONFIG.particles.lifetime) * 0.8; ctx.fillStyle = `rgba(135, 206, 250, ${alpha})`; ctx.beginPath(); ctx.arc(particle.x, particle.y, particle.size * 1.5, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = `rgba(30, 144, 255, ${alpha * 0.3})`; ctx.beginPath(); ctx.arc(particle.x, particle.y, particle.size * 2.5, 0, Math.PI * 2); ctx.fill(); }); const gradient = ctx.createLinearGradient( state.player.x, state.player.y, state.player.x + Math.cos(state.player.swordAngle) * CONFIG.sword.length, state.player.y + Math.sin(state.player.swordAngle) * CONFIG.sword.length ); gradient.addColorStop(0, CONFIG.sword.colors.primary); gradient.addColorStop(0.6, CONFIG.sword.colors.secondary); gradient.addColorStop(1, CONFIG.sword.colors.tertiary); ctx.strokeStyle = CONFIG.sword.colors.glow; ctx.lineWidth = 10; ctx.beginPath(); ctx.moveTo(state.player.x, state.player.y); ctx.lineTo( state.player.x + Math.cos(state.player.swordAngle) * CONFIG.sword.length, state.player.y + Math.sin(state.player.swordAngle) * CONFIG.sword.length ); ctx.stroke(); ctx.strokeStyle = gradient; ctx.lineWidth = 6; ctx.stroke(); } if (state.player.isDefending) { const numLayers = CONFIG.defense.numLayers; const maxRadius = CONFIG.player.size * CONFIG.defense.maxRadiusMultiplier; const baseAlpha = CONFIG.defense.baseAlpha; for (let i = numLayers - 1; i >= 0; i--) { const radius = CONFIG.player.size / 2 + (maxRadius - CONFIG.player.size / 2) * (i / numLayers); const alpha = baseAlpha * (1 - i / numLayers); const pulseOffset = Math.sin(Date.now() / 500) * 3; const glowGradient = ctx.createRadialGradient( state.player.x, state.player.y, radius - 5, state.player.x, state.player.y, radius + pulseOffset ); glowGradient.addColorStop(0, `rgba(30, 144, 255, ${alpha})`); glowGradient.addColorStop(1, 'rgba(30, 144, 255, 0)'); ctx.beginPath(); ctx.arc(state.player.x, state.player.y, radius + pulseOffset, 0, Math.PI * 2); ctx.fillStyle = glowGradient; ctx.fill(); } const mainAuraGradient = ctx.createRadialGradient( state.player.x, state.player.y, CONFIG.player.size / 2 - 2, state.player.x, state.player.y, CONFIG.player.size / 2 + 8 ); mainAuraGradient.addColorStop(0, 'rgba(30, 144, 255, 0.3)'); mainAuraGradient.addColorStop(0.5, 'rgba(30, 144, 255, 0.2)'); mainAuraGradient.addColorStop(1, 'rgba(30, 144, 255, 0)'); ctx.beginPath(); ctx.arc(state.player.x, state.player.y, CONFIG.player.size / 2 + 8, 0, Math.PI * 2); ctx.fillStyle = mainAuraGradient; ctx.fill(); ctx.beginPath(); ctx.arc(state.player.x, state.player.y, CONFIG.player.size / 2, 0, Math.PI * 2); ctx.fillStyle = '#111'; ctx.fill(); ctx.beginPath(); ctx.arc(state.player.x, state.player.y, CONFIG.player.size / 2, 0, Math.PI * 2); ctx.strokeStyle = CONFIG.sword.colors.secondary; ctx.lineWidth = 5; ctx.stroke(); ctx.beginPath(); ctx.arc(state.player.x, state.player.y, CONFIG.player.size / 2 - 3, 0, Math.PI * 2); ctx.strokeStyle = 'rgba(30, 144, 255, 0.3)'; ctx.lineWidth = 2; ctx.stroke(); const numParticles = CONFIG.defense.particleCount; const baseOrbitRadius = CONFIG.player.size * CONFIG.defense.orbitRadiusMultiplier; const rotationSpeed = CONFIG.defense.rotationSpeed; for (let i = 0; i < numParticles; i++) { const particleSpeed = 0.15 + Math.sin(animationTime * 0.002 + i) * 0.03; const radialOffset = Math.sin(animationTime * 0.002 + i * 0.5) * 4; const orbitRadius = baseOrbitRadius + radialOffset; const angle = (i / numParticles) * Math.PI * 2 + animationTime * rotationSpeed * 0.001; const x = state.player.x + Math.cos(angle) * orbitRadius; const y = state.player.y + Math.sin(angle) * orbitRadius; const size = 2 + Math.sin(animationTime * 0.003 + i * 0.8) * 1.5; const baseAlpha = 0.6 + Math.sin(animationTime * 0.002 + i) * 0.2; ctx.beginPath(); ctx.arc(x, y, size * 2, 0, Math.PI * 2); ctx.fillStyle = `rgba(30, 144, 255, ${baseAlpha * 0.3})`; ctx.fill(); ctx.beginPath(); ctx.arc(x, y, size, 0, Math.PI * 2); ctx.fillStyle = `rgba(135, 206, 250, ${baseAlpha})`; ctx.fill(); if (i > 0) { const prevAngle = ((i - 1) / numParticles) * Math.PI * 2 + animationTime * rotationSpeed * 0.001; const prevX = state.player.x + Math.cos(prevAngle) * orbitRadius; const prevY = state.player.y + Math.sin(prevAngle) * orbitRadius; ctx.beginPath(); ctx.moveTo(prevX, prevY); ctx.lineTo(x, y); ctx.strokeStyle = `rgba(30, 144, 255, ${baseAlpha * 0.2})`; ctx.lineWidth = 1; ctx.stroke(); } } } else { ctx.fillStyle = CONFIG.player.color; ctx.fillRect( state.player.x - CONFIG.player.size / 2, state.player.y - CONFIG.player.size / 2, CONFIG.player.size, CONFIG.player.size ); } ctx.restore(); }; const lerp = (start, end, t) => { return start * (1 - t) + end * t; }; const render = () => { ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT); const screenCenterX = -state.camera.x + GAME_WIDTH / 2; const screenCenterY = -state.camera.y + GAME_HEIGHT / 2; const distX = state.player.x - screenCenterX; const distY = state.player.y - screenCenterY; if (Math.abs(distX) > CAMERA_DEADZONE_X / 2) { const bufferX = (GAME_WIDTH - CAMERA_DEADZONE_X) / 2; const targetOffsetX = distX > 0 ? GAME_WIDTH - bufferX : bufferX; state.camera.targetX = -(state.player.x - targetOffsetX); } if (Math.abs(distY) > CAMERA_DEADZONE_Y / 2) { const bufferY = (GAME_HEIGHT - CAMERA_DEADZONE_Y) / 2; const targetOffsetY = distY > 0 ? GAME_HEIGHT - bufferY : bufferY; state.camera.targetY = -(state.player.y - targetOffsetY); } state.camera.x = lerp(state.camera.x, state.camera.targetX, CONFIG.camera.ease); state.camera.y = lerp(state.camera.y, state.camera.targetY, CONFIG.camera.ease); ctx.save(); ctx.translate(state.camera.x, state.camera.y); const gridSize = CONFIG.grid.size; ctx.strokeStyle = CONFIG.grid.color; ctx.lineWidth = 1; const startX = Math.floor((-state.camera.x) / gridSize) * gridSize; const startY = Math.floor((-state.camera.y) / gridSize) * gridSize; const endX = startX + GAME_WIDTH + gridSize; const endY = startY + GAME_HEIGHT + gridSize; for (let x = startX; x < endX; x += gridSize) { ctx.beginPath(); ctx.moveTo(x, startY); ctx.lineTo(x, endY); ctx.stroke(); } for (let y = startY; y < endY; y += gridSize) { ctx.beginPath(); ctx.moveTo(startX, y); ctx.lineTo(endX, y); ctx.stroke(); } state.footprints.forEach(footprint => { const age = (animationTime - footprint.createdAt) / CONFIG.footprints.lifetime; if (age >= 1) return; const alpha = Math.max(0, 1 - age * age); ctx.save(); ctx.translate(footprint.x + footprint.offset, footprint.y + footprint.offset); const radius = Math.max(0.1, footprint.size * (1 - age * 0.5)); if (radius > 0) { ctx.beginPath(); ctx.arc(0, 0, radius * 2, 0, Math.PI * 2); ctx.fillStyle = `rgba(17, 17, 17, ${alpha * 0.1})`; ctx.fill(); ctx.beginPath(); ctx.arc(0, 0, radius, 0, Math.PI * 2); ctx.fillStyle = `rgba(17, 17, 17, ${alpha * 0.3})`; ctx.fill(); } ctx.restore(); }); renderPlayer(); ctx.restore(); }; const gameLoop = (currentTime) => { if (!lastFrameTime) { lastFrameTime = currentTime; animationTime = 0; } const deltaTime = currentTime - lastFrameTime; if (deltaTime >= FRAME_TIME) { animationTime += FRAME_TIME; updatePlayer(); render(); lastFrameTime = currentTime; } requestAnimationFrame(gameLoop); }; window.addEventListener('keydown', handleKeyDown); window.addEventListener('keyup', handleKeyUp); const resizeCanvas = () => { GAME_WIDTH = window.innerWidth; GAME_HEIGHT = window.innerHeight; canvas.width = GAME_WIDTH; canvas.height = GAME_HEIGHT; if (!state.player.x) { state.player.x = GAME_WIDTH / 2; state.player.y = GAME_HEIGHT / 2; } }; window.addEventListener('resize', resizeCanvas); resizeCanvas(); requestAnimationFrame(gameLoop);