在画布上绘制漂亮的Laser(Star Wars) [英] Drawing a nice looking Laser (Star Wars) on the canvas

查看:80
本文介绍了在画布上绘制漂亮的Laser(Star Wars)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在对画布上的某些激光效果进行动画处理,目的是使我的游戏更加有趣。

Im currently in the process of animation some laser effects on the canvas for the purpose of making my game a bit more enjoyable.

为此,我

到目前为止,我基本上只画了一条红色或蓝色的短线,然后在其顶部绘制一条较细的白色线条,从而给人以渐变的印象。
我也使用linecap = round;

So far, im basicly only drawing a short line in red or blue and then drawing a thinner, white, line on top of it, so it gives the impression of a gradient. I also use linecap = "round";

我当前的代码:

function drawProjectile(weapon, ox, oy, x, y){
    var trailEnd = getPointInDirection(weapon.projSize*-2, getAngleFromTo({x: ox, y: oy}, {x: x, y: y}), x, y);

    fxCtx.lineCap = "round";

    fxCtx.beginPath(); //the background, wider beam
    fxCtx.moveTo(x+cam.o.x, y+cam.o.y);
    fxCtx.lineTo(trailEnd.x+cam.o.x, trailEnd.y+cam.o.y);
    fxCtx.closePath();
    fxCtx.strokeStyle = weapon.animColor //blue or red
    fxCtx.lineWidth = weapon.projSize;
    fxCtx.stroke();

    fxCtx.globalAlpha = 0.5; // the inner, thin, white beam
    fxCtx.strokeStyle = "white";
    fxCtx.lineWidth = 2;
    fxCtx.stroke();

    fxCtx.globalAlpha = 1;
    fxCtx.lineCap = "butt"; 
}

有人可以建议如何改善激光束效果吗?

Can someone advice how to improve my laser beam effect ?

推荐答案

将画布用作图像,辉光等。



下面的演示创建了所有内容它所需的图形作为屏幕外画布。当激光击中地面时,背景也会被留下燃烧痕迹。

Using canvas as images, glows, and more.

The demo below creates all the graphics it needs as offscreen canvases. The background is also drawn on to leave a burn mark when a laser hits the ground.

激光

激光镜头分为3层 ctx.drawImage 调用。前两个是 ctx.globalCompositeOperation = lighter 发出的光芒。一个具有固定的alpha,第二个具有随机alpha。最后一个绘制的 ctx.globalCompositeOperation = source-over 只是一条线的图像。

Laser shots are 3 layered ctx.drawImage calls. The first 2 are glow with ctx.globalCompositeOperation = "lighter" . One has fixed alpha, the second has a random alpha. The last is drawn ctx.globalCompositeOperation = "source-over" and is just an image of a line.

激光打印机有4个图像(画布),分别称为laserRed,laserGRed,laserGreen和laserGGreen。

There are 4 images (canvas) for the laaser called laserRed, laserGRed, laserGreen, and laserGGreen. the ones with the extra G are the laser glow.

当激光射击结束时,我从预渲染的图像中绘制了4帧扩展的辉光。在这4帧的第一帧中,我绘制到背景画布上并留下了燃烧痕迹。

When the laser shot is at the end I draw 4 frames of expanding glow from pre- rendered image. In the first of the 4 frames I draw to the background canvas leaving a burn mark.

预渲染的图像

所有使用的图形均在函数 onResize 中呈现,该函数在样板代码中重新调整大小。

All the graphics used are rendered in the function onResize which is called on resize from the boilerplate code.

函数 display 每帧调用一次并处理所有动画。

The function display is called once every frame and handles all the animation.

有一个名为imageTools的对象,它具有一些辅助功能,使编码更简单。 var image = imageTools.createImage(width,height)创建一个画布。该图像的上下文附加了 image.ctx ,因此您可以像在任何画布上一样对其进行绘制。然后,您可以使用 imageTools.drawImage(image,x,y,scale,rotation,alpha)在全局画布上绘制该图像。缩放,旋转和alpha。

