主要内容
矢量运动
所有这些矢量数学的东西听起来都像我们应该知道的东西,但这是为什么?它将如何帮我们写代码?事实上,我们需要有一定的耐心。使用
PVector
类所能达到的的神奇效用还需要时间来显现。在第一次学习新的数据结构时,这实际上是一种常见的情况。例如,当你第一次了解数组时,使用数组似乎比仅仅用变量代替多个事物要复杂得多。但当你需要处理成百上千个东西时,情况就不一样了。
PVector
也是如此。现在看起来更多的工作会在以后得到回报,而且回报相当丰盛。你不必着急,因为你的奖励会在下一章到来。速度
然而,目前我们要把重点放在简单的东西上。使用向量对运动进行编程是什么意思?我们已经在 弹跳球的例子中看到了这一切的初级阶段。屏幕上的对象有一个位置(即它在任何给定的时刻在哪里)以及一个速度(关于它应该如何从一个时刻移动到下一个时刻的说明)。将速度添加到位置:
position.add(velocity);
然后我们在那个位置绘制对象:
ellipse(position.x, position.y, 16, 16);
这是绘制运动物体的新手教程:
- 将速度添加到位置
- 在位置绘制对象
在弹跳球示例中, 所有这些代码都发生在 ProcessingJS 的
draw
函数中。我们现在要做的是将所有的运动逻辑封装在一个 object 内。这样,我们就可以在所有 ProcessingJS 程序中为移动对象编程创建基础。在这种情况下,我们将创建一个通用的 Mover 对象,它将描述在屏幕上移动的物体。因此,我们必须考虑以下两个问题:
- Mover 有哪些数据?
- Mover有哪些功能?
我们的运动新手教程算法可以告诉我们这些问题的答案。
Mover
对象有两个数据:position
和 velocity
。这两个数据都是 PVector
对象。我们可以从编写构造函数开始,该函数可以将这些属性初始化为适当的随机值:var Mover = function() {
this.position = new PVector(random(width), random(height));
this.velocity = new PVector(random(-2, 2), random(-2, 2));
};
它的功能也差不多很简单。Mover 需要移动,也需要被看到。我们将这些需求实现为名为
update()
与 display()
的方法。我们将把所有的运动逻辑代码放在 update()
中,并在 display()
中绘制对象。Mover.prototype.update = function() {
this.position.add(this.velocity);
};
Mover.prototype.display = function() {
stroke(0);
strokeWeight(2);
fill(127);
ellipse(this.position.x, this.position.y, 48, 48);
};
如果面向对象编程对你来说是全新的,那么这里可能会显得有些混乱。毕竟,我们在本章的开头讨论了
PVector
。PVector
对象是用于创建位置对象和速度对象的模板。那么,他们在另一个对象,Mover
中做了什么呢?事实上, 这是非常一般的一件事。对象只是保存数据(和功能)的东西。这些数据可以是数字、字符串、数组或其他对象!在本课程中,我们将一遍又一遍地看到这一点。例如,在粒子教程中,我们将编写一个对象来描述粒子系统(ParticleSystem)。该 ParticleSystem
对象将有一个数组的 Particle
对象...并且每一个 Particle
对象都有一些 PVector
对象!让我们通过合并一个函数来完成
Mover
对象,以确定对象到达窗口边缘时应该做什么。 现在让我们做一些简单的事情,让它环绕边缘:Mover.prototype.checkEdges = function() {
if (this.position.x > width) {
this.position.x = 0;
}
else if (this.position.x < 0) {
this.position.x = width;
}
if (this.position.y > height) {
this.position.y = 0;
}
else if (this.position.y < 0) {
this.position.y = height;
}
};
既然
Mover
对象已经完成,我们可以看一下我们在主程序中需要做什么。 我们首先声明并初始化新的 Mover 实例:var mover = new Mover();
然后我们在
draw
中调用相应的函数:draw = function() {
background(255, 255, 255);
mover.update();
mover.checkEdges();
mover.display();
};
这是完整的运行示例。 尝试改变数字,或者注释掉几行代码,看看会发生什么:
加速度
好。 现在,我们应该有信心说明:(1)什么是
PVector
;(2)我们如何在物体内部使用PVector
来跟踪它的位置和运动。 这是很好的第一步,我们可以给自己鼓鼓掌。 然而,在起立鼓掌和欢呼之前,我们需要再向前迈出一步。 毕竟,观看运动菜鸟教程的例子相当无聊—圆永远不会加速,永不减速,永不拐弯。 对于更有趣的运动,对于出现在我们周围的现实世界中的运动,我们需要在我们的 Mover
对象加速中再添加一个 PVector
。我们在这里使用的加速度的严格定义是:速度的变化率。让我们想想这个定义。这是一个新概念吗?并不是。速度被定义为位置的变化率。实质上,我们正在开发一种 "滴流" 效应。加速度会影响速度,进而影响位置 (说起来,当我们看到力如何影响加速度,从而影响位置时,这一点将变得更加关键)。在代码中,这将表现为:
velocity.add(acceleration);
position.add(velocity);
作为一项练习,从现在开始,让我们为自己制定一个规则。 让我们在其余的这些教程中完成每个示例,而不要触及速度和位置的具体值(初始化它们除外)。 换句话说,我们现在对编写运动物体的目标是:提出一个算法来计算加速度,让涓滴效应发挥其神奇作用。 (事实上,你会找到打破这个规则的理由,但重要的是要说明我们的运动算法背后的原理。)因此我们需要提出一些计算加速度的方法:
- 恒定的加速度
- 完全随机的加速度
- 向鼠标方向的加速度
算法 #1,恒定的加速度,并不是特别有趣,但它是最简单的,它将帮助我们开始将加速度结合到我们的代码中。
我们需要做的第一件事是在 并一直保持该值,因为我们当前的算法是 constant acceleration。 你可能会想,“天哪,这些值看起来非常小!”这是对的,它们非常小。 重要的是要意识到我们的加速度值(以像素为单位)将随时间在速度中累积,大约每秒30次,具体取决于我们的草图的帧速率。 因此,为了将速度矢量的大小保持在合理的范围内,我们的加速度值应该设置并保持在一个很小值。
Mover
构造函数中添加另一个 PVector
属性来表示加速度。 我们将它初始化为 var Mover = function() {
this.position = new PVector(width/2,height/2);
this.velocity = new PVector(0, 0);
this.acceleration = new PVector(-0.001, 0.01);
};
注意上面我们将初始速度设置为0 - 因为我们知道在程序运行时我们会逐渐加速它,这要归功于加速度。 我们将在
update()
方法中执行此操作:Mover.prototype.update = function() {
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
};
由于我们不断提高速度,如果我们让程序运行得足够长,我们就会冒着速度值变得非常大的风险。 我们希望速度有一个最大限制。 我们可以使用
PVector
limit
方法来做到这一点,它会将向量限制在给定的大小。Mover.prototype.update = function() {
this.velocity.add(this.acceleration);
this.velocity.limit(10);
this.position.add(this.velocity);
};
这转化为以下内容:
速度的大小是多少? 如果不到10,不用担心; 只是保持原样。 但是,如果它超过10,则将其减少到10!
速度的大小是多少? 如果不到10,不用担心; 只是保持原样。 但是,如果它超过10,则将其减少到10!
让我们来看看
Mover
对象的变化,完成acceleration
和limit()
:现在转到算法#2,随机的加速度。 在这种情况下,我们不是在对象的构造函数中初始化加速,而是希望在每个循环中选择一个新的加速,即每次调用
update()
。Mover.prototype.update = function() {
this.acceleration = PVector.random2D();
this.velocity.add(this.acceleration);
this.velocity.limit(10);
this.position.add(this.velocity);
};
因为随机向量是归一化的,我们可以尝试用两种不同的方式来缩放它:
- 将加速度用恒定值缩放:
acceleration = PVector.random2D();
acceleration.mult(0.5);
- 将加速度用随机值缩放:
acceleration = PVector.random2D();
acceleration.mult(random(2));
虽然这看起来很明显,但重要的是理解加速度不仅仅指的是移动中物体的 速度增加 或 速度减少 ,而是指在速度或方向上的任何速度变化。 加速用于引导对象,我们将在以后的章节中一次又一次地看到这一点,因为我们开始编写对象,以决定如何在屏幕上移动。
“自然模拟”系列课程是由 Daniel Shiffman 的 "编程的本质" 衍生而来,基于 知识共享 著名-非商用性 3.0 本地化许可协议。