여기서 다루는 벡터 수학 개념을 보면 전부 알아야 할 것 같습니다. 그러나 왜 그럴까요? 실제로 코드를 작성하는 데 어떻게 도움이 될까요? 이 점을 느끼려면 인내심이 필요할 것 같습니다. PVector 클래스를 이용하는 것이 얼마나 좋은 일인지는 나중에 알게 될 것입니다. 이는 새로운 자료 구조를 처음 배울 때 흔히 나타나는 현상입니다. 예를 들어, 배열을 처음 배울 때는 여러 정보를 나타내기 위해 여러 변수를 이용하는 것보다 배열을 이용하는 것이 더 귀찮게 보일 수 있습니다. 그러나 100개, 1000개 또는 10000개의 무엇인가를 다루다 보면 어떤 게 좋은지 바로 알게 됩니다. PVector도 마찬가지입니다. 지금은 더 귀찮은 작업처럼 보일 수 있는 것이 나중에 빛을 발할 것입니다. 바로 다음 장에서 배울 것이므로 너무 오래 기다릴 필요는 없습니다.

속도

그러나 지금은 단순함에 중점을 두고 배워봅시다. 벡터를 이용하여 움직임을 프로그램한다는 것은 무슨 의미일까요? 전에 배운 공치기 예제에서 벡터를 이용하는 것이 무슨 의미인지 잠깐 보았습니다. 화면상의 물체는 속도와(어떤 순간에서 다음 순간으로 이동하는 경로) 위치를(특정 순간에서 물체의 위치) 가지고 있습니다. 속도함수는 위치함수에 추가됩니다.
location.add(velocity);
그 후 해당 위치에 물체를 그립니다.
ellipse(location.x,location.y,16,16);
움직임의 코딩 기본 원칙은 다음과 같습니다.
  • 위치에 속도을 더합니다.
  • 위치에 물체를 그립니다.
탱탱볼 예제에서 모든 코드는 ProcessingJS의 draw 함수 안에 들어갑니다. 이제는 움직임에 대한 모든 로직을 객체(object) 로 감싸야 합니다. 이런 방식으로 모든 ProcessingJS 프로그램에서 움직이는 객체를 생성하는 토대를 만들 수 있습니다.
이 경우에는 화면에서 움직이는 어떤 물체를 나타내는 일반적인 Mover 객체를 생성합니다. 따라서 반드시 다음과 같은 두 질문을 고려해야 합니다.
  • mover는 어떤 데이터를 가지나요?
  • mover는 어떤 기능을 가지나요?
위에서 본 움직임의 코딩 기본 원칙에 의하면, Mover 객체에는 두 개의 데이터 locationvelocity가 있으며, 둘 다 PVector 객체입니다. 우선 적절한 난수값으로 프로퍼티를 초기화하는 생성자 함수를 만들어 봅시다.
var Mover = function() {
  this.position = new PVector(random(width), random(height));
  this.velocity = new PVector(random(-2, 2), random(-2, 2));
};
기능은 매우 간단합니다. Mover는 움직여야 하고, 화면에 보여야 합니다. update()display()라는 메소드를 이용하여 구현해 봅시다. 움직임과 관련된 모든 코드를 update()에 넣고 객체를 display()에서 그립니다.
Mover.prototype.update = function() {
  this.position.add(this.velocity);
};

