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();
}