跳到主要内容

第二章 准备工作

安装p5.js

开使使用p5.js和JavaScript有好几种方式,其中一种是进入p5.js官网并在系统中下载p5.js的源代码(见图 2-1)。

在写本教程时,下载页中有一个名为p5.js complete的链接,其中包含p5.js库以及一个示例项目。下载压缩包,可在其中看到一个empty-example文件夹。该文件夹中有两个文件:sketch.js文件用于编写JavaScript代码,index.html用于在Chrome等浏览器中打开并显示sketch.js文件中的JavaScript代码运行结果。还可通过作者的GitHub仓库获取相同的代码。

虽然可通过像NotePad这样的普通文本编辑器来修改sketch.js文件中的内容,但建议使用Sublime Text等代码编辑器来进行修改。

代码编辑器与Notepad或Word这样的文本编辑器非常相似,但它有着易于编码的特殊功能,如对给定编程语言的特殊词语进行语法高亮显示,我们这里的编程语言是JavaScript。Sublime Text是你可以使用的一种代码编辑器,可下载并免费试用。

或许入门p5.js最简单的方式是在线编辑器。在线编辑器可在浏览器中使用,并且不需要在系统中进行任何安装。我在学习时很喜欢这种方式,因为入门很轻松。

在准备本书时一款易于使用的在线代码编辑器可通过如下链接获取:

p5.js在线编辑器

如果出于某种原因无法访问以上链接,也可以尝试使用我的Codepen账号中托管的p5.js模板:Codepen - p5.js简单模板CodePen是一个社交开发平台,这里可以在浏览器中编写代码并分享给其它开发人员。它是一个很棒的开发和测试环境。Codepen和上述所提到的p5.js编辑器的区别在于p5.js编辑器中仅能运行p5.js相关的代码,而Codepen上可以运行任意前端代码。

p5.js官网下载源代码

图2-1. 下载p5.js 源码的网页

p5.js在线编辑器

图2-2. p5.js在线编辑器

在线编辑器的使用方法是,在我们写好代码要运行时点击页面上方的播放按钮键。按下按钮后将在右侧的窗口显示代码运行结果。Codepen的在线编辑器略有不同,只要做任何修改它都会自动执行。现在按下播放按钮不会看到什么效果,因为我们还没有编码在屏幕上画任何形状。此时生成的是一个空的画面,但我们也看到编辑器里是有代码的,这段代码我几乎在所有的p5.js程序中都会使用到,为读者方便以下是这段代码(示例2-1)。

示例2-1. p5.js默认代码

function setup() {
createCanvas(400, 400);
}

function draw() {
background(220);
}

我们先删除这段代码。在使用p5.js学习JavaScript之前,我们先来了解一些JavaScript的基础。通过GitHub仓库可查看本书的所有示例代码。

JavaScript简易入门

我们可以在屏幕上编写简单如1 + 1的语句。这一个加两个数据加和的合法JavaScript代码。但如果这时按下播放按钮执行代码还是不会有任何输出。这有点让人泄气,因为我们觉得至少应输出计算的结果才对。

要在屏幕上看到JavaScript运行的结果,我们可以使用console.log()函数。

函数是一种编辑结构,其中包含执行特定操作的其它代码。通过调用所定义的函数名可执行复杂的操作。在我们调用函数(或称作执行函数)时,可以写下它的名字,本处为console.log,并在其后添加小括号。如果需要输入来运行其功能,我们只需像本例中那样将输入内容放在括号内。

console.log是一个内置JavaScript函数,用于在编辑器下方控制台中显示(或记录)函数内的给定值。我说内置的意思是大部分JavaScript执行环境都包含该函数。例如,浏览器界面中有一个称作控制台的区域,通过开发者工具可访问。p5.js和Codepen在线编辑器在编辑器下方也有这个叫作控制台的区域。

我们还可以有用户定义的函数,由我们自己创建,除非分享给其它人否则只能自己使用。p5.js这样的函数库有很多专属的函数。我们将使用p5.js的函数来在屏幕上画图形,并创建各类交互和动画视效。后面我们会深入到函数的概念,但现在只需要知道JavaScript有一个console.log函数,它接收一个值并在编辑器下方的控制台中显示该值。一开始我们学习的函数名字中间都不会有点号。在这个层面console.log有些不一样,但后面我们会解释为什么使用点号。

我们来在代码中添加更多的console.log语句(示例2-2)。

示例2-2. console.log语句

