Основно съдържание
Курс: Програмиране > Раздел 5
Урок 8: Системи от частици- Въведение в системите от частици
- Една частица
- Предизвикателство: Падащи листа
- Система от частици
- Предизвикателство: Мехурчета на риби
- Системи от системи от частици
- Предизвикателство: Разпалване
- Видове частици
- Предизвикателство: Вълшебен котел
- Системи частици със сили
- Предизвикателство: Речни камъни
- Проект: Колонии от създания
© 2024 Khan AcademyУсловия за ползванеДекларация за поверителностПолитика за Бисквитки
Видове частици
Сега ще използваме по-напреднали техники от обектно-ориентираното програмиране като наследяване, затова може би ще искаш да си припомниш "наследяването" от курса Въведение в 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;
В този момент имаме обекти
Confetti
, които се държат по съвсем същия начин като обектите Particle
. Смисълът на наследяването не е да правим двойници, а да правим нови обекти, които споделят много от функционалността си, но и се различават по някакъв начин. Каква е разликата в обекта Confetti
? Ами, ако съдим само по името, изглежда че трябва да изглежда по различен начин. Нашите обекти Particle
са елипси, но конфетите обикновено са малки квадратни парченца хартия, затова най-малкото трябва да променим 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
да се върти, докато се носи във въздуха. Разбира се, можем да моделираме ъглова скорост и ускорение, както направихме в секцията за Осцилация. Вместо това да опитаме едно бързо и мръсно решение.Знаем, че частицата има позиция х между 0 и широчината на прозореца. Ами ако помислим върху следното: когато х позицията на частицата е 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();
};
Ето как изглежда – рестартирай няколко пъти, за да видиш ефекта на ротацията:
Освен това можем да базираме theta на позицията y, което ще даде малко по-различен ефект. Защо това е така? Частицата има ненулево постоянно ускорение по посока y, което означава, че скоростта y е линейна функция на времето, а позицията y е параболична функция на времето. Можеш да видиш какво означава това на графиките по-долу (които са генерирани въз основа на горната програма):
Това означава, че ако базираме ротацията на конфетите на позицията y, ротацията също ще бъде параболична. Това няма да е съвсем точно от физична гледна точка, тъй като действителната ротация на падащите във въздуха конфети е доста сложна, но опитай самостоятелно и виж колко реалистично изглежда това движение! Можеш ли да се сетиш за други функции, които биха изглеждали дори още по-реалистични?
Разнообразна система от частици ParticleSystem
Това, което наистина искаме, е да можем да създаваме много обекти
Particle
и много обекти Confetti
. Затова направихме обекта ParticleSystem
, така че вероятно бихме могли да го разширим, за да следи и обектите Confetti
, нали? Ето един начин да го направим, като копираме това, което направихме за обектите 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();
}
};
Забележи, че имаме два отделни масив, един за частиците и един за конфетите. Всеки път, когато правим нещо с частиците, трябва да го направим и с масива на конфетите! Това е дразнещо, защото означава, че трябва да пишем два пъти повече код и ако променим нещо, трябва да го променяме на две места. Можем да избегнем това повторение, защото можем да запазваме обекти от различен тип в масивите в JavaScript, а тъй като нашите обекти имат един и същи интерфейс – извикваме метода
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);
}
}
};
Ето го всичко заедно!
Искаш ли да се присъединиш към разговора?
Все още няма публикации.