如何为 Processing 中绘制的形状添加交互和动画?

How can add interaction and animation to shapes drawn in Processing?

我正在尝试编写一个 canvas 充满形状(房屋)的代码,并在处理过程中为它们制作动画。

这是一个形状示例:

void house(int x, int y) { 
  pushMatrix(); 
  translate(x, y); 
  fill(0, 200, 0); 
  triangle(15, 0, 0, 15, 30, 15); 
  rect(0, 15, 30, 30); 
  rect(12, 30, 10, 15); 
  popMatrix();
}

我所说的动画是指在随机方向移动它们。

我还想添加基本交互:将鼠标悬停在房子上时,它的颜色会发生变化。

目前我已经设法渲染 canvas 满屋子:

void setup() { 
  size(500, 500); 
  background(#74F5E9); 
  for (int i = 30; i < 500; i = i + 100) { 
    for (int j = 30; j < 500; j = j + 100) { 
      house(i, j);
    }
  }
} 
void house(int x, int y) { 
  pushMatrix(); 
  translate(x, y); 
  fill(0, 200, 0); 
  triangle(15, 0, 0, 15, 30, 15); 
  rect(0, 15, 30, 30); 
  rect(12, 30, 10, 15); 
  popMatrix();
}

在没有看到源代码的情况下:很难判断您尝试的草图。

它们可以通过多种方式进行动画处理,不清楚您的意思。比如是每个方格的position/rotation/scale,还是每个方格的corners/vertices,都是?

你脑子里可能有一个清晰的想法,但是现在的问题形式是模棱两可的。我们也不知道您对 classes/objects/PVector/PShape/etc 等各种概念的接受程度。如果你要 'story board' 这个动画会是什么样子?将问题分解并以任何人都能理解的方式进行解释实际上也可以帮助您自己找到解决方案。

处理有很多例子。根据我对您的问题的理解,这里有一些我认为相关的内容。

您可以查看对象和创建形状示例:

  1. 文件 > 示例 > 基础知识 > 对象 > 对象:演示分组 drawing/animation(缓动、阻尼)。您可以调整此示例绘制一个正方形,一旦您对 look/motion 感到满意,您可以使用数组或 ArrayList
  2. 对多个进行动画处理
  3. 文件 > 示例 > 主题 > 创建形状 > PolygonPShapeOOP3:使用 PShape 制作对象动画的好例子。
  4. File > Examples > Topics > Create Shapes > WigglePShape:此示例演示如何访问和修改 PShape
  5. 的顶点

作为参考,我只是copy/pasting上面提到的例子:

对象

/**
 * Objects
 * by hbarragan. 
 * 
 * Move the cursor across the image to change the speed and positions
 * of the geometry. The class MRect defines a group of lines.
 */

MRect r1, r2, r3, r4;
 
void setup()
{
  size(640, 360);
  fill(255, 204);
  noStroke();
  r1 = new MRect(1, 134.0, 0.532, 0.1*height, 10.0, 60.0);
  r2 = new MRect(2, 44.0, 0.166, 0.3*height, 5.0, 50.0);
  r3 = new MRect(2, 58.0, 0.332, 0.4*height, 10.0, 35.0);
  r4 = new MRect(1, 120.0, 0.0498, 0.9*height, 15.0, 60.0);
}
 
void draw()
{
  background(0);
  
  r1.display();
  r2.display();
  r3.display();
  r4.display();
 
  r1.move(mouseX-(width/2), mouseY+(height*0.1), 30);
  r2.move((mouseX+(width*0.05))%width, mouseY+(height*0.025), 20);
  r3.move(mouseX/4, mouseY-(height*0.025), 40);
  r4.move(mouseX-(width/2), (height-mouseY), 50);
}
 
class MRect 
{
  int w; // single bar width
  float xpos; // rect xposition
  float h; // rect height
  float ypos ; // rect yposition
  float d; // single bar distance
  float t; // number of bars
 
  MRect(int iw, float ixp, float ih, float iyp, float id, float it) {
    w = iw;
    xpos = ixp;
    h = ih;
    ypos = iyp;
    d = id;
    t = it;
  }
 
  void move (float posX, float posY, float damping) {
    float dif = ypos - posY;
    if (abs(dif) > 1) {
      ypos -= dif/damping;
    }
    dif = xpos - posX;
    if (abs(dif) > 1) {
      xpos -= dif/damping;
    }
  }
 
  void display() {
    for (int i=0; i<t; i++) {
      rect(xpos+(i*(d+w)), ypos, w, height*h);
    }
  }
}

PolygonPShapeOOP3:

/**
 * PolygonPShapeOOP. 
 * 
 * Wrapping a PShape inside a custom class 
 * and demonstrating how we can have a multiple objects each
 * using the same PShape.
 */


// A list of objects
ArrayList<Polygon> polygons;

// Three possible shapes
PShape[] shapes = new PShape[3];

void setup() {
  size(640, 360, P2D);
  
  shapes[0] = createShape(ELLIPSE,0,0,100,100);
  shapes[0].setFill(color(255, 127));
  shapes[0].setStroke(false);
  shapes[1] = createShape(RECT,0,0,100,100);
  shapes[1].setFill(color(255, 127));
  shapes[1].setStroke(false);
  shapes[2] = createShape();  
  shapes[2].beginShape();
  shapes[2].fill(0, 127);
  shapes[2].noStroke();
  shapes[2].vertex(0, -50);
  shapes[2].vertex(14, -20);
  shapes[2].vertex(47, -15);
  shapes[2].vertex(23, 7);
  shapes[2].vertex(29, 40);
  shapes[2].vertex(0, 25);
  shapes[2].vertex(-29, 40);
  shapes[2].vertex(-23, 7);
  shapes[2].vertex(-47, -15);
  shapes[2].vertex(-14, -20);
  shapes[2].endShape(CLOSE);

  // Make an ArrayList
  polygons = new ArrayList<Polygon>();
  
  for (int i = 0; i < 25; i++) {
    int selection = int(random(shapes.length));        // Pick a random index
    Polygon p = new Polygon(shapes[selection]);        // Use corresponding PShape to create Polygon
    polygons.add(p);
  }
}

void draw() {
  background(102);

  // Display and move them all
  for (Polygon poly : polygons) {
    poly.display();
    poly.move();
  }
}

// A class to describe a Polygon (with a PShape)

class Polygon {
  // The PShape object
  PShape s;
  // The location where we will draw the shape
  float x, y;
  // Variable for simple motion
  float speed;

  Polygon(PShape s_) {
    x = random(width);
    y = random(-500, -100); 
    s = s_;
    speed = random(2, 6);
  }
  
  // Simple motion
  void move() {
    y+=speed;
    if (y > height+100) {
      y = -100;
    }
  }
  
  // Draw the object
  void display() {
    pushMatrix();
    translate(x, y);
    shape(s);
    popMatrix();
  }
}

摆动形状:

/**
 * WigglePShape. 
 * 
 * How to move the individual vertices of a PShape
 */


// A "Wiggler" object
Wiggler w;

void setup() {
  size(640, 360, P2D);
  w = new Wiggler();
}

void draw() {
  background(255);
  w.display();
  w.wiggle();
}

// An object that wraps the PShape

class Wiggler {
  
  // The PShape to be "wiggled"
  PShape s;
  // Its location
  float x, y;
  
  // For 2D Perlin noise
  float yoff = 0;
  
  // We are using an ArrayList to keep a duplicate copy
  // of vertices original locations.
  ArrayList<PVector> original;

  Wiggler() {
    x = width/2;
    y = height/2; 

    // The "original" locations of the vertices make up a circle
    original = new ArrayList<PVector>();
    for (float a = 0; a < radians(370); a += 0.2) {
      PVector v = PVector.fromAngle(a);
      v.mult(100);
      original.add(new PVector());
      original.add(v);
    }
    
    // Now make the PShape with those vertices
    s = createShape();
    s.beginShape(TRIANGLE_STRIP);
    s.fill(80, 139, 255);
    s.noStroke();
    for (PVector v : original) {
      s.vertex(v.x, v.y);
    }
    s.endShape(CLOSE);
  }

  void wiggle() {
    float xoff = 0;
    // Apply an offset to each vertex
    for (int i = 1; i < s.getVertexCount(); i++) {
      // Calculate a new vertex location based on noise around "original" location
      PVector pos = original.get(i);
      float a = TWO_PI*noise(xoff,yoff);
      PVector r = PVector.fromAngle(a);
      r.mult(4);
      r.add(pos);
      // Set the location of each vertex to the new one
      s.setVertex(i, r.x, r.y);
      // increment perlin noise x value
      xoff+= 0.5;
    }
    // Increment perlin noise y value
    yoff += 0.02;
  }

  void display() {
    pushMatrix();
    translate(x, y);
    shape(s);
    popMatrix();
  }
}

更新

根据您的评论,这里修改了您的草图版本,因此悬停房屋的颜色发生了变化:

// store house bounding box dimensions for mouse hover check
int houseWidth  = 30;
// 30 px rect height + 15 px triangle height
int houseHeight = 45;

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

void draw(){
  background(#74F5E9); 
  for (int i = 30; i < 500; i = i + 100) { 
    for (int j = 30; j < 500; j = j + 100) {
      // check if the cursor is (roughly) over a house
      // and render with a different color
      if(overHouse(i, j)){
        house(i, j, color(0, 0, 200));
      }else{
        house(i, j, color(0, 200, 0));
      }
    }
  }
}

void house(int x, int y, color fillColor) { 
  pushMatrix(); 
  translate(x, y); 
  fill(fillColor); 
  triangle(15, 0, 0, 15, 30, 15); 
  rect(0, 15, 30, 30); 
  rect(12, 30, 10, 15); 
  popMatrix();
}

// from Processing RollOver example
// https://processing.org/examples/rollover.html
boolean overRect(int x, int y, int width, int height) {
  if (mouseX >= x && mouseX <= x+width && 
      mouseY >= y && mouseY <= y+height) {
    return true;
  } else {
    return false;
  }
}

// check if the mouse is within the bounding box of a house
boolean overHouse(int x, int y){
  // offset half the house width since the pivot is at the tip of the house 
  // the horizontal center
  return overRect(x - (houseWidth / 2), y, houseWidth, houseHeight);
}

代码已注释,但主要内容如下:

  • house() 函数已更改,您可以指定颜色
  • overRect() 函数已从 Rollover 示例中复制
  • overHouse() 函数使用 overRect(),但添加了水平偏移以考虑到房屋是从中间顶点绘制的(房屋尖端是形状的轴心点)

关于动画,Processing有很多examples:

让我们开始以正弦运动为例。 sin() 函数取一个角度(默认为弧度)和 returns 一个介于 -1.0 和 1.0 之间的值

由于您已经在二维网格中计算每个房屋的位置,因此您可以使用 sin() 偏移每个位置以使其动画化。它的好处是循环的:无论你提供什么角度,你总是得到 -1.0 和 1.0 之间的值。这将为您省去需要将每个房屋的当前 x、y 位置存储在数组中的麻烦,以便您可以在不同的方向上递增它们。

这是使用 sin() 制作动画的上述草图的修改版本:

// store house bounding box dimensions for mouse hover check
int houseWidth  = 30;
// 30 px rect height + 15 px triangle height
int houseHeight = 45;

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

void draw(){
  background(#74F5E9); 
  for (int i = 30; i < 500; i = i + 100) { 
    for (int j = 30; j < 500; j = j + 100) {
      
      // how fast should each module move around a circle (angle increment)
      // try changing i with j, adding i + j or trying other mathematical expressions
      // also try changing 0.05 to other values
      float phase = (i + frameCount) * 0.05;
      // try changing amplitude to other values
      float amplitude = 30.0;
      // map the sin() result from it's range to a pixel range (-30px to 30px for example)
      float xOffset = map(sin(phase), -1.0, 1.0, -amplitude, amplitude);
      // offset each original grid horizontal position (i) by the mapped sin() result
      float x = i + xOffset;
      // check if the cursor is (roughly) over a house
      // and render with a different color
      if(overHouse(i, j)){
        house(x, j, color(0, 0, 200));
      }else{
        house(x, j, color(0, 200, 0));
      }
    }
  }
}

void house(float x, float y, color fillColor) { 
  pushMatrix(); 
  translate(x, y); 
  fill(fillColor); 
  triangle(15, 0, 0, 15, 30, 15); 
  rect(0, 15, 30, 30); 
  rect(12, 30, 10, 15); 
  popMatrix();
}

// from Processing RollOver example
// https://processing.org/examples/rollover.html
boolean overRect(int x, int y, int width, int height) {
  if (mouseX >= x && mouseX <= x+width && 
      mouseY >= y && mouseY <= y+height) {
    return true;
  } else {
    return false;
  }
}

// check if the mouse is within the bounding box of a house
boolean overHouse(int x, int y){
  // offset half the house width since the pivot is at the tip of the house 
  // the horizontal center
  return overRect(x - (houseWidth / 2), y, houseWidth, houseHeight);
}

通读评论并尝试调整代码以更好地理解其工作原理并从中获得不同动画的乐趣。

主要变化是:

  • 修改 house() 函数以使用浮点 x,y 位置(而不是 int):这是为了避免在使用 sin() 时将 float 转换为 intmap() 并获得更平滑的运动(而不是“捕捉”到整个像素的运动)
  • 映射正弦到可用于动画的位置

将计算 x 偏移量的 3 条指令包装到一个可重用函数中可以让您进行进一步的实验。如果您对每个房屋的 y 位置使用类似的技术会怎样? x 和 y 怎么样?

逐步查看代码。尝试理解它、改变它、破坏它、修复它并重新使用代码制作新草图。