There is an object called imageTools that has some helper functions to make the coding a little simplier. var image = imageTools.createImage(width,height) creates a canvas. The image has the context attached image.ctx so you can draw to it just like any canvas. You can then draw that image onto the global canvas with imageTools.drawImage(image,x,y,scale,rotation,alpha) The image is drawn at it center point with a scale, rotation, and alpha.

项目符号使用对象池,以使GC(垃圾收集)不会产生太大干扰。

The bullets use an object pool so that the GC (Garbage collection) does not interfere to much.

我没有设置任何限制,因此那些具有高分辨率设备或低端计算机的人可能会发现速度变慢。如果OP有问题,您可以通过加快项目符号的执行时间来减少项目符号的数量,也可以将渲染减少到仅两层或一层。但这比使用画布矢量调用,阴影等进行渲染要快得多。

I did not set any limits so those with high res devices or those with low end machines may see some slowdown. If its a problem OP you can reduce the bullet count by making them go a little faster, you can also reduce the rendering to just two or one layer. But this is a lot faster than if you were rendering with canvas vector calls, shadows, etc...

我将其余的工作交给您解决。在评论中它有点稀疏,但是我没有太多时间。

I will leave the rest for you to work out. Its a bit sparse in the comments but I did not have much time.

底部还有一些不太容易理解的样板。

There is also some boilerplate at the bottom that is not very readable.

左键开始战争。

/*************************************************************************************
 * Called from boilerplate code and is debounced by 100ms 
 * Creates all the images used in the demo.
 ************************************************************************************/ 
