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

Wenn du hinter einem Webfilter bist, stelle sicher, dass die Domänen *. kastatic.org und *. kasandbox.org nicht blockiert sind.

Hauptinhalt

Partikeltypen

Jetzt werden wir fortgeschrittenere Techniken der objektorientierten Programmierung wie Vererbung verwenden. Vielleicht möchtest du Objektvererbung im Kurs "Einführung in JS" wiederholen und dann hier weitermachen. Keine Sorge, wir warten auf dich!
Fühlst du dich schon wohl mit den Funktionsweisen von Vererbung? Gut! Wir werden sie nämlich einsezten, um unterschiedliche Arten von Unterobjekten Particle zu erzeugen, welche funktional viel gemeinsam haben, aber sich auch in einigen Hauptpunkten unterscheiden.
Schauen wir uns eine einfache Implementierung von Particle noch einmal an:
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);
};
Als nächstes erzeugen wir einen neuen Objekttyp, der auf Particle basiert und den wir Confetti nennen. Wir beginnen mit einem Konstruktur, welcher als Parameter die gleiche Zahl annimmt, und einfach den Konstruktur von Particle aufruft und sie weitergibt:
var Confetti = function(position) {
  Particle.call(this, position);
};
Um nun sicherzustellen, dass unsere Objekte Confetti die gleichen Methoden haben wie die Objekte Particle, müssen wir spezifizieren, dass ihr Prototyp auf dem Prototyp Particle basieren soll:
Confetti.prototype = Object.create(Particle.prototype);
Confetti.prototype.constructor = Confetti;
Nun haben wir Objekte Confetti, welche sich genauso verhalten wie die Objekte Particle. Bei Vererbung geht es aber nicht einfach darum, Duplikate zu erstellen, sondern neue Objekte, die in vielen Funktionen gleich sind, aber auch einige Unterschiede aufweisen. Was sind also die Unterschiede bei dem Objekt Confetti? Wenn man schon mal nur vom Namen ausgeht, dann sollte es wohl anders aussehen. Unsere Objekte Particle sind Ellipsen, Konfetti besteht aber normalerweise aus kleinen quadratischen Papierstückchen. Also sollten wir mindestens schon mal die Methode display ändern, um sie stattdessen als Rechtecke anzuzeigen:
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);
};
Hier ist ein Programm mit einer Objektinstanz Particle und einer Objektinstanz Confetti. Beachte, dass sie sich ähnlich verhalten, aber unterschiedlich aussehen:

Rotation hinzufügen

Bauen wir das ganze noch weiter aus. Sagen wir mal, der Partikel Confetti soll rotieren, während er durch die Luft fliegt. Natürlich könnten wir, wie im Kapitel über Oszillation gelernt, die Winkelgeschwindigkeit und -beschleunigung modellieren. Stattdessen wählen wir aber eine einfachere, dafür nicht ganz perfekte Lösung.
Wir wissen, dass ein Partikel eine x-Position irgendwo zwischen 0 und der Fensterbreite hat. Wir könnten also definieren: Wenn die x-Position des Partikels gleich 0 ist, sollte seine Rotation 0 sein, wenn die x-Position gleich der Fensterbreite ist, sollte seine Rotation gleich TWO_PI sein? Kommt dir das bekannt vor? Wenn wir einen Wert mit einem Bereich haben, den wir auf einen anderen Bereich abbilden wollen, können wir einfach die Funktion map() von ProcessingJS verwenden, um den neuen Wert zu berechnen.
var theta = map(this.position.x, 0, width, 0, TWO_PI);
Und um ihm noch ein bisschen Schwung mitzugeben, können wir den Bereich des Winkels auf 0 bis TWO_PI*2 abbilden. Schauen wir uns an, wie dieser Code in die Methode display() passt.
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();
};
So sieht das dann aus. Starte es ein paar Mal neu, um den Rotationseffekt zu sehen:
Wir könnten Theta auch auf der y-Position basieren lassen, was einen etwas anderen Effekt hat. Warum ist das so? Naja, das Partikel zeigt eine konstante Beschleunigung in der y-Richtung, die nicht 0 ist. Das heißt, die y-Geschwindigkeit ist eine lineare Zeitfunktion, und die y-Position ist eigentlich eine parabolische Zeitfunktion. An den folgenden Graphen (die auf Basis des vorherigen Programms erstellt wurden) kannst du sehen, was das bedeutet:
Drei Diagramme der Position (eine Linie, die nach unten zeigt), Geschwindigkeit (eine gerade Linie von oben nach unten) und Beschleunigung (eine gerade Linie).
Es bedeutet, dass die Rotation des Konfetti auch parabolisch verläuft, wenn wir seine Rotation auf der y-Position basieren lassen. Die Darstellung wird nicht sehr akkurat sein, da die tatsächliche Rotation eines fallenden Konfetti ziemlich kompliziert ist. Aber probier es aus und schau dir an, wie realistisch es wirkt! Kannst du dir andere Funktionen vorstellen, die vielleicht noch realistischer aussehen?

Ein vielfältiges ParticleSystem

Was wir aber wirklich wollen, ist viele Objekte Particle und viele Objekte Confetti zu erzeugen. Dafür haben wir das Objekt ParticleSystem erstellt. Vielleicht können wir es also erweitern, um die Objekte Confetti zu verfolgen? Wir könnten das zum Beispiel so machen, wie wir auch beim Objekt Particle vorgegangen sind:
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();
  }
};
Beachte, dass wir zwei getrennte Arrays haben, eins für Partikel und eins für Konfetti. Jedes Mal, wenn wir das Array Particles ändern, müssen wir dasselbe im Array Confetti ändern! Das ist anstrengend, weil wir so doppelt so viel Code schreiben müssen, und wir Änderungen immer an zwei verschiedenen Orten vornehmen müssen. Wir können diese Verdopplung vermeiden, da wir in JS unterschiedliche Objekttypen im gleichen Array speichern können und weil unsere Objekte die gleiche Schnittstelle haben: Wir rufen die Methode run() auf, und beiden Objekttypen definieren diese Schnittstelle. Wir speichern also wieder nur ein einzelnes Array und entscheiden nach dem Zufallsprinzip, welche Art von Partikelobjekt wir hinzufügen. Dann iterieren wir wieder nur durch ein einziges Array. Diese Änderung ist viel einfacher. Alles was wir modifizieren müssen ist die Methode 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);
    }
  }
};
Jetzt haben wir alles zusammen!

Willst du an der Diskussion teilnehmen?

Noch keine Beiträge.
Verstehst du Englisch? Klick hier, um weitere Diskussionen auf der englischen Khan Academy Seite zu sehen.