写给设计师的 Processing 编程指南(11)-数据储物箱

这节将会介绍一个在  Processing 中非常重要的概念 - 数组。之前储存数据,都会用到 int ,float 这类变量。假设我们有 100 个数据想保存,用老方法就会非常繁琐。下面介绍的数组可以用更便捷的方式存放更多数据。它就有点像一排带有数字编号的箱子,根据编号可以存取需要的信息。

下面先介绍数组的基本语法

数组的声明与创建

数组声明时,必须指定一个储存的数据类型。尽管它能装多个数据,但数据类型必须一致。例如只储存整型数据的整型数组,只储存浮点型数据的浮点型数组。

下面是数组的声明格式:

数据类型 []变量名;

当你希望声明一个整形数组,就可以这么写。数据类型后跟一个方括号。

int[] data;

声明完之后,还不能直接使用。而是需要先对数组进行初始化,初始化的格式为

new 数据类型[数组长度];

因而,声明了以后可以这么写

data = new int[5];

初始化时需要同时指定数组的长度,不能忽略这个步骤,否则就会出错。给数组指定长度,有点像搬家时买纸箱。得先估计物品的多少来决定箱子的个数。除了可以声明了以后再初始化,也能在一行代码中直接完成。

int[] data = new int[5];

当完成这些步骤以后。就可以开始往里面放东西了。通过方括号“[]”和下标就能对数组元素进行读取或者写入。下标相当于是箱子的编号。数组的下标都是从 0 开始的,所以第一个元素的下标为 0。第二个元素下标为 1。以此类推,最后一个元素的下标则为“ 数组长度 - 1 ”。

下标从 0 开始是约定俗成的,虽然有些违反直觉,但只要多用就会熟悉。

熟悉下标后,若要对数组的一个元素进行赋值,可以这样写

data[0] = 10;

如果要读取其中的数据,验证是否有正常保存,可以用 println 来测试。

println(data[0]); // 输出结果为 10

有关数组的基本语法先介绍到这里,下面看一个完整的实例

代码示例(11-1):

int []numbers;
void setup() {
  numbers = new int[5];
  numbers[0] = 10;
  numbers[1] = 20;
  numbers[2] = 30;
  numbers[3] = 40;
  numbers[4] = 50;
  println(numbers[0]);
  println(numbers[1]);
  println(numbers[2]);
  println(numbers[3]);
  println(numbers[4]);
  println(numbers.length);
}

  • 这个例子里创建了一个大小为 5 的整形数组,并对数组元素进行了写入和输出。

  • 末尾的“ numbers.length ” 可以获取数组的大小。格式为” 变量名.length “。

  • 下标不能小于 0 或者大于等于数组的长度。在此例中不能写 numbers[-1] 或 numbers[5],否则程序就会报错

除此以外,赋值还有另外一种方法

int []numbers = {10,20,30,40,50};

这样就无需先指定数组的大小。只要在大括号中,依序写上各个元素,并用逗号隔开即可。

最后,值得注意的是。若是数组声明后,不进行赋值,int 类型的元素值默认都为 0。当类型为 float 时,默认值为 0.0。当类型为 String 时,默认值为 null。它表示空值,代表里面没有任何东西。

用 for 循环对Processing赋值

有些时候我们不希望手动地对每个数组元素进行赋值,这时就要用到 for 循环。下面的例子就结合了随机函数,让每个元素在初始化时都赋上随机值。

代码示例(11-2):

float []numbers;
void setup() {
  numbers = new float[5];
  for (int i = 0; i < numbers.length; i++) {
    numbers[i] = random(100);
  }
  for (int i = 0; i < numbers.length; i++) {
    println(numbers[i]);
  }
}

输出结果:

代码说明:

  • 在 for 循环的终结条件中,用到了 numbers.length 。这是一个比较常规的写法。虽然也能直接写成 5。但这么做的好处是,即使前面修改了数组的大小,后面都能保证写入或输出所有的数据。而无需重新修改终结条件的数值。

