이제부터 상속성 같은 고급 객체지향 프로그래밍 기술을 사용할 겁니다. JS란? 수업에서 "상속성"을 복습하고 다시 돌아오세요. 걱정하지 말아요. 보고 올 때까지 기다릴게요!
이제 상속성의 작동 원리를 잘 이해했나요? 상속성을 잘 알아야 하는 이유는 이를 이용하여 서로 다른 유형의 Particle 하위 객체를 생성할 것이기 때문입니다. 이 하위 객체는 대부분 같은 기능을 갖지만 몇 가지 차이가 있습니다.
단순한 Particle 코드를 복습해 봅시다:
var Particle = function(position) {
  this.acceleration = new PVector(0, 0.05);
  this.velocity = new PVector(random(-1, 1), random(-1, 0));
  this.position = position.get();
};

Particle.prototype.run = function() {
  this.update();
  this.display();
};

Particle.prototype.update = function(){
  this.velocity.add(this.acceleration);
  this.position.add(this.velocity);
};

Particle.prototype.display = function() {
  fill(127, 127, 127);
  ellipse(this.position.x, this.position.y, 12, 12);
};
다음으로 Particle을 기반으로 새로운 객체형을 생성합니다. 이것을 Confetti라 합니다. 인수와 똑같은 수를 입력받는 생성자 함수를 생성하고, 이를 전달하는 Particle 생성자를 호출하게 만드는 것부터 시작합시다.
var Confetti = function(position) {
  Particle.call(this, position);
};
이제 Confetti 객체들이 Particle 객체들과 같은 메소드를 공유하게 하려면 해당 객체의 프로토타입이 Particle 프로토타입과 같은 기반을 가져가도록 만들어야 합니다.
Confetti.prototype = Object.create(Particle.prototype);
Confetti.prototype.constructor = Confetti;
이 시점에서 Particle object와 완전히 똑같은 방법으로 작동하는 Confetti object가 생깁니다. 상속성의 요점은 복제하는 것이 아니라 많은 기능을 공유할 뿐만 아니라 새로운 기능도 가진 객체를 만드는 것입니다. 그러면 Confetti 객체는 어떻게 다를까요? Confetti는 축제 때 뿌리는 형형색색의 색종이 조각입니다. 이름만 보면 Confetti 객체는 서로 다른 모양을 하고 있을 것 같은 생각이 듭니다. Particle 객체는 타원형이지만, confetti는 일반적으로 직사각형 모양의 작은 조각입니다. 그러므로 display 메소드를 변경해서 직사각형 모양으로 바꿔야 합니다.
Confetti.prototype.display = function(){
  rectMode(CENTER);
  fill(0, 0, 255, this.timeToLive);
  stroke(0, 0, 0, this.timeToLive);
  strokeWeight(2);
  rect(0, 0, 12, 12);
};
다음은 Particle 객체 인스턴스 한 개와 Confetti 객체 인스턴스 한 개가 있는 프로그램입니다. 두 객체 인스턴스는 모두 비슷하게 행동하나 서로 다른 모양을 하고 있습니다.

회전 추가하기

