Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save GreenShaman/958586e48be5052ec7d6 to your computer and use it in GitHub Desktop.
Save GreenShaman/958586e48be5052ec7d6 to your computer and use it in GitHub Desktop.
Canvas Blend Modes, Glow & Shape Maps and Multi-Coloured Sprites
<div id="game"></div>
var globalTimer = 0;
var globalGlowLoop = 0;
var itemTypes = 5;
var shapeMaps = []; // TODO
var glowMaps = []; // TODO
var itemSprites = new Image();
itemSprites.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAABgCAYAAABbjPFwAAADwUlEQVR4nO2asU7rMBSG+yKMPABiZ+h0ERNsXJYrRu6V2Fh4AQaEGChdQIgJBgYWJgSsLAxIiBF4AARMTKi++aOc6tR1HMfHTijyL/1qm/TU33/stA6iMxgMOrCvpPViSQcvq7+7u1NXV1fq9PRUHR4eqrqfK6rPoFThv4Xz13U+gw++s7NTO4C0nkL8Klz/AzJ5Dx6ivggwV9gbojWlAJm+vr4UHJrNKnYBj13EdYMA/uPjo7kABeSfzMuZF9kMzLnOBnUdfnt7Uy8vL/kjuQn4f5l/l8BbQwD68/NzCAt47qghigDLDvBzT09PRgiC18G58QMVM0DZshn6+vp6/ubmxgjhAo8fp5gB5m3g8O7u7uLJyYlzAEDzbYHkh801xBg0gdvgSQTMoUVbgrrC+iZjqZABrsPH2gg2okGAbXhSkkFLS0sKdj2ua2ZmRsGux4OJAF9fX9XFxcUILB2H8Xx2drYUcG9vT62vr4/A0nEYz6empsIGARSgCRLPOSQPYDoPKEATJJ5zSB7AdD5IAA7Iu206ZwrAAan7fNnx90QJwGeAg5qOVc0ABcD79JB4X/AAFKIqgAleB+RrnwfgQYPDl4U4OjoahrDBc0D+mur5sWjwkOlidel+mXT46AKcZAZ0mWYgqvQAOvzEzADBUgd9A6A+Ju+YAMdh6wbQz5kC1GmAlzCAzbHrk368ut2uIrfNUluAxh8OHh8fJy8Ah8fjRAVoDH56elrBIT+z0c4Dfm1tLViIVpYN4Hu9XpAQraz5kAFau2B5iNDXRGOiEHWvCdzUSHaR0voR0Qy4hqCbe99Nl3d9BtexAbouJXROsmP0rg954bYi6jBmIikpSa6NjQ21urqae2FhIXedbxdpvUgYvOycy425tF4kdMzlfWUQ0nqxbAC0LLAcYtWL5AsWql4sG4BL16T1YlWty9jnR5TdgOTu9/tqe3tb0esqfZsQBE/gm5ubyiWAPgj+Aerh4UGZzvlAOgfgXSfjnsCpuBDBn5+fq62trWZ3ubzr8MrKivMMcB0cHDQPD/GuE7xPgKSkpKTmhX+1vL+/N/ry8rLyK1VaL9L7+7siPT8/j/j29ja3DQL1VWNEC8Hh9RAEbwvhAk8aq+/1euIAmOKyrp+dnQ19fHys9vf3x2BR7zqWqT6IeAi96zZ4Xl81RjR4DqHD07JxGdwWIjo8CZ12WTa2ev1YY/AcguwzOA/RODwfWDK4tP7nKMTXbNKPVXaFKJU/TGZ9R1T8DeqTkpKSkpKSkpI81fZGTryL/Q7b8P/CoXlWBeFGFAAAAABJRU5ErkJggg==";
var enchSprite = new Image();
enchSprite.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAArUlEQVR4nO2WjQqAIAyEff+XXn8oFtN5zmmLDoLQfdeZlAsUQjguRLQr3Sv5y4DaLShT5NGH53wc5NNVTHrmxBo2HSAtX083iTczNgtmIimttBh4tUtfb/qeAZPbj0jJ8wOygYqH5G57VDxXqNpbkIfUcwpyHu3Fj5WgAYr8K/oBFB7eDxTTVUx65sSavx+wNP5WPzCcd3fgLO8HRMORcrc9Kt5VP3Caz+St+oENabmTe+GCasUAAAAASUVORK5CYII=";
/**
* Manages rendering objects on canvas.
*/
var Engine = (function() {
// Initzalize Renderer
function Engine() {
this.canvas = document.createElement('canvas');
this.canvas.tabIndex = 1;
this.canvas.width = 320;
this.canvas.height = 240;
this.context = this.canvas.getContext('2d');
this.generateEffectMaps();
}
Engine.prototype.generateEffectMaps = function() {
var tempContext = document.createElement('canvas').getContext('2d');
tempContext.canvas.width = 16;
tempContext.canvas.height = 16;
//Generate shape maps.
for (var i = 0; i < itemTypes; i++) {
tempContext.globalCompositeOperation = 'source-over';
tempContext.clearRect(0, 0, 16, 16);
tempContext.drawImage(itemSprites, 0, 0 + (i * 16), 16, 16, 0, 0, 16, 16);
tempContext.drawImage(itemSprites, 16, 0 + (i * 16), 16, 16, 0, 0, 16, 16);
tempContext.drawImage(itemSprites, 32, 0 + (i * 16), 16, 16, 0, 0, 16, 16);
for (var a = 0; a < 6; a++) {
tempContext.globalCompositeOperation = 'lighter';
tempContext.drawImage(itemSprites, 0, 0 + (i * 16), 16, 16, 0, 0, 16, 16);
tempContext.drawImage(itemSprites, 16, 0 + (i * 16), 16, 16, 0, 0, 16, 16);
tempContext.drawImage(itemSprites, 32, 0 + (i * 16), 16, 16, 0, 0, 16, 16);
}
shapeMaps[i] = convertCanvasToImage(tempContext.canvas);
}
//Generate glow maps.
for (var i = 0; i < itemTypes; i++) {
tempContext.globalCompositeOperation = 'source-over';
tempContext.clearRect(0, 0, 16, 16);
tempContext.drawImage(shapeMaps[i], 0, 0, 16, 16, -1, 0, 16, 16);
tempContext.drawImage(shapeMaps[i], 0, 0, 16, 16, 1, 0, 16, 16);
tempContext.drawImage(shapeMaps[i], 0, 0, 16, 16, 0, -1, 16, 16);
tempContext.drawImage(shapeMaps[i], 0, 0, 16, 16, 0, 1, 16, 16);
tempContext.globalCompositeOperation = 'destination-out';
tempContext.drawImage(shapeMaps[i], 0, 0, 16, 16, 0, 0, 16, 16);
glowMaps[i] = convertCanvasToImage(tempContext.canvas);
}
tempContext.globalCompositeOperation = 'source-over';
}
//Refreshes the screen with everything in the scene.
Engine.prototype.render = function(scene) {
var _this = this;
this.context.save();
globalTimer++;
//Viewport
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
//Render Scene
this.context.fillStyle = "#030303";
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.context.font = "bold 9px Lucida Bright,Georgia,serif";
this.context.globalCompositeOperation = 'source-over';
/*for (var i = 0; i < thePlayer.inventory.length; i++) {
thePlayer.inventory[i].render(_this.context, 8 + ((i % 19) * 16), 8 + (Math.floor(i / 19) * 16));
}*/
/*for (var i = 0; i < shapeMaps.length; i++) {
_this.context.drawImage(shapeMaps[i], 0, 0, 16, 16, 0 + (i * 16), 160, 16, 16);
}
for (var i = 0; i < glowMaps.length; i++) {
_this.context.drawImage(glowMaps[i], 0, 0, 16, 16, 0 + (i * 16), 176, 16, 16);
}*/
for (var i = 0; i < Math.min(thePlayer.inventory.length, 13); i++) {
thePlayer.inventory[i].render(_this.context, 8, 8 + (i * 16));
switch (thePlayer.inventory[i].parts) {
case 1: this.context.fillStyle = "#F3F3F3"; break;
case 2: this.context.fillStyle = "#8FF"; break;
case 3: this.context.fillStyle = "#F8F"; break;
case 4: this.context.fillStyle = "#FF8"; break;
default: this.context.fillStyle = "#FFF"; break;
}
_this.context.fillText(thePlayer.inventory[i].name, 26, 8 + (i * 16) + 8);
this.context.fillStyle = "#C3C3C3";
_this.context.fillText(thePlayer.inventory[i].parts + " Parts", 26, 8 + (i * 16) + 16);
}
if (thePlayer.inventory.length > 13) {
this.context.fillStyle = "#F3F3F3";
_this.context.fillText("More...", 8, 4 + (14 * 16));
}
scene.map(function(o) {
if ('render' in o) {
if (scene) {
o.render(_this.context);
}
}
});
this.context.restore();
};
return Engine;
})();
var Player = (function(){
function Player() {
this.inventory = [];
}
return Player;
})();
var Item = (function() {
function Item(type, parts) {
this.colour = [];
this.type = type;
this.parts = parts;
this.name = "";
this.sprite = new Image();
this.sprite.src = itemSprites.src;
this.enchEff = new Image();
this.enchEff.src = enchSprite.src;
this.tempContext = document.createElement('canvas').getContext('2d');
this.tempContext.canvas.width = 16;
this.tempContext.canvas.height = 16;
this.initRando();
this.generateSprite();
}
Item.prototype.render = function(context, x, y) {
context.globalCompositeOperation = 'source-over';
context.drawImage(this.sprite, 0, 0, 16, 16, x, y, 16, 16);
if (this.parts > 2) {
//Draw on temp canvas.
this.tempContext.globalCompositeOperation = 'source-over';
this.tempContext.clearRect(0, 0, 16, 16);
this.tempContext.drawImage(glowMaps[this.type], 0, 0, 16, 16, 0, 0, 16, 16);
//Apply the colours!
this.tempContext.globalCompositeOperation = 'multiply';
this.tempContext.fillStyle = this.colour[2];
this.tempContext.fillRect(0, 0, 16, 16);
//Remove outside.
this.tempContext.globalCompositeOperation = 'destination-in';
this.tempContext.drawImage(glowMaps[this.type], 0, 0, 16, 16, 0, 0, 16, 16);
context.globalAlpha = globalGlowLoop;
//Draw the Item.
context.drawImage(this.tempContext.canvas, 0, 0, 16, 16, x, y, 16, 16);
context.globalAlpha = 1;
}
if (this.parts > 3) {
var enchOffset = 16 - ((globalTimer / 4) % 16);
//Draw on temp canvas.
this.tempContext.globalCompositeOperation = 'source-over';
this.tempContext.clearRect(0, 0, 16, 16);
this.tempContext.drawImage(this.enchEff, 0 + enchOffset, 0 + enchOffset, 16, 16, 0, 0, 16, 16);
//Apply the colours!
this.tempContext.globalCompositeOperation = 'multiply';
this.tempContext.fillStyle = this.colour[3];
this.tempContext.fillRect(0, 0, 16, 16);
//Remove outside.
this.tempContext.globalCompositeOperation = 'destination-in';
this.tempContext.drawImage(this.enchEff, 0 + enchOffset, 0 + enchOffset, 16, 16, 0, 0, 16, 16);
this.tempContext.drawImage(shapeMaps[this.type], 0, 0, 16, 16, 0, 0, 16, 16);
context.globalAlpha = 0.5;
context.globalCompositeOperation = 'lighter';
context.drawImage(this.tempContext.canvas, 0, 0, 16, 16, x, y, 16, 16);
context.globalCompositeOperation = 'source-over';
context.globalAlpha = 1;
}
};
Item.prototype.initRando = function() {
var rando;
switch (this.type) {
case 0: this.name = "Potion"; break;
case 1: this.name = "Tunic"; break;
case 2: this.name = "Sword"; break;
case 3: this.name = "Amulet"; break;
case 4: this.name = "Gemstone"; break;
default: this.name = "ERROR"; break;
}
if (this.parts > 0) {
rando = randomRangeRounded(0, 11);
switch (rando) {
case 0: this.colour.push("#CBA"); this.name = "Iron " + this.name; break;
case 1: this.colour.push("#45E"); this.name = "Cobalt " + this.name; break;
case 2: this.colour.push("#B96"); this.name = "Bronze " + this.name; break;
case 3: this.colour.push("#6E7"); this.name = "Emerald " + this.name; break;
case 4: this.colour.push("#DDE"); this.name = "Silver " + this.name; break;
case 5: this.colour.push("#BA2"); this.name = "Golden " + this.name; break;
case 6: this.colour.push("#3AC"); this.name = "Crystal " + this.name; break;
case 7: this.colour.push("#132"); this.name = "Ebonic " + this.name; break;
case 8: this.colour.push("#888"); this.name = "Neutral " + this.name; break;
case 9: this.colour.push("#600"); this.name = "Blooded " + this.name; break;
case 10: this.colour.push("#5C3"); this.name = "Wacky " + this.name; break;
case 11: this.colour.push("#EEE"); this.name = "Light " + this.name; break;
default: this.colour.push("#404"); this.name = "ERROR " + this.name; break;
}
} else {
this.name = "NO PARTS";
}
if (this.parts > 1) {
rando = randomRangeRounded(0, 6);
switch (rando) {
case 0: this.colour.push("#BA2"); this.name = "Guilded " + this.name; break;
case 1: this.colour.push("#CBA"); this.name = "Bolstered " + this.name; break;
case 2: this.colour.push("#FBD"); this.name = "Unusual " + this.name; break;
case 3: this.colour.push("#C45"); this.name = "Enrosed " + this.name; break;
case 4: this.colour.push("#75A"); this.name = "Warpular " + this.name; break;
case 5: this.colour.push("#888"); this.name = "Nullified " + this.name; break;
case 6: this.colour.push("#EEE"); this.name = "Pale " + this.name; break;
default: this.colour.push("#404"); this.name = "ERROR " + this.name; break;
}
}
if (this.parts > 2) {
rando = randomRangeRounded(0, 10);
switch (rando) {
case 0: this.colour.push("#0DF"); this.name += " of Souls"; break;
case 1: this.colour.push("#C22"); this.name += " of Rage"; break;
case 2: this.colour.push("#CB2"); this.name += " of Cheese"; break;
case 3: this.colour.push("#0BE"); this.name += " of Neon"; break;
case 4: this.colour.push("#1D1"); this.name += " of Tech"; break;
case 5: this.colour.push("#FFA"); this.name += " of the Divine"; break;
case 6: this.colour.push("#888"); this.name += " of Null"; break;
case 7: this.colour.push("#080"); this.name += " of Links"; break;
case 8: this.colour.push("#801"); this.name += " of Blood"; break;
case 9: this.colour.push("#00C"); this.name += " of Posidon"; break;
case 10: this.colour.push("#EEE"); this.name += " of Purity"; break;
default: this.colour.push("#404"); this.name += " of ERRORs"; break;
}
}
if (this.parts > 3) {
rando = randomRangeRounded(0, 7);
switch (rando) {
case 0: this.colour.push("#B00"); this.name += " I"; break;
case 1: this.colour.push("#BB0"); this.name += " II"; break;
case 2: this.colour.push("#0B0"); this.name += " III"; break;
case 3: this.colour.push("#0BB"); this.name += " IV"; break;
case 4: this.colour.push("#00B"); this.name += " V"; break;
case 5: this.colour.push("#B0B"); this.name += " VI"; break;
case 6: this.colour.push("#BBB"); this.name += " VII"; break;
case 7: this.colour.push("#FFF"); this.name += " IIX"; break;
default: this.colour.push("#404"); this.name += " ???"; break;
}
}
};
Item.prototype.generateSprite = function() {
var tempContext2 = document.createElement('canvas').getContext('2d');
tempContext2.canvas.width = 16;
tempContext2.canvas.height = 16;
this.tempContext.globalCompositeOperation = 'source-over';
this.tempContext.clearRect(0, 0, 16, 16);
this.tempContext.drawImage(itemSprites, 16, 0 + (this.type * 16), 16, 16, 0, 0, 16, 16);
this.tempContext.globalCompositeOperation = 'multiply';
this.tempContext.fillStyle = this.colour[0];
this.tempContext.fillRect(0, 0, 16, 16);
this.tempContext.globalCompositeOperation = 'destination-in';
this.tempContext.drawImage(itemSprites, 16, 0 + (this.type * 16), 16, 16, 0, 0, 16, 16);
tempContext2.drawImage(this.tempContext.canvas, 0, 0, 16, 16, 0, 0, 16, 16);
if (this.parts > 1) {
this.tempContext.globalCompositeOperation = 'source-over';
this.tempContext.clearRect(0, 0, 16, 16);
this.tempContext.drawImage(itemSprites, 32, 0 + (this.type * 16), 16, 16, 0, 0, 16, 16);
this.tempContext.globalCompositeOperation = 'multiply';
this.tempContext.fillStyle = this.colour[1];
this.tempContext.fillRect(0, 0, 16, 16);
this.tempContext.globalCompositeOperation = 'destination-in';
this.tempContext.drawImage(itemSprites, 32, 0 + (this.type * 16), 16, 16, 0, 0, 16, 16);
tempContext2.drawImage(this.tempContext.canvas, 0, 0, 16, 16, 0, 0, 16, 16);
}
tempContext2.drawImage(itemSprites, 0, 0 + (this.type * 16), 16, 16, 0, 0, 16, 16);
this.sprite = convertCanvasToImage(tempContext2.canvas);
};
return Item;
})();
/**
* Main Start
*/
var engine, scene, thePlayer;
function start() {
engine = new Engine();
thePlayer = new Player();
var canvas = engine.canvas;
document.getElementById('game').appendChild(canvas);
canvas.focus();
scene = [];
for (var i = 0; i < 266; i++) {
thePlayer.inventory.push(new Item(randomRangeRounded(0, itemTypes - 1), randomRangeRounded(1, 4)));
}
}
function animate() {
globalGlowLoop = Math.abs(Math.sin(globalTimer / 30));
engine.render(scene);
requestAnimationFrame(animate);
}
function randomRangeRounded(min, max) {
return Math.floor((Math.random() * ((max + 0.49) - min)) + min);
}
function getRandomColor() {
var letters = '0123456789ABCDEF'.split('');
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
function convertCanvasToImage(canvas) {
// Credit to David Walsh.
var image = new Image();
image.src = canvas.toDataURL("image/png");
return image;
}
start();
animate();
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #154615;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: sans-serif;
color: #fff;
}
body {
background-color: rgba(0, 0, 0, 1.0);
background-image:
linear-gradient(rgba(0, 160, 0, 0.25), rgba(0, 64, 0, 0.1)),
radial-gradient(circle, rgba(46, 224, 72, 0.9), rgba(16, 64, 24, 0.1));
}
canvas {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6);
outline-style: dashed;
outline-color: 'white';
outline-width: 1px;
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: pixelated;
}
canvas:focus {
outline-style: solid;
outline-color: 'white';
outline-width: 1px;
}
@media (min-width:640px) and (min-height:480px) {
canvas {
transform: scale(2);
}
}
@media (min-width:960px) and (min-height:720px) {
canvas {
transform: scale(3);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment