使用p5.js画出动画滑稽碰撞图

使用p5.js做了一个动态碰撞的滑稽笑脸,效果如图
使用p5.js画出动画滑稽碰撞图首先是定义了一个画滑稽的函数,其中参数分别是滑稽的横纵坐标、大小以及眼珠的朝向,L可取-1和1两个值,-1向右,1向左。脸用了两个填充的圆重叠(也可一个圆加边框)。嘴和眼睛以及眉毛是分别画两个圆弧相重叠而成。

function huaji(x,y,size,l){
	noStroke();
	fill(237,148,14);
	ellipse(x,y,1.06*size);
        fill(252,222,12);
        ellipse(x,y,size);//脸
	
	fill(173,91,10);
	arc(x, y, 0.8*size, 0.8*size, 0, PI, CHORD);
	fill(252,222,12);
	arc(x, y-0.01*size, 0.8*size, 0.7*size, 0, PI, CHORD);//笑
	
	fill(254,240,205);
	arc(x-0.25*size, y, 0.8*size, 0.6*size, 1.3*PI,1.7* PI, PIE);
	fill(252,222,12);
	arc(x-0.25*size, y, 0.8*size, 0.5*size, 1.3*PI,1.7* PI, PIE);
	
	
	fill(254,240,205);
	arc(x+0.25*size, y, 0.8*size, 0.6*size, 1.3*PI,1.7* PI, PIE);
	fill(252,222,12);
	arc(x+0.25*size, y, 0.8*size, 0.5*size, 1.3*PI,1.7* PI, PIE);//眼壳子
	if(l==-1){
	    fill(173,91,10);
  	    ellipse(x-0.34*size,y-0.28*size,0.1*size);
	    ellipse(x+0.16*size,y-0.28*size,0.1*size);//眼珠
	}
       if(l==1){
	    fill(173,91,10);
  	    ellipse(x-0.1*size,y-0.28*size,0.1*size);
	    ellipse(x+0.35*size,y-0.28*size,0.1*size);//眼珠
	}
	
	fill(58,43,1);
	arc(x-0.28*size, y-0.35*size,0.20*size, 0.20*size, 1.1*PI,2.1* PI, OPEN);
	fill(252,222,12);
	arc(x-0.28*size, y-0.35*size, 0.21*size, 0.16*size, 1*PI,2.2* PI, OPEN);
	
	fill(58,43,1);
	arc(x+0.28*size, y-0.35*size,0.20*size,0.20*size, 0.9*PI,1.9* PI, CHORD);
	fill(252,222,12);
	arc(x+0.28*size, y-0.35*size, 0.21*size, 0.15*size, 0.9*PI,1.9* PI, CHORD);//眉毛

	
	fill(241,74,56);
	ellipse(x-0.25*size,y-0.18*size,0.25*size,0.07*size);
	ellipse(x+0.25*size,y-0.18*size,0.25*size,0.07*size);
}

接下来是碰撞,

var numBalls = 10;
var spring = 0.05;
var gravity = 0.03;
var friction = -0.9;
var balls = [];


function setup() {
  createCanvas(720, 400);
  for (var i = 0; i < numBalls; i++) {
    balls[i] = new Ball(
      random(width),
      random(height),
      random(30, 100),
      i,
      balls
    );
  }
  noStroke();
  fill(255, 204);
}

function draw() {
  background(0);
  balls.forEach(ball => {
    ball.collide();
    ball.move();
    ball.display();
  });
}



以上可以看出,定义了一些变量以及初始化了画布,并初始化了十个ball。
对每一个ball执行碰撞、移动和展示。我们接下来看看这三个函数分别是怎样的。
首先,第一个函数colide中,对每一个球求距离并与两球半径和进行比较,当小于两球半径之和的时候,证明两球相撞了,求出加速度,对两球速度进行改变。
第二个函数move中,如果球撞到墙,将其速度*-0.9,即反向并略减少速度,而且每次都有个向下的加速度。所以后来的滑稽会慢慢停下。在前两个函数中都对m进行取反的操作,即可每次碰撞都变化眼珠的朝向,而当速度非常小的时候,将m的值固定。
第三个函数display则是调用滑稽函数。

function Ball(xin, yin, din, idin, oin) {
  this.x = xin;
  this.y = yin;
  var vx = 0;
  var vy = 0;
  var m=1;
  this.diameter = din;
  this.id = idin;
  this.others = oin;

  this.collide = function() {
    for (var i = this.id + 1; i < numBalls; i++) {
      var dx = this.others[i].x - this.x;
      var dy = this.others[i].y - this.y;
      var distance = sqrt(dx * dx + dy * dy);
      var minDist = this.others[i].diameter / 2 + this.diameter / 2;
      if (distance < minDist) {
        var angle = atan2(dy, dx);
        var targetX = this.x + cos(angle) * minDist;
        var targetY = this.y + sin(angle) * minDist;
        var ax = (targetX - this.others[i].x) * spring;
        var ay = (targetY - this.others[i].y) * spring;
        vx -= ax;
        vy -= ay;
        this.others[i].vx += ax;
        this.others[i].vy += ay;
	m*=-1;
      }
    }
  };

  this.move = function() {
    vy += gravity;
    this.x += vx;
    this.y += vy;
    if (this.x + this.diameter / 2 > width) {
      this.x = width - this.diameter / 2;
      vx *= friction;
			m*=-1;
    } else if (this.x - this.diameter / 2 < 0) {
      this.x = this.diameter / 2;
      vx *= friction;
			m*=-1;
    }
    if (this.y + this.diameter / 2 > height) {
      this.y = height - this.diameter / 2;
      vy *= friction;
			m*=-1;
    } else if (this.y - this.diameter / 2 < 0) {
      this.y = this.diameter / 2;
      vy *= friction;
			m*=-1;
    }
		if((vx<0.1&&vx>-0.1)&&(vy<=0.1&&vy>-0.1)){
			 m=-1;}
  };

  this.display = function() {
    huaji(this.x, this.y, this.diameter, m);
  };
}

运行效果:https://editor.p5js.org/weihao/sketches/SyvVYveam

参考文献:p5.js官方文档
https://p5js.org/zh-Hans/examples/motion-bouncy-bubbles.html