2018년 9월 디자인과 코딩을 함께 다루는 유튜브 채널을 열었습니다. "타입과 미디어" 사이트는 당분간 유지할 계획이지만, 새로운 정보는 유튜브 비디오를 통해서 소개하겠습니다. 많은 관심 부탁드립니다! — 정대인

애니메이션

세부 목차

앞선 포스팅의 몇몇 예제에는 움직임이 포함되어 있었습니다. 프로세싱에서 움직임을 표현한다는 것은 변수를 설정해놓고, 그 변수에 매 프레임마다 변화를 가하는 것이라고 볼 수 있습니다. 변수는 도형의 위치가 될 수도 있고, 색깔이 될 수도 있습니다. 변수가 무엇을 나타내는지, 어디에 쓰이는지에 따라서 움직임과 변화의 특징이 결정됩니다.

일차원 움직임

글자의 가로축 x 위치의 좌표값을 설정하는 변수를 만들고, 그 변수의 값을 업데이트해서 움직임을 만들어봅시다. 마우스를 클릭하면 위치가 리셋됩니다.

PFont font;
float xpos;

void setup() {
  size(600, 100);
  
  font = createFont("SansSerif", 80);
  textFont(font);
  fill(0);

  xpos = 20;
}

void draw() {
  background(255);
  
  text("안녕", xpos, 80);
  
  xpos += 5;
}

void mousePressed() {
  xpos = 20;
}
Your browser does not support canvas.

위의 예제에서 draw() 안에 있는 xpos += 5;가 움직임을 만들어내는 구문입니다. 5는 매 프레임 변화치를 의미하기 때문에 속도라고 할 수 있습니다. 5를 다른 숫자로 바꿔서 실행해봅시다. 그 숫자에 따라서 글자의 움직임의 속도가 빨라지거나 느려집니다.

PFont font;
float xpos;
float xspeed;

String s = "안녕";

void setup() {
  size(600, 100);
  
  font = createFont("SansSerif", 80);
  textFont(font);
  fill(0);

  xpos = 20;
  xspeed = 5;
}

void draw() {
  background(255);
  
  text(s, xpos, 80);
  
  xpos += xspeed;
  if (xpos >= width) {
    xpos = -textWidth(s);
  }
}
Your browser does not support canvas.

위의 예제는 첫번째 예제와 거의 비슷하지만, xspeed라는 변수를 새로 만들었고, 조건문을 사용해서 글자가 화면 바깥으로 벗어나면 화면 왼쪽에서 다시 등장하도록 하였습니다.

아래 예제는 글자가 양쪽 모서리에 닿으면, 방향을 바꾸도록 조건문을 만들었습니다. 조건문은 xpos가 양쪽 모서리에 닿았는지 검사합니다. 모서리에 닿았을 경우에는 xspeed, 즉 방향을 반대로 바꿔줍니다.

PFont font;
float xpos;
float xspeed;

String s = "안녕";

void setup() {
  size(600, 100);

  font = createFont("SansSerif", 80);
  textFont(font);
  fill(0);

  xpos = 20;
  xspeed = 5;
}

void draw() {
  background(255);

  text(s, xpos, 80);

  xpos += xspeed;
  if (xpos >= width - textWidth(s) || xpos <= 0) {
    xspeed = xspeed * -1;
  }
}
Your browser does not support canvas.

이차원 움직임

2차원 움직임은 1차원과 같지만, xy축의 좌표를 모두 업데이트하면 됩니다.

PFont font;
float xpos;
float ypos;

float xspeed;
float yspeed;

String s = "안녕";

void setup() {
  size(600, 200);

  font = createFont("SansSerif", 80);
  textFont(font);
  fill(0);

  xpos = 20;
  ypos = 100;
  
  xspeed = 5;
  yspeed = 3;
}

void draw() {
  background(255);

  text(s, xpos, ypos);

  xpos += xspeed;
  ypos += yspeed;

  if (xpos >= width - textWidth(s) || xpos <= 0) {
    xspeed = xspeed * -1;
  }
  if (ypos >= height || ypos <= 60) {
    yspeed = yspeed * -1;
  }
}

void mousePressed() {
  xspeed = random(2, 10);
  yspeed = random(2, 10);
}
Your browser does not support canvas.

lerp()

‘lerp’는 linear interpolation의 줄임말입니다. 우리말로는, 선형보간법이라고 합니다. 간단하게 말해서, 두 값의 사이값을 일정한 간격으로 구해주는 것을 말합니다. 우리의 주관심사인 화면과 시각화에 빗대서 말해보면 두 점 사이의 거리를 일정한 간격으로 나눠주는 것이라고 할 수도 있습니다.

lerp(start, stop, amount);

선형적인 움직임

lerp() 함수는 두 개의 숫자 사이의 값을 0에서 1 사이의 비율로 지정해서 구할 수 있게 해줍니다. 예를 들어서 lerp(0, 100, 0.5)라고 하면, 반환값은 0100사이의 중간값인 50.0이 반환됩니다. amount를 일정하게 늘리거나 줄여주면 선형적(linear)인 움직임을 얻을 수 있습니다.

float x;
float from;
float to;
float amt;
int direction = 1;

void setup() {
  size(600, 200);
  fill(0);

  from = 20;
  to = 200;
}

void draw() {
  background(255);

  beginShape();
  vertex(100, 40);
  vertex(160, 40);
  vertex(160, 120);
  vertex(150, 120);
  vertex(150, 50);
  vertex(100, 50);
  endShape(CLOSE);

  rect(180, 30, 10, 110);
  rect(190, 70, x, 10);

  x = lerp(from, to, amt);
  //x =  from + ((to - from) * amt);

  amt += 0.02 * direction;
  if ( amt >= 1 || amt <= 0) direction *= -1;
}