console.log(1 + 1)
console.log(5 + 10)
console.log(213 * 63)
console.log(321314543265 + 342516463155)

示例2-3中为示例2-2中代码执行后控制台中显示的信息。

示例2-3. console.log语句执行结果

2
15
13419
663831006420

可以看到代码是从上到下按顺序执行的。有些编程结构可以改变这种顺序,这后面我们将会了解到。还可以看出计算机丝毫不介意处理大的数值。我们可以把人类要花上几天的运算交给计算机来执行。

示例2-2中的最后一条console.log语句处理的是非常大的数字。如果我们要在下一行对运算结果再减去10该怎么做呢?以我们现在的知识要进行这一运算还需要重复输入这些数字:

console.log(321314543265 + 342516463155 - 10)

这显然非常浪费时间。幸运的是计算很擅于存储和记住数值。因此我们可以创建被称为变量的东西来存储该值。

在编程语言中,变量是引用一个值的名称。

我们可以使用一个变量名在引入该值,这样无需再次输入这些值。如下所示:

var bigNumber = 321314543265 + 342516463155
console.log(bigNumber)
console.log(bigNumber - 10)

我们通过关键词 var 创建了一个名为bigNumber的变量,var关键词用于创建变量时使用。在var之后跟变量名,我们这里选用的是bigNumber。

选择的关键词在当前上下文中能讲得通这点很重要。本例中可能还不会有什么问题,但在程序变得越来越复杂之后,能明确含义的变量会有助于在阅读代码时能够理解意图。如查我们把存储这种大数的变量命名为cat就讲不通,其他人在阅读我们的代码时也会摸不着头脑。我们自己几个月后再来看代码也会搞不清状况。程序员一直致力于让自己的代码可读性尽可能强。

一但声明了变量,就可以使用等号运算符进行赋值。这初看起来可能有些不正常。在数学中,等号运算符用于表示等号两边的值相等。而这里我们用它来赋值,它将右边的运算值赋值给左边的变量。在很多编程语言中这都是非常普通的步骤。

既然我们的变量已经指向了值,我们就可以使用变量名来代替值进行操作。前面也提到过,变量名最后是有意义的。还有一些规则来规定变量名中有哪些字符可用哪些不可用。比如,在变量名中不能使用中间杠、感叹号或空格等特殊字符。另一个限制是我们不能使用JavaScript的保留字作为变量名,如不能JavaScript中用的var来作为变量名。如果尝试使用var作为变量名,如var var = 5,JavaScript会抛出一个错误。

译者注: 我们中国人还应注意不要使用中文输入法的标点符号,如,;等,因输入法的原因造成的错误很多时候不容易排错,所以在写代码时请保持使用英文输入法

这里提到了规则可能会让有些人不安。编程不是应该是件很有趣的事吗?但不用担心,保留字的清单很短的,所以我们无需记忆。在不断深入学习编程语言的过程中,我们会渐渐很自然的知道哪些名称不能使用。

关于规则,还需提到另外一条规则。JavaScript需要我们在每条语句后加分号。其实不加程序也能正常运行,但在某些极端情况下可以会执行失败,后面也很难排查。所以虽然会显得略为麻烦,推荐在每条语句结束时添加分号。前面的代码实际上应该像示例2-4这么写:

示例2-4. 使用分号

console.log(1 + 1);
console.log(5 + 10);
console.log(213 * 63);
var bigNumber = 321314543265 + 342516463155;
console.log(bigNumber);
console.log(bigNumber - 10);

注意执行bigNumber - 10并不会修改变量bigNumber的初始值。下例中,console.log语句的输出结果还是10:

var x = 10;
x + 5;
console.log(x);

如果想要修改变量值,需要重新对其赋值(示例2-5)。

var bigNumber = 321314543265 + 342516463155;
console.log(bigNumber);
bigNumber = 3;
console.log(bigNumber);

上例中第二个console.log输出的值是3,因为我们重新赋值变量值为3覆盖了初始值。

JavaScript中有数据类型的概念用于区别不同值的类型(其它语言中也有)。我们这里使用的数字属于数值类型。还有一种用于展示文本信息的数据类型称为字符串。

在JavaScript中,我们不能随便写一个词就希望它代表数据。比如,在console.log中放入单词hello。如果现在这么做,会出现报错信息。JavaScript还不知道hello代表什么意思,它会假设这是一个尚未定义的变量。

console.log(hello);
> Uncaught ReferenceError: hello is not defined (sketch: line 1)

译者注: 不同编辑器下报错信息可能会存在细微差别

