问题描述
我正在尝试为游戏制作一些灯光效果,当我只使用一个灯光时,它可以正常工作,但是我不能制作多个灯光。
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,canvas.width,canvas.height)
let x = 100,y = 100,radio = 70
let gradient = context.createradialGradient(x,y,x,radio)
// Gradient's color from 0% to 100% is white with alpha 0.
gradient.addColorStop(0,'rgba(255,255,0)')
// Gradient's color from 100% to out is black with alpha 1.
gradient.addColorStop(1,'rgba(0,1)')
context.fillStyle = gradient
context.fillRect(0,canvas.height)
// Draw UI: Buttons,Hp/Sp bars,etc.
我正在尝试制作一个黑色矩形,并用alpha 0将其打印一些圆圈,然后使用context.fillRect(0,canvas.height)
将其绘制在我的tilemap上,这是这样做的方法吗?。
编辑:
我不需要亮度效果,我要制作的是黑屏(覆盖所有画布大小),在屏幕上打印“孔”(圆圈,灯),然后在我的游戏上绘制阴影效果。
解决方法
使用globalCompositeOperation
这将控制如何将渲染的内容与画布合成。在示例中,使用ctx.globalCompositeOperation = "lighter";
添加到像素RGB通道。
使用变换
而不是每次要移动灯光时都创建新的渐变。为灯光创建一次渐变,然后通过变换移动它。在示例中,我使用setTransform
来放置灯光。然后在0,0处绘制圆弧,这样我就不必每次都创建一个新的渐变。
傻瓜
我们人类知道灯光的外观,因此绘制不具有某些灯光属性的灯光不会蒙蔽眼睛。使用线性(仅2个色标)渐变永远看起来都不正确
如果2D画布无法提供实时进行高质量照明的方法,则可以使用由于距光源的距离而导致的强度下降以及由于光线所照射的角度而导致的反射强度来创建渐变。表面作为梯度的路径
由于我们只需要为每个灯光创建一次渐变,因此可以在创建时为其添加很多细节。请参见示例函数Light(x,y,size,r,g,b)
示例
在画布上单击并按住以添加灯光。灯光在画布上缓慢移动,并随着它们的消失而褪色。某些设备可能有很多灯光故障(变慢)。
灯光已设置为具有许多不同的级别,有些几乎看不见,有些则过度曝光。全屏观看效果最佳。
var W,H;
const LIGHT_ADD_TIME = 10;
const lights = [];
var canAddTimer = 0;
const ctx = canvas.getContext("2d");
function Light(x,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,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,1,this.x,this.y);
ctx.globalCompositeOperation = "lighter";
ctx.globalAlpha = this.alpha ** 2;
ctx.fillStyle = this.grad;
ctx.beginPath();
ctx.arc(0,this.size,Math.PI * 2);
ctx.fill();
}
}
function update(){
var i = 0;
sizeCanvas();
const size = Math.min(W,H);
ctx.setTransform(1,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.fillStyle = "#321";
ctx.fillRect(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,randI(400,1256),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);
const hexCol = (r,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>