If you're seeing this message, it means we're having trouble loading external resources on our website.

Jeżeli jesteś za filtrem sieci web, prosimy, upewnij się, że domeny *.kastatic.org i *.kasandbox.org są odblokowane.

Główna zawartość

Typy cząstek

Teraz użyjemy bardziej zaawansowanych technik programowania obiektowego, takich jak dziedziczenie — możecie więc przypomnieć sobie ma czym polega dziedziczenie w naszym kursie Wstęp do JS i wrócić tutaj, jak już to uczynicie. Nie martwcie się, zaczekamy!
Jesteście już pewni co do tego, jak działa dziedziczenie? Dobrze, ponieważ będziemy używać dziedziczenia do utworzenia różnych rodzajów pod-obiektów Particle, które mają tą samą funkcjonalność, ale różnią się w kluczowych kwestiach.
Przejrzyjmy prostą implementację obiektów 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);
};
Następnie tworzymy nowy typ obiektu na podstawie Particle, który nazwiemy Confetti. Zaczniemy od funkcji konstruktora, która akceptuje tę samą liczbę argumentów i po prostu wywołuje konstruktor Particle, przekazując go dalej.
var Confetti = function(position) {
  Particle.call(this, position);
};
Teraz aby upewnić się, że nasze obiekty Confetti dzielą te same metody co obiekty Particle, musimy określić, że ich prototyp jest oparty na prototypie obiektu Particle:
Confetti.prototype = Object.create(Particle.prototype);
Confetti.prototype.constructor = Confetti;
W tej chwili mamy obiekt Confetti który zachowuje się dokładnie tak samo, jak obiekt Particle. Celem dziedziczenia nie jest tworzenie duplikatów obiektów, celem jest tworzenie nowych obiektów które dzielą wiele funkcji, ale różnią się w pewien sposób. Tak więc czym różni się obiekt Confetti? Cóż, patrząc tylko na jego nazwę wydaje się, że powinien wyglądać inaczej. Nasze obiekty Particle są elipsami, ale confetti to zwykle małe kawałki kwadratowego papieru, tak więc moglibyśmy przynajmniej zmienić metodę display aby pokazywał prostokąty:
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);
};
Oto program z jedną instancją obiektu Particle i jedną instancją obiektu Confetti. Zauważcie, że zachowują się podobnie, ale różnią się wyglądem:

Dodawanie rotacji

Sprawmy, aby było to trochę bardziej wyrafinowane. Powiedzmy, że chcemy aby cząstka Confetti obracała się podczas lotu. Możemy oczywiście zamodelować prędkość kątową i przyspieszenie, tak jak zostało to zrobione w sekcji o Oscylacjach. Zamiast tego, spróbujemy szybszego i prowizorycznego rozwiązania.
Wiemy, że cząstka ma współrzędną x pomiędzy zerem a szerokością okna. Co powiecie na to: gdy współrzędna x cząstki wynosi 0, ich obrót powinien również wynosić 0; gdy współrzędna x jest równa szerokości okna, jej obrót będzie wynosił TWO_PI? Brzmi znajomo? Gdy mamy wartość z jednego zakresu, którą chcemy odwzorować w innym zakresie, możemy użyć funkcji map() oferowanej przez ProcessingJS aby łatwo wyliczyć nową wartość.
var theta = map(this.position.x, 0, width, 0, TWO_PI);
Aby trochę bardziej to urozmaicić, możemy odwzorowywać kąt w zakresie od 0 do TWO_PI*2. Zobaczmy, jak powinien wyglądać ten kod wewnątrz metody 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();
};
Oto końcowy wynik - uruchomcie aplikację parę razy, aby zobaczyć efekt obrotu:
Moglibyśmy również liczyć wartość theta na podstawie współrzędnej y, co przyniosłoby trochę inny efekt. Dlaczego? Cóż, cząstka ma stałą przyspieszenie w kierunku y różną od zera, co oznacza, że prędkość y jest funkcją liniową zależną od czasu, a pozycja y jest funkcją kwadratową zależną od czasu. Możecie zobaczyć, co to oznacza na poniższych wykresach (które są wygenerowane na podstawie powyższego programu):
Oznacza to, że jeżeli oprzemy rotację confetti na pozycji y, obrót również będzie paraboliczny. Nie będzie to zbyt dokładne od strony fizyki, gdyż aktualny obrót spadającego confetti jest raczej skomplikowany, ale wypróbujcie tą opcję i sprawdźcie, jak realistycznie to wygląda! Czy możecie wymyślić inne funkcje, która będą wyglądać jeszcze bardziej realistycznie?

Różnorodny ParticleSystem

Teraz chcielibyśmy utworzyć wiele obiektów typu Particle oraz wiele obiektów typu Confetti. W końcu po to stworzyliśmy obiekt ParticleSystem, więc może po prostu zmodyfikujemy go, aby również monitorował obiekty Confetti? Oto jeden ze sposobów, aby to osiągnąć, bazując na tym, co zrobiliśmy z obiektami Particle:
var ParticleSystem = function(position) {
  this.origin = position;
  this.particles = [];
  this.confettis = [];
};

ParticleSystem.prototype.addParticle = function() {
    this.particles.push(new Particle(this.origin));
    this.confettis.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();
  }
};
Zauważ, że mamy dwie oddzielne tablice, jedną dla cząsteczek i jedna dla confetti. Za każdym razem gdy robimy coś z tablicą cząstek, musimy też to zrobić z tablicą confetti! Jest to raczej irytujące, ponieważ oznacza to, ze musimy pisać dwa razy tyle kodu, a jeżeli będziemy chcieli to zmienić, będziemy musieli to zmienić w dwóch miejscach. Mogliśmy co prawda uniknąć tego powielenia, gdyż możemy przechowywać obiekty innych typów w tablicach w JavaScripcie oraz z racji tego, że nasze obiekty mają te same interfejsy - wywołujemy po prostu metodę run(), a oba nasze typy obiektów definiują ten interfejs. Wrócimy więc do przechowywania jednej tablicy, będziemy losowo wybierać który typ cząsteczki dodać i wrócimy do iterowania po pojedynczej tablicy. To znacznie prostsza zmiana - wszystko, co musimy zrobić to zmodyfikować metodę 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);
    }
  }
};
A oto całość!