diff options
Diffstat (limited to 'js')
-rw-r--r-- | js/MAP.md | 1 | ||||
-rw-r--r-- | js/mountain/game.js | 3 | ||||
-rw-r--r-- | js/mountain/hill.js | 319 | ||||
-rw-r--r-- | js/mountain/index.html | 12 |
4 files changed, 335 insertions, 0 deletions
diff --git a/js/MAP.md b/js/MAP.md index 79452cd..52b3573 100644 --- a/js/MAP.md +++ b/js/MAP.md @@ -12,6 +12,7 @@ - `irc`, a better irc bot - `magic-bird`, is a game about a magical bird that needs to get back home to lay an egg - `map-maker`, a little tool for making, and exporting maps for other games +- `mountain`, a companion game to hill, platformer about getting to the top - `name-gen`, a funny random name generator and HTML canvas shape drawer - `notes`, really bad note taking surface - `peep`, a pulsing shape toy...not done, not nearly a complete thought diff --git a/js/mountain/game.js b/js/mountain/game.js new file mode 100644 index 0000000..3c96005 --- /dev/null +++ b/js/mountain/game.js @@ -0,0 +1,3 @@ +// a 2d platformer game, where the character can jump, double jump, move left and right. +// That character can avoid spikes and obstacles. + diff --git a/js/mountain/hill.js b/js/mountain/hill.js new file mode 100644 index 0000000..0e960a9 --- /dev/null +++ b/js/mountain/hill.js @@ -0,0 +1,319 @@ +const canvas = document.getElementById('gameCanvas'); +canvas.width = window.innerWidth; +canvas.height = window.innerHeight; +canvas.style.backgroundColor = '#f0f0f0'; +const ctx = canvas.getContext('2d'); + +(function(){ + + const COLORS = { + player: '#2d2d2d', + particles: '#2d2d2d', + objects: '#2d2d2d', + terrain: '#2d2d2d' + }; + + const gravity = 1; + const jumpForce = 14.75; + + const player = { + x: 200, + y: 0, + vx: 16, + vy: 16, + vm: 32, + jumpForce: gravity + jumpForce, + onGround: false, + width: 20, + height: 20, + distanceTraveled: 0 + }; + + const terrain = { + start: { x: 0, y: 0 }, + end: { x: canvas.width, y: canvas.height }, + height: 8 + }; + + const objects = []; + let lastObjectX = 0; + + const particles = []; + + const createObject = () => { + const object = { + x: lastObjectX + 10 * canvas.width / 10, + width: Math.random() * 100, + height: Math.random() * 22 + }; + + let terrainY = ((terrain.end.y - terrain.start.y) / (terrain.end.x - terrain.start.x)) * (object.x - terrain.start.x) + terrain.start.y; + + object.y = terrainY - object.height; + + objects.push(object); + + lastObjectX = object.x; + }; + + const isColliding = (obj1, obj2) => ( + obj1.x < obj2.x + obj2.width && + obj1.x + obj1.width > obj2.x && + obj1.y < obj2.y + obj2.height && + obj1.y + obj1.height > obj2.y + ); + + const checkCollision = () => { + objects.forEach(object => { + if (isColliding(player, object)) { + if (player.vx >= 1) { + player.vx -= 0.5; + player.vy -= 0.5; + } + } + }); + }; + + const updatePlayer = () => { + player.vy += gravity; + + player.x += player.vx; + player.y += player.vy; + + const terrainY = (terrain.end.y - terrain.start.y) / (terrain.end.x - terrain.start.x) * (player.x - terrain.start.x) + terrain.start.y; + + if (player.y + player.height > terrainY) { + player.y = terrainY - player.height; + player.vy = 0; + } + + if (particlesEnabled) { + particles.push({ + x: player.x, + y: player.y, + vx: Math.random() * 2 - 1, + vy: Math.random() * 2 - 1, + lifespan: player.vx * 4 + }); + } + }; + + const updateObjects = () => { + for (let object of objects) { + object.y += gravity; + + let terrainY = ((terrain.end.y - terrain.start.y) / (terrain.end.x - terrain.start.x)) * (object.x - terrain.start.x) + terrain.start.y; + + if (object.y + object.height > terrainY) { + object.y = terrainY - object.height; + } + } + }; + + const drawPlayer = () => { + + if (player.vx < 4) { + ctx.fillStyle = COLORS.player; + ctx.font = '22px Arial'; + const text = 'Jump to build up speed!'; + ctx.fillText(text, player.x, player.y - 12); + } else { + if (distanceMeterEnabled) { + ctx.fillStyle = '#aaa'; + ctx.font = '22px Arial'; + const text = player.distanceTraveled.toFixed(0) + ' px'; + ctx.fillText(text, player.x, player.y - 12); + } + } + + ctx.fillStyle = COLORS.player; + ctx.fillRect(player.x, player.y, player.width, player.height); + }; + + const drawObjects = () => { + ctx.fillStyle = COLORS.objects; + objects.forEach(object => { + ctx.beginPath(); + ctx.arc(object.x, object.y, object.width / 2, 0, Math.PI * 2, true); + ctx.fill(); + }); + }; + + const drawTerrain = () => { + ctx.beginPath(); + ctx.moveTo(terrain.start.x, terrain.start.y); + ctx.lineTo(terrain.end.x, terrain.end.y); + ctx.strokeStyle = COLORS.terrain; + ctx.lineWidth = terrain.height; + ctx.stroke(); + }; + + const drawParticles = () => { + if (particlesEnabled) { + ctx.fillStyle = COLORS.particles; + particles.forEach((particle, index) => { + ctx.fillRect(particle.x, particle.y, 5, 5); + particle.x += particle.vx; + particle.y += particle.vy; + particle.lifespan--; + if (particle.lifespan === 0) { + particles.splice(index, 1); + } + }); + } + }; + + + const draw = () => { + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.save(); + ctx.translate(-player.x + canvas.width / 4, -player.y + canvas.height / 2); + drawPlayer(); + terrain.start.x = player.x - canvas.width; + terrain.end.x = player.x + canvas.width; + drawObjects(); + drawTerrain(); + drawParticles(); + ctx.restore(); + }; + + const update = (currentTime = 0) => { + const deltaTime = currentTime - lastUpdateTime; + + player.distanceTraveled = Math.abs(player.x - 200); + + if (deltaTime >= frameDelay) { + createObject(); + updatePlayer(); + updateObjects(); + checkCollision(); + lastUpdateTime = currentTime; + } + + requestAnimationFrame(update); + }; + + const initialize = () => { + terrain.start = { x: 0, y: 0 }; + terrain.end = { x: canvas.width, y: canvas.height }; + }; + + const gameLoop = () => { + initialize(); + draw(); + requestAnimationFrame(gameLoop); + }; + + const playerDoJump = () => { + const terrainY = (terrain.end.y - terrain.start.y) / (terrain.end.x - terrain.start.x) * (player.x - terrain.start.x) + terrain.start.y; + if (player.y + player.height >= terrainY) { + player.vy = player.jumpForce * -1; + if (player.vx < player.vm) { + player.vx += 0.5; + } + } + }; + + const handleKeyDown = event => { + if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowUp' || event.key === 'w' || event.key === 'W') { + playerDoJump(); + } + }; + + canvas.addEventListener('click', playerDoJump); + canvas.addEventListener('touchstart', playerDoJump); + window.addEventListener('keydown', handleKeyDown); + + window.addEventListener('resize', () => { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + draw(); + }); + + const helpText = 'Controls:\n\nJump: space, up arrow, w or tap the screen on mobile\nFullscreen: f or the button in the upper-right\nParticles: p\nDistance: d\nHelp: h' + + document.addEventListener('keydown', function(event) { + if (event.key === 'f') { + if (canvas.requestFullscreen) { + canvas.requestFullscreen(); + } else if (canvas.mozRequestFullScreen) { + canvas.mozRequestFullScreen(); + } else if (canvas.webkitRequestFullscreen) { + canvas.webkitRequestFullscreen(); + } else if (canvas.msRequestFullscreen) { + canvas.msRequestFullscreen(); + } + } else if (event.key === 'p' || event.key === 'P') { + particlesEnabled = !particlesEnabled; + } else if (event.key === 'h' || event.key === 'H') { + alert(helpText); + } else if (event.key === 'd' || event.key === 'D') { + distanceMeterEnabled = !distanceMeterEnabled; + } + }); + + let particlesEnabled = true; + let distanceMeterEnabled = false; + + const fullscreenButton = document.createElement('button'); + fullscreenButton.style.position = 'absolute'; + fullscreenButton.style.top = '10px'; + fullscreenButton.style.right = '10px'; + fullscreenButton.textContent = 'Fullscreen'; + + const distanceMeterButton = document.createElement('button'); + distanceMeterButton.style.position = 'absolute'; + distanceMeterButton.style.top = '40px'; + distanceMeterButton.style.right = '10px'; + distanceMeterButton.textContent = 'Distance'; + + const particlesButton = document.createElement('button'); + particlesButton.style.position = 'absolute'; + particlesButton.style.top = '70px'; + particlesButton.style.right = '10px'; + particlesButton.textContent = 'Particles'; + + const helpButton = document.createElement('button'); + helpButton.style.position = 'absolute'; + helpButton.style.top = '100px'; + helpButton.style.right = '10px'; + helpButton.textContent = 'Help'; + + fullscreenButton.addEventListener('click', function() { + if (canvas.requestFullscreen) { + canvas.requestFullscreen(); + } else if (canvas.mozRequestFullScreen) { + canvas.mozRequestFullScreen(); + } else if (canvas.webkitRequestFullscreen) { + canvas.webkitRequestFullscreen(); + } else if (canvas.msRequestFullscreen) { + canvas.msRequestFullscreen(); + } + }); + + distanceMeterButton.addEventListener('click', function() { + distanceMeterEnabled = !distanceMeterEnabled; + }); + + particlesButton.addEventListener('click', function() { + particlesEnabled = !particlesEnabled; + }); + + helpButton.addEventListener('click', function() { + alert(helpText); + }); + + canvas.parentNode.appendChild(fullscreenButton); + canvas.parentNode.appendChild(distanceMeterButton); + canvas.parentNode.appendChild(particlesButton); + canvas.parentNode.appendChild(helpButton); + + + let lastUpdateTime = 0; + const fps = 60; + const frameDelay = 1000 / fps; + + update(); + gameLoop(); + +} )(); \ No newline at end of file diff --git a/js/mountain/index.html b/js/mountain/index.html new file mode 100644 index 0000000..016ad87 --- /dev/null +++ b/js/mountain/index.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>mountain</title> + <script src="game.js"></script> +</head> +<body> + <canvas id="mountain"></canvas> +</body> +</html> \ No newline at end of file |