Reputation: 105
I'm trying to make some light effects for a game and when i use one only light it works fine but i can't make multiple lights.
app.js
let canvas = document.getElementById("game")
let context = canvas.getContext('2d')
// Drawing: background, tilemap, objects, player, monsters, npc, etc.
context.fillStyle = 'white'
context.fillRect(0, 0, canvas.width, canvas.height)
let x = 100, y = 100, radio = 70
let gradient = context.createRadialGradient(x, y, 0, x, y, radio)
// Gradient's color from 0% to 100% is white with alpha 0.
gradient.addColorStop(0, 'rgba(255, 255, 255, 0)')
// Gradient's color from 100% to out is black with alpha 1.
gradient.addColorStop(1, 'rgba(0, 0, 0, 1)')
context.fillStyle = gradient
context.fillRect(0, 0, canvas.width, canvas.height)
// Draw UI: Buttons, Hp/Sp bars, etc.
I'm trying to make a black rect and print it some circles with alpha 0 then use context.fillRect(0, 0, canvas.width, canvas.height)
to draw it over my tilemap, is this the way to do this?.
Edit:
I don't want brightness effect, what i'm trying to make is a black screen(covering all canvas size), print it "holes"(circles, lights) and then draw it over my game making shadow effects.
Upvotes: 1
Views: 1669
Reputation: 54099
globalCompositeOperation
This will control how the rendered content is composited with the canvas. In the example use ctx.globalCompositeOperation = "lighter";
to add to the pixel RGB channels.
Rather than create a new gradient each time you want to move a light. Create the gradient for the light once, and move it via the transform. In the example I use setTransform
to position a light. Then draw the arc at 0, 0 so I don't need to create a new gradient each time.
We humans know how lights should look so drawing a light that does not take a some of lights properties will not fool the eye. Using a linear (only 2 color stops) gradient will never look right
Thought the 2D canvas does not provide a way to do high quality lighting in real time, you can create the gradient using fall off of intensity due to distance from the light source, and reflected intensity due to the angle the light ray hits the surface as path of the gradient
As we only need to create the gradient once per light we can add a lot of detail to it when creating. See example function Light(x, y, size, r, g, b)
Click and hold on canvas to add lights. Lights slowly move of canvas and fade as they go. Some devices may have trouble (slow down) with lots of lights.
The lights have been set up to have many different levels , some barely visible, others over exposing. Best viewed full screen.
var W, H;
const LIGHT_ADD_TIME = 10;
const lights = [];
var canAddTimer = 0;
const ctx = canvas.getContext("2d");
function Light(x, y, size, r, g, b) { // color must be in the form #FFFFFF
this.x = x;
this.y = y;
this.alpha = 1;
const dir = rand(Math.PI * 2);
const speed = rand(0.1, 2);
this.dx = Math.cos(dir) * speed;
this.dy = Math.sin(dir) * speed;
this.alphaStep = rand(1) < 0.08 ? rand(0.1, 0.01) : rand(0.001, 0.005);
this.size = size;
this.grad = ctx.createRadialGradient(0,0,0, 0, 0, size);
const step = 1 / size;
var i = 0;
var h = size / 2, hSqr = h * h;
const br = r * hSqr, bg = g * hSqr, bb = b * hSqr, aa = 256 * hSqr;
h = rand(h, size * 2), hSqr = h * h;
while(i < 1) {
const distSqr = (i * size) ** 2 + hSqr;
const ref = (h / distSqr ** 0.5) / distSqr;
this.grad.addColorStop(i, hexCol(br * ref, bg * ref, bb * ref, aa * ref * (1-i)));
i += step;
}
this.grad.addColorStop(1,"#0000");
}
Light.prototype = {
update() {
this.x += this.dx;
this.y += this.dy;
this.alpha -= this.alphaStep;
if (this.alpha < 0 || this.x + this.size < 0 || this.x - this.size > W || this.y + this.size < 0 || this.y - this.size > H) {
return false;
}
return true;
},
draw() {
ctx.setTransform(1,0,0,1,this.x, this.y);
ctx.globalCompositeOperation = "lighter";
ctx.globalAlpha = this.alpha ** 2;
ctx.fillStyle = this.grad;
ctx.beginPath();
ctx.arc(0, 0, this.size, 0, Math.PI * 2);
ctx.fill();
}
}
function update(){
var i = 0;
sizeCanvas();
const size = Math.min(W,H);
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.fillStyle = "#321";
ctx.fillRect(0,0,W,H);
ctx.fillStyle = "#354";
ctx.fillRect(size / 12,size / 12,W -size /6,H -size /6);
ctx.fillStyle = "#234";
ctx.fillRect(size / 8,size / 8,W -size /4,H -size /4);
ctx.fillStyle = "#435";
ctx.fillRect(size / 4,size / 4,W -size /2,H -size /2);
if (canAddTimer <= 0) {
if(mouse.button) {
canAddTimer = LIGHT_ADD_TIME;
const size = rand(Math.min(W,H) / 3, Math.min(W,H) * 2 );
const col = randI(128,256);
const L = new Light(mouse.x, mouse.y, size, randI(400,1256), randI(400,1256), randI(400,1256));
lights.push(L);
}
} else {
canAddTimer--;
}
while (i < lights.length) {
const L = lights[i];
if(L.update() === false) {
lights.splice(i,1);
} else { i++ }
}
lights.forEach(L => L.draw());
ctx.globalCompositeOperation = "source-over";
requestAnimationFrame(update);
}
const mouse = {x: 0, y: 0, button: false};
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
const randI = (min = 2, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const randHex = (m, M) => randI(m, M).toString(16).padStart((M > 16 ? 2 : 1), "0");
const randCol = (m, M) => "#" + randHex(m,M) + randHex(m,M) + randHex(m,M);
const hexCol = (r,g,b,a) => "#" +
((r > 255 ? 255 : r < 0 ? 0 : r) | 0).toString(16).padStart(2, "0") +
((g > 255 ? 255 : g < 0 ? 0 : g) | 0).toString(16).padStart(2, "0") +
((b > 255 ? 255 : b < 0 ? 0 : b) | 0).toString(16).padStart(2, "0") +
((a > 255 ? 255 : a < 0 ? 0 : a) | 0).toString(16).padStart(2, "0");
function sizeCanvas() {
if (innerWidth !== W || innerHeight !== H) {
W = canvas.width = innerWidth;
H = canvas.height = innerHeight;
}
}
requestAnimationFrame(update);
body { user-select: none }
canvas { position: absolute; top: 0px; left: 0px; }
div { position: absolute; color: white; top: 10px; left: 10px; pointer-events: none; }
<canvas id="canvas"></canvas>
<div>Click to add moving lights</div>
Upvotes: 1