Click anywhere to make an explosion.
Forked from towc's Pen Square exploder (mini-game).
A Pen by Artem Zubkov on CodePen.
Click anywhere to make an explosion.
Forked from towc's Pen Square exploder (mini-game).
A Pen by Artem Zubkov on CodePen.
| <canvas id=c></canvas> | |
| <div id=stats><span id=score>0</span>/<span id=outOf>0</span></div> | |
| <div id=rules> | |
| Click anywhere to make an explosion.<br /> | |
| If any of the particles will bump into your explosion they'll explode as well, making a chain reaction.<br /> | |
| Click again to start a new game!<br /> | |
| Let's rock in this hole! Uhhh-ha-ha-ha! Let's do it!!!<br /> | |
| <br /> | |
| </div> |
| "use strict"; | |
| var w | |
| , h | |
| , amount | |
| , clicked = false | |
| , ctx = c.getContext("2d") | |
| , inGame = false | |
| , cells = [] | |
| , cellsExplored = [] | |
| , settings = Settings() | |
| , nextInit = false | |
| , killTimer | |
| , expColor | |
| , expImg | |
| ; | |
| function restart() { | |
| nextInit = true; | |
| if (!inGame) { | |
| init(); | |
| } | |
| } | |
| function Settings() { | |
| return { | |
| maxSize: 6, | |
| minSize: 2, | |
| maxSpeed: 2, | |
| speedExplosionX: 0, | |
| speedExplosionY: 0, | |
| firstExplosionDegree: 10, | |
| stepExplosion: 3, | |
| maxSizeExp: 20, | |
| maxColor: 255, | |
| minColor: 100, | |
| composite: "lighter", | |
| limitParticles: 1999, | |
| channelsR: false, | |
| channelsG: true, | |
| channelsB: true, | |
| restart: restart | |
| }; | |
| } | |
| function getRandomColor(min) { | |
| return { | |
| r: settings.channelsR ? ((Math.random() * (255 - min)) | 0) + min : 0, | |
| g: settings.channelsG ? ((Math.random() * (255 - min)) | 0) + min : 0, | |
| b: settings.channelsB ? ((Math.random() * (255 - min)) | 0) + min : 0 | |
| }; | |
| } | |
| /** | |
| * @param {Cell} a | |
| * @param {Cell} b | |
| */ | |
| function checkExplosion(a, b) { | |
| if (a === b || b.exploded) { | |
| return; | |
| } | |
| var distX = a.x - b.x; | |
| var distY = a.y - b.y; | |
| var dist = Math.sqrt(distX * distX + distY * distY) - b.size / 2; | |
| if (dist <= a.explosionSize) { | |
| b.explode(); | |
| } | |
| } | |
| function applyMask(value, mask) { | |
| value = Math.abs(((value * mask) / 255) | 0); | |
| return value < 256 ? value : 255; | |
| } | |
| /** | |
| * Generate a neon ball | |
| * @param {Number} w width | |
| * @param {Number} h height | |
| * @param {Number} r | |
| * @param {Number} g | |
| * @param {Number} b | |
| * @param {Number} a alpha | |
| * @returns {HTMLCanvasElement} | |
| */ | |
| function generateFireBall(w, h, a, r, g, b) { | |
| r = parseInt(r); | |
| r = isNaN(r) || r > 255 ? 255 : r; | |
| g = parseInt(g); | |
| g = isNaN(g) || g > 255 ? 255 : g; | |
| b = parseInt(b); | |
| b = isNaN(b) || b > 255 ? 255 : b; | |
| var tempCanvas = document.createElement("canvas"); | |
| tempCanvas.width = w; | |
| tempCanvas.height = h; | |
| var imgCtx = tempCanvas.getContext("2d"); | |
| var gradient = imgCtx.createRadialGradient( | |
| w / 2, | |
| h / 2, | |
| 0, | |
| w / 2, | |
| h / 2, | |
| w / 2 | |
| ); | |
| gradient.addColorStop(0, "rgba(" + [applyMask(r, 255), applyMask(g, 255), applyMask(b, 255), a] + ")"); | |
| gradient.addColorStop(0.3, "rgba(" + [applyMask(r, 254), applyMask(g, 239), applyMask(b, 29), a] + ")"); | |
| gradient.addColorStop(0.4, "rgba(" + [applyMask(r, 254), applyMask(g, 88), applyMask(b, 29), a] + ")"); | |
| gradient.addColorStop(0.6, "rgba(" + [applyMask(r, 239), applyMask(g, 27), applyMask(b, 51), a * 0.05] + ")"); | |
| gradient.addColorStop(0.88, "rgba(" + [applyMask(r, 153), applyMask(g, 10), applyMask(b, 27), a * 0.05] + ")"); | |
| gradient.addColorStop(0.92, "rgba(" + [applyMask(r, 254), applyMask(g, 39), applyMask(b, 17), a * 0.1] + ")"); | |
| gradient.addColorStop(0.98, "rgba(" + [applyMask(r, 254), applyMask(g, 254), applyMask(b, 183), a * 0.2] + ")"); | |
| gradient.addColorStop(1, "rgba(" + [applyMask(r, 254), applyMask(g, 39), applyMask(b, 17), 0] + ")"); | |
| imgCtx.fillStyle = gradient; | |
| imgCtx.fillRect(0, 0, w, h); | |
| return tempCanvas; | |
| } | |
| function Cell(size, x, y) { | |
| var c = (this.color = getRandomColor(settings.minColor)); | |
| this.img = canvas_utils.colorize( | |
| canvas_utils.resources.images.particle, | |
| c.r, //(c.r = 0) | |
| c.g, | |
| c.b, | |
| 1 | |
| ); | |
| this.expImg = generateFireBall(64, 64, 1, 255, c.g, c.b); | |
| var m = settings.maxSize > settings.minSize ? settings.maxSize : settings.minSize; | |
| var mm = m === settings.maxSize ? settings.minSize : settings.maxSize; | |
| this.size = size || Math.random() * (m - mm) + mm; | |
| this.initSize = this.size; | |
| this.x = x || Math.random() * w; | |
| this.y = y || Math.random() * h; | |
| this.vx = Math.random() * settings.maxSpeed * 2 - settings.maxSpeed; | |
| this.vy = Math.random() * settings.maxSpeed * 2 - settings.maxSpeed; | |
| this.exploded = false; | |
| this.explosionSize = settings.maxSizeExp / 5; | |
| this.expV = settings.stepExplosion; | |
| } | |
| function rand(max, min) { | |
| min = min || 0; | |
| return Math.random() * (max - min) + min; | |
| } | |
| Cell.prototype.update = function () { | |
| var s = this.size * 2; | |
| var s2 = s / 2; | |
| if (!this.exploded) { | |
| ctx.moveTo(this.x, this.y); | |
| } | |
| this.x += this.vx * rand(rand(5)); | |
| this.y += this.vy * rand(rand(5)); | |
| if (this.x < 0 || this.x > w) { | |
| this.vx *= -1; | |
| this.x = this.x > 0 ? w : 0; | |
| } | |
| if (this.y < 0 || this.y > h) { | |
| this.vy *= -1; | |
| this.y = this.y > 0 ? h : 0; | |
| } | |
| if (!this.exploded) { | |
| ctx.lineTo(this.x, this.y); | |
| ctx.drawImage(this.img, this.x - s2, this.y - s2, s, s); | |
| return; | |
| } | |
| this.explosionSize += (this.expV / this.explosionSize) * 10; | |
| if (this.size > 0) { | |
| this.size -= 0.05; | |
| } | |
| if (this.explosionSize < 0) { | |
| cellsExplored.splice(cellsExplored.indexOf(this), 1); | |
| return; | |
| } | |
| if (this.explosionSize > settings.maxSizeExp * 2) { | |
| this.expV *= -1; | |
| this.vx *= 0; | |
| this.vy *= 0; | |
| } | |
| if (this.now == null) { | |
| cells.splice(cells.indexOf(this), 1); | |
| cellsExplored.push(this); | |
| } | |
| s = | |
| this.now && this.now-- | |
| ? 2 | |
| : settings.firstExplosionDegree - (this.now || 0) || 1; | |
| s *= this.explosionSize; | |
| s2 = s / 2; | |
| this.now = this.now || settings.firstExplosionDegree; | |
| ctx.drawImage(this.expImg, this.x - s2, this.y - s2, s, s); | |
| var l = cells.length; | |
| while (l--) { | |
| checkExplosion(this, cells[l]); | |
| } | |
| }; | |
| Cell.prototype.explode = function () { | |
| this.exploded = true; | |
| this.vx *= settings.speedExplosionX; | |
| this.vy *= settings.speedExplosionY; | |
| score.textContent = parseInt(score.textContent) + 1; | |
| }; | |
| function click(e) { | |
| if (!inGame) { | |
| init(); | |
| return; | |
| } | |
| if (clicked) { | |
| nextInit = true; | |
| return; | |
| } | |
| e = e.touches && e.touches.length ? e.touches[0] : e; | |
| var cell = new Cell(settings.maxSize, e.pageX, e.pageY); | |
| cells.push(cell); | |
| cell.explode(); | |
| clicked = true; | |
| } | |
| c.addEventListener("click", click, false); | |
| c.addEventListener("touchstart", click, false); | |
| window.addEventListener("resize", settings.restart); | |
| function anim() { | |
| if (nextInit) { | |
| nextInit = false; | |
| return init(); | |
| } | |
| if (inGame) { | |
| window.requestAnimationFrame(anim); | |
| } | |
| ctx.save(); | |
| ctx.globalCompositeOperation = "destination-out"; | |
| ctx.fillStyle = "rgba(0, 0, 0, .2)"; | |
| ctx.fillRect(0, 0, w, h); | |
| ctx.globalCompositeOperation = settings.composite; | |
| ctx.fillStyle = "none"; | |
| ctx.strokeStyle = "#fff"; | |
| ctx.beginPath(); | |
| var l = cells.length; | |
| while (l--) { | |
| cells[l].update(); | |
| } | |
| ctx.stroke(); | |
| l = cellsExplored.length; | |
| while (l--) { | |
| cellsExplored[l].update(); | |
| } | |
| ctx.restore(); | |
| if (!cells.length && !cellsExplored.length) { | |
| gameOver(); | |
| } | |
| } | |
| function gameOver() { | |
| inGame = killTimer-- > 0; | |
| console.log(killTimer); | |
| if (!inGame) { | |
| rules.textContent = "Yo-ho-ho!!! Yyyyeah!!!"; | |
| rules.classList.remove("close"); | |
| } | |
| } | |
| function init() { | |
| w = +window.innerWidth; | |
| h = +window.innerHeight; | |
| amount = ((w * h) / 500) | 0; | |
| amount = amount > settings.limitParticles ? settings.limitParticles : amount; | |
| outOf.textContent = amount + 1; | |
| score.textContent = "0"; | |
| c.width = w; | |
| c.height = h; | |
| ctx.fillStyle = "black"; | |
| ctx.fillRect(0, 0, w, h); | |
| cells.splice(0); | |
| cellsExplored.splice(0); | |
| var n = amount; | |
| while (n--) { | |
| cells.push(new Cell()); | |
| } | |
| clicked = false; | |
| inGame = true; | |
| if (killTimer != undefined) { | |
| rules.className = "close"; | |
| } | |
| killTimer = 30; | |
| anim(); | |
| } | |
| init(); | |
| setTimeout(function () { | |
| rules.className = "close"; | |
| }, 3000); | |
| function initConsole() { | |
| var gui = new dat.GUI({ | |
| load: JSON, | |
| preset: "Default" | |
| }); | |
| var f = gui.addFolder("Particles"); | |
| f.add(settings, "maxSize", 0, 100).listen(); | |
| f.add(settings, "minSize", 0, 100).listen(); | |
| f.add(settings, "maxSpeed", 0, 100).listen(); | |
| f.add(settings, "limitParticles", 1, 10000).step(10).listen(); | |
| f = gui.addFolder("Explosion"); | |
| f.add(settings, "maxSizeExp", 0, 100).listen(); | |
| f.add(settings, "stepExplosion", 1, 100).listen(); | |
| f.add(settings, "speedExplosionX", 0, 100).listen(); | |
| f.add(settings, "speedExplosionY", 0, 100).listen(); | |
| f.add(settings, "firstExplosionDegree", 2, 100).listen(); | |
| f = gui.addFolder("Colors"); | |
| f.add(settings, "minColor", 0, 255).listen(); | |
| f.add(settings, "channelsR").listen(); | |
| f.add(settings, "channelsG").listen(); | |
| f.add(settings, "channelsB").listen(); | |
| gui | |
| .add(settings, "composite", [ | |
| "source-over", | |
| "source-in", | |
| "source-out", | |
| "source-atop", | |
| "destination-over", | |
| "destination-in", | |
| "destination-out", | |
| "destination-atop", | |
| "lighter", | |
| "darker", | |
| "copy", | |
| "xor" | |
| ]) | |
| .listen(); | |
| gui.add(settings, "restart"); | |
| gui.remember(settings); | |
| } | |
| initConsole(); |
| <script src="https://cdn.rawgit.com/artzub/3efd4efcbd8758e4b7e5/raw/0a89298b31b293ed4825b2fc5c54453b5c3101ab/canvas_utils.js"></script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script> |
| body, | |
| html { | |
| width: 100vw; | |
| height: 100vh; | |
| overflow: hidden; | |
| background: #000; | |
| } | |
| canvas { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| background: #000; | |
| cursor: crosshair; | |
| } | |
| #stats { | |
| color: white; | |
| background-color: rgba(255, 255, 255, 0.3); | |
| font-size: 20px; | |
| padding: 10px; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| } | |
| #rules { | |
| opacity: 1; | |
| color: white; | |
| background-color: rgba(0, 0, 0, 0.7); | |
| font-size: 20px; | |
| position: absolute; | |
| text-align: center; | |
| top: 40vh; | |
| width: 96vw; | |
| left: 0; | |
| padding: 2vw; | |
| overflow: hidden; | |
| max-height: 100vh; | |
| max-width: 100vw; | |
| } | |
| #rules.close { | |
| max-height: 0; | |
| max-width: 0; | |
| opacity: 0; | |
| transition: max-height 2s, opacity 2s, max-width 0.2s 2s; | |
| } |