var onResize = function(){                       
    // create a background as drawable image
    background = imageTools.createImage(canvas.width,canvas.height);
    // create tile image
    tile = imageTools.createImage(64,64);
    tile.ctx.fillStyle = imageTools.createGradient(ctx,"linear",0,0,64,64,["#555","#666"]);
    tile.ctx.fillRect(0,0,64,64); 
    tile.ctx.fillStyle = "#333"; // add colour
    tile.ctx.globalCompositeOperation = "lighter"; 
    tile.ctx.fillRect(0,0,62,2);
    tile.ctx.fillRect(0,0,2,62);
    tile.ctx.fillStyle = "#AAA"; // multiply colour to darken
    tile.ctx.globalCompositeOperation = "multiply"; 
    tile.ctx.fillRect(62,1,2,62);
    tile.ctx.fillRect(1,62,62,2);
    for(var y = -32; y < canvas.height; y += 64 ){
        for(var x = -32; x < canvas.width; x += 64 ){
            background.ctx.drawImage(tile,x,y);
        }
    }
    background.ctx.globalCompositeOperation = "multiply"; // setup for rendering burn marks

    burn = imageTools.createImage(flashSize/2,flashSize/2);
    burn.ctx.fillStyle = imageTools.createGradient(ctx,"radial",flashSize/4,flashSize/4,0,flashSize/4,["#444","#444","#333","#000","#0000"]);
    burn.ctx.fillRect(0,0,flashSize/2,flashSize/2); 
    
    
    glowRed = imageTools.createImage(flashSize,flashSize);
    glowRed.ctx.fillStyle = imageTools.createGradient(ctx,"radial",flashSize/2,flashSize/2,0,flashSize/2,["#855F","#8000"]);
                                                    // #855F is non standard colour last digit is alpha
                                                    // 8,8 is ceneter 0 first radius 8 second
    glowRed.ctx.fillRect(0,0,flashSize,flashSize); 
    
    glowGreen = imageTools.createImage(flashSize,flashSize);
    glowGreen.ctx.fillStyle = imageTools.createGradient(ctx,"radial",flashSize/2,flashSize/2,0,flashSize/2,["#585F","#0600"]);
                                                    // #855F is non standard colour last digit is alpha
                                                    // 8,8 is ceneter 0 first radius 8 second
    glowGreen.ctx.fillRect(0,0,flashSize,flashSize); 
    
    // draw the laser 
    laserLen = 32;
    laserWidth = 4;
    laserRed = imageTools.createImage(laserLen,laserWidth);
    laserGreen = imageTools.createImage(laserLen,laserWidth);
    laserRed.ctx.lineCap = laserGreen.ctx.lineCap = "round";
    laserRed.ctx.lineWidth = laserGreen.ctx.lineWidth = laserWidth;
    laserRed.ctx.strokeStyle = "#F33";
    laserGreen.ctx.strokeStyle = "#3F3";
    laserRed.ctx.beginPath();
    laserGreen.ctx.beginPath();
    laserRed.ctx.moveTo(laserWidth/2 + 1,laserWidth/2);
    laserGreen.ctx.moveTo(laserWidth/2 + 1,laserWidth/2);
    laserRed.ctx.lineTo(laserLen - (laserWidth/2 + 1),laserWidth/2);
    laserGreen.ctx.lineTo(laserLen - (laserWidth/2 + 1),laserWidth/2);
    laserRed.ctx.stroke();
    laserGreen.ctx.stroke();
    
    // draw the laser glow FX
    var glowSize = 8;
    laserGRed = imageTools.createImage(laserLen + glowSize * 2,laserWidth + glowSize * 2);
    laserGGreen = imageTools.createImage(laserLen + glowSize * 2,laserWidth + glowSize * 2);
    laserGRed.ctx.lineCap = laserGGreen.ctx.lineCap = "round";
    laserGRed.ctx.shadowBlur = laserGGreen.ctx.shadowBlur = glowSize;
    laserGRed.ctx.shadowColor = "#F33"
    laserGGreen.ctx.shadowColor = "#3F3";
    laserGRed.ctx.lineWidth = laserGGreen.ctx.lineWidth = laserWidth;
    laserGRed.ctx.strokeStyle = "#F33";
    laserGGreen.ctx.strokeStyle = "#3F3";
    laserGRed.ctx.beginPath();
    laserGGreen.ctx.beginPath();
    laserGRed.ctx.moveTo(laserWidth/2 + 1 + glowSize,laserWidth/2 + glowSize);
    laserGGreen.ctx.moveTo(laserWidth/2 + 1 + glowSize,laserWidth/2 + glowSize);
    laserGRed.ctx.lineTo(laserLen + glowSize * 2 - (laserWidth/2 + 1 + glowSize),laserWidth/2 + glowSize);
    laserGGreen.ctx.lineTo(laserLen + glowSize * 2 - (laserWidth/2 + 1 + glowSize),laserWidth/2 + glowSize);
    laserGRed.ctx.stroke();
    laserGGreen.ctx.stroke();
    
    
                           
    readyToRock = true;    
    
}
var flashSize = 16;
const flashBrightNorm = 4 * (flashSize/2) * (flashSize/2) * Math.PI; // area of the flash
var background,tile,glowRed,glowGreen,grad, laserGreen,laserRed,laserGGreen,laserGRed,readyToRock,burn;
readyToRock = false; 

/*************************************************************************************
 * create or reset a bullet
 ************************************************************************************/    
function createShot(x,y,xx,yy,speed,type,bullet){ // create a bullet object
    if(bullet === undefined){
        bullet = {};
    }
    var nx = xx-x; // normalise
    var ny = yy-y;
    var dist = Math.sqrt(nx*nx+ny*ny);
    nx /= dist;
    ny /= dist;
    bullet.x = x;
    bullet.y = y;
    bullet.speed = speed;
    bullet.type = type;
    bullet.xx = xx;
    bullet.yy = yy;
    bullet.nx = nx; // normalised vector
    bullet.ny = ny;
    bullet.rot =  Math.atan2(ny,nx); // will draw rotated so get the rotation 
    bullet.life = Math.ceil(dist/speed); // how long to keep alive

    return bullet;
}
// semi static array with object pool.
var bullets=[]; // array of bullets
var bulletPool=[]; // array of used bullets. Use to create new bullets this stops GC messing with frame rate
const BULLET_TYPES = {
    red : 0,
    green : 1,
}
/*************************************************************************************
 * Add a bullet to the bullet array
 ************************************************************************************/    
