diff options
author | elioat <elioat@tilde.institute> | 2024-12-29 06:20:10 -0500 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2024-12-29 06:20:10 -0500 |
commit | 56375df183c97b1f5b5121af82aa024b918af88a (patch) | |
tree | c6c21d661b240277aafaf10bd718277bbc585c1c /html/isometric-bounce/js/game.js | |
parent | 326e29526606bf7a3d7623705e3baabf5302a071 (diff) | |
download | tour-56375df183c97b1f5b5121af82aa024b918af88a.tar.gz |
*
Diffstat (limited to 'html/isometric-bounce/js/game.js')
-rw-r--r-- | html/isometric-bounce/js/game.js | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/html/isometric-bounce/js/game.js b/html/isometric-bounce/js/game.js new file mode 100644 index 0000000..fb6530d --- /dev/null +++ b/html/isometric-bounce/js/game.js @@ -0,0 +1,303 @@ +function createGame() { + const state = { + canvas: document.getElementById('gameCanvas'), + ctx: null, + gridSize: 10, + tileWidth: 50, + tileHeight: 25, + offsetX: 0, + offsetY: 0, + particles: [], + player: { + x: 0, + y: 0, + targetX: 0, + targetY: 0, + size: 20, + path: [], + currentWaypoint: null, + jumpHeight: 0, + jumpProgress: 0, + isJumping: false, + startX: 0, + startY: 0 + } + }; + + state.ctx = state.canvas.getContext('2d'); + + function toIsometric(x, y) { + return { + x: (x - y) * state.tileWidth / 2, + y: (x + y) * state.tileHeight / 2 + }; + } + + function fromIsometric(screenX, screenY) { + const adjustedX = screenX - state.offsetX; + const adjustedY = screenY - state.offsetY; + + const x = (adjustedX / state.tileWidth + adjustedY / state.tileHeight) / 1; + const y = (adjustedY / state.tileHeight - adjustedX / state.tileWidth) / 1; + + return { x: Math.round(x), y: Math.round(y) }; + } + + function resizeCanvas() { + state.canvas.width = window.innerWidth; + state.canvas.height = window.innerHeight; + + state.offsetX = state.canvas.width / 2; + state.offsetY = state.canvas.height / 3; + + const minDimension = Math.min(state.canvas.width, state.canvas.height); + const scaleFactor = minDimension / 800; + state.tileWidth = 50 * scaleFactor; + state.tileHeight = 25 * scaleFactor; + state.player.size = 20 * scaleFactor; + } + + function dustyParticles(x, y) { + const particleCount = 12; + for (let i = 0; i < particleCount; i++) { + const baseAngle = (Math.PI * 2 * i) / particleCount; + const randomAngle = baseAngle + (Math.random() - 0.5) * 0.5; + + const speed = 0.3 + Math.random() * 0.4; + const initialSize = (state.player.size * 0.15) + (Math.random() * state.player.size * 0.15); + const greyValue = 220 + Math.floor(Math.random() * 35); + + state.particles.push({ + x, y, + dx: Math.cos(randomAngle) * speed, + dy: Math.sin(randomAngle) * speed, + life: 0.8 + Math.random() * 0.4, + size: initialSize, + color: `rgb(${greyValue}, ${greyValue}, ${greyValue})`, + initialSize, + rotationSpeed: (Math.random() - 0.5) * 0.2, + rotation: Math.random() * Math.PI * 2 + }); + } + } + + function updateParticles() { + for (let i = state.particles.length - 1; i >= 0; i--) { + const particle = state.particles[i]; + + particle.x += particle.dx; + particle.y += particle.dy; + particle.dy += 0.01; + particle.rotation += particle.rotationSpeed; + particle.life -= 0.03; + particle.size = particle.initialSize * (particle.life * 1.5); + + if (particle.life <= 0) { + state.particles.splice(i, 1); + } + } + } + + function findPath(startX, startY, endX, endY) { + const path = []; + + if (startX !== endX) { + const stepX = startX < endX ? 1 : -1; + for (let x = startX + stepX; stepX > 0 ? x <= endX : x >= endX; x += stepX) { + path.push({ x, y: startY }); + } + } + + if (startY !== endY) { + const stepY = startY < endY ? 1 : -1; + for (let y = startY + stepY; stepY > 0 ? y <= endY : y >= endY; y += stepY) { + path.push({ x: endX, y }); + } + } + + return path; + } + + function updatePlayer() { + const jumpDuration = 0.1; + const maxJumpHeight = state.tileHeight; + + if (!state.player.currentWaypoint && state.player.path.length > 0) { + state.player.currentWaypoint = state.player.path.shift(); + state.player.isJumping = true; + state.player.jumpProgress = 0; + state.player.startX = state.player.x; + state.player.startY = state.player.y; + } + + if (state.player.currentWaypoint && state.player.isJumping) { + state.player.jumpProgress += jumpDuration; + state.player.jumpProgress = Math.min(state.player.jumpProgress, 1); + + state.player.jumpHeight = Math.sin(state.player.jumpProgress * Math.PI) * maxJumpHeight; + + state.player.x = state.player.startX + (state.player.currentWaypoint.x - state.player.startX) * state.player.jumpProgress; + state.player.y = state.player.startY + (state.player.currentWaypoint.y - state.player.startY) * state.player.jumpProgress; + + if (state.player.jumpProgress >= 1) { + state.player.isJumping = false; + state.player.jumpHeight = 0; + state.player.x = state.player.currentWaypoint.x; + state.player.y = state.player.currentWaypoint.y; + dustyParticles(state.player.x, state.player.y); + state.player.currentWaypoint = null; + } + } + } + + function drawGrid() { + for (let x = 0; x < state.gridSize; x++) { + for (let y = 0; y < state.gridSize; y++) { + const iso = toIsometric(x, y); + + // Diamonds! + state.ctx.beginPath(); // Start a new path + state.ctx.moveTo(iso.x + state.offsetX, iso.y + state.offsetY - state.tileHeight/2); // Move to the top point of the diamond + state.ctx.lineTo(iso.x + state.offsetX + state.tileWidth/2, iso.y + state.offsetY); // Draw line to the right point of the diamond + state.ctx.lineTo(iso.x + state.offsetX, iso.y + state.offsetY + state.tileHeight/2); // Draw line to the bottom point of the diamond + state.ctx.lineTo(iso.x + state.offsetX - state.tileWidth/2, iso.y + state.offsetY); // Draw line to the left point of the diamond + state.ctx.closePath(); // Close the path to complete the diamond + + state.ctx.strokeStyle = '#666'; + state.ctx.stroke(); + state.ctx.fillStyle = '#fff'; + state.ctx.fill(); + } + } + } + + function drawParticles() { + state.particles.forEach(particle => { + const iso = toIsometric(particle.x, particle.y); + + state.ctx.save(); + state.ctx.translate(iso.x + state.offsetX, iso.y + state.offsetY); + state.ctx.rotate(particle.rotation); + + state.ctx.beginPath(); + const points = 5; + for (let i = 0; i < points * 2; i++) { + const angle = (i * Math.PI) / points; + const radius = particle.size * (i % 2 ? 0.7 : 1); + const x = Math.cos(angle) * radius; + const y = Math.sin(angle) * radius; + i === 0 ? state.ctx.moveTo(x, y) : state.ctx.lineTo(x, y); + } + state.ctx.closePath(); + + state.ctx.fillStyle = `rgba(${particle.color.slice(4, -1)}, ${particle.life * 0.5})`; + state.ctx.fill(); + + state.ctx.restore(); + }); + } + + function drawPlayer() { + const iso = toIsometric(state.player.x, state.player.y); + const jumpOffset = state.player.jumpHeight || 0; + + let squashStretch = 1; + if (state.player.isJumping) { + const jumpPhase = Math.sin(state.player.jumpProgress * Math.PI); + if (state.player.jumpProgress < 0.2) { + squashStretch = 1 + (0.3 * (1 - state.player.jumpProgress / 0.2)); + } else if (state.player.jumpProgress > 0.8) { + squashStretch = 1 - (0.3 * ((state.player.jumpProgress - 0.8) / 0.2)); + } else { + squashStretch = 1 + (0.1 * jumpPhase); + } + } + + const shadowScale = Math.max(0.2, 1 - (jumpOffset / state.tileHeight)); + state.ctx.beginPath(); + state.ctx.ellipse( + iso.x + state.offsetX, + iso.y + state.offsetY + 2, + state.player.size * 0.8 * shadowScale, + state.player.size * 0.3 * shadowScale, + 0, 0, Math.PI * 2 + ); + state.ctx.fillStyle = `rgba(0,0,0,${0.2 * shadowScale})`; + state.ctx.fill(); + + const bodyHeight = state.player.size * 2 * squashStretch; + const bodyWidth = state.player.size * 0.8 * (1 / squashStretch); + + state.ctx.save(); + state.ctx.translate(iso.x + state.offsetX, iso.y + state.offsetY - jumpOffset); + state.ctx.scale(1, 0.5); + state.ctx.fillStyle = '#ff4444'; + state.ctx.strokeStyle = '#aa0000'; + state.ctx.fillRect(-bodyWidth/2, -bodyHeight, bodyWidth, bodyHeight); + state.ctx.strokeRect(-bodyWidth/2, -bodyHeight, bodyWidth, bodyHeight); + state.ctx.restore(); + + state.ctx.beginPath(); + state.ctx.ellipse( + iso.x + state.offsetX, + iso.y + state.offsetY - state.player.size * squashStretch - jumpOffset, + state.player.size * (1 / squashStretch), + state.player.size * 0.5 * squashStretch, + 0, 0, Math.PI * 2 + ); + state.ctx.fillStyle = '#ff4444'; + state.ctx.fill(); + state.ctx.strokeStyle = '#aa0000'; + state.ctx.stroke(); + } + + function gameLoop() { + state.ctx.clearRect(0, 0, state.canvas.width, state.canvas.height); + + drawGrid(); + updateParticles(); + drawParticles(); + updatePlayer(); + drawPlayer(); + + requestAnimationFrame(gameLoop); + } + + function handleClick(e) { + const rect = state.canvas.getBoundingClientRect(); + const clickX = e.clientX - rect.left; + const clickY = e.clientY - rect.top; + + const gridPos = fromIsometric(clickX, clickY); + + if (gridPos.x >= 0 && gridPos.x < state.gridSize && + gridPos.y >= 0 && gridPos.y < state.gridSize) { + + state.player.targetX = Math.round(gridPos.x); + state.player.targetY = Math.round(gridPos.y); + + state.player.path = findPath( + Math.round(state.player.x), + Math.round(state.player.y), + state.player.targetX, + state.player.targetY + ); + + state.player.currentWaypoint = null; + } + } + + function init() { + resizeCanvas(); + window.addEventListener('resize', resizeCanvas); + state.canvas.addEventListener('click', handleClick); + gameLoop(); + } + + return { init }; +} + +window.onload = () => { + const game = createGame(); + game.init(); +}; \ No newline at end of file |