这是个人工做室,我想要作一个量子破碎能量球,html
const breakBall = {}
复制代码
先设定好球的半径,git
const ballRadius = 60
复制代码
我须要1000块碎片,github
const chipLen = 1000
复制代码
准备个大盒子放碎片,省得不当心丢了,canvas
const chips = []
复制代码
先来设计碎片,量子破碎给个人是感受是「尖锐」的,我打算把碎片作成随机的三角形:bash
由于三点肯定一个圆,因此能够用三个角度来表示一个碎片。但为了防止随机的角度太小,加了些固定数值用来限制,dom
function rdmVertexDeg() {
return [(0 / 3 + Math.random() / 1.5) * Math.PI,
(2 / 3 + Math.random() / 1.5) * Math.PI,
(4 / 3 + Math.random() / 1.5) * Math.PI]
}
复制代码
碎片大小由三点圆的半径决定,且须要在必定的范围内,函数
function rdmSize() {
return Math.random() * 20
}
复制代码
要让这些碎片组成球状,能够用三角函数的知识,将它们排列起来,ui
function rdmPos2Ball() {
// 用与球中心的距离、角度肯定碎片的位置及碎片朝向
return {
dist: Math.random() * ballRadius,
deg: Math.random() * Math.PI * 2,
selfDeg: Math.random() * Math.PI * 2
}
}
复制代码
粒子老是保持运动的,我须要让每一个碎片动起来。粒子会受到引力围绕球心运动,且自身也会作自转的运动,就像地球和太阳同样,spa
function rdmRotateDeg() {
// 随机生成碎片公转和自转的速度,用很小的角度值表示
return {
perDeg: Math.PI * 2 * ( 1 - 2* Math.random()) * 0.008,
perSelfDeg: Math.PI * 2 * ( 1 - 2* Math.random()) * 0.01
}
}
复制代码
给碎片准备颜料。球颜色的话我打算用「灰色」为底色,「深灰色」为装饰,「淡蓝色」为主色。为了凸显主色,总体带点透明的科幻感。颜色的比值设为 15:10:5,赶忙先记下来,设计
const colorData =
[{ occur: 5, alpha: 0.8, style: 'hsl(178, 100%, 60%)' },
{ occur: 10, alpha: 0.8, style: '#222' },
{ occur: 15, alpha: 0.8, style: '#555' }];
复制代码
作一个随机吐色墨喷水管,
const colors = []
colorData.forEach(color => {
for (let i = 0; i < color.occur; i++) {
colors.push(color);
}
});
function rdmColor() {
return colors[colors.length * Math.random() | 0];
}
复制代码
好啦,设计好了,开始制做碎片,
for(let i = 0; i < chipLen; i++) {
const chip = {
color: rdmColor(),
size: rdmSize() * (i / chipLen), // 我但愿随机碎片有从小到大的趋势
vertexDeg: rdmVertexDeg(),
pos2Ball: rdmPos2Ball(),
rotate: rdmRotateDeg()
}
chips.push(chip);
}
复制代码
碎片作好了,我准备把它放到暗黑森林里,因此要申请使用那的入口,
// html
<canvas id="darkForest" style="backgroud:#000"></canvas>
const darkForest = document.getElementById('darkForest')
// 我须要整个森林的使用权
darkForest.width = window.innerWidth
darkForest.height = window.innerHeight
const entry = darkForest.getContext('2d')
复制代码
能量球须要能量来运行它,因此我得买个能量鼠,做为球的引擎。我选了个无限版本,它无时无刻释放出能量,
const mouseEngine = buyAMouseEngine('mousemove');
复制代码
看了下能量鼠的原理,
function buyAMouseEngine(type) {
return {
config: function (action) {
window.addEventListener(type, action);
}
}
}
复制代码
能量鼠的位置做为球的中心点,配置一下,
const mousePos = []
mouseEngine.config((e) => {
mousePos[0] = e.clientX;
mousePos[1] = e.clientY;
})
// 一开始我但愿它在森林的中心
mousePos[0] = darkForest.width / 2
mousePos[1] = darkForest.height / 2
复制代码
想在黑暗森林里存在,必须提供一份运动轨迹说明,
const explain = function() {
// 为了准确描述,先重置下申请的入口
entry.clearRect(0, 0, darkForest.width, darkForest.height)
// 每一个碎片都须要描述一遍
chips.forEach((chip, idx) => {
entry.beginPath();
entry.globalAlpha = chip.color.alpha;
entry.fillStyle = chip.color.style;
// 计算碎片的坐标
const pos = []
chip.pos2Ball.deg += chip.rotate.perDeg;
pos[0] = Math.cos(chip.pos2Ball.deg) * chip.pos2Ball.dist * (idx/chips.length);
pos[1] = Math.sin(chip.pos2Ball.deg) * chip.pos2Ball.dist * (idx/chips.length);
// 计算碎片方向
chip.pos2Ball.selfDeg += chip.rotate.perSelfDeg;
// 球的总体描述
entry.moveTo(
mousePos[0] + pos[0] + Math.cos(chip.vertexDeg[0] + chip.pos2Ball.selfDeg) * chip.size,
mousePos[1] + pos[1] + Math.sin(chip.vertexDeg[0]+ chip.pos2Ball.selfDeg) * chip.size)
entry.lineTo(
mousePos[0] + pos[0] + Math.cos(chip.vertexDeg[1]+ chip.pos2Ball.selfDeg) * chip.size,
mousePos[1] + pos[1] + Math.sin(chip.vertexDeg[1]+ chip.pos2Ball.selfDeg) * chip.size)
entry.lineTo(
mousePos[0] + pos[0] + Math.cos(chip.vertexDeg[2]+ chip.pos2Ball.selfDeg) * chip.size,
mousePos[1] + pos[1] + Math.sin(chip.vertexDeg[2]+ chip.pos2Ball.selfDeg) * chip.size)
entry.closePath();
entry.fill();
})
requestAnimationFrame(explain);
}
复制代码
提交说明,
explain()
复制代码
先加个能量鼠移动时的运动轨迹存储器,
const mouseStack = {}
// 轨迹数据
mouseStack.data = new Array(100).fill(null).map(() => [...mousePos]);
// 随机因子
mouseStack.factor = 0
复制代码
随机获轨迹存储器坐标,
function getMouseStack(pos) {
const stackLen = mouseStack.data.length
return mouseStack.data[(stackLen +
(mouseStack.factor - pos * stackLen) | 0) % stackLen]
}
复制代码
更新下说明explain,从新提交给黑暗森林,
// 在森林里,须要点燃能量鼠存储器的随机因子
mouseStack.factor++;
// 把此刻能量鼠的位置写入存储器
const stack = getMouseStack(0);
stack[0] = mousePos[0];
stack[1] = mousePos[1];
复制代码
将碎片随机排列到运动轨迹上,
const rdmStack = getMouseStack(1 - idx / chips.length);
mousePos[0] = rdmStack[0];
mousePos[1] = rdmStack[1];
复制代码
从新提交说明,
explain()
复制代码