function addBullet(xx,yy,type){
    var bullet,x,y;
    if(bulletPool.length > 0){
        bullet = bulletPool.pop(); // get bullet from pool
    }
    if(type === BULLET_TYPES.red){
        x = canvas.width + 16 + 32 * Math.random();
        y = Math.random() * canvas.height;
    }else if(type === BULLET_TYPES.green){
        x = - 16 - 32 * Math.random();
        y = Math.random() * canvas.height;
    }
    // randomise shoot to position
    var r = Math.random() * Math.PI * 2;
    var d = Math.random() * 128 + 16;
    xx += Math.cos(r)* d;
    yy += Math.sin(r)* d;
    bullets[bullets.length] = createShot(x,y,xx,yy,16,type,bullet);
    
}
/*************************************************************************************
 * update and draw bullets
 ************************************************************************************/    
function updateDrawAllBullets(){
    var i,img,imgGlow;
    for(i = 0; i < bullets.length; i++){
        var b = bullets[i];
        b.life -= 1;
        if(b.life <= 0){ // bullet end remove it and put it in the pool
            bulletPool[bulletPool.length] = bullets.splice(i,1)[0];
            i--; // to stop from skipping a bullet
        }else{
            
            if(b.life < 5){
                if(b.life===4){
                    b.x += b.nx * b.speed * 0.5;  // set to front of laser 
                    b.y += b.ny * b.speed * 0.5;
                    var scale = 0.9 + Math.random() *1;
                    background.ctx.setTransform(scale,0,0,scale,b.x,b.y);
                    background.ctx.globalAlpha = 0.1 + Math.random() *0.2;;
                    background.ctx.drawImage(burn,-burn.width /2 ,-burn.height/2);
                }
                if(b.type === BULLET_TYPES.red){
                    img = glowRed;
                }else{
                    img = glowGreen;
                }
                ctx.globalCompositeOperation = "lighter"; 
                imageTools.drawImage(img,b.x,b.y,(4-b.life)*(4-b.life),b.rot,1);//b.life/4);                
                imageTools.drawImage(img,b.x,b.y,4,b.rot,b.life/4);                
                ctx.globalCompositeOperation = "source-over"; 

                
            }else{
                b.x += b.nx * b.speed;
                b.y += b.ny * b.speed;
    
                if(b.type === BULLET_TYPES.red){
                    img = laserRed;
                    imgGlow = laserGRed;
                }else{
                    img = laserGreen;
                    imgGlow = laserGGreen;
                }
                ctx.globalCompositeOperation = "lighter"; 
                imageTools.drawImage(imgGlow,b.x,b.y,1,b.rot,1);
                imageTools.drawImage(imgGlow,b.x,b.y,2,b.rot,Math.random()/2);
                ctx.globalCompositeOperation = "source-over"; 
                imageTools.drawImage(img,b.x,b.y,1,b.rot,1);
            }
        }
    }
    
    
}
/*************************************************************************************
 * Main display loop
 ************************************************************************************/
function display() { 
    if(readyToRock){
        ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
        ctx.globalAlpha = 1; // reset alpha
        ctx.drawImage(background,0,0);
        ctx.globalCompositeOperation = "source-over"; 
        if(mouse.buttonRaw & 1){
            addBullet(mouse.x,mouse.y,BULLET_TYPES.red);
            addBullet(mouse.x,mouse.y,BULLET_TYPES.green);
        }
        updateDrawAllBullets();
    }
    
}

/*************************************************************************************
 * Tools for creating canvas images and what not
 ************************************************************************************/
