diff --git a/src/content/tutorials/zh-Hans/repeating-with-loops.mdx b/src/content/tutorials/zh-Hans/repeating-with-loops.mdx new file mode 100644 index 0000000000..d5dedb2bfe --- /dev/null +++ b/src/content/tutorials/zh-Hans/repeating-with-loops.mdx @@ -0,0 +1,1405 @@ +--- +title: "循环重复" +description: "使用循环和数组创建毛毛虫爬行比赛!" +category: introduction +categoryIndex: 5 +featuredImage: ../images/featured/loops.png +featuredImageAlt: 三只有眼睛的粉色毛毛虫在起跑线上等待比赛,文字显示“点击开始”。 +relatedContent: + references: + - en/p5/for + - en/p5/noloop + - en/p5/random +authors: + - Joanne Amarisa + - Layla Quiñones + - Greg Benedis-Grab +--- + +import EditableSketch from "../../../components/EditableSketch/index.astro"; +import Callout from "../../../components/Callout/index.astro"; +import AnnotatedLine from "../../../components/AnnotatedLine/index.astro"; + +## 介绍 + +你是否想通过少量代码就绘制出许多形状,从而将你的项目提升到新的水平?想象绘制一排树、一叠书、彩虹的拱形,或者蜂巢的内部。为了创建由相同形状的多个版本组成的图形,我们不再只写单独的形状,而是进入循环和数组的奇妙世界。让我们学习如何用少量代码创建重复的图案吧! + +![一个拱形和一个六边形垂直排列,标签为 “单个对象” 箭头从这些形状指向下方垂直排列在 “多个对象” 标签下的对应图案。单个拱形指向五个垂直堆叠、形成彩虹形状的拱形。六边形指向排列成蜂巢镶嵌图案的多个六边形。](../images/introduction/loops-intro.png) + +绘制每个形状都写一行新代码将会非常繁琐。相反的,我们可以使用 *loops*(循环),它允许我们执行并重复代码块任意次数。在本教程中,我们使用循环和数组来创建重复的可视化效果 [一个毛毛虫赛跑的草图](https://editor.p5js.org/gbenedis@gmail.com/sketches/BrmtZ36ET). + +![三只粉色的毛毛虫正在画布右侧爬向绿色的终点线。](../images/introduction/caterpillar-race-end.gif) + +一组毛毛虫将从起跑线开始比赛,第一个到达终点线的毛毛虫获胜。每次运行草图时,获胜的毛毛虫都会不同! + +在本教程中,你将学会: + +- 使用 *for 循环* 绘制和更新重复的任务和形状 +- 使用自定义函数更新变量和程序状态,并在草图运行时进行修改 +- 使用 [条件语句](/reference/p5/if) 和 `random()` 生成不同结果 +- 使用鼠标触发和 [布尔变量](/reference/p5/Boolean) 运行和停止你的草图 +- 将毛毛虫的位置存储在数组中 + +### 你将需要 + +- [p5.js Web 编辑器](https://editor.p5js.org/) +- 理解如何使用 x 和 y 坐标在 p5.js 中绘制基本图形和文本 + - 你可以参考我们之前的教程, [Get Started(入门指南)](/tutorials/zh-Hans/get-started) +- 理解变量和条件语句 + - 可以参考我们之前的教程, [Variables and Change(变量与变化)](/tutorials/zh-Hans/variables-and-change/) and [Conditionals and Interactivity(条件语句与交互)](/tutorials/zh-Hans/conditionals-and-interactivity) +- 理解自定义函数和参数 + - 可以参考我们之前的教程, [Organizing Code with Functions(使用函数组织代码)](/tutorials/zh-Hans/organizing-code-with-functions) + + +## 步骤 1 – 绘制赛道 + +- 在 [p5.js Web 编辑器](https://editor.p5js.org/) 中新建一个项目,给它起一个名称,例如 “Caterpillar Race(毛毛虫比赛)”,然后保存。 +- 在 `setup(),` 中创建一个 500x500 像素的画布。 +- 在 `setup()` 函数上方声明两个新的全局变量,分别用于定义起跑线和终点线的 x 坐标。我们将这两个变量命名为 startLine 和 finishLine。 + - 根据你希望放置的起点线和终点线的位置,为它们赋予 x 轴上的数值。在本示例中,我们将 startLine 设为 30,finishLine 设为 360。 +- 在 `draw()` 函数中: + - 设置背景颜色。例如,我们将其设置为棕色 `background(121, 96, 76)` ; + - 绘制一个矩形,并将它的 x 坐标设为 `startLine`。将矩形的高度设为 `height` ,使其在画布中垂直铺满。 + - 在 `finishLine` 的位置再绘制一个矩形,并重复相同的步骤。 + - 给这两个矩形填充不同的颜色。 +- 别忘记给你的草图命名并保存。 + +你的代码看起来可能会像下面这样: + +```js + +``` + +## 步骤 2 – 绘制毛毛虫身体的一节段并让它移动 + +接下来,我们将在画布上绘制毛毛虫的一个节段,并让它从起跑线移动到终点线。当到达终点时,它将停止前进。 + +- 声明一个新的全局变量 `circX` 并将它的值设为 `startLine`。 + - 这个变量将作为毛毛虫节段的 x 坐标。 +- 声明一个新的全局变量`circY` 并将它的值设为 250 (也就是画布高度的一半)。 + - 这个变量将作为毛毛虫节段的 y 坐标。 +- 在 `draw()` 函数中: + - 使用 `circle()` 函数绘制毛毛虫的身体。 传入 `circX`, `circY`, 和 50 作为参数, 分别表示 x 坐标、y 坐标和直径: `circle(circX, circY, 50);` + - 我们将圆形填充为白色,并设置黑色细描边。 + - 在绘制圆形的语句下方, 使用 `circX += 20;` 来让 `circX` 的值增加20。 + - 这表示每次 `draw()` 函数运行时, `circX` 都会增加 20 个像素点。程序会在比上一次位置右移 20 个像素的地方绘制新的圆,从而形成毛毛虫前进的效果。 +- 在 `draw()` 函数的末尾,添加如下的条件语句: + + ```js + if (circX > finishLine) { +   noLoop(); + } + ``` + +`noLoop()` 函数会阻止 `draw()` 函数再次运行。 `if` 语句会在圆形的x坐标超过 finishLine 的值时,通过调用 `noLoop()` 来停止 `draw()` 的执行。 你可以访问 p5.js 参考文档 了解更多关于 [`noLoop()`](/reference/p5/noLoop) 的信息。 + +在 `setup()` 函数中, 添加: `frameRate(3);` + +- [frame rate(帧率)](/reference/p5/frameRate) 指的是 `draw() ` 函数在一秒钟内运行的次数。将帧率设置为较低的数值,可以让动画中的移动过程看起来更加明显、夸张,适合本示例中的动画效果。 + +你的完整代码应该类似下面这样: + +```js + finishLine) { +    noLoop(); +  } +} +`} /> +``` + +### [ If 语句](/reference/p5/if) + +if 语句(如上面示例中使用的那样)表示:只有当某个条件为真时,才会执行对应的一段代码。它的通常写法如下: + +```js +if () { +  +} +``` + +条件写在 if 语句 后面的圆括号 () 中。花括号 { } 用来标记代码块的开始和结束。 在步骤 2 中, 条件 `circX > finishLine` 的作用是:当圆形的x坐标大于 `finishLine` 的值时,通过调用 `noLoop()` 来停止 `draw()` 函数的再次运行。 + +你可以访问 p5.js 参考文档中的 [if](/reference/p5/if) 页面了解更多内容。 + + +## 步骤 3 – 绘制一条毛毛虫 + +我们将重复步骤 2 中的毛毛虫节段,来创建一排圆形,从而组成毛毛虫的身体。我们将使用一个 *for 循环*,来连续绘制多个圆形。 + + +### 3.1 – 声明毛毛虫身体的属性 + +在 `setup()` 之上: + +- 声明一个名为 `segments` 的新变量,并将其赋值为 6。 + - 这个变量用于定义组成毛毛虫身体的圆形数量。 +- 声明一个名为 `spacing` 的变量,并将其赋值为 20。 + - 这个变量用于定义毛毛虫身体各个分段之间的像素间距。 +- 声明一个名为 `segmentSize` 的新变量,并将其赋值为 50。 + - 这个变量用于定义圆形身体分段的直径。 + +### 3.2 – 使用 *for 循环* 构建毛毛虫的身体 + +在 `draw()` 中: + +- 在绘制终点线的代码之后,声明一个新的局部变量 `x` ,用于定位所有身体分段: `let x = circX;` +- 使用 `for (let i = 0; i < segments; i += 1) { }` 添加一个 *for 循环* + - *for 循环* 会多次重复执行花括号 {} 内的代码。 + - 将绘制 `circle()` 的代码行移动到 *for 循环* 的花括号中。 +- 在 for 循环后添加: `circX += spacing`  + - 这会在每次 `draw()` 运行时,让毛毛虫的身体向右移动。 + +你的代码应该看起来像这样: + +```js + finishLine) { +    noLoop(); +  } +} +`} /> +``` + + +#### [For 循环](/reference/p5/for) + +for 循环可以多次执行一段(或一个代码块)的代码。for 循环可以像下面这样写: + +```js +for (let i = 0; i < number; i += 1) {  + // 你希望执行 number 次的代码内容 +} +``` + +在 for 循环的 `{}` 内,我们编写希望重复执行的代码。通过设置循环的条件,我们可以指定代码重复执行的次数。 + +一个 for 循环由圆括号 () 中的三个语句定义,每个语句之间用分号分隔。它们分别是: + + `for (${bottom('init', ' let i = 0; ')} ${bottom('cond', 'i < number;')} ${bottom('instr', ' i += 1 ')}) { }`}> + Initialization + Condition + Instruction + + +- **初始化**: 用一个数值来初始化迭代变量 `i` ,作为开始计数的起点。 + + ```js + let i = 0; + ``` + +- **条件:** 用于保持循环继续运行的条件。只要该条件为 `true` ,loop 循环就会持续执行。当该条件为 `false`,for 循环就会停止。 + + ```js + i < number; + ``` + +- **指令:** 告诉程序在每次循环执行后,如何改变计数的数值。 + + ```js + i += 1; + ``` + +第一个表达式 `let i = 0` 用于初始化(或启动)for 循环。 + +- i 是一个用于定义 for 循环初始状态的变量,也被称为 *索引(index)* 变量。通常,索引从 0 开始,并在每次循环中递增。这个变量名可以是你喜欢的任何名称。一般来说,常使用单个字母,例如 i、j、k,当然你也可以使用更具描述性的名称。 + +第二个表达式 `i < number`, 是循环继续运行的条件。它是一个布尔表达式,可以返回 `true` 或 `false`。只要这个表达式是 `true`,for 循环就会继续运行,此时索引变量 (`i`) 会继续递增。 +- `number` 可以是任何数值或存储数值的变量。计数时通常使用整数。 + - 例如: `i < 5;` 或 `i < segments;` +- 在这种情况下, `number` 决定了 for 循环运行的次数,因为初始值 `i` 从0开始。 +- 在上面的毛毛虫示例中,我们将这个值赋给变量 `segments` ,它指定了构成毛毛虫身体的圆的数量。只要索引值没有达到 `segments` 中存储的数值,循环就会继续绘制圆。 + - 当绘制的圆的数量不再少于 `segments` 时,程序将退出 for 循环,并继续执行下一行代码。 + +第三个表达式,`i += 1` 表示每次循环迭代结束时索引的变化。 + +- 一个 [**循环迭代**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration) 指的是循环运行一次。例如,如果一个 for 循环运行了 3 次,则它有 3 次迭代。 +- 在这个例子中, `i` 变量每次 for 循环运行时都会加 1 。这意味着第一次运行 for 循环时,`i` 为 0。第二次运行循环时,`i` 为 1。第三次运行循环时,`i` 为 2,依此类推(直到达到值 `number`)。表达式 `i += 1` 也可以写成 `i++`。 +总结如下:  + +1. 当程序执行 for 循环时,首先在第一个表达式中声明循环索引变量。 +2. 程序检查第二个表达式(布尔表达式)。如果该表达式为 `true` ,则执行花括号内的代码。 +3. 循环结束时,迭代变量 `i` 的值会根据第三个表达式的定义进行更改。  +4. 循环重复此过程,直到第二个表达式中的条件为 `false`。当条件为 `false` 时,程序退出 for 循环并继续执行代码中的下一行。 + +访问 [for 循环 参考](/reference/p5/for) 了解更多信息。 + + +### 3.3 – 为毛毛虫添加更多细节 + +- 如果需要,你可以调节毛毛虫身体的 `fill()` 颜色。在本例中,我们将其设置为粉色: `fill(255, 0, 200);` +- 绘制毛毛虫的眼睛。眼睛的位置与绘制身体的最后一个圆的 x 和 y 坐标相同。这意味着它位于 for 循环绘制完所有圆之后,因此我们将在 for 循环之后, `noLoop()` 之前添加眼睛。这样可以确保眼睛绘制在毛毛虫身体最后一个圆的上方。 + - 在 `setup()` 上方声明一个新的全局变量 `eyeSize`,并将其值赋为 15。 + - 绘制两个圆。 + - 为它们填充黑色,并添加粗的白色描边。 + - 添加第一只眼睛并设置其坐标和大小: `circle(x, circY-eyeSize, eyeSize);` + - 这会将眼睛放置在最后一个绘制的圆的顶部。 + - 添加第二只眼睛并设置其坐标和大小: `circle(x - eyeSize, circY-eyeSize, eyeSize);` + - 我们将第二只眼睛的 x 坐标减去 `eyeSize`,这样两只眼睛就能并排显示,互不重叠。 +- 完成毛毛虫的绘制后,将代码封装到名为 `drawCaterpillar()` 的自定义函数中。你可以随意修改毛毛虫的图形! + - 将 `drawCaterpillar()` 函数定义在 `draw()` 之外。 + - 该函数应包含你用来绘制毛毛虫身体的圆的 for 循环,以及用于绘制毛毛虫眼睛的两个圆。 + - 请记住在 `draw()` 函数中调用它!在 `draw()` 函数中,在结束循环的 if-statement 之前,输入 `drawCaterpillar();` + +以下是你的草图目前可能的样子: + +```js + finishLine) { +    noLoop(); +  } +} + +function drawCaterpillar() { +  // 创建一个循环,用圆形来组成身体 +  let x = circX; + +  for (let i = 0; i < segments; i += 1) { +    fill(255, 0, 200); +    stroke(0); +    strokeWeight(1); +    circle(x, circY, segmentSize); + +    x+=spacing; +  } +  +  // 绘制毛毛虫的眼睛 +  fill(0); +  stroke(255); +  strokeWeight(3); +  circle(x, circY - eyeSize, eyeSize); +  circle(x - eyeSize, circY - eyeSize, eyeSize); +} +`} /> +``` + + +调整 `bodySize` 和 `eyeSize` 变量,尝试毛毛虫的不同的身体和眼睛大小。 + + + +## 步骤 4 – 泛化 `drawCaterpillar()` 函数 + +在草图首次运行时,我们希望每条毛毛虫都位于起始线上。这意味着我们需要为函数添加参数,以便我们可以通过修改它们来指定毛毛虫的位置。 +- 转到 `drawCaterpillar()` 函数。 + - 在函数括号内添加 `x` 作为参数: `function drawCaterpillar(x) { ...}` + - 注意,我们已经在 `drawCaterpillar()` 函数里将 `x` 声明为局部变量。我们可以移除该声明,并使用 `x` 参数代替。 + - 在函数括号内添加 `y` 作为第二个参数: `function drawCaterpillar(x,y) { ... }` + - 在函数体中将 `circY` 替换为 `y` 。 + +你可以在之前的教程 [使用函数组织代码](/tutorials/zh-Hans/organizing-code-with-functions), 和 p5.js 的参考文档 [函数](/reference/p5/function) 中了解更多关于自定义函数、参数和实参的信息。 + +你的自定义函数现在应该类似于这样: + +```js +function drawCaterpillar(x,y) { +  // 创建一个循环,用圆形来组成身体 +  for (let i = 0; i < segments; i += 1) { +    fill(255, 0, 200); +    stroke(0); +    strokeWeight(1); +    circle(x, y, 50); + +    x += spacing; +  } +  +  // 绘制毛毛虫的眼睛 +  fill(0); +  stroke(255); +  strokeWeight(3); +  circle(x, y - eyeSize, eyeSize); +  circle(x - eyeSize, y - eyeSize, eyeSize); +} +``` + +- 在 `draw()` 函数中,调用三次 `draw Caterpillar()` 来绘制三条毛毛虫。 + +你的 draw 函数应该如下所示: + +```js +function draw() { +  // 绘制背景 +  background(121, 96, 76); +  // 绘制起跑线和终点线 +  noStroke(); +  fill(0); +  rect(startLine, 0, 5, height); +  fill(0, 255, 0); +  rect(finishLine, 0, 20, height); + +  // 绘制三条毛毛虫 +  drawCaterpillar(circX,circY-150); +  drawCaterpillar(circX,circY); +  drawCaterpillar(circX,circY+150); + +  circX += spacing; +  // 当x到达终点线时停止循环 +  if (circX > finishLine) { +    noLoop(); +  } +} +``` + + +## 步骤 5 – 创建一个 `drawCaterpillars()` 函数 + +现在屏幕上同时有三条毛毛虫在移动。我们可以使用循环来绘制这三条毛毛虫,而不是分别调用三次 `drawCaterpillar()` 函数。 + +- 在草图的 `setup()` 上方声明一个新的全局变量,用于储存你想要的毛毛虫数量: `let numCaterpillars = 3;` +- 在 `draw()` 函数外创建一个新的自定义函数,并将其命名为 `drawCaterpillars()` + - 在 `drawCaterpillars()` 函数内创建一个新的 for 循环。 + - 在循环的第一个表达式中初始化循环变量,并将其初始值设为 0。 + - 在循环的第二个表达式中,将条件设置为 `i < numCaterpillars` 。 + - 在循环的第三个表达式中递增循环变量的值。 + - 在 for 循环的括号内调用 `drawCaterpillar(circX, circY);` 。 +- 在 `draw()` 函数中, 调用 `drawCaterpillars();` + +你的 `draw()` 和 `drawCaterpillars()` 函数应该看起来如下: + +```js +let numCaterpillars = 3; + +// 变量声明和设置 + +function draw() { +  // 绘制背景 +  background(121, 96, 76); +  // 绘制起跑线和终点线 +  noStroke(); +  fill(0); +  rect(startLine, 0, 5, height); +  fill(0, 255, 0); +  rect(finishLine, 0, 20, height); + + circX += spacing; + +  // 在画布上绘制毛毛虫 +  drawCaterpillars(); + +  // 当x到达终点线时停止循环 +  if (circX > finishLine) { +    noLoop(); +  } +} + +function drawCaterpillars() { +  for (let i = 0; i < numCaterpillars; i += 1) { +    drawCaterpillar(circX,circY); +  } +} +``` + +程序应该可以运行,但这次你只会看到一条毛毛虫。这是因为三条毛毛虫被画在了一起。为了解决这个问题,下一步我们将调整每条毛毛虫的 y 坐标: + +```js + finishLine) { +    noLoop(); +  } +} + +function drawCaterpillars() { +  for (let i = 0; i < numCaterpillars; i += 1) { +    drawCaterpillar(circX,circY); +  } +} + +function drawCaterpillar(x,y) { +  // 创建一个循环,用圆形来组成身体 +  for (let i = 0; i < segments; i += 1) { +    fill(255, 0, 200); +    stroke(0); +    strokeWeight(1); +    circle(x, y, 50); + +    x += spacing; +  } +  +  // 绘制毛毛虫的眼睛 +  fill(0); +  stroke(255); +  strokeWeight(3); +  circle(x, y - eyeSize, eyeSize); +  circle(x - eyeSize, y - eyeSize, eyeSize); +} +`} /> +``` + + +## 步骤 6 – 为比赛摆放毛毛虫 + +**回顾!** 在步骤 3 和 4 中,我们的草图中有两个自定义函数。 + +- `drawCaterpillar(x,y)` 使用参数 x 和 y 绘制一条毛毛虫。 +- `drawCaterpillars()` 调用 `drawCaterpillar()` 函数并将其放在一个 for 循环中,以便我们可以绘制多条毛毛虫。 + +现在,每条毛毛虫的 x 和 y 坐标都相同。我们将调整 for 循环中的代码,以 y 坐标为参数设置毛毛虫之间的间距。 + +- 转到 `drawCaterpillars()` 函数的声明。 + - 在 *for 循环* 内部,添加一行新的代码: `let padding = height / numCaterpillars;` + - 这行代码声明了一个名为 `padding` 的变量,用于定义每条毛毛虫之间的垂直距离。 + - 我们将画布中的 `height` 值除以草图中毛毛虫的数量,并将结果赋值给 `padding`。这样就将画布垂直分割成行(每条毛毛虫占一行)。每行的高度就是 `padding` 的值。 + - 初始化变量 `y` 并将其赋值为: `let y = (i + 0.5) * padding`,其中 `y` 决定了每条毛毛虫的 y 坐标。 + - 使用变了 `i` ,for 循环的每次迭代都会将一条毛毛虫放在它自己的行中。 + - 将 `i` 加上 0.5 可以使毛毛虫在该行居中。 +- 与步骤 5 类似,将 `y` 作为参数添加到 `drawCaterpillar()` 函数调用的括号中。 + - 现在这行代码应该是: `drawCaterpillar(circX, y);` + +总而言之,我们已经将 `x` 和 `y` 作为 `drawCaterpillar()` 函数内部使用的参数。在 `drawCaterpillars()` 函数中使用此函数时,我们使用`circX` 和 `y` 作为参数。 + +您可以在我们之前的教程 [使用函数组织代码](/tutorials/zh-Hans/organizing-code-with-functions) 中了解更多关于自定义函数、参数和实参的信息。 + +到目前为止,你的 `drawCaterpillars()` 函数应该如下所示: + +```js +function drawCaterpillars() { +  for (let i = 0; i < numCaterpillars; i += 1) { +    let padding = height/numCaterpillars; +    let y = (i + 0.5) * padding; + +    drawCaterpillar(circX, y, 6); +  } +} +``` + + +### 使用 for 循环索引变量进行绘图 + +for 循环的索引变量 (`i`) 会在每次循环迭代时递增或递减。在本教程中, `i` 变量从 0 开始,每次 for 循环运行时递增 1。 + +- 第一次运行 for 循环时, `i` 是 0.  +- 第二次运行 for 循环时, `i` 是 1. +- 第三次运行循环时, `i` 是 2,依此类推。 + +我们可以在绘图中使用索引变量来进行加、减、乘、除运算,使得每次 for 循环迭代的结果都不同。 + +在本例中,我们使用 `i` 来计算每条毛毛虫之间的间距(见下表): + +- 第一次迭代时, `i` 为 0 , `y` 为 `0.5 * padding`。第一条毛毛虫将绘制在该 y 值处。 +- 在第二次迭代中, `i` 为 1 , `y` 变为 `1.5 * padding`。第二条毛毛虫绘制在新的 y 值处。 +- 在第三次迭代中, `i` 为 2 , `y` 变为 `2.5 * padding`。第三条毛毛虫绘制在新的 y 值处。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +毛毛虫 (循环迭代) + + + +`x` + + + +`y` + +
+ +First + + + +0 + + + +`0.5 * padding` + +
+ +Second + + + +1 + + + +`1.5 * padding` + +
+ +Third + + + +2 + + + +`2.5 * padding` + +
+ +`drawCaterpillar()` 和 `drawCaterpillars()` 现在应该看起来像这样: + +```js +// ... 变量声明、setup() 和 draw() + +function drawCaterpillars() { +  for (let i = 0; i < numCaterpillars; i += 1) { +    // 每条毛虫之间留有空间。 +    let padding = height / numCaterpillars; +    let y = (i + 0.5) * padding; + +    // 绘制毛毛虫 +    drawCaterpillar(circX, y); +  } +} + +function drawCaterpillar(x, y) { +  // 创建一个循环,用圆形来组成身体 +  for (let i = 0; i < segments; i += 1) { +    fill(255, 0, 200); +    stroke(0); +    strokeWeight(1); +    circle(x, y, 50); + +    x+=spacing; +  } + +  // 绘制毛毛虫的眼睛 +  fill(0); +  stroke(255); +  strokeWeight(3); +  circle(x, y - eyeSize, eyeSize); +  circle(x - eyeSize, y - eyeSize, eyeSize); +} +``` + +整体效果如下: + +```js + finishLine) { +    noLoop(); +  } +} + +function drawCaterpillars() { +  for (let i = 0; i < numCaterpillars; i += 1) { +    // 每条毛虫之间留有空间。 +    let padding = height / numCaterpillars; +    let y = (i + 0.5) * padding; + +    // 绘制毛毛虫 +    drawCaterpillar(circX, y); +  } +} + +function drawCaterpillar(x, y) { +  // 创建一个循环,用圆形来组成身体 +  for (let i = 0; i < segments; i += 1) { +    fill(255, 0, 200); +    stroke(0); +    strokeWeight(1); +    circle(x, y, 50); + +    x+=spacing; +  } + +  // 绘制毛毛虫的眼睛 +  fill(0); +  stroke(255); +  strokeWeight(3); +  circle(x, y - eyeSize, eyeSize); +  circle(x - eyeSize, y - eyeSize, eyeSize); +} +`} /> +``` + + +更改全局变量 `numCaterpillars` 的值,看看 `drawCaterpillars()` 中的 for 循环是如何在画布上垂直排列它们的。 + + + +## 步骤 7 – 创建一个数组来保存每只毛毛虫的位置 + +现在我们已经将毛毛虫垂直放置在画布上,接下来需要给每只毛毛虫分配一个唯一的 x 坐标。我们可以使用数组来实现这一点。该数组将包含每只毛毛虫的 x 坐标。然后,我们就可以让毛毛虫在屏幕上随机移动,从而实现一场比赛。 + + +### 7.1 – 创建一个 [数组](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) + +- 在 `numCaterpillars` 之后声明 `caterpillarEnds` 数组:`let caterpillarEnds = [];` + - 该数组将保存每条毛毛虫最左侧线段的 x 坐标。 + + +你将在下一个教程 [数据结构花园](/tutorials/data-structure-garden) 中学习更多关于数组的知识。 访问 MDN 的 [数组](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) 参考文档了解更多信息。 + +- 在 `setup()`函数中为每条毛毛虫添加 x 坐标,并将它们全部放置在起始线上。 + - 创建一个 for 循环,对每条毛毛虫重复执行一次:`for (let i=0; i < numCaterpillars; i++) { }` + - 在花括号内,添加代码 `caterpillarEnds.push(startLine);` + - 这会将值 `startLine` 添加到 `caterpillarEnds` 数组中,每条毛毛虫都在草图中绘制一次。 + - `.push()` 方法用于在数组末尾添加新值。 + +现在 `setup()` 应该如下所示: + +```js +function setup() { +  createCanvas(500, 500); + +  // 设置较慢的帧率 +  frameRate(6); + +  for (let i=0; i < numCaterpillars; i++) { +    // 将最后一个片段的位置添加到数组中 +    caterpillarEnds.push(startLine); +  } +} +``` + +- 在 `drawCaterpillars()` 函数中,修改 `drawCaterpillar(circX,y)` 这行代码: + - 现在我们将使用 `caterpillarEnds` 数组,而不是 `circX`。 + - 由于 for 循环中的索引 `i` 代表了 `caterpillarEnds` 数组中的正确索引位置,我们可以将 `circX` 改为 `caterpillarEnds[i]`: `drawCaterpillar(caterpillarEnds[i], y);` +- 从函数中移除 `circX` 。 + - 注意: `draw()` 函数末尾仍使用了 `circX` 。请先将其移除,稍后我们会再添加回去。 + +你的 `drawCaterpillars()` 函数应该如下所示: + +```js +function drawCaterpillars() { +  for (let i = 0; i < numCaterpillars; i += 1) {   +    // 每条毛虫之间留有空间 +    let padding = height / numCaterpillars; +    let y = (i + 0.5) * padding; + +    // 使用数组绘制毛毛虫 +    drawCaterpillar(caterpillarEnds[i], y); +  } +} +``` + + +### 7.2 – 随机移动毛毛虫 + +- 在 `draw()` 函数之外创建一个新的自定义函数,将其命名为 `moveCaterpillars()` +- 定义一个新的 for 循环,循环次数与毛毛虫的数量相同: `for (let i = 0; i < numCaterpillars; i += 1) { }` + - 这与 `drawCaterpillars()` 函数中使用的 for 循环语法相同,因为它允许你单独引用每只毛毛虫。 +- 在循环内部,为 `move` 赋一个局部随机值:  + + ```js + let move = random(5,30); + ``` + +- 比赛开始后,每次运行 `draw()` 函数时,每条毛毛虫都会随机向右移动 5 到 30 像素。 +- 你可以根据希望毛毛虫移动的范围修改 `random()` 函数中的值。访问 [p5.js 中关于 `random()` 函数的参考文档](/reference/p5/random) 了解更多信息。 + +你的函数可能如下所示: + +```js +function moveCaterpillars() { +  for (let i = 0; i < numCaterpillars; i += 1) { +    // 给每只毛毛虫随机赋予一个速度 +    let move = random(5, 30); +    caterpillarEnds[i] += move; + } +} +``` + +综上所述,上述函数中的事件顺序如下: + +- 每次循环迭代时,我们将 `move` 赋值为 5 到 30 之间的随机数。 + - 这意味着每条毛毛虫都会被分配一个不同的随机 `move` 值。 +- 然后我们更新 `caterpillarEnds` 数组中第 `i` 个位置的值, `drawCaterpillar()` 函数根据这个值来确定毛毛虫的显示位置。 + + +### 7.3 – 移动毛毛虫 + +- 最后,在调用 `drawCaterpillars()` 函数之前,先在 `draw()` 函数中调用 `moveCaterpillars()` 函数。 + +你的代码应该看起来像这样! + +```js + +``` + + +## 步骤 8 – 让毛毛虫做出爬行的动作 + +为了模拟 ‘爬行’ 动作,我们将使用 `random()` 函数随机化每条毛毛虫的节段数量。回顾一下,全局变量 `segments` 决定了毛毛虫显示的节段数量。现在,我们将把它改为 `drawCaterpillar()` 函数中的一个参数。 + +- 转到 `drawCaterpillar()` 函数的定义。 + - 添加一个名为 `segments` 的新参数。现在它应该看起像这样: `function drawCaterpillar(x, y, segments) { ... }` + - 你可以删除草图顶部的全局变量 `segments` 。 +- 转到 `drawCaterpillars()` 函数的定义。 + - 在调用 `drawCaterpillar()` 函数的括号中,添加第三个参数 `crawl` : `drawCaterpillar(x, y, crawl);` + - 在这行代码上方,通过输入 `let crawl = round(random(3, 6));` 为 `crawl` 变量赋一个新的随机值。 + - 这意味着在程序运行期间,程序会持续绘制长度随机(3 到 6 个圆)的毛毛虫,并将它们从起点移动到终点。 + - 你可以根据希望毛毛虫爬行的长度来修改 `random()` 的值。 + - `crawl` 变量的随机值将填充 `drawCaterpillar()` 函数定义中的 `segments` 参数,该参数决定绘制的圆的数量。 + +你的函数应该如下所示: + +```js +// ... 变量声明, setup(), draw(), moveCaterpillars() functions +function drawCaterpillars() { +  for (let i = 0; i < numCaterpillars; i += 1) { +    // 更新每条毛毛虫的 x 坐标 +    let padding = height / numCaterpillars; +    let y = (i + 0.5) * padding; + +    // 更新毛毛虫的长度   +    let crawl = round(random(3, 6)); + +    // 绘制毛毛虫 +    drawCaterpillar(x, y, crawl); +  } +} + +function drawCaterpillar(x, y, segments) { +  // 创建一个循环,用圆形来组成身体 +  for (let i = 0; i < segments; i += 1) { +    fill(255, 0, 200); +    stroke(0); +    strokeWeight(1); +    circle(x, y, 50); +    x += spacing; +  } + +  // 绘制毛毛虫的眼睛 +  fill(0); +  stroke(255); +  strokeWeight(3); +  circle(x, y - eyeSize, eyeSize); +  circle(x - eyeSize, y - eyeSize, eyeSize); +} +``` + +## 步骤 9 – 使用鼠标触发开始比赛 + +- 声明一个名为 `isRacing` 的全局布尔变量,并将其值设置为 `false`: `let isRacing = false`; + - 该变量将作为状态变量,存储毛毛虫是否正在比赛的信息,其值为布尔值 (`true` 或 `false`). +- 定义一个 `mousePressed()` 函数。 + - 在函数中,将 `isRacing` 布尔值设置为 `true` 。 +- 该函数应如下所示: + + ```js + // 当用户按下鼠标时,比赛开始。 + function mousePressed() { +   isRacing = true; + } + ``` + +- 在 `draw()` 函数中,在调用 `drawCaterpillars()` 函数之前添加一个新的 if 语句:`if (isRacing === true) { }` +- 将 `moveCaterpillars()` 函数移动到 if 语句中。 + - 这意味着毛毛虫只有在比赛开始后(鼠标按下后)才会开始移动。 +- 你的代码应该如下所示: + +```js +// ... 定义全局变量 + +let isRacing = false; + +function setup() { +  createCanvas(500, 500); +  // 设置较慢的帧率 +  frameRate(3); + +  for (let i=0;i= finishLine) {}` +- 在这个 if 语句中,使用文本字面量动态地将获胜者写入文本框: + + ```js + text(`Caterpillar ${i + 1} wins!`, width / 2, height / 2); + ``` + + - 这段代码会动态地写入文本,显示获胜的毛毛虫的索引。由于索引从 0 开始,而我们通常从 1 开始计数毛毛虫,因此我们将 `i` 的值加 1。 + - 与步骤 9.1 类似,通过添加 `textAlign(CENTER);` 将文本居中对齐,并为文本添加样式。 +- 在 if 语句中,还要包含 `noLoop()` 函数,用于停止 `draw()` 的运行。 + +你的 `checkWinner()` 函数应该如下所示: + +```js +function checkWinner() { +  for (let i = 0; i < caterpillarEnds.length; i += 1) { +    if (caterpillarEnds[i] >= finishLine) { +      // 设置文本样式 +      textSize(24); +      textAlign(CENTER); +      fill(255); +      noStroke(); + +      // 显示信息 +      text(`Caterpillar ${i + 1} wins!`, width / 2, height / 2); + +      noLoop(); +    } +  } +} +``` + +你的最终草图应该看起来像这样: + +```js += finishLine) { +      // 设置文本样式 +      textSize(24); +      textAlign(CENTER); +      fill(255); +      noStroke(); + +      // 显示信息 +      text(\`Caterpillar \${i + 1} wins!\`,width/2,height/2); + +      // 通过停止 draw() 函数运行来结束比赛 +      noLoop(); +    } +  } +} +`} /> +``` + +## 总结 + +恭喜你完成本教程!你现在已经学会了如何在 p5.js 中使用循环生成多个动态图形。访问 [p5.js 参考](/reference) 或之前的教程,了解更多关于 [条件语句, 交互性](/tutorials/conditionals-and-interactivity) [变量](/tutorials/variables-and-change), 和 [函数](/tutorials/organizing-code-with-functions) 的内容。 + + +## 下一步 + +- 下一个教程: [数据结构花园](/tutorials/data-structure-garden) + + +## 参考资料 + +- [在你的代码中做出决策 - MDN](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/conditionals) +- [循环和迭代 - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration) +- [数组 - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) + - [`.push()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push)