這是個人工做室,我想要作一個量子破碎能量球,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()
複製代碼