能邀请你来看烟花吗

先看效果

flag 不倒的 flag 终于没倒!

这个我是在一个现成的网站的基础上优化而成的。那个三百多行代码没有一个注释,还有很多逻辑十分奇怪,看得我好辛苦。但还是非常感谢它,我的很多参数是基于这个网站设定的,如果让我自己猜还不知道要多久。

这个玩意的 3D 效果是完全通过手写写出来的,就是每个粒子都有三维坐标,绘制的时候再投影到某个平面上。应该还有更高级的工具来制作 3D 效果。

HTML 部分

网页的结构非常简单,只需要布置一个 <canvas> 元素,其余的操作都在 JS 里面实现:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>大哥大嫂过年好</title>
    <style>
        html,
        body {
            width: 100%;
            height: 100%;
            overflow: hidden;
            background: black;
        }
        #canvas {
            width: 100%;
            height: 100%;
        }
    </style>
</head>
<body>
    <canvas id="canvas" width="825" height="631"></canvas>
    <script>
      ...
    </script>
</body>
</html>

JavaScript 部分

初始化全局变量

变量名我应该写的还算比较清楚。

canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
var
		PI = Math.PI,
    ctx = canvas.getContext("2d"),
    cx = canvas.width / 2,
    cy = canvas.height / 2,  //中心坐标
    playerx = 0, playery = -15, playerz = -15,  //观测坐标,(x, y) 是显示器的平面,x向右y向下z向里
    leftright = 0,  //视角(左右旋转的角度)
    scale = 600,
    seedTimer = 0, seedLife = 100, seedInterval = 4,
    gravity = .015,
    seeds = new Array(),
    sparkimgs = new Array(),
    sparks = new Array(),
    frames = 0,
    temp;

for (let i = 1; i <= 10; i++) {
  	temp = new Image();
  	temp.src = "images/spark" + i + ".png";
  	sparkimgs.push(temp);
}

动画函数

这个函数将会不断自调用。

function frame() {
    "use strict"
    if (frames > 100000) {
      	seedTimer = frames = 0
    }
    frames++;
    flipScreen();
    everythingMove();
    requestAnimationFrame(frame);
 }

每一帧的绘制

	function flipScreen() {
    "use strict"
    var x, z, y, point, size, dis, alpha, p1, p2;

    ctx.clearRect(0, 0, cx * 2, cy * 2);
    //画网格,中心是(0, 25, 0)
    ctx.fillStyle = "#ff8";
    for (let i = -150; i <= 150; i += 10) {
        for (let j = -150; j <= 150; j += 10) {
            x = i, z = j, y = 50;
            point = project(x, y, z);
            if (point.d != -1) {
                size = 300 / (0.1 + point.d);
                //size = 2000;
                dis = Math.sqrt(x * x + z * z);
                alpha = 0.75 - Math.pow(dis / 100, 6) * 0.75;  //越大越不透明
                if (alpha > 0) {
                    ctx.globalAlpha = alpha;
                    ctx.fillRect(point.x - size/2, point.y - size/2, size, size);
                }
            }
        }
    }
    //画种子
    ctx.globalAlpha = 1;
    for (let i = 0; i < seeds.length; i++) {
        point = project(seeds[i].x, seeds[i].y, seeds[i].z);
        if (point.d != -1) {
            size = 200 / (0.1 + point.d);
            ctx.fillRect(point.x - size/2, point.y - size/2, size, size);
        }
    }
    //画炸开后的火星
    p1 = new Object();
    for (let i = 0; i < sparks.length; i++) {
        point = project(sparks[i].x, sparks[i].y, sparks[i].z);
        if (point.d != -1) {
            size = sparks[i].radius * 200 / (0.1 + point.d);
            if (sparks[i].alpha < 0) sparks[i].alpha = 0;
            if (sparks[i].trail.length) {
                switch (sparks[i].img) {
                    case sparkimgs[0]: ctx.strokeStyle = "#f84"; break;
                    case sparkimgs[1]: ctx.strokeStyle = "#84f"; break;
                    case sparkimgs[2]: ctx.strokeStyle = "#8ff"; break;
                    case sparkimgs[3]: ctx.strokeStyle = "#fff"; break;
                    case sparkimgs[4]: ctx.strokeStyle = "#4f8"; break;
                    case sparkimgs[5]: ctx.strokeStyle = "#f44"; break;
                    case sparkimgs[6]: ctx.strokeStyle = "#f84"; break;
                    case sparkimgs[7]: ctx.strokeStyle = "#84f"; break;
                    case sparkimgs[8]: ctx.strokeStyle = "#fff"; break;
                    case sparkimgs[9]: ctx.strokeStyle = "#44f"; break;
                }
                p1.x = point.x, p1.y = point.y;
                for (let j = sparks[i].trail.length - 1; j >= 0; j--) {
                    p2 = project(sparks[i].trail[j].x, sparks[i].trail[j].y, sparks[i].trail[j].z);
                    if (p2.d != -1) {
                        ctx.globalAlpha = j / sparks[i].trail.length * sparks[i].alpha / 2;
                        ctx.beginPath();
                        ctx.moveTo(p1.x, p1.y);
                        ctx.lineWidth = 1.2 + sparks[i].radius * 10 / (sparks[i].trail.length - j) / (0.1 + p2.d);
                        ctx.lineTo(p2.x, p2.y);
                        ctx.stroke();
                        p1 = p2;
                    }
                }
            }
            ctx.globalAlpha = sparks[i].alpha;
            ctx.drawImage(sparks[i].img, point.x - size/2, point.y - size/2, size, size);
        }
    }
}

处理粒子运动

