一、簡述
大家在用Canvas實(shí)現(xiàn)球體碰撞的動(dòng)畫場景時(shí)是否感覺無從下手,或者是知道怎么去實(shí)現(xiàn),但是要進(jìn)行大量的計(jì)算,考慮非常多的細(xì)節(jié),比如要考慮將外力分解為水平和垂直兩個(gè)方向,然后考慮各種碰撞的檢測,發(fā)生重疊后的修正,還有可能考慮引力方向和大小、透明度漸變等等這些,思考這些個(gè)計(jì)算邏輯時(shí)真的很讓人頭大,今天我就和大家分享一個(gè)非常好用的庫,一個(gè)專門用于在Canvas上實(shí)現(xiàn)球體碰撞和球體交互場景的庫--sphere-collision,通過簡單配置便可快速實(shí)現(xiàn)碰撞效果,而且還提供了鉤子函數(shù),擴(kuò)展性極強(qiáng),讓你實(shí)現(xiàn)想要的效果。
二、案例展示
下面展示幾個(gè)案例的實(shí)現(xiàn)效果,這些都是使用這個(gè)庫實(shí)現(xiàn)的動(dòng)畫效果,只要涉及到球體碰撞或球體交互的場景都可以使用這個(gè)庫。
1. 探照燈效果
2. 球體碰撞交互效果
3. 消滅行星小游戲
4. 各類球體自由落體交互效果
5. 炫酷倒計(jì)時(shí)動(dòng)畫
以上案例源碼均在visualization-collection開源項(xiàng)目中,github地址:https://github.com/hepengwei/visualization-collection
三、基本用法
安裝
npm i sphere-collision
使用
import SphereCollision from "sphere-collision";
const canvasWidth = 600; // 畫布寬度
const canvasHeight = 600; // 畫布高度
const globuleRadius = 60; // 球半徑
const beforeDrawGlobules = (sphereCollision) => {
const { ctx } = sphereCollision;
// 繪制整個(gè)畫布的背景色
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.beginPath();
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
};
const canvas = document.getElementById("myCanvas");
if (canvas) {
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const ctx = canvas.getContext("2d");
// 實(shí)例化SphereCollision對(duì)象
const sphereCollision = new SphereCollision(
ctx,
canvas,
[
{
initX: 400,
initY: 400,
vx: 6,
vy: 3,
radius: globuleRadius,
},
],
{ beforeDrawGlobules }
);
// 開使執(zhí)行動(dòng)畫
sphereCollision.start();
}
四、API文檔
SphereCollisionC
參數(shù)(順序從上往下依次傳入) | 數(shù)據(jù)類型 | 說明 | 默認(rèn)值 | 是否必需 |
---|---|---|---|---|
ctx | CanvasRenderingContext2D | Context對(duì)象 | null | 是 |
canvas | HTMLCanvasElement | Canvas對(duì)象 | null | 是 |
globuleOptionsList | GlobuleOptions[] | 初始化球體的配置列表 | [] | 否 |
options | SphereCollisionOptions | 配置參數(shù) | SphereCollisionOptions | 否 |
屬性 | ||||
ctx | CanvasRenderingContext2D | Context對(duì)象 | ||
canvas | HTMLCanvasElement | Canvas對(duì)象 | ||
frameId | number | requestAnimationFrame方法返回的ID | ||
globuleList | GlobuleC[] | 保存所有球體實(shí)例的列表 | ||
animationState | enum AnimationState{"waitStart","inAnimation","stop"} | 當(dāng)前動(dòng)畫狀態(tài) | ||
mousePos | MousePos | 當(dāng)前鼠標(biāo)在Canvas中的位置坐標(biāo) | ||
prevMousePos(v1.1.0新增) | MousePos | 前一次鼠標(biāo)在Canvas中的位置坐標(biāo) | ||
isMouseDown(v1.1.0新增) | boolean | 當(dāng)前鼠標(biāo)在Canvas中是否按下 | ||
mouseDownPos(v1.1.0新增) | MousePos | 鼠標(biāo)在Canvas中按下時(shí)的位置坐標(biāo) | ||
mouseInGlobuleList(v1.1.0新增) | GlobuleC[] | 當(dāng)前鼠標(biāo)所在球體中的球體實(shí)例列表 | ||
方法 | ||||
start | () => void | 必須調(diào)用該方法,Canvas才渲染,開始執(zhí)行相關(guān)動(dòng)畫 | ||
createGlobule | (globuleOptions: GlobuleOptions) => Globule | 創(chuàng)建球體實(shí)例的方法 | ||
updateGlobuleList | (newGlobuleList: GlobuleC[]) => void | 更新球體實(shí)例列表,用于動(dòng)態(tài)增加或減少球體實(shí)例 | ||
stop | () => void | 停止整個(gè)frame動(dòng)畫 |
GlobuleOptions
屬性 | 數(shù)據(jù)類型 | 說明 | 默認(rèn)值 | 是否必需 |
---|---|---|---|---|
id | any | 任意值,可存放數(shù)據(jù) | 否 | |
initX | number | 初始x坐標(biāo)(向右為正方向) | 0 | 否 |
initY | number | 初始y坐標(biāo)(向下為正方向) | 0 | 否 |
vx | number | 在水平方向的速度(向右為正方向) | 0 | 否 |
vy | number | 在垂直方向的速度(向下為正方向) | 0 | 否 |
radius | number | 半徑 | 10 | 否 |
color | string | 顏色 | "#666666" | 否 |
isPureColor(v1.1.3新增) | boolean | 是否為純色 | false | 否 |
alpha | number | 透明度 | 1 | 否 |
alphaChangeV | number | 透明度改變的速度(正數(shù)增加,負(fù)數(shù)減?。?/td> | 0 | 否 |
bgImg | string | 背景圖片 | "" | 否 |
collisionLossV | number | 碰撞時(shí)的速度損失 | 0 | 否 |
moveLossV | number | 移動(dòng)時(shí)的速度損失 | 0 | 否 |
gDirection | "toInit" | "toBottom" | "toTop" | "toLeft" | "toRight" | 引力方向。"toInit",指向球體的初始位置;"toBottom",指向正下方;"toTop",指向正上方;"toLeft",指向正左方;"toRight",指向正右方(后四個(gè)為v1.1.0新增) | 否 | |
gCoefficient | number | 引力系數(shù) | 0 | 否 |
requiredMouseInteraction | boolean | 是否需要鼠標(biāo)交互 | false | 否 |
mouseInteractionBehavior(v1.1.0新增) | "over" | "drag" | 鼠標(biāo)交互行為。"over",鼠標(biāo)穿過;"drag",鼠標(biāo)拖拽 | 當(dāng)requiredMouseInteraction為true時(shí),默認(rèn)值為"over",當(dāng)為false時(shí),則沒有默認(rèn)值 | 否 |
fixedPos | boolean | 是否固定位置 | false | 否 |
receiveOutForce | boolean | 是否接受外力 | true | 否 |
receiveWallForce | boolean | 是否接受墻的力(與墻體發(fā)生碰撞) | true | 否 |
resistanceWallDirection(v1.1.3新增) | Direction[] | 有阻力的墻的方向 | ["bottom", "top", "left", "right"] | 否 |
onlyCheckCollision | boolean | 當(dāng)不接受外力時(shí),是否檢測碰撞(檢測碰撞相關(guān)狀態(tài)但不獲取外力) | false | 否 |
perfectlyElasticCollision(v1.1.10 新增) | boolean | 是否完全彈性碰撞 | false | 否 |
maxMouseOutForce | number | 鼠標(biāo)交互時(shí)能提供的最大力限制 | null | 否 |
maxMoveV | number | 最大移動(dòng)速度 | null | 否 |
beforeDrawGlobule | (globule: GlobuleC) => void | 每一幀繪制該球體之前執(zhí)行的鉤子函數(shù) | null | 否 |
afterDrawGlobule | (globule: GlobuleC) => void | 每一幀繪制該球體之后執(zhí)行的鉤子函數(shù) | null | 否 |
afterCalculateNextFrameGlobule | (nextFrameGlobule: GlobuleC) => void | 每一幀繪制球體后,計(jì)算并修改為下一幀狀態(tài)后執(zhí)行的鉤子函數(shù) | null | 否 |
SphereCollisionOptions
屬性 | 數(shù)據(jù)類型 | 說明 | ||
---|---|---|---|---|
collisionRectX | number | 球體發(fā)生碰撞的矩形區(qū)域的左上角x坐標(biāo)(向右為正方向) | 0 | 否 |
collisionRectY | number | 球體發(fā)生碰撞的矩形區(qū)域的左上角y坐標(biāo)(向下為正方向) | 0 | 否 |
collisionRectWidth | number | 球體發(fā)生碰撞的矩形區(qū)域的寬度 | canvas.width | 否 |
collisionRectHeight | number | 球體發(fā)生碰撞的矩形區(qū)域的高度 | canvas.height | 否 |
monitorMousePos | boolean | 是否監(jiān)聽鼠標(biāo)的位置 | false | 否 |
beforeDrawGlobules | (sphereCollision: SphereCollisionC) => void | 每一幀繪制所有球體之前執(zhí)行的鉤子函數(shù) | null | 否 |
afterDrawGlobules | (sphereCollision: SphereCollisionC) => void | 每一幀繪制所有球體之后執(zhí)行的鉤子函數(shù) | null | 否 |
onMouseDownCanvas(v1.1.0新增) | (event: MouseEvent, sphereCollision: SphereCollisionC) => void | 鼠標(biāo)按下時(shí)執(zhí)行的鉤子函數(shù) | null | 否 |
onMouseMoveCanvas(v1.1.0新增) | (event: MouseEvent, sphereCollision: SphereCollisionC) => void | 鼠標(biāo)移動(dòng)時(shí)執(zhí)行的鉤子函數(shù) | null | 否 |
onMouseUpCanvas(v1.1.0新增) | (event: MouseEvent, sphereCollision: SphereCollisionC) => void | 鼠標(biāo)松開時(shí)執(zhí)行的鉤子函數(shù) | null | 否 |
onMouseLeaveCanvas(v1.1.0新增) | (event: MouseEvent, sphereCollision: SphereCollisionC) => void | 鼠標(biāo)離開時(shí)執(zhí)行的鉤子函數(shù) | null | 否 |
MousePos
屬性 | 數(shù)據(jù)類型 | 說明 |
---|---|---|
mouseX | number | null | 鼠標(biāo)在Canvas中的x坐標(biāo)(向右為正方向) |
mouseY | number | null | 鼠標(biāo)在Canvas中的y坐標(biāo)(向下為正方向) |
Direction
屬性 | 數(shù)據(jù)類型 | 說明 |
---|---|---|
Direction | "bottom" | "top" | "left" | "right" | 方向 |
GlobuleC
屬性 | 數(shù)據(jù)類型 | 說明 |
---|---|---|
id | any | 任意值,可存放數(shù)據(jù) |
ctx | CanvasRenderingContext2D | Context對(duì)象 |
canvas | HTMLCanvasElement | Canvas對(duì)象 |
requiredMouseInteraction | boolean | 是否需要鼠標(biāo)交互 |
mouseInteractionBehavior(v1.1.0新增) | "over" | "drag" | 鼠標(biāo)交互行為。"over",鼠標(biāo)穿過;"drag",鼠標(biāo)拖拽 |
maxMouseOutForce | number | null | 鼠標(biāo)交互時(shí)能提供的最大力限制 |
initX | number | 初始x坐標(biāo)(向右為正方向) |
initY | number | 初始y坐標(biāo)(向下為正方向) |
x | number | x坐標(biāo)(向右為正方向) |
y | number | y坐標(biāo)(向下為正方向) |
previousX(v1.1.8 新增) | number | null | 前一幀 x 坐標(biāo)(向右為正方向) |
previousY(v1.1.8 新增) | number | null | 前一幀 y 坐標(biāo)(向右為正方向) |
vx | number | 在水平方向的速度(向右為正方向) |
vy | number | 在垂直方向的速度(向下為正方向) |
radius | number | 半徑 |
color | string | 顏色 |
isPureColor(v1.1.3新增) | boolean | 是否為純色 |
alpha | number | 透明度 |
alphaChangeV | number | 透明度改變的速度(正數(shù)增加,負(fù)數(shù)減?。?/td> |
bgImg | string | 背景圖片 |
collisionLossV | number | 碰撞時(shí)的速度損失 |
moveLossV | number | 移動(dòng)時(shí)的速度損失 |
gDirection | "toInit" | "toBottom" | "toTop" | "toLeft" | "toRight" | 引力方向。"toInit",指向球體的初始位置;"toBottom",指向正下方;"toTop",指向正上方;"toLeft",指向正左方;"toRight",指向正右方(后四個(gè)為v1.1.0新增) |
gCoefficient | number | 引力系數(shù) |
fixedPos | boolean | 是否固定位置 |
receiveOutForce | boolean | 是否接受外力 |
receiveWallForce | boolean | 是否接受墻的力(與墻體發(fā)生碰撞) |
resistanceWallDirection(v1.1.3新增) | Direction[] | 有阻力的墻的方向 |
onlyCheckCollision | boolean | 當(dāng)不接受外力時(shí),是否檢測碰撞(檢測碰撞相關(guān)狀態(tài)但不獲取外力) |
perfectlyElasticCollision(v1.1.10 新增) | boolean | 是否完全彈性碰撞 |
mousePos | MousePos | 鼠標(biāo)相對(duì)于canvas的位置坐標(biāo) |
maxMoveV | number | null | 最大移動(dòng)速度 |
controlledByMouse(v1.1.0新增) | boolean | 當(dāng)前是否受鼠標(biāo)控制(被鼠標(biāo)拖拽) |
inCollisionGlobule | boolean | 是否與其他球體發(fā)生碰撞的狀態(tài) |
inCollisionGlobuleList | GlobuleC[] | 與其他球體碰撞的其他球體實(shí)例列表 |
inCollisionWall | boolean | 是否與墻體發(fā)生碰撞的狀態(tài) |
beforeDrawGlobule | (globule: GlobuleC) => void | null | 每一幀繪制該球體之前執(zhí)行的鉤子函數(shù) |
afterDrawGlobule | (globule: GlobuleC) => void | null | 每一幀繪制該球體之后執(zhí)行的鉤子函數(shù) |
afterCalculateNextFrameGlobule | (nextFrameGlobule: GlobuleC) => void | null | 每一幀繪制球體后,計(jì)算并修改為下一幀狀態(tài)后執(zhí)行的鉤子函數(shù) |
方法 | ||
addOutForce | (outForceVX: number, outForceVY: number, isCollision?:boolean = false) => void | 添加外力。用于動(dòng)態(tài)增加或減小球體的速度 |
五、特別說明
- 球體的fixedPos屬性為true時(shí), 獲取不了外力,所以球體不會(huì)移動(dòng),但還是會(huì)使其他球體受到外力,進(jìn)行反彈。
- 球體的receiveOutForce屬性為false時(shí),獲取不了鼠標(biāo)穿過和其他球體碰撞產(chǎn)生的外力,即使另一個(gè)球體的receiveOutForce屬性為true。
- 球體的receiveWallForce屬性為false時(shí),獲取不了來自墻體的外力,所以不會(huì)與墻體發(fā)生碰撞。
- 球體的beforeDrawGlobule和afterDrawGlobule兩個(gè)鉤子函數(shù),主要作用是在繪制球體之前和之后分別去繪制其他元素,而afterCalculateNextFrameGlobule鉤子函數(shù)是用于做一些其他的邏輯判斷,因?yàn)橹挥羞@個(gè)函數(shù)里拿到的球體實(shí)例是帶有是否發(fā)生碰撞等相關(guān)信息的
- 如果實(shí)例化SphereCollision對(duì)象時(shí)傳入了beforeDrawGlobules鉤子函數(shù),并且需要在每一幀繪制前要清除整個(gè)畫布,則需要使用者自己清除,這是考慮到有些不需要清除的場景。
- 如果要獲取鼠標(biāo)相關(guān)的信息或者想要實(shí)現(xiàn)鼠標(biāo)交互,實(shí)例化SphereCollision對(duì)象時(shí)就必須在第四個(gè)參數(shù)options中傳入monitorMousePos為true。
- 如果給球體配置了 gDirection 和 gCoefficient 屬性,建議就不要配置 collisionLossV 了。
- 非完全彈性碰撞會(huì)有速度的抵消,可能碰撞后的 x 軸或 y 軸方向上的速度相互抵消為 0,球體可能會(huì)停下來;完全彈性碰撞則不會(huì)有速度的抵消,速度大小不會(huì)改變(速度為 0 除外,速度為 0 的,碰撞后會(huì)和撞它的另一球的速度大小一致),碰撞后僅改變方向; 在碰撞時(shí)如果其中有一個(gè)球體的 perfectlyElasticCollision 為 true,則兩球均使用完全彈性碰撞算法進(jìn)行計(jì)算。
- 如果需要的話,可以在任何時(shí)候動(dòng)態(tài)地修改球體實(shí)例的所有屬性值,以滿足自己的自定義需求。
六、關(guān)鍵版本更新日志
-
v1.1.10
- 新增支持球體完全彈性碰撞
- 優(yōu)化代碼
-
v1.1.8
- 優(yōu)化碰撞算法
- 解決一些已知bug
-
v1.1.3
- 新增支持純色球體
- 新增支持四面墻中只有部分墻面有阻力,與球體發(fā)生碰撞
- 解決一些已知bug
-
v1.1.0
- 考慮后續(xù)的功能擴(kuò)展,修改了SphereCollision類的參數(shù),將之前的第四個(gè)及之后的參數(shù)都統(tǒng)一放到options對(duì)象中,并作為第四個(gè)參數(shù)傳入。
- 新增支持上下左右四個(gè)引力方向
- 新增支持球體進(jìn)行鼠標(biāo)拖拽交互
- 新增的相關(guān)屬性及方法可自行查閱API文檔
更多個(gè)人文章