var imageTools = (function () {
    // This interface is as is. No warenties no garenties, and NOT to be used comercialy
    var workImg,workImg1,keep; // for internal use
    var xdx,xdy,spr; // static vars for drawImage and drawSprite
    keep = false; 
    var tools = {
        canvas : function (width, height) {  // create a blank image (canvas)
            var c = document.createElement("canvas");
            c.width = width;
            c.height = height;
            return c;
        },
        createImage : function (width, height) {
            var i = this.canvas(width, height);
            i.ctx = i.getContext("2d");
            return i;
        },
        drawImage : function(image, x, y, scale, ang, alpha) {
            ctx.globalAlpha = alpha;
            xdx = Math.cos(ang) * scale;
            xdy = Math.sin(ang) * scale;
            ctx.setTransform(xdx, xdy, -xdy, xdx, x, y);
            ctx.drawImage(image, -image.width/2,-image.height/2);
        },
        hex2RGBA : function(hex){ // Not CSS colour as can have extra 2 or 1 chars for alpha
                                  // #FFFF & #FFFFFFFF last F and FF are the alpha range 0-F & 00-FF
            if(typeof hex === "string"){
                var str = "rgba(";
                if(hex.length === 4 || hex.length === 5){
                    str += (parseInt(hex.substr(1,1),16) * 16) + ",";
                    str += (parseInt(hex.substr(2,1),16) * 16) + ",";
                    str += (parseInt(hex.substr(3,1),16) * 16) + ",";
                    if(hex.length === 5){
                        str += (parseInt(hex.substr(4,1),16) / 16);
                    }else{
                        str += "1";
                    }
                    return str + ")";
                }
                if(hex.length === 7 || hex.length === 8){
                    str += parseInt(hex.substr(1,2),16) + ",";
                    str += parseInt(hex.substr(3,2),16) + ",";
                    str += parseInt(hex.substr(5,2),16) + ",";
                    if(hex.length === 5){
                        str += (parseInt(hex.substr(7,2),16) / 255).toFixed(3);
                    }else{
                        str += "1";
                    }
                    return str + ")";                
                }
                return "rgba(0,0,0,0)";
            }
        },            
        createGradient : function(ctx, type, x, y, xx, yy, colours){ // Colours MUST be array of hex colours NOT CSS colours
                                                                     // See this.hex2RGBA for details of format
            var i,g,c;
            var len = colours.length;
            if(type.toLowerCase() === "linear"){
                g = ctx.createLinearGradient(x,y,xx,yy);
            }else{
                g = ctx.createRadialGradient(x,y,xx,x,y,yy);
            }
            for(i = 0; i < len; i++){
                c = colours[i];
                if(typeof c === "string"){
                    if(c[0] === "#"){
                        c = this.hex2RGBA(c);
                    }
                    g.addColorStop(Math.min(1,i / (len -1)),c); // need to clamp top to 1 due to floating point errors causes addColorStop to throw rangeError when number over 1
                }
            }
            return g;
            
        },
    };
    return tools;
})();



// CODE FROM HERE DOWN IS SUPPORT CODE AN HAS LITTLE TO DO WITH THE ANSWER