function everythingMove() {
    "use strict"
    var x, y, z, dis2, dis, st, angle, delta, point;
    if (seedTimer < frames) {
        produceSeed();
        seedTimer = frames + seedInterval * Math.random() * 10;
    }
    st = new Array();
    for (let i = 0; i < seeds.length; i++) {
        seeds[i].x += seeds[i].vx;
        seeds[i].y += seeds[i].vy;
        seeds[i].z += seeds[i].vz;
        seeds[i].vy += gravity;
        if (frames - seeds[i].born > seedLife) {
            explode(seeds[i].x, seeds[i].y, seeds[i].z);
            st.push(i);
        }
    }
    for (let i = 0; i < st.length; i++) {
        seeds.splice(st[i], 1);
    }
    for (let i = 0; i < sparks.length; i++) {
        
        if (sparks[i].alpha > 0) {
            sparks[i].alpha -= .017;
            sparks[i].radius /= 1.02;
            sparks[i].x += sparks[i].vx;
            sparks[i].y += sparks[i].vy;
            sparks[i].z += sparks[i].vz;
            sparks[i].vy += gravity;
            sparks[i].vx /= 1.075;
            sparks[i].vy /= 1.075;
            sparks[i].vz /= 1.075;
            point = new Object();
            ({x:point.x, y:point.y, z:point.z} = sparks[i]);
            if (sparks[i].trail.length) {
                ({x, y, z} = sparks[i].trail[sparks[i].trail.length - 1]);
                dis2 = (point.x - x) * (point.x - x) + (point.y - y) * (point.y - y) + (point.z - z) * (point.z - z);
                if (dis2 > 10) {
                    sparks[i].trail.push(point);
                }
            } else {
                sparks[i].trail.push(point);
            }
            if (sparks[i].trail.length > 9) sparks[i].trail.splice(0, 1);

        } else {
            sparks.splice(i, 1);
        }
    }
    angle = Math.atan2(playerz, playerx);
    dis = Math.sqrt(playerx * playerx + playerz * playerz);
    dis += Math.sin(frames / 150) /1.5;
    delta = Math.sin(frames / 550) / 40;
    playerx = Math.cos(angle + delta) * dis;
    playerz = Math.sin(angle + delta) * dis;
    leftright = PI/2 + angle + delta;
}

把三维的点投影到屏幕

function project(x, y, z) {
    "use strict"
    var angle, d, ua, ub, uc;
    x -= playerx, y -= playery, z -= playerz;
    angle = Math.atan2(z, x);
    d = Math.sqrt(x * x + z * z);
    x = Math.cos(angle - leftright) * d;
    z = Math.sin(angle - leftright) * d;
    var rx1 = -1000, ry1 = 1, rx2 = 1000, ry2 = 1, rx3 = 0, ry3 = 0, rx4 = x, ry4 = z,

    uc = (ry4 - ry3) * (rx2 - rx1) - (rx4 - rx3) * (ry2 - ry1);
    if (!uc) return { x: 0, y: 0, d: -1 };
    ua = ((rx4 - rx3) * (ry1 - ry3) - (ry4 - ry3) * (rx1 - rx3)) / uc;
    ub = ((rx2 - rx1) * (ry1 - ry3) - (ry2 - ry1) * (rx1 - rx3)) / uc;
    
    if (!z) z = .000000001;
    if (ua > 0 && ua < 1 && ub > 0 && ub < 1) {
        return {
            x: cx + (rx1 + ua * (rx2 - rx1)) * scale,
            y: cy + y / z * scale,
            d: Math.sqrt(x * x + y * y + z * z)
        };
    } else {
        return {
            x: cx + (rx1 + ua * (rx2 - rx1)) * scale,
            y: cy + y / z * scale,
            d: -1
        };
    }
}

生成一个礼花

function produceSeed() {
    "use strict"
    var seed = new Object();
    seed.x = -80 + Math.random() * 160;
    seed.z = -80 + Math.random() * 160;
    seed.y = 50;
    seed.vx = .08 - Math.random() * .16;
    seed.vz = .08 - Math.random() * .16;
    seed.vy = -1.6;
    seed.born = frames;
    seeds.push(seed);
}

爆炸

function explode(x, y, z) {
    "use strict"
    var
        t = 50 + parseInt(Math.random() * 150),
        vspark = 1 + Math.random() * 3,
        type = parseInt(Math.random() * 3),
        indexs = new Array(),
        spark, angle1, angle2, boom, v, dis;
    
    indexs[0] = parseInt(Math.random() * 10);
    indexs[1] = parseInt(Math.random() * 10);
    indexs[2] = parseInt(Math.random() * 10);

    for (let i = 1; i <= t; i++) {
        spark = new Object();
        spark.x = x, spark.y = y, spark.z = z;
        angle1 = 2 * PI * Math.random();
        angle2 = 2 * PI * Math.random();
        v = vspark * (1 + Math.random() / 6);
        spark.vx = Math.sin(angle1) * Math.cos(angle2) * v;
        spark.vz = Math.sin(angle1) * Math.sin(angle2) * v;
        spark.vy = Math.cos(angle1) * v;

        spark.img = sparkimgs[indexs[parseInt(Math.random() * (type + 1))]];
        spark.radius = 28 + Math.random() * 25;
        spark.alpha = 1;
        spark.trail = new Array();
        sparks.push(spark);
    }
    boom = new Audio("audios/pow" + (1 + parseInt(Math.random() * 4)) + ".ogg"); 
    dis = Math.sqrt((x - playerx) * (x - playerx) + (y - playery) * (y - playery) + (z - playerz) * (z - playerz));
    boom.volume = 1.5 / (.1 + dis / 10);
    boom.play();
}
  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值