Toggle navigation
Sign Up
Log In
Explore
Works
Folders
Tools
Collections
Artists
Groups
Groups
Topics
Tasks
Tasks
Jobs
Teams
Jobs
Recommendation
More Effects...
JS
'use strict'; var seed = 5625463739 + +new Date(); var fov = 90; var segmentSize = 16; var cameraElevation = 10; var baseSpeed = 0.3; var accelerationSpeed = 0.001; var speed = baseSpeed; var segmentCount = 12; var turnSpeed = Math.PI * 0.02; var zoomFactor = 5; var obstacleSize = Math.PI / 2; var topSpeed = speed; var random = function () { var s = seed; return function () { return alea(s++)(); }; }(); setTimeout(function () { var canvasEl = document.getElementById('js-canvas'); var width = canvasEl.width; var height = canvasEl.height; var canvasCtx = canvasEl.getContext('2d'); var input = { right: false, left: false }; setupEventListeners(input); var tunnel = tunnelFactory.create(segmentCount, width, height); var hero = heroFactory.create(); var scoreEl = document.getElementById('js-score-readout'); animate(0, canvasCtx, input, tunnel, hero, width, height, scoreEl); }, 0); function animate(iteration, canvasCtx, input, tunnel, hero, width, height, scoreEl) { handleKeys(input, hero); var centerX = width / 2; var centerY = height / 2; canvasCtx.clearRect(0, 0, width, height); tunnel.render(canvasCtx, centerX, centerY, hero.rotation % (Math.PI * 2)); hero.render(canvasCtx, width, height, centerX, centerY, iteration); checkCollision(hero, tunnel, width, centerY); if (speed > topSpeed) { topSpeed = speed; } scoreEl.innerHTML = 'Current Speed: ' + Math.floor(speed * 100) + '
Top Speed: ' + Math.floor(topSpeed * 100); requestAnimationFrame(function () { animate(iteration + 1, canvasCtx, input, tunnel, hero, width, height, scoreEl); }); } function checkCollision(hero, tunnel, width, centerY) { var slice = tunnel.slices.slice(0).sort(function (a, b) { return a.value - b.value; })[2]; // refactor, magic number var buffer = Math.PI * 2; hero.isHit = false; hero.color = slice.color; speed += accelerationSpeed; if (slice.obstacleAngle !== false) { var heroRotation = hero.rotation; var obstacleAngle = slice.obstacleAngle; var originalHit = obstacleAngle <= heroRotation && heroRotation < obstacleAngle + obstacleSize; var ghostHit = obstacleAngle - buffer <= heroRotation && heroRotation < obstacleAngle - buffer + obstacleSize; if (originalHit || ghostHit) { hero.isHit = true; speed *= 0.99; speed = Math.max(baseSpeed, speed); } } } var heroFactory = { create: function create() { var state = { color: '#00ff00', rotation: 0, isHit: false, render: function render(ctx, width, height, centerX, centerY, iteration) { heroFactory.render(state, ctx, width, height, centerX, centerY, iteration); } }; return state; }, render: function render(state, ctx, width, height, centerX, centerY, iteration) { var radius = 20; var cameraDistance = 1 / Math.tan(fov / 2) * (width / 2); var zIndex = cameraElevation * cameraDistance / Math.max(0, segmentSize * 2 + segmentSize / 2) * zoomFactor; ctx.translate(centerX, centerY); ctx.shadowColor = state.color; ctx.fillStyle = state.color; ctx.strokeStyle = state.color; if (state.isHit) { if (Math.floor(iteration / 4) % 2 === 0) { zIndex += 2; } else { zIndex -= 2; } } ctx.shadowBlur = radius; ctx.beginPath(); ctx.arc(0, zIndex, radius, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.stroke(); ctx.shadowBlur = 0; // begin inner circle ctx.globalAlpha = 0.2; ctx.beginPath(); ctx.arc(0, zIndex, radius, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); ctx.stroke(); ctx.globalAlpha = 1; ctx.translate(-centerX, -centerY); } }; var tunnelFactory = { create: function create(sliceCount, width, height) { var slices = Array.apply(undefined, Array(sliceCount)).map(function (_, index) { return { color: 'hsl(' + (random() * 360 | 0) + ', 95%, 50%)', value: (index + 1) * segmentSize, obstacleAngle: index % 2 === 0 ? random() * (Math.PI * 2) : false }; }); var state = { width: width, height: height, slices: slices, render: function render(ctx, width, height, centerX, centerY, rotation) { tunnelFactory.render(state, ctx, width, height, centerX, centerY, rotation); } }; return state; }, render: function render(state, ctx, centerX, centerY, rotation) { var cameraDistance = 1 / Math.tan(fov / 2) * (state.width / 2); state.slices.slice(0).sort(function (a, b) { return b.value - a.value; }).forEach(function (slice, index) { if (slice.value - 1 >= segmentSize) { ctx.translate(centerX, centerY); ctx.rotate(-(rotation - Math.PI / 2)); // begin tunnel ring ctx.fillStyle = slice.color; ctx.strokeStyle = slice.color; ctx.beginPath(); var _zIndex = cameraElevation * cameraDistance / Math.max(0, slice.value - 1) * zoomFactor; ctx.arc(0, 0, _zIndex, 0, Math.PI * 2); _zIndex = cameraElevation * cameraDistance / Math.max(0, slice.value) * zoomFactor; ctx.arc(0, 0, _zIndex, 0, Math.PI * 2, true); ctx.closePath(); ctx.stroke(); ctx.fill(); // end tunnel ring if (slice.obstacleAngle !== false) { ctx.rotate(slice.obstacleAngle); // begin obstacle ring back ctx.fillStyle = '#dedede'; ctx.strokeStyle = '#dedede'; ctx.beginPath(); var _zIndex2 = cameraElevation * cameraDistance / Math.max(0, slice.value) * zoomFactor; ctx.arc(0, 0, _zIndex2, 0, obstacleSize); _zIndex2 = cameraElevation * cameraDistance / Math.max(0, slice.value + 6) * zoomFactor; ctx.arc(0, 0, _zIndex2, obstacleSize, 0, true); ctx.closePath(); ctx.stroke(); ctx.fill(); // end obstacle ring back // begin obstacle ring front ctx.fillStyle = '#dedede'; ctx.strokeStyle = '#dedede'; ctx.beginPath(); _zIndex2 = cameraElevation * cameraDistance / Math.max(0, slice.value) * zoomFactor; ctx.arc(0, 0, _zIndex2, 0, obstacleSize); _zIndex2 = cameraElevation * cameraDistance / Math.max(0, slice.value - 10) * zoomFactor; ctx.arc(0, 0, _zIndex2, obstacleSize, 0, true); ctx.closePath(); ctx.stroke(); ctx.fill(); // end obstacle ring front ctx.rotate(-slice.obstacleAngle); } ctx.rotate(rotation - Math.PI / 2); ctx.translate(-centerX, -centerY); } slice.value -= speed; if (slice.value <= 0) { // reuse rings that move out of sight slice.value = state.slices.length * segmentSize; slice.color = 'hsl(' + (random() * 360 | 0) + ', 95%, 50%)'; if (slice.obstacleAngle !== false) { slice.obstacleAngle = random() * (Math.PI * 2); } } }); // begin center cap ctx.fillStyle = '#3b3251'; ctx.strokeStyle = '#3b3251'; ctx.beginPath(); var zIndex = cameraElevation * cameraDistance / (state.slices.length * segmentSize) * zoomFactor; ctx.arc(centerX, centerY, zIndex, 0, Math.PI * 2); ctx.closePath(); ctx.stroke(); ctx.fill(); // end center cap } }; function handleKeys(input, hero) { if (input.left) { document.getElementById('left').className = 'key left is-down'; hero.rotation += turnSpeed; } if (input.right) { document.getElementById('right').className = 'key right is-down'; hero.rotation -= turnSpeed; } hero.rotation = (hero.rotation + Math.PI * 2) % (Math.PI * 2); } function setupEventListeners(input) { window.addEventListener('keydown', handleKeyDown(input)); window.addEventListener('keyup', handleKeyUp(input)); } function handleKeyUp(input) { return function (_ref) { var keyCode = _ref.keyCode; document.getElementById('left').className = 'key left'; document.getElementById('right').className = 'key right'; switch (keyCode) { case 39: input.right = false; break; case 37: input.left = false; break; } }; } function handleKeyDown(input) { return function (e) { var keyCode = e.keyCode; switch (keyCode) { case 38: e.preventDefault(); break; case 39: input.right = true; break; case 40: e.preventDefault(); break; case 37: input.left = true; break; } }; } function createInterpolator(valueStart, valueDelta, duration) { return function (t) { t /= duration / 2; if (t < 1) { return valueDelta / 2 * (t * t) + valueStart; } t--; return -valueDelta / 2 * (t * (t - 2) - 1) + valueStart; }; }
CSS
html, body { background-color: #333; overflow: hidden; margin: 0; } body { font-family: sans-serif; padding: 0; } canvas { background-color: #3b3251; position: absolute; top: calc(50vh - 200px); left: calc(50vw - 200px); } .score-readout { color: #fff; font-weight: bold; height: 410px; left: calc(50vw - 200px); position: absolute; text-align: center; text-shadow: 1px 0 #000,0 1px #000,-1px 0 #000,0 -1px #000; top: calc(50vh - 200px); width: 410px; } .click-area { background-color: rgba(0, 0, 0, 0.8); color: #fff; cursor: pointer; font-weight: bold; height: 410px; left: calc(50vw - 200px); margin: 0 auto; position: absolute; text-align: center; text-shadow: 1px 0 #000,0 1px #000,-1px 0 #000,0 -1px #000; top: calc(50vh - 200px); width: 410px; } .click-area .message { display: block; position: relative; top: 205px; margin-top: -1em; } .keys { position: absolute; bottom: 1em; left: 50%; text-align: center; font-size: 10px; width: 17.4em; margin-left: -8.7em; } .keys:hover .key { background-color: rgba(255, 255, 255, 0.3); } .keys .key { margin: 0 0.4em; display: inline-block; line-height: 2.5em; width: 5em; border-radius: 0.4em; background-color: rgba(255, 255, 255, 0.15); } .keys .key.up { margin: 0 5em 0.8em; } .keys .key:hover, .keys .key.is-down { background-color: rgba(39, 212, 156, 0.7); color: white; }
HTML
Top Speed: 12
◀
▶
Click to Begin
Use Keyboard Arrow Keys to Steer
Join Effecthub.com
Working with Global Gaming Artists and Developers!
Login
Sign Up
Or Login with Your Email Address:
Email
Password
Remember
Or Sign Up with Your Email Address:
Your Email
This field must contain a valid email
Set Password
Password should be at least 1 character
Stay informed via email