数组储存人物信息

假如我们希望储存不同类型的数据,如人物信息,货物价格。就可以考虑用不同类型的数组 来储存这些数据。

代码示例(11-3):

String[] name;
boolean[] gender;
float[] heights;
int[] age;

void setup() {
  name = new String[]{"Mike", "Jake", "Kate"};
  gender = new boolean[]{true, true, false};
  heights = new float[]{0.98, 1.34, 1.7};
  age = new int[]{5, 10, 18};
  for (int i = 0; i < 3; i++) {
    println("name:" + name[i]+" gender:" + gender[i] + " height:" + heights[i] + " age:" + age[i]);
  }
}

运行结果:

  • 作为“人”这一个体,会有名字,身高,性别这些与之关联的属性。但由于这些属性都是不同的类型。我们不能把它都放到一个数组里。像名字显然属于字符,会用到 String。性别非男即女,可以考虑用boolean。年龄通常是整数,所以用 int。身高一般包含小数,所以用 float。

  • 只要把相应人物的各类属性依序录入。就能通过同一个下标。取出对应人物的属性,获得我们想要的结果。在这个程序中,在同一行内就会分别输出姓名,性别,身高,年龄。

数组绘制随机圆点

下面开始抛开抽象的字符,尝试在图形世界中使用数组。

前面的章节,提到过可以在 randomSeed 在画面上画随机的原点。通过 randomSeed 可以不用创建变量,但这样做显然会有局限。这里学习了数组以后,就能够用简便的方法把每个点的坐标都记录下来。

代码示例(11-4):

float []pointsX, pointsY, radiuses;
int num;

void setup() {
  size(700, 700);
  num = 50;
  pointsX = new float[num];
  pointsY = new float[num];
  radiuses = new float[num];
  for (int i = 0; i < num; i++) {
    pointsX[i] = random(width);
    pointsY[i] = random(height);
    radiuses[i] = random(20, 60);
  }
}

void draw() {
  background(0);
  noStroke();
  for (int i = 0; i < num; i++) {
    ellipse(pointsX[i], pointsY[i], radiuses[i], radiuses[i]);
  }
}

运行效果:

  • 对照前面的例子,可以用数组实现完全相同的结果

  • 这里创建了两个数组 – pointsX 和 pointsY。因此能分别保存圆点的横纵坐标。

对随机点进行连线

有人可能会有疑惑,既然 randomSeed 也能实现同样效果。那为什么要独立地储存坐标点?下面的例子就通过数组,调取前后两个点的数据,并进行连线。如果是用 randomSeed 的方式,是无法知道上一个 random 产生的数值是什么。因为每次绘图都是用完即弃,并没有用某个容器把数据装起来。

代码示例(11-5):

float []pointsX, pointsY, radiuses;
int num;

void setup() {
  size(700, 700);
  num = 50;
  pointsX = new float[num];
  pointsY = new float[num];
  radiuses = new float[num];
  for (int i = 0; i < num; i++) {
    pointsX[i] = random(width);
    pointsY[i] = random(height);
    radiuses[i] = random(4, 14);
  }
}

void draw() {
  background(0);
  noStroke();
  for (int i = 0; i < num; i++) {
    ellipse(pointsX[i], pointsY[i], radiuses[i], radiuses[i]);
    if (i > 0) {
      stroke(255);
      line(pointsX[i], pointsY[i], pointsX[i - 1], pointsY[i - 1]);
    }
  }
}

运行效果:

代码说明:

  • 绘制直线可以用 line 函数,前后两组坐标分别表示线的两个端点。假如我们想将数组 中的前后两个坐标相连,第一个坐标下标为 i,那前一个坐标自然就是 i – 1。

  • 用 if 语句并把判断条件写成 i > 0 ,是为了保证下标不越界,程序不出错。因为当 i = 0 时,pointX[ i – 1 ] 的下标就为 – 1 。因而需要多加一个判断,跳过当 i = 0 的情况。

