量子破碎能量球

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

最後我把設計方案整理了下,放在人際關係庫裏,想要玩玩個人量子破碎能量球,請!

相關文章
相關標籤/搜索