但如果我们就是要在电脑上输出hello该怎么办呢?很多程序都会需要处理文本数据,比如处理给定姓名或地址等。这时我们可以在数据两边加上引号,用于表示该值是一个字串。

console.log('hello');

JavaScript这次就不会报错了。每当处理文本数据时,就需要在两边加上引号,这会将其注册为字串。这里所说的文本数据,也包含数字。字串也可由数值来构成:

console.log('1234');

这时,就不能把它当成代数数字来执行数学运算了,而是当成文本处理。

我们可以对字串执行操作,但产生的结果与对数字的操作不同,其实我们可以对字串进行相加操作:

console.log('hello' + 'world');
> 'helloworld'

这会将两个单词进行合并。前面我说对包含数值的字串不能进行数据运算的意思如下:

console.log('1' + '1');
> '11'

这时,数据不会作为数字而是作为字串看待,并没有进行求和运算而是进行了合并。这种对字串的合并在编程中通常称作连接(concatenation)。

字串听起来非常奇怪,它实际上指的是字符串。字符串在计算机世界中是由一个个字符组成的集合。我们既可以通过单引号'也可以通过双引号"来定义字符串,但结束符要和开头的符号一致。同时在编程中,我们不应对一个字符串使用一种引号,对另一个字符串使用另一种引号。对于开发程序而言,保持一致性非常的重要。

在结束这一部分前还要提注释的概念。注释允许我们在程序里编写不被计算机执行的内容,参见示例2-6。

示例2-6. 在程序中使用注释

// 各类示例(这是一条注释)
console.log(1 + 1);
console.log(5 + 10);
console.log(213 * 63);
var bigNumber = 321314543265 + 342516463155;
console.log(bigNumber);
console.log(bigNumber - 10);

以双斜线//开头的语句在JavaScript中会被忽略。双斜线让我们可以对单行进行注释,如何对多行进行注释,可以在每行开头使用双斜线,也可以使用/* */ 符号,参见示例2-7。

示例2-7. 使用//和/* */进行注释

// 各类示例
// 通过多行注释来禁用前3行
/*
console.log(1 + 1);
console.log(5 + 10);
console.log(213 * 63);
*/
var bigNumber = 321314543265 + 342516463155;
console.log(bigNumber);
console.log(bigNumber - 10);

不管你信不信,这些JavaScript知识已经足够我们学习使用p5.js了。如果你使用的是代码编辑器,点击新建项目按钮在新的编辑器窗口创建带有p5.js模板的代码。

p5.js入门

在p5.js代码编辑器中新建项目时会看到包含这两个名称的函数声明:setup和draw(示例2-8)。

示例2-8. 默认函数声明

function setup() {
}
function draw() {
}

这两个函数在我们编写的几乎所有p5.js程序中都需要进行声明。p5.js会查找这些函数定义并执行其内的内容。这两个函数的执行方式是有区别的。

setup函数内的区块(大括号中间的区域),用于编写初始化程序所执行的代码。setup函数的代码仅在draw函数之前执行一次。

function setup() {
// 在这个大括号内编写setup函数
}

draw函数才是见证奇迹的地方。draw函数内编写的任何代码都会由p5.js重复执行。这让我们可以创建各种动画和交互作品。

p5.js会确保setup函数在draw函数之前执行。重复说明下,p5.js只执行一次setup函数,而draw函数则会被反复执行(其实是接近每秒60次)。这也我们如何通过p5.js来创建交互和动画内容。

我们可以通过在代码的不同地方放置console.log来查看这一效果。使用不同值分别在setup函数内、draw函数内以及两个函数之外来放置console.log()语句(示例2-9)。

示例2-9. 记录setup和draw函数的行为

function setup() {
console.log('setup');
}
function draw() {
console.log('draw');
}
console.log('hello');

执行这段代码并立即停止。可以发现 输出的第一条是hello,这是我们预期内的行为。函数的调用应由JavaScript来执行。意料之外的可能是setup和draw函数也同时被执行了。说意料之外是因为这些仅仅是函数声明,它们定义函数的行为,我们仍需执行函数才能进行相应的使用。

这表示如果我们只是使用JavaScript,我们应显式地调用setup和draw函数来让console.log输出里面的信息:

setup();
draw();
console.log('hello');

但在使用p5.js库时我们却不需要这么做。因为这是p5.js库的架构方式,它查找名为setup和draw的函数声明并替我们执行这些函数。p5.js接管这一执行的原因是它以一种特殊的方式对函数进行执行。