약간 더 정교하게 만들어 봅시다. Confetti 입자가 공기 중에 날아다닐 때 회전하게 만든다고 생각해 보세요. 물론 진동을 배울 때 했던 것처럼 각속도와 각가속도를 모델링할 수 있지만 대신 빠르고 간편한 방법을 써보겠습니다.
입자가 진공 상태에 있으면,
입자의 x 위치는 반드시 0과 화면 너비 사이 어느 지점에 있습니다. 만약 입자의 x 위치가 0이면 회전은 0이라고 두고, 입자의 x 위치가 너비와 같으면 회전이 TWO_PI와 같다고 두면 어떻게 될까요? 익숙하지 않나요? 한 범위의 값을 다른 범위에 대응시켜야 할 때 ProcessingJS의 map() 함수를 이용하여 새로운 값을 쉽게 계산할 수 있습니다.
var theta = map(this.position.x, 0, width, 0, TWO_PI);
그리고 좀 더 많이 회전하게 하려면 각의 범위를 0에서 TWO_PI*2까지 지정하면 됩니다. 이 코드가 display() 메소드에 얼마나 적합한 지를 살펴 보겠습니다.
Confetti.prototype.display = function(){
  rectMode(CENTER);
  fill(0, 0, 255);
  stroke(0, 0, 0);
  strokeWeight(2);
  pushMatrix();
  translate(this.position.x, this.position.y);
  var theta = map(this.position.x, 0, width, 0, TWO_PI * 2);
  rotate(theta);
  rect(0, 0, 12, 12);
  popMatrix();
};
코드를 실행하면 이렇게 보입니다. 재실행을 몇 번 해보고 회전의 주는 효과를 살펴보세요.
세타를 y 위치로 잡을 수도 있지만, 효과는 약간 달라집니다. 왜 그럴까요? 입자는 y 방향으로 상수만큼 가속을 받습니다. 즉, y 속도는 시간에 대한 1차 함수이고 y 위치는 시간에 대한 2차 함수입니다. 위 프로그램을 아래에 그래프로 나타냈습니다. 아래 그래프를 보고 이 말의 의미를 이해해보세요.
즉, confetti를 회전시킬 때 y 위치를 이용했다면 회전 정도가 포물선 곡선을 따라 움직일 겁니다. 이런 움직임은 물리적으로 정확하지 않습니다. 왜냐하면, 실제 공기 중에서 떨어지는 색종이는 꽤 복잡하게 회전하기 때문입니다. 그러나 실제로 해보면 프로그램이 상당히 현실감 있게 느껴집니다! 다른 함수를 써서 좀 더 현실감 있게 만들어 볼 수 있을 것 같나요?

다양한 입자 시스템

이제 원래 목표대로 Particle 객체와 Confetti 객체를 많이 생성해야 합니다. ParticleSystem 객체를 만든 목적이 바로 여기에 있습니다. 그럼 이 객체를 확장해서 Confetti 객체를 관리할 수 있도록 만들 수 있을까요? 아래에 Particle 객체 코드를 복사해서 이를 구현하는 코드로 나타냈습니다.
var ParticleSystem = function(position) {
  this.origin = position;
  this.particles = [];
  this.confettis = []; // SAD FACE!
};

ParticleSystem.prototype.addParticle = function() {
    this.particles.push(new Particle(this.origin));
    this.particles.push(new Confetti(this.origin));
};

ParticleSystem.prototype.run = function(){
  for (var i = this.particles.length-1; i >= 0; i--) {
    var p = this.particles[i];
    p.run();
  }
for (var i = this.confettis.length-1; i >= 0; i--) {
    var p = this.confettis[i]; p.run();
  }
};
위 코드에는 입자를 위한 배열과 색종이를 위한 배열, 총 두 개의 별개 배열이 있습니다. 입자 배열에 무언가를 하면, 똑같이 색종이 조각 배열에도 해야 합니다! 그럼 코드를 두 번 써야 하고 한 번 수정해야 할 때마다 두 번 고쳐야 하므로 번거롭습니다. 사실 이런 번거로운 작업은 피할 수 있습니다. 왜냐하면, 자바스크립트에서는 서로 다른 유형의 객체를 배열에 저장할 수 있고 객체의 인터페이스가 동일하기 때문입니다. 위 코드는 run() 메소드를 호출하고 두 유형의 객체가 모두 해당 인터페이스를 정의하는 구조로 되어있습니다. 그러므로 단일 배열을 저장하고, 임의로 어떤 유형의 입자 객체를 추가할지를 결정하고 단일 배열에 대해 반복을 수행하게 할 겁니다. 이는 훨씬 더 간단한 수정 방법입니다. 결국 addParticle 메소드만 수정하면 되는 셈입니다.
var ParticleSystem = function(position) {
  this.origin = position;
  this.particles = [];
};

ParticleSystem.prototype.addParticle = function() {
  var r = random(1);
  if (r < 0.5) {
    this.particles.push(new Particle(this.origin));
  } else {
    this.particles.push(new Confetti(this.origin));
  }
};

ParticleSystem.prototype.run = function(){
  for (var i = this.particles.length-1; i >= 0; i--) {
    var p = this.particles[i];
    p.run();
    if (p.isDead()) {
      this.particles.splice(i, 1);
    }
  }
};
코드를 다 합치면 이렇게 됩니다!