小技巧:

在上例的基础上。我们可以创建一个局部变量 showNum 来影响 for 循环的终结条件。最终产生一个连线的动画效果。setup 函数可保持不变,将 draw 函数替换成如下代码

void draw() {
  background(0);
  noStroke();
  int showNum = int(millis()/1000.0 * 5);
  if (showNum > num) {
    showNum = num;
  }
  for (int i = 0; i < showNum; i++) {
    ellipse(pointsX[i], pointsY[i], radiuses[i], radiuses[i]);
    if (i > 0) {
      stroke(255);
      line(pointsX[i], pointsY[i], pointsX[i - 1], pointsY[i - 1]);
    }
  }
}

运行效果:

代码说明:

  • 程序之所以产生动画,与 showNum 的数值有关

  • 在创建 showNum 时,它就是一个随着程序运行时间越长,数值不断增大的变量。在这里每过一秒,数值就会增大 5。

  • 最开始 for 循环的终结条件写的是” i < num “,这会将数组 中的点一并画出来。但如果取的是一个变化的数值。就可以从下标为 0 的元素开始,一点一点地展示数据

  • 之后若想制作一个画板,播放绘画轨迹。就能用类似的方式去实现。

用beginShape,endShape 画折线

前面的例子结合了 if 条件语句。逐条绘制了直线。下面将介绍一个方法,会让写法更简洁。先看基本实例

代码示例(11-6):

void setup() {
  size(700, 700);
}

void draw() {
  background(0);
  beginShape();
  vertex(350,100);
  vertex(100,600);
  vertex(600,600);
  endShape();
}

运行结果:

代码说明:

  • 在绘制图形时,会用到beginShape 和 endShape  进行包裹。beginShape 放开头,endShape 放末尾

  • 其中vertex 函数放在两者之间,它表示顶点,其中的两个参数作为点的横纵坐标。

  • 程序中写了三个 vertex 函数,因而会把三个顶点依序连起来

由于没有写 noFill 函数,所以绘制的结果会是填充图形,而不是折线。只要在其中加上

noFill();
stroke(255);

就能只显示轮廓,也就成了折线。

掌握了这种写法,例 11-5 的 draw 函数部分就能写成。

void draw() {
  background(0);
  noStroke();
  beginShape();
  for (int i = 0; i < num; i++) {
    ellipse(pointsX[i], pointsY[i], radiuses[i], radiuses[i]);
     vertex(pointsX[i], pointsY[i]);
  }
  endShape();
}

运行结果是一样的:

数组绘制彩色方块

下面将介绍 color型数组 的使用方法,先看示例

代码示例(11-7):

float[] rectsW, rectsH;
color[] colors;
int num;

void setup() {
  size(700, 700);
  num = 150;
  rectsW = new float[num];
  rectsH = new float[num];
  colors = new color[num];
  for (int i = 0; i < num; i++) {
    rectsW[i] = random(200);
    rectsH[i] = random(600);
    colors[i] = color(random(255), random(255), random(255));
  }
}

void draw() {
  background(0);
  rectMode(CENTER);
  blendMode(ADD);
  for (int i = 0; i < num; i++) {
    float alpha = mouseX/(float)width * 255;
    fill(colors[i], alpha);
    float x = width/(float)num * i;
    rect(x, height/2, rectsW[i], rectsH[i]);
  }
}

运行效果:

代码说明:

  • color 在声明时和其他类型并没有差别。但赋值时要注意单位,不能省略掉 color()。它必须是作为 color 型的数据,才能被 存储 到数组中。

  • 写程序时要考虑哪些数据需要用数组储存。若有固定规律可以不用。在这个例子中,矩形的纵坐标都是固定的,而横坐标可以用 i 推算。因此就不需要用额外的数组来储存。

  • 使用加色模式,可以让图形叠加后,更容易产生辉光效果。这里创建了局部变量 alpha,通过移动鼠标就能改变色彩的透明度