Your browser does not support canvas.

위의 예제에서 주석처리 된 x = from + ( (to - from) * amt ); 부분이 바로 lerp() 함수의 원리입니다. 어느 쪽을 써도 같은 결과를 얻을 수 있습니다. 프로세싱은 이렇게 많이 쓰이는 수식을 편리하게 함수로 제공하고 있습니다.

부드러운 움직임

항상 비율을 사용해서 두 숫자 사이의 값을 얻을 수 있기 때문에 start 또는 stop의 값에 따라서 똑같은 비율amount이라도 결과값은 달라집니다. 이를 이용하면 애니메이션에서 좀 더 부드러운 움직임을 얻을 수 있습니다.

PFont font;
float xpos;
float targetXpos;
String s = "안녕";

void setup() {
  size(600, 100);

  font = createFont("SansSerif", 80);
  textFont(font);
  fill(0);

  xpos = 20;
  targetXpos = width - 200;
}

void draw() {
  background(255);

  text(s, xpos, height-20);

  xpos = lerp(xpos, targetXpos, 0.1);
}

void mousePressed() {
  targetXpos = mouseX;
}
Your browser does not support canvas.

일정한 간격으로 점선 그리기

lerp() 함수는 애니메이션에만 쓰이는 것은 아닙니다. 예를 들어서, 일직선 상에 점을 일정한 간격으로 찍는 것은 간단합니다. 하지만 대각선 상에 점을 일정한 간격으로 찍어야한다면, 좀 더 복잡한 계산이 필요할 것입니다. 이 때, 반복문과 lerp()를 사용하면, 점선을 그리기가 쉬워집니다.

float x1;
float y1;
float x2;
float y2;

float px; // x position of point
float py; // y position of point
int numPoints;

void setup() {
  size(600, 100);
  stroke(0);
  strokeWeight(2);

  x1 = 20;
  y1 = 20;
  x2 = 120;
  y2 = 80;
  numPoints = 20;
}

void draw() {
  background(255);

  for (int i = 0; i < numPoints; i++) {
    px = lerp(x1, x2, (float)i/numPoints);
    py = lerp(y1, y2, (float)i/numPoints);
    point(px, py);
  }

  if (mousePressed) {
    x2 = mouseX;
    y2 = mouseY;
  }
}
Your browser does not support canvas.

random

random() 함수는 프로세싱에서 제공하는 함수 중 가장 흥미로운 함수 중 하나라고 할 수 있습니다.

random(high);
random(low, high);

1개의 인수일 때는 0과 최고값 사이, 2개의 인수를 받을 때에는 최소값과 최고값 사이에 있는 실수를 무작위로 돌려줍니다. 이렇게 돌려받은 값은 위치, 색깔, 크기 등등 실수가 쓰이는 모든 곳에서 사용될 수 있습니다. 아래의 예제는 마우스 버튼을 누르고 있는 동안, 배경색이 무작위로 바뀝니다.

void setup() {
  size(600, 100);
  background(255);
}

void draw() {
  if (mousePressed) {
    background( random(255) );
  }
}
Your browser does not support canvas.
PFont font;
float xpos;
float ypos;

String s = "짜증나";

void setup() {
  size(600, 200);
  font = createFont("SansSerif", 80);
  textFont(font);
  fill(0);

  xpos = 200;
  ypos = 100;
}

void draw() {
  background(255);
  
  if (mousePressed) {
    scale( random(0.7, 1.3), random(0.5, 1.5) );
    xpos += random(-5, 5);
    ypos += random(-5, 5);
  }
  text(s, xpos, ypos);
}
Your browser does not support canvas.
void setup() {
  size(600, 100);
  background(255);
  stroke(0);
  strokeWeight(1);
}

void draw() {
  ellipse( random(width), random(height), 30, 30);
}

void mousePressed() {
  background( random(255), random(255), random(255) );
  fill( random(255), 100, 100);
}
Your browser does not support canvas.

noise

random() 함수가 돌려주는 값은 그 이전과 그 다음의 값에 아무런 연관성이 없습니다. 그래서 앖서 살펴본 배경색을 바꾸는 예제의 경우처럼, 그 범위가 커질수록, 혼란스러운 색의 변화를 볼 수 있습니다. noise()의 경우는, 이전과 이후의 값에 연관성이 있어서 무작위의 값을 돌려주기는 하지만, 좀 더 부드러운 변화를 느낄 수 있습니다.

float val;

void setup() {
  size(600, 100);
}

void draw() {
  background( noise(val) * 255 );
  
  val += 0.02;
}
Your browser does not support canvas.

noise()가 반환하는 값은 언제나 0.0에서 1.0사이의 값입니다. 이를 색상값에 바로 적용하면, 그 차이가 매우 미미하므로 위의 예제에서는 255를 곱해서 0에서 255 사이의 값이 나오도록 하고 있습니다.

PFont font;
String s = "비틀비틀";
float xpos, ypos;
float xoff, yoff;

void setup() {
  size(600, 200);
  font = createFont("SansSerif", 60);
  textFont(font);
  fill(0);
  
  xpos = 200;
  ypos = 150;
}

void draw() {
  background( 255 );
  
  if (mousePressed) {
    xpos += noise(xoff) * 4 - 2;
    ypos += noise(yoff) * 4 - 2;
  }
  
  text(s, xpos, ypos);
  
  xoff += 0.2;
  yoff += 0.3;
}
Your browser does not support canvas.