Conteúdo principal
Curso: Programação > Unidade 5
Lição 8: Sistemas de partículasTipos de partículas
Agore nós vamos usar técnicas de orientação a objetos mais avançadas, como herança, então você talvez queira revisar "Herança" no curso de Introdução ao JS e voltar mais tarde. Não se preocupe, esperaremos por você!
Se sentindo confiante sobre como herança funciona? Ótimo, porque nós vamos usar herança para construir diferentes tipos de sub-objetos
Particle
, os quais compatilham muitas das funcionalidades mas também se diferenciam de algumas formas.Vamos revisar uma implementação simplificada de
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);
};
Agora, nós criamos um novo tipo de objeto baseado em
Particle
, o qual nós vamos chamar de Confetti
. Nós vamos começar com o construtor que aceita o mesmo número de argumentos, e simplesmente chamar o construtor de Particle
, passando os argumentos adiante:var Confetti = function(position) {
Particle.call(this, position);
};
Agora, para ter certeza que os nossos objetos do tipo
Confetti
compartilhem os mesmos métodos dos objetos do tipo Particle
, nós precisamos especificar que os seus protótipos devem ser baseados no protótipo de Particle
:Confetti.prototype = Object.create(Particle.prototype);
Confetti.prototype.constructor = Confetti;
Nesse ponto, nós temos os objetos
Confetti
que agem exatamente igual aos objetos Particle
. O ponto da herança não é fazer cópias, mas criar novos objetos que compartilham um monte de funcionalidades mas também se diferenciam de algumas formas. Então, como um objeto Confetti
se diferencia? Bem, somente baseado no nome, eles devem parecer diferentes. Os nossos objetos Particle
são elipses, mas confete, pelo menos os americanos, são normalmente pequenos pedaços quadrados de papel, então ao menos nós devemos mudar o método display
para mostrá-los como retângulos.: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);
};
Aqui temos um programa com uma instância de objeto
Particle
e uma instância de objeto Confetti
. Note que eles se comportam de forma parecida, mas são diferentes na aparência: Adicionando rotação
Vamos fazer sofisticar um pouco. Digamos que nós queremos que o
Confetti
gire enquanto ele voa pelo ar. Nós poderíamos, claro, modelar a velocidade angular e aceleração como fizemos na seção Oscilações. Ao invés disso, vamos tentar uma solução rápida e simplificada.Se as partículas estivessem no vácuo,
Nós sabemos que a partícula tem uma posição x com valor entre 0 e a largura da tela. E se nós disséssemos: quando a posição x da partícula for 0, sua rotação deve ser 0; quando a posição x for igual à largura da tela, sua rotação deve ser
TWO_PI
? Isso te lembra de alguma coisa? Sempre que nós temos um valor que um intervalo que nós queremos mapear em um outro intervalo, nós podemos usar a função map()
do ProcessingJS para facilitar o cálculo do novo valor.var theta = map(this.position.x, 0, width, 0, TWO_PI);
E somente para dar um pouco mais de rotação, nós podemos na verdade mapear o intervalo do ângulo de 0 a
TWO_PI*2
. Vamos ver como o código fica no método 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();
};
Aqui é como o resultado se parece - reinicie algumas vezes para ver o efeito da rotação:
Nós podíamos também basear o theta na posição y, que tem um efeito um pouco diferente. Por que isso? Bem, a partícula tem uma aceleração constante diferente de 0 na direção y, o que significa que a velocidade y é uma função linear do tempo, e que a posição y é na verdade uma função parabólica do tempo. Você pode ver o que isso significa nos gráficos abaixo (que foram gerados baseados no programa acima):
Isso significa que se basearmos a rotação do confete na posição y, a rotação também será parabólica. Isso não será fisicamente muito correto, já que a verdadeira rotação do confete caindo no ar é muito mais complicada, mas você pode tentar você mesmo e ver o quão realista parece! Você pode pensar em quais outras funções pareceriam mais realistas?"
Um sistema ParticleSystem diferente
Agora, o que nós realmente queremos é sermos capazes de criar vários objetos
Particle
e vários objetos Confetti
. É para isso que nós fizemos o objeto ParticleSystem
, então talvez nós possamos extendê-lo para também cuidar de objetos Confetti
? Aqui está uma forma de fazer isso, copiando o que fizemos para os objetosParticle
:var ParticleSystem = function(position) {
this.origin = position;
this.particles = [];
this.confettis = []; // CARA TRISTE!
};
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();
}
};
Note que temos dois arrays separados, um para as partículas e outro para os confetes. Cada vez que fazemos algo com o array partículas, temos que fazer o mesmo com o array confetes! Isso é irritante, porque significa que temos que escrever o dobro de código, e se mudarmos algo, temos que mudar em dois lugares. Nós poderíamos na verdade evitar a duplicidade, porque o JavaScript nos permite armazenar tipos diferentes de objetos em arrays, e porque os nossos objetos têm a mesma interface - nós estamos chamando o método
run()
, e os dois tipos de objeto definem essa interface. Então, vamos voltar e armazenar em um único array, nós vamos aleatoriamente decidir que tipo de objeto partícula adicionar, e vamos voltar a iterar através de um único array. Isso é muito mais simples - tudo que temos que modificar é o método 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);
}
}
};
Agora, tudo junto!
Quer participar da conversa?
Nenhuma postagem por enquanto.