p5.js 只执行一次setup函数,然后只我们不手动停止就会反复执行draw函数,只要不停止就会一直执行。这对于任何图形化界面属于标准操作,想法浏览器、你玩的游戏或者使用的操作系统。这些程序会持续运行并在屏幕上进行显示,除非你关闭掉程序。这也是为什么p5.js对draw函数循环执行,这样内容才会一直在屏幕上显示,不会只显示一秒就消失。

函数进阶

我们再来深入聊聊函数,因为它是我们所编写程序的重要组成部分。

函数名通常是动词。它代码运行函数所会执行的指定操作。打比方说,我们可能会有一个drawCat函数在屏幕上画出一只猫:

drawCat();

但这其实不是虚构的,因为我确实为本章创建了一个名为drawCat的函数(图2-3)。我们可以在JavaScript中创建任意我们想创建的函数,这让我们在编程时拥有了无限能力。

drawCat01

图2-3. drawCat函数的图形化输出

好吧,坦白说这个函数并没有画出多好看的猫。

我们通过调用函数名并在其后加上括号来执行函数。有时根据函数的创建或定义,可带有参数。这代表所接收的输入值会影响函数的输出结果。例如,drawCat可接收一个数值,来决定所画的猫的大小。或者该数字决定屏幕中所画的猫的个数。这完全取决于函数是如何构建的。

在本例中,我所创建的函数可接收一个输入值来控制所画的猫头的大小(图2-4):

drawCat(2);

drawCat02

图2-4. 画猫脸

很可惜p5.js并不会自带drawCat函数,我需要自己创建,但它带有很多有用的函数让我们可以毫不费力地执行复杂任务。要使用p5.js库来编程,我们将使用它自带的函数,这是由那些创建这个库的聪明人所编写的。

以下这个p5.js库中的函数可能是编写所有项目都会用到的:createCanvas函数。createCanvas为我们在网页中创建一块画面来供我们使用。但要使用该函数,我们需要传入两个由逗号分隔的值:画布区域的宽和高。我们应在setup函数内createCanvas,因为只需要在作画前执行一次即可。

我们向函数内传入800和300并执行来查看效果(示例2-10)。可能并没有什么改变,仅仅打开的浏览器窗口会变大一些。它现在使用的正是我们所传入的尺寸。我们再修改一下尺寸来查看窗口大小的变化。

示例2-10. 使用createCanvas函数

function setup() {
createCanvas(800, 300);
}

function draw() {
}

还有一个经常用到的函数,名为background。background函数使用给定值来设置画面的颜色。我们会在另一章中讲解p5.js中颜色值的使用,现在我们可以在该函数中传入(220,220,220)来让背景变成淡灰色(示例2-11)。

示例2-11. 使用background函数

function setup() {
createCanvas(800, 300);
background(220,220,220);
}

function draw() {
}

可以看到代码会从上到下执行。p5.js首先会为我们创建一块画布,然后将背景设置为灰色。

再次强调下,p5.js需要定义了setup和draw才能正常运作。在使用p5.js时,我们的任务是把相关内容放在这些函数内供p5.js执行。这和p5.js的架构有关。p5.js的创建者希望部分代码只执行一次,来满足初始化和配置的需求,而另一些像作画、动画这样的交互内容则始终保持执行。

我们在这两个函数中使用p5.js库中所自带的函数,如createCanvas和background。这些函数已由其他人为我们定义好,我们没不知道里面的具体代码。但我们其实也不必知道这些细节,只关心函数能做什么以及如何使用。

函数让我们轻松地执行复杂任务。通过使用createCanvas函数,我们无需了解在页面上创建画布元素的运行原理。这些细节对我们是抽象隐藏起来的。我们只需要知道如何调用函数来为我们所用。

最后,我们将再调用一个函数,这次是在draw函数内进行定义,来在页面上画出一个矩形(示例2-12)。

画矩形需要使用到rect函数。rect函数中需要传入四个值:画布区域左上解的x和y轴位置,以及矩形的长和宽。

无需深入p5.js中坐标系如何运行,我们为函数分别传入这些值:x值50,y值100,宽200,高100(图2-5)。

示例2-12. 画一个矩形

function setup() {
createCanvas(800, 300);
background(220,220,220);
}

function draw() {
rect(50, 100, 200, 100);
}

图2-5. rect函数的输出

图2-5.rect函数的输出

通过调用该函数,我们在屏幕上画出了第一个形状。

p5.js中的坐标系