Mover.prototype.display = function() {
  stroke(0);
  strokeWeight(2);
  fill(127);
  ellipse(this.position.x, this.position.y, 48, 48);
};
객체 지향적 프로그래밍이 새롭다면 약간 혼란스러운 부분이 있을 수 있습니다. 이 단원에서 PVector를 논의하는 것부터 시작했습니다. PVector 객체는 위치 객체와 속도 객체를 만들기 위한 템플릿입니다. 그러면 왜 다른 객체인 Mover의 안에 있는 걸까요? 그런데 지금 이 상황은 사실 매우 일상적인 코딩입니다. 간단히 말해 객체는 데이터 (및 기능) 를 가지고 있는 코드입니다. 데이터는 숫자, 문자열, 배열 또는 다른 객체일 수 있습니다! 이 과정에서는 객체를 계속해서 보게 될 겁니다. 예를 들어, 입자 단원에서는 객체를 사용하여 입자 무리를 구현할 겁니다. ParticleSystem 객체는 데이터로 Particle 객체의 배열을 가지고 있고 각 Particle 객체는 데이터로 여러 PVector 객체를 가지고 있습니다!
객체가 창의 가장자리에 도달했을 때 행동을 부여하는 함수를 통합하여 Mover 객체를 마무리하도록 하겠습니다. 지금은 간단하게 가장자리 주위를 둘러싸 봅시다:
Mover.prototype.checkEdges = function() {

  if (this.position.x > width) {
    this.position.x = 0;
  } 
  else if (this.position.x < 0) {
    this.position.x = width;
  }

  if (this.position.y > height) {
    this.position.y = 0;
  } 
  else if (this.position.y < 0) {
    this.position.y = height;
  }
};
이제 Mover 객체를 마무리하고 주 프로그램에서 해야할 것을 살펴보겠습니다. 먼저 새로운 Mover 인스턴스를 선언하고 초기화합니다.
var mover = new Mover();
다음은 draw에서 적절한 함수를 호출합니다.
draw = function() {
  background(255, 255, 255);

  mover.update();
  mover.checkEdges();
  mover.display(); 
};
이제 예제가 완성되었습니다. 숫자를 바꿔보고 코드가 어떻게 동작하는지 살펴보세요.

가속도

좋습니다. 이 시점에서 두 가지 개념과 친숙해졌을 겁니다: (1) PVector가 무엇인지 (2) 위치와 움직임에 대한 정보를 얻기 위해 객체 내에서 PVector를 어떻게 이용하는지. 이것은 훌륭한 첫 걸음이고 박수 받을만 합니다. 그러나 기립 박수와 열광을 받을 수준까지 오려면 한 가지 큰 개념을 배워야 합니다. 우리가 만든 예제는 꽤 지루합니다. 원이 절대로 빨라지거나 느려지거나 다른 방향으로 돌지 않습니다. 보다 재미있는 움직임 즉, 우리 주위의 실세계에서 볼 수 있는 움직임을 만들기 위해 Mover 객체에 또 하나의 PVector인 가속도를 추가하여야 합니다.
여기서 배울 가속도는 속도의 변화율로 정의합니다. 이 정의에 대해 잠시 생각해 봅시다. 새로운 개념인가요? 아닙니다. 속도는 위치의 변화율로 정의합니다. 이 개념은 어떻게 보면 연쇄작용과 같습니다. 가속도는 속도에 영향을 주고 마찬가지로 속도는 위치에 영향을 미칩니다. (이 사실은 다음 수업에서 힘이 가속도에, 가속도가 속도에, 속도가 위치에 영향을 미치는 것을 배울 때 더 중요해질 겁니다). 코드에서는 가속도를 다음과 같이 나타낼 수 있습니다:
velocity.add(acceleration);
location.add(velocity);
이제부터 연습으로 규칙을 하나 만듭시다. 앞으로 나오는 부분에서는 속도와 위치의 값을 (초기화를 제외하고는) 건드리지 않고 모든 예제를 작성해 보겠습니다. 다시 말하자면, 지금 움직임을 프로그래밍할 때 가속도를 계산하는 방법에 대한 알고리즘을 제시하고 연쇄작용이 일어나서 가속도를 알아서 구현하도록 만드는 것이 목적입니다. (사실, 규칙을 어겨야 하는 이유를 찾을 수도 있지만, 먼저 움직임에 대한 알고리즘의 원칙을 이해하는 것이 더 중요합니다.) 그래서 가속도를 계산하는 방법을 찾아야 합니다.
  1. 일정한 가속도
  2. 완전히 임의의 가속도
  3. 마우스 방향으로의 가속도