若去掉加色模式,就能看到矩形的原始色彩效果

数组在圆周运动的应用(1)

前面有介绍过如何用三角函数来制作圆周运动。单个物体作圆周运动,就需要创建三个变量来分别保存角度,横坐标与纵坐标。若想创建多个物体,就可以考虑用数组。这样每个属性都能独立控制。

代码示例(11-8):

float[] circlesX, circlesY, speed, angle;
int num;

void setup() {
  size(700, 700);
  num = 130;
  circlesX = new float[num];
  circlesY = new float[num];
  speed = new float[num];
  angle = new float[num];

  for (int i = 0; i < num; i++) {
    speed[i] = random(0.002, 0.03);
  }
}

void draw() {
  background(0);
  translate(width/2,height/2);
  for (int i = 0; i < num; i++) {
    float r = 30 + i * 2;
    angle[i] += speed[i];
    circlesX[i] = r * cos(angle[i]);
    circlesY[i] = r * sin(angle[i]);
    ellipse(circlesX[i], circlesY[i], 8,8);
    if (i > 0) {
      stroke(255);
      line(circlesX[i], circlesY[i], circlesX[i - 1], circlesY[i - 1]);
    }
  }
}

运行效果:

代码说明:

  • 因为希望每个圆的运动速度都有区别,所以创建了 speed 变量来独立储存速度信息

还可以用之前提到的beginShape,endShape 来画折线。结果都是一样的。但对于用beginShape,endShape 画的图形,若开启填充效果,就会有非常特别的效果。试着把上例的 draw 函数按如下方式替换。

void draw() {
  background(0);
  translate(width/2, height/2);
  noStroke();
  beginShape();
  for (int i = 0; i < num; i++) {
    float r = 30 + i * 2;
    angle[i] += speed[i];
    circlesX[i] = r * cos(angle[i]);
    circlesY[i] = r * sin(angle[i]);
    ellipse(circlesX[i], circlesY[i], 8, 8);
    vertex(circlesX[i], circlesY[i]);
  }
  endShape();
}

运行效果:

这样就能将绘制的折线变成多边形。由于程序中会自动把重叠的部分进行反色处理,因而产生了上面的效果。

数组在圆周运动的应用(2)

基于上面的代码结构,并对参数进行一些修改。试着 将 draw 函数中的 background 写在 setup 函数中试试。

代码示例(11-9):

float[] circlesX, circlesY, speed, angle;
int num;

void setup() {
  size(700, 700);
  num = 5;
  circlesX = new float[num];
  circlesY = new float[num];
  speed = new float[num];
  angle = new float[num];
  for (int i = 0; i < num; i++) {
    speed[i] = random(0.002, 0.03);
  }
  background(0);
}

void draw() {
  translate(width/2, height/2);
  noFill();
  stroke(255,50);
  beginShape();
  for (int i = 0; i < num; i++) {
    float r = 30 + i * 60;
    angle[i] += speed[i];
    circlesX[i] = r * cos(angle[i]);
    circlesY[i] = r * sin(angle[i]);
    ellipse(circlesX[i], circlesY[i], 8, 8);
    vertex(circlesX[i], circlesY[i]);
  }
  endShape();
}

点的运动轨迹:

实际运行效果:


仅有黑白二色难免单调。结合前面提到的色彩模式,可以让图形产生富有层次变化的效果。试着替换着色部分的代码

 blendMode(ADD);
colorMode(HSB);
stroke(int(millis()/50.0) % 255,255,255,10);

运行效果:

关于数组还有更多高级用法,后面的时间就交给大家尽情探索~

未经允许不得转载:前端撸码笔记 » 写给设计师的 Processing 编程指南(11)-数据储物箱

上一篇:

下一篇: