教程 数据结构花园

数据结构花园

By Portia Morrell, Jaleesa Trapp, Kate Maschmeyer

交互式花园预览,你点击后,浅蓝色画布上会出现花朵。

交互式花园预览,你点击后,浅蓝色画布上会出现花朵。

在本教程中,你将使用 p5.js 来模拟一个交互式花园。在这个项目中,画布上会出现 20 朵随机生成的花朵,每朵花都有不同的颜色和大小。你还可以点击画布添加新的、独特的花朵,让花园更加丰富。每朵花都有凋谢动画,会逐渐缩小直至消失,就像花朵自然凋谢一样。

在本教程中,你将:

  • 通过定义、创建和使用自定义对象,了解JavaScript 对象等数据结构。
  • 学习使用对象方法操作JavaScript 数组等内置对象。
  • 使用 for 循环遍历多个对象。

准备工作

开始之前,你应该能够:

  • 使用 p5.js 在画布上添加和自定义形状和颜色
  • 声明、初始化、使用和更新自定义变量以及带有参数和返回值的函数
  • 在画布上添加动画
  • 注释代码并处理错误信息
  • 创建和使用自定义函数
  • 理解如何使用 for 循环来管理重复性任务

如需了解有关数组、循环和自定义函数的更多信息,请访问使用函数组织代码使用循环重复教程。

  • 变量和变化教程中,你学习了如何使用变量存储数据。变量可用于存储单个值,例如数字或文本。
  • 使用媒体对象制作动画教程中,你学习了如何使用 p5.js 内置的 p5.Imagep5.Graphics 对象。这些对象将数据(例如图像)与称为方法的特殊函数捆绑在一起,使你能够对这些数据执行操作。

在本教程中,你将学习如何:

  • 定义对象
  • 创建和使用自定义函数来更新和绘制对象
  • 使用数组管理多个对象

让我们开始吧!

JavaScript 对象

我们已经了解到,变量一次只能存储一个值(数字、字符串或布尔值)。数组可以通过索引存储多个值。然而,有时我们希望将关于同一事物的多个相关数据存储在一起。我们可以使用JavaScript 对象来实现这一点,它们通过将属性和值配对来将数据捆绑在一起。属性是与对象关联的特殊类型的变量或名称。每个属性都存储一个值。例如,如果我们想创建一个基于花朵的 JavaScript 对象,它可以拥有描述其位置(x 和 y)以及表情符号的属性。

步骤 1:创建你的第一个对象

让我们从创建一朵花开始,打造我们的花园。我们可以先用表情符号来绘制它,所以我们需要花朵的 x 坐标、y 坐标以及表情符号的数据。

  • 新建一个 p5.js 项目,并将其命名为“Data Structure Garden”(数据结构花园)。保存项目。

  • 通过将花朵的画布坐标与用于绘制它的表情符号绑定,创建一个花朵对象。我们将该对象存储在一个名为 flower 的变量中。在setup()函数上方添加以下代码:

    // 创建 flower 对象。
    let flower =
      x: 200
      y: 100
      emoji: '🌸'
    };
  • setup() 函数中添加以下代码行,即可将对象打印到控制台:

    // 在控制台中打印 flower 对象。
    console.log(flower);
  •  将背景色设置为 "lightblue"

你的代码应该如下所示:

尝试在 p5.js 网页编辑器中打开目前为止的代码。你应该能在控制台中看到你的花朵对象。注意,你可以点击控制台中的箭头来查看对象的属性和值。

以下是定义对象的一般语法:

// 定义一个对象。
let objectName = {
  property1: value1,
  property2: value2,
  property3: value3
};

对象语法包括:

  • 大括号 ({}) 括起一组属性

    • 属性可以是名称,例如变量或函数的名称,也可以是字符串。在属性名称中使用空格时,使用字符串通常会很有帮助。例如,我可能会创建一个具有“名字”和“姓氏”属性的对象,如下所示:

      let ironMan =
        "first name" : "Tony",
        "last name" : "Stark"
      }
  • 使用冒号 (:) 分隔每个属性及其对应的值;

    • 值可以是任何数据类型,甚至可以是函数!
  • 使用逗号 (,) 分隔每对 property : value

对象名称
let 
flower
 = {
    x    
: 
  200  
,
属性
    y    
: 
  200  
,
属性
  emoji  
: 
  "🌸" 
属性
}

你可以通过在定义对象时初始化新变量来存储对象。在上面的代码中,你声明了一个名为 flower 的变量。然后,你使用一个对象初始化了 flower 变量,该对象的属性代表一朵可以绘制在画布上的花。

要检查对象的属性和值,你可以使用 console.log(),它会在控制台中显示以下内容:

▼ {x: 200, y: 100, emoji: "🌸"}
   x: 200`
   y: 100`
   emoji: "🌸"