알고리즘 #1, 일정한 가속도 는 별로 흥미롭지는 않지만 가장 단순해서 가속도를 코드에 구현하기 시작할 때 좋습니다.
첫 번째 할 일은 Mover 생성자에 가속도를 나타낼 또 다른 PVector 프로퍼티를 추가하는 것입니다. 현재 알고리즘은 일정한 가속도이므로 값을 (0.001,0.01) (-0.001, 0.01) 로 초기화한 후 계속 유지합니다. 값이 너무 작다고 생각할 수도 있습니다. 맞습니다. 값이 아주 작습니다. 이 값은 픽셀 단위로 측정되는 스케치 프레임 비율에 따라 초당 약 30배정도 증가합니다. 그래서 속도 벡터의 크기를 합리적인 범위 내에서 유지하기 위해 가속도 값은 꽤 작은 값에서 시작하고 유지되어야 합니다.
var Mover = function() {
  this.position = new PVector(width/2,height/2);
  this.velocity = new PVector(0, 0);
  this.acceleration = new PVector(-0.001, 0.01);
  this.topspeed = 10;
};
위에서 보았듯이, 속도는 전과 마찬가지로 0에서 시작해야 프로그램이 실행되면서 가속도로 인해 속도가 점점 높아지는 것을 인지할 수 있습니다. 이는 update() 메소드에서 수행합니다:
Mover.prototype.update = function() {
  this.velocity.add(this.acceleration);
  this.position.add(this.velocity);  
};
프로그램에서 속도가 계속해서 증가하기 때문에 프로그램을 오랫동안 실행하면 속도 값이 엄청나게 커질 위험이 있습니다. 따라서 속도에 최댓값을 두어서 제한할 필요가 있습니다. PVector limit 메소드를 사용해서 벡터를 정해진 크기로 제한할 수 있습니다.
Mover.prototype.update = function() {
  this.velocity.add(this.acceleration);
  this.velocity.limit(10);
  this.position.add(this.velocity);  
};
위 코드를 다음과 같이 풀어쓸 수 있습니다.
속도의 크기는 어느 정도일까요? 속도의 크기가 10보다 작으면 괜찮습니다. 그대로 놔둡니다. 그런데 속도의 크기가 10보다 크면 10으로 감소시킵니다!
accelerationlimit()을 써서 Mover 객체 움직임에 어떤 변화를 줬는지 살펴봅시다.
알고리즘 #2 완전히 임의적인 가속도 는 어떻게 할지 생각해 봅시다. 여기에서는 객체의 생성자에서 가속도를 초기화하는 대신 각 사이클 즉, update()가 호출될 때마다 새로운 가속도를 선택해야 합니다.
Mover.prototype.update = function() {
  this.acceleration = PVector.random2D();
  this.velocity.add(this.acceleration);
  this.velocity.limit(10);
  this.position.add(this.velocity);  
};
무작위 벡터는 정규화된 벡터이기 때문에 두 개의 방법으로 크기를 조정할 수 있습니다.
  1. 가속도를 상수값으로 조정합니다:
    acceleration = PVector.random2D();
    acceleration.mult(0.5);
  1. 가속도를 임의의 값으로 조정합니다:
    acceleration = PVector.random2D();
    acceleration.mult(random(2));
명확해 보이지만 가속도가 단순히 움직이는 물체의 속도를 빠르게 하거나 느리게하는 것이 아니라 속도의 크기나 방향의 변화라는 것을 이해하는 것이 중요합니다. 가속도는 객체의 방향을 트는데 이용되고 다른 단원에서 화면 안에서 움직이는 방법을 결정하는 객체를 반복해서 프로그래밍 할 것입니다.

본 "내추럴 시뮬레이션" 과정은 다니엘 쉬프만(Daniel Shiffman)이 저술한 "The Nature of Code"의 내용을 차용한 것이며, 본 내용물의 저작권은 Creative Commons Attribution-NonCommercial 3.0 Unported License를 적용합니다.
로딩 중