前言
前段时间写了个小demo,要求使用p5绘制一个细胞免疫的流程,因为是临时帮的忙只有一天多的开发时间,但是基本的流程都实现了,记录一下。
准备工作
vscode初始化p5项目
p5.js 是一个JavaScript的函数库,它对canvas 2D绘图进行了比较方便的封装,使得不怎么熟悉这方面的人也能进行可视化开发,p5有线上开发环境:https://editor.p5js.org/。在本地使用安装p5直接使用node命令即可:
npm install p5js
因为我已经有了改库的源文件,所以我的项目的文件目录结构如下:
index.html -- 页面入口
p5.js -- 依赖文件1
p5.sound.min.js -- 依赖文件2
sketch.js -- 自己写的代码
style.css -- 样式文件
本地运行项目
本地运行p5推荐两个方法,我采用第二个
- 通过安装并运行http-server本地启动浏览器
- vscode 安装插件Go-Live,直接点击即可在浏览器自动启动
免疫过程
接下来是主逻辑的书写,官网api参考:https://p5js.org/reference/,细胞免疫过程分为五步来写,没有开发按钮,通过点击屏幕进行下一步动画,整体过程还是比较麻烦的,附主要代码。
- 绘制简要的细胞:细胞壁、红细胞、内部细胞
- 病毒出现,红细胞向病毒靠拢
- 释放信号细胞传递信息给内部细胞
- 内部细胞与信号细胞结合,生成免疫细胞
- 免疫细胞攻击病毒,病毒消失
1 细胞结构
将canvas的尺寸定为500*500大小,画一条线简单代表细胞壁
// 绘制细胞壁
bezier(
0, 150,
200,170,
250,350,
300,500
);
// 红细胞类
class RedCeil {
constructor(startX, startY) {
this.x = startX;
this.y = startY;
}
update(x, y) {
//movement
this.x += x;
this.y += y
}
display() {
push();
translate(this.x, this.y);
noStroke();
fill('red');
ellipseMode(CENTER);
circle(0, 0, 15);
fill(230, 10, 80)
pop();
}
}
2 病毒出现
// 添加病毒类需要使用mousePressed方法获取点击位置
function mousePressed() {
//···
if (hasVirus) return
//···
// 添加病毒
let v = new Virus(mouseX, mouseY);
// console.log([mouseX, mouseY], v)
hasVirus = true
virusPosition.push(mouseX, mouseY)
virusList.push(v);
3 信号细胞运动
// 添加病毒类需要使用mousePressed方法获取点击位置
function mousePressed() {
//···
if (hasVirus) return
// ···
// 提前生成信号细胞的点位,在病毒周边
signCellPosition.push(
[mouseX,mouseY-15],
[mouseX-15,mouseY+10],
[mouseX+15,mouseY-10],
[mouseX+15,mouseY+15],
[mouseX-15,mouseY-15],
)
signCellPosition.forEach((item) => {
distanceXYB.push([BCellPosotionList[0][0] - item[0] , BCellPosotionList[0][1] - item[1] ])
})
4 出现免疫细胞
// 抗体细胞展示--抗体细胞的生命周期
if (antiList.length) { // 病毒展示
for (let i = 0; i < antiList.length; i++) {
antiList[i].display()
if ((step==4) && (!inScoped(antiList[i].x, antiList[i].y, virusList[0].x, virusList[0].y, 10))){
antiList[i].update(distanceXYANTI[i][0] * Math.random() / 100, distanceXYANTI[i][1] * Math.random()/ 100);
}
}
// ···
// 停止运动的时机
function inScoped(x, y, x0, y0, limit) {
let lx = x - x0
let ly = y - y0
let distance = Math.pow(lx * lx + ly * ly, .5)
return distance < limit
}
5 病毒被消灭
// 死亡的病毒 随机生成点
class DeadVirus {
constructor(startX, startY) {
this.x = startX;
this.y = startY;
}
display() {
push();
translate(this.x, this.y);
noStroke();
stroke('blue')
point(0, 0);
fill(230, 10, 80)
pop();
}
// 随机点模拟死亡病毒
for (let i=0; i<50; i++){
let dv = new DeadVirus((Math.random()*2-1) * 10 + virusPosition[0], (Math.random()*2-1) * 10 + virusPosition[1] )
pointList.push(dv)
}
总结
因为开发时间有限,整体效果比较简陋。动画效果的关键是获取起始位置到目标位置的相对距离,在到达目标位置之前,在动画函数中都判断下是否到达了目标附近,若未到达了就继续运动,否则结束运动。