现在让们花点时间来讨论下p5.js中坐标系如何运作。

要定位平面上的任意一 hkko,我们需要使用一个两轴组成的坐标系。纵轴称为Y轴,横轴称为X轴,两轴相交之处称为原点。我们画形状的画布中,原点位于左上角。从该点向下,Y值递增,向右 X值递增(图2-6)。

p5.js中的坐标原点

图2-6. 坐标原点

在屏幕上画矩形时,所传入的坐标值定义了矩形的左上角位置(示例2-13和图2-7)。

示例2-13. 画一个矩形

function setup() {
createCanvas(800, 300);
background(220,220,220);
}

function draw() {
rect(400, 150, 100, 100);
}

示例2-13. 画一个矩形

图2-7. 画一个矩形

如果这和你的预期不同,可以调用p5.js的另一个函数rectMode,传入值 CENTER来修改我们程序中矩形的绘制方式(示例2-14)。这个函数更多是配置和初始化相关的,因此我们将它放在setup函数内。

示例2-14. 使用rectMode函数以及CENTER值

function setup() {
createCanvas(800, 300);
background(220,220,220);
rectMode(CENTER);
}

function draw() {
rect(400, 150, 100, 100);
}

图2-8. 居中矩形输出

图2-8. 居中矩形输出

p5.js中还有一个ellipse函数用于绘制(椭)圆形。ellipse使用方法与rect函数非常相似。前两个参数 x 和 y 是圆的中心坐标点,第三个参数是横向半径,第四个参数是纵向半径。使用ellipse函数绘制正圆,我们需要为横向半径和纵向半径传入相同的值(示例2-15)。

译者注: 原文为 radius 半径,实际测试应为直径

如果你测试在屏幕上绘制不同图形时,可能会注意到调用的图形绘制函数总是在之前图形的上一层进行绘制。我们可以修改函数调用的顺序来影响图形叠放顺序。

示例2-15. 使用ellipse函数

function setup() {
createCanvas(800, 300);
background(220,220,220);
rectMode(CENTER);
}

function draw() {
rect(400, 150, 100, 100);
ellipse(350, 120, 100, 100);
}

图2-9. 输出圆形和居中矩形

图2-9. 输出圆形和居中矩形

我还想再介绍一个函数 line。从名称可以看出,line函数在屏幕上绘制直线。需要为该函数传入四个参数:起始点x和y坐标、终止点x和y坐标。可以使用 line 函数做下测试,就能更好的理解p5.js中坐标系是如何运作的。比如,你可以在整个画布绘制一个大大的 X。

译者补充:

function setup() {
createCanvas(300, 300);
background(220,220,220);
}

function draw() {
line(0, 0 , 300, 300);
line(0, 300, 300, 0)
}

使用 p5.js 画 X

总结

本章中我们对p5.js进行了快速的入门,并且可以在屏幕上绘制图形。

我们了解了需要名为setup和draw的两个函数内定义我们的代码内容。仅需执行一次的代码放在setup函数内,需要动画或交互的内容放在draw函数内。p5.js要求我们在这两个函数内编写代码。这并非编辑的准则、惯例之类的。我们可能使用另一个库,恰恰不需要在代码中遵循这种结构。这种要求与p5.js这个库本身的架构有关。我们需要使用这两个函数定义来通过p5.js绘制。

这类需重复书写且不做修改的代码,称为样板(boilerplate)代码。有太多的样板不是件好事,因为我们会发展自己重复大量的操作,但本例中样板的数量还是非常利于管理的。

在这些函数的定义中我们使用了p5.js 库所自带的createCanvas, background函数,以及一些图形函数如rect。如前所述,函数是一种将代码整合在一起以供复用的编程结构。函数也从大量的复杂性中进行了抽象。我们无需知道函数的运行机制,只需要知道如何使用它即可。我们可以完全不了解createCanvas是如何在网页中创建画布元素的,只要知道如何使用它就不成问题。想想我们开车时,不需要知道内燃机的运行原理一样可以开好。我们只需要知道如何使用方向盘、踏板等操作汽车即可。函数的道理与开车相似。

接下来,我们将创建自己函数来处理程序中的复杂问题并创建可复用的代码块。

尝试绘制图2-10中的图形:

图2-10. 图像练习

图2-10. 图像练习

译者补充:

function setup() {
createCanvas(800, 300);
background(220,220,220);
}

function draw() {
ellipse(0, 150, 300, 300);
rect(150, 0, 500, 300);
ellipse(800, 150, 300, 300);
}