步骤 2:使用你的对象

你可以使用变量名、属性名和点号表示法来访问存储在对象属性中的任何值,如下所示:

// 使用点号表示法访问 objectName 对象的 property1 属性值。
objectName.property1 
  • 通过将花朵对象的属性传给 text() 函数,在画布上绘制花朵。

    • draw() 函数中,添加以下代码:

      // 显示花朵表情符号。
      text(flower.emoji, flower.x, flower.y);
  • 你可以在 text() 函数之前使用 textSize() 来设置花朵的大小。

    // 增大文本/表情符号的大小。
    textSize(100);

你的代码应该如下所示:

要访问花朵对象中的值,我们使用点号表示法,通过点运算符来访问对象中的属性和值。

在上面的代码中,你访问了:

  • 花朵的表情符号(flower.emoji
  • 花朵的 x 坐标(flower.x
  • 花朵的 y 坐标(flower.y

由于这些值是字符串和数字,因此可以像普通变量一样,直接在 text() 函数中使用它们。

访问 p5.js 参考文档,了解更多关于对象的信息。

试试这个!
  • 为花朵对象添加 size 属性。
  • textSize() 的值替换为花朵对象的 size 属性值。

示例 

步骤 3:定义 createFlower() 函数

现在,让我们定义一个可以在画布上显示不同花朵的函数。我们将定义一个 createFlower() 函数,以便自定义我们创建的花朵对象。我们需要确保可以更改不同花朵的 x 和 y 坐标,以及它们的大小和颜色。一种方法是为花朵对象添加 sizecolor 属性。在这个项目中,我们还希望花朵出现一段时间后消失。为此,我们将为花朵对象添加一个 lifespan 属性。

我们希望调用 createFlower() 时能返回信息,就像内置的 text() 函数会在画布上显示文本一样。因此,我们必须指定一个返回值。使用返回值是将函数计算出的信息保存到变量或数组中的好方法。

在这个例子中,我们将使用 random() 来为绘制在画布上的花朵创造多样性。

  • 定义一个 createFlower() 函数,它会创建一个随机花朵对象。每次调用 createFlower() 时,所有值都会随机设置。

    • draw()setup() 函数之外,添加以下函数声明:

      // 创建随机 flower 对象的函数。
      function createFlower() {
        // 定义 flower 对象。
        let flower = {
          x: random(20, 380),
          y: random(20, 380),
          size: random(20, 75),
          lifespan: random(255, 300),
          color: color(random(255), random(255), random(255)),
        };
        // 返回 flower 对象。
        return flower;
      }
  • 使用 createFlower() 函数创建一个花朵对象,并将其存储在名为 myFlower 的变量中,以此测试该函数。

    • draw() 函数中添加以下代码:

      // 创建 flower 对象。
      let myFlower = createFlower();
  • 使用 myFlower 对象的属性在画布上绘制一个椭圆。

    • draw() 函数中添加以下代码:  

      // 使用 myFlower 对象的属性绘制椭圆。
      fill(myFlower.color);
      ellipse(myFlower.x, myFlower.y, myFlower.size);
  • 通过将帧率设置为每秒一帧,降低每朵花出现的速度。在 setup() 函数中,添加: frameRate(1);

你的代码应如下所示:

在上面的代码中,你使用 createFlower() 创建并返回一个具有随机值的新花朵对象,然后将该对象存储在变量 myFlower 中。随后可以通过 myFlower 访问这些随机生成的属性值,并用它们在画布上绘制椭圆。每次运行 draw() 时,都会创建一个新的随机花朵对象,并使用该对象的属性在画布上绘制一个新的随机椭圆。

访问 p5.js 参考文档,了解有关 return 返回值和 random() 的更多信息。

试试这个!
  • 更改花朵在 x 和 y 坐标系中的位置和大小的随机范围。
  • 在颜色生成方面发挥创意;例如,可以只使用蓝色、红色或紫色等色调。

示例 

步骤 4:定义带参数的 drawFlower() 函数

既然我们知道 createFlower() 会创建并返回一个新的花朵对象,接下来让我们定义 drawFlower() 函数。我们的 drawFlower() 函数会接收一个花朵对象作为参数,并将其绘制到画布上。这有助于使我们的代码更清晰易懂。

  • 在所有其他函数之外,定义一个 drawFlower() 函数,它接收一个花朵对象作为参数。

    function drawFlower(flower) { }
  • drawFlower() 函数内部,画出花朵的各个部分,包括花瓣和花心。请记住使用花朵对象的 xy 坐标、sizecolor 属性。你可以将以下代码添加到函数体中:

    noStroke();
    fill(flower.color);
    
    // 绘制花瓣。
    ellipse(flower.x, flower.y, flower.size / 2, flower.size); 
    ellipse(flower.x, flower.y, flower.size, flower.size / 2);
    
    // 绘制黄色花心。
    fill(255, 204, 0);
    circle(flower.x, flower.y, flower.size / 2);
  • 接下来,我们将测试 drawFlower() 函数。请将以下代码添加到 draw() 函数中:

    // 测试 drawFlower() 函数。
    let flower1 = createFlower();
    drawFlower(flower1);

你的代码应该类似于这样:

在上面的代码中,createFlower() 用于创建并返回一个随机花朵,该花朵存储在 flower1 中。然后,把 flower1 传给 drawFlower(),它会读取花朵对象的属性,在画布上画出花朵。每次运行 draw() 函数时,这个过程都会重复,因此画布上的花朵会随机出现和消失。

试试这个!

使用其他二维形状更改和修改花朵图案:

示例

步骤 5:学习使用数组

在开始使用数组创建花园之前,让我们先通过几个简单的例子更好地理解数组的工作原理。JavaScript 数组是一种对象,它可以在一个变量名中存储多个值。数组中的每个值都是一个元素,每个元素都有一个特定的位置,称为索引。任何JavaScript 数组中第一个元素索引始终为 0。

示例 1:创建并绘制花名数组

let flowers = [
"Rose"
, 
"Daisy"
, 
"Tulip"
];
012

在这个例子中,我们使用方括号([ ])和逗号创建了一个数组,逗号用于分隔数组中的每个元素。该数组是一个字符串集合,每个字符串代表一种花朵,并存储在名为 flowers 的变量中。数组中的每个元素都包含一个花朵名称的字符串。可以通过索引号访问每个元素。例如, "Rose" 位于索引 0"Daisy" 位于索引 1"Tulip" 位于索引 2

用于保存元素的变量名要访问的数组数组中的索引
let 
   rose   
 = 
flowers
[
   0   
];

在上面的代码片段中,我们使用索引表示法 flowers[0] 访问了 flowers 数组中索引为 0 的第一个元素。在这个例子中,我们使用这种语法在画布上放置文本,文本名称取自 flowers 数组中的各个元素。

然后,我们将该值赋给一个名为 rose 的新变量。rose 现在存储的是字符串 "Rose",也就是 flowers 数组中的第一个花名。其他元素中存储的值也可以用同样的方法访问。

示例 2:使用 for 循环遍历花名数组

我们的第二个例子使用 for 循环遍历数组中的每个元素,并将文本绘制到画布上。遍历是指对数组中的每个元素重复执行一段代码的过程。

从索引 0 开始……直到数组末尾
for (
let i = 0;
 
i < flowers.length
; i += 1) {
text(
flowers[i]
, 10, 50+(50 * i));
获取索引 i 处的元素
}

为了遍历数组,上面的代码片段使用了一个 for 循环来遍历 flowers 数组中的每个元素。在这个 for 循环中,索引 i0 开始,每轮加 1,直到达到比数组元素个数少 1 的数为止——因为数组索引从 0 开始,最后一个元素的索引就是元素个数减 1。可以用 array.length 属性获取任何数组的元素个数。在上面的 for 循环中,我们使用 flowers.length 获取 flowers 数组的长度。为了访问数组中的值,我们在 for 循环中使用 flowers[i],并在 text() 函数中将每个元素的值以文本形式显示在画布上。

示例 3:向数组中添加新元素

let flowers = ["Rose", "Daisy", "Tulip"
        
];
新元素会添加到这里
flowers
.push("Sunflower");
将新元素添加到 flowers 数组末尾

使用数组方法 array.push(),你可以将新元素添加到数组末尾。在本例中,我们将 "Sunflower" 添加到 flowers 数组的末尾。

示例 4:删除数组元素

let flowers = [
"Rose"
, 
"Daisy"
, 
"Tulip"
];
012
flowers.splice(
   0   
, 
   1   
);
从这个索引开始……删除一个元素

要从数组中删除元素,我们使用 array.splice() 函数。array.splice() 函数接收两个参数来修改 flowers 数组:一个是起始索引,另一个是要删除的元素数量。例如,flowers.splice(0,1) 会从数组中删除 Rose 元素,只留下 DaisyTulip 元素。

示例 5:使用 for..of 循环遍历数组

声明一个变量来存储当前元素遍历数组的关键字要遍历的数组
for (
let flower
 
   of   
 
flowers
) {
print(
   flower   
);
对当前元素执行操作
}

我们还可以使用 for..of 循环来遍历数组。在循环过程中,flower 变量会临时存储每个元素。循环中的 print(flower) 语句会将每个花名依次打印到控制台,即“Rose”、“Daisy”和“Tulip”。

当代码变得更加复杂时,使用 for..of 循环可以使代码更易于阅读,例如以下示例:使用索引的 for 循环for..of 循环

步骤 6:使用数组生成多朵花

现在,是时候使用数组在屏幕上生成多朵花了。

 

  • 首先,在程序顶部声明 flowers 数组。空方括号 [] 表示一个空的 flowers 数组。

    // 创建 flowers 数组。
    let flowers = [];
  • 在所有其他函数之外,定义 flowerPower() 函数。

    // 创建 20 个花朵对象的函数。
    function flowerPower() {}
  • flowerPower() 函数内部,使用 for 循环创建 20 个花朵对象,并将它们添加到 flowers 数组中。

    for(let i = 0; i < 20; i += 1) {
      // 在随机位置创建一朵花。
      let flower = createFlower();
      // 将花朵添加到 flowers 数组中。
      flowers.push(flower);
    }
  • setup() 函数中调用 flowerPower() 函数来创建 20 个花朵对象。

    // 生成 20 个花朵对象。
    flowerPower();
  • draw() 函数中使用 for..of 循环遍历数组,绘制 flowers 数组中的每朵花。for..of 循环的意思是,对于 flowers 数组中的每个花朵对象,调用 drawFlower() 函数。

    // 遍历 flowers 数组中的每一朵花。
    for (let flower of flowers) {
      drawFlower(flower);
    }

你的代码应该类似于这样:

试试这个!

在你的 flowerPower() 函数中添加一个参数,这样就可以方便地指定初始花朵数量。

示例

步骤 7:更新数组中的花朵

现在我们已经测试了 flowers 数组,让我们绘制凋谢效果。使用数组控制花朵的好处之一是能够更新每朵花中的值。

  • 在所有其他函数之外,定义一个名为 updateAndDrawFlowers() 的函数:

    function updateAndDrawFlowers() {}
  • 这个函数会遍历数组中的每个花朵对象,并逐一绘制。

    for (let flower of flowers) {
       drawFlower(flower);
    }
  • for 循环内,减小花朵对象的 sizelifespan 以模拟凋谢。

    添加一个凋谢动画效果,在 drawFlower() 函数绘制完花朵后,花朵尺寸会缩小 1%。我们把当前尺寸乘以 0.99 再存回花朵的 size 属性,以此实现凋谢效果。然后把花朵的生命周期减 1,同样存回花朵的 lifespan 属性。使用乘法赋值运算符递减运算符,语法如下:

    // 让花朵逐渐凋谢。
    flower.size *= 0.99;
    
    // 减少生命周期。
    flower.lifespan -= 1
  • 当花朵对象的生命周期为零或负数时,我们将使用 array.splice() 方法将其从数组中移除。

    for (let flower of flowers) {
      // 绘制花朵。
      drawFlower(flower);
    
      // 让花朵逐渐凋谢。
      flower.size *= 0.99;
    
      // 减少生命周期。
      flower.lifespan -= 1
    
      if (flower.lifespan <= 0) {
        // 保存花朵的索引。
        let i = flowers.indexOf(flower);
    
        // 移除已凋谢的花朵。
        flowers.splice(i, 1); 
      }
    }
  • draw() 函数中添加 updateAndDrawFlowers()

  • setup() 函数中移除对 frameRate(1) 的调用。

你的代码应该类似于这样:

试试这个!

尝试用减法、随机数或任何你能想到的其他方法来模拟凋谢效果。

示例

步骤 8:使用 mousePressed() 向数组添加更多花朵

最后,让我们使用 mousePressed()mouseXmouseY 向画布添加更多花朵。每次点击画布时,都会向花朵数组中添加一个新的花朵对象。

  • 在所有其他函数之外,编写你的 mousePressed() 函数:

    function mousePressed() {
      let flower = createFlower();
    
      // 将 x 重新赋值为 mouseX。
      flower.x = mouseX; 
      
      // 将 y 重新赋值为 mouseY。
      flower.y = mouseY;
    
      // 将花朵添加到 flowers 数组中。
      flowers.push(flower);
    }

你的代码应该类似于这样:

试试这个!
  • 当鼠标按下时,在 mouseXmouseY 位置周围添加多朵花。(示例)
  • 尝试不同的花朵形状或颜色。(示例)
  • 向花园中添加新的元素,例如蜜蜂或蝴蝶,并观察它们如何与花朵互动。(示例)

总结

恭喜你完成数据结构花园教程!

在本教程中,你学习了 JavaScript 对象和数组的基本概念,并用它们创建了一个动态、可交互的花园。使用数组可以帮助你高效管理多个对象及其各自的属性和行为。这是编程中一项很实用的技能,也为之后构建更复杂的交互程序打下基础。

下一步

再次祝贺你完成了本教程。继续尝试,继续编码,最重要的是,享受学习和创作的乐趣!

资源