//==================================================================================================
// The following code is support code that provides me with a standard interface to various forums.
// It provides a mouse interface, a full screen canvas, and some global often used variable
// like canvas, ctx, mouse, w, h (width and height), globalTime
// This code is not intended to be part of the answer unless specified and has been formated to reduce
// display size. It should not be used as an example of how to write a canvas interface.
// By Blindman67
if(typeof onResize === "undefined"){
    window["onResize"] = undefined;  // create without the JS parser knowing it exists.
                                     // this allows for it to be declared in an outside 
                                     // modal.
}
const RESIZE_DEBOUNCE_TIME = 100;
var w, h, cw, ch, canvas, ctx, mouse, createCanvas, resizeCanvas, setGlobals, globalTime = 0, resizeCount = 0;
createCanvas = function () {
    var c,
    cs;
    cs = (c = document.createElement("canvas")).style;
    cs.position = "absolute";
    cs.top = cs.left = "0px";
    cs.zIndex = 1000;
    document.body.appendChild(c);
    return c;
}
resizeCanvas = function () {
    if (canvas === undefined) {
        canvas = createCanvas();
    }
    canvas.width = window.innerWidth-2;
    canvas.height = window.innerHeight-2;
    ctx = canvas.getContext("2d");
    if (typeof setGlobals === "function") {
        setGlobals();
    }
    if (typeof onResize === "function") {
        resizeCount += 1;
        setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
    }
}
function debounceResize() {
    resizeCount -= 1;
    if (resizeCount <= 0) {
        onResize();
    }
}
setGlobals = function () {
    cw = (w = canvas.width) / 2;
    ch = (h = canvas.height) / 2;
    mouse.updateBounds();
}
mouse = (function () {
    function preventDefault(e) {
        e.preventDefault();
    }
    var mouse = {
        x : 0,
        y : 0,
        w : 0,
        alt : false,
        shift : false,
        ctrl : false,
        buttonRaw : 0,
        over : false,
        bm : [1, 2, 4, 6, 5, 3],
        active : false,
        bounds : null,
        crashRecover : null,
        mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
    };
    var m = mouse;
    function mouseMove(e) {
        var t = e.type;
        m.x = e.clientX - m.bounds.left;
        m.y = e.clientY - m.bounds.top;
        m.alt = e.altKey;
        m.shift = e.shiftKey;
        m.ctrl = e.ctrlKey;
        if (t === "mousedown") {
            m.buttonRaw |= m.bm[e.which - 1];
        } else if (t === "mouseup") {
            m.buttonRaw &= m.bm[e.which + 2];
        } else if (t === "mouseout") {
            m.buttonRaw = 0;
            m.over = false;
        } else if (t === "mouseover") {
            m.over = true;
        } else if (t === "mousewheel") {
            m.w = e.wheelDelta;
        } else if (t === "DOMMouseScroll") {
            m.w = -e.detail;
        }
        if (m.callbacks) {
            m.callbacks.forEach(c => c(e));
        }
        if ((m.buttonRaw & 2) && m.crashRecover !== null) {
            if (typeof m.crashRecover === "function") {
                setTimeout(m.crashRecover, 0);
            }
        }
        e.preventDefault();
    }
    m.updateBounds = function () {
        if (m.active) {
            m.bounds = m.element.getBoundingClientRect();
        }
    }
    m.addCallback = function (callback) {
        if (typeof callback === "function") {
            if (m.callbacks === undefined) {
                m.callbacks = [callback];
            } else {
                m.callbacks.push(callback);
            }
        } else {
            throw new TypeError("mouse.addCallback argument must be a function");
        }
    }
    m.start = function (element, blockContextMenu) {
        if (m.element !== undefined) {
            m.removeMouse();
        }
        m.element = element === undefined ? document : element;
        m.blockContextMenu = blockContextMenu === undefined ? false : blockContextMenu;
        m.mouseEvents.forEach(n => {
            m.element.addEventListener(n, mouseMove);
        });
        if (m.blockContextMenu === true) {
            m.element.addEventListener("contextmenu", preventDefault, false);
        }
        m.active = true;
        m.updateBounds();
    }
    m.remove = function () {
        if (m.element !== undefined) {
            m.mouseEvents.forEach(n => {
                m.element.removeEventListener(n, mouseMove);
            });
            if (m.contextMenuBlocked === true) {
                m.element.removeEventListener("contextmenu", preventDefault);
            }
            m.element = m.callbacks = m.contextMenuBlocked = undefined;
            m.active = false;
        }
    }
    return mouse;
})();

// Clean up. Used where the IDE is on the same page.
var done = function () {
    window.removeEventListener("resize", resizeCanvas)
    mouse.remove();
    document.body.removeChild(canvas);
    canvas = ctx = mouse = undefined;
}

resizeCanvas();
mouse.start(canvas, true);
mouse.crashRecover = done;
window.addEventListener("resize", resizeCanvas);

function update(timer) { // Main update loop
    globalTime = timer;
    display(); // call demo code
    requestAnimationFrame(update);
}
requestAnimationFrame(update);
/** SimpleFullCanvasMouse.js end **/

这篇关于在画布上绘制漂亮的Laser(Star Wars)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