U3 如何构建粒子系统
面向对象编程(OOP)
①面向过程编程(POP)的思路
②面向对象编程(OOP)的思路
把不同类型的对象之间的互动逻辑建立起来
③自定义类
类首字母用大写,方法用小写
④类的属性和方法
⑤声明,建立并调用属于自定义类的对象
- 面向对象实例
屏幕录制2023-05-3010.55.01
Polygon triangle,square,pentage,hexagon;//声明建立调用自身类
void setup(){
size(800,800);
smooth(8);
//初始化,建立三角形
triangle = new Polygon(3,80,width*0.2,height*0.2);
square = new Polygon(4,80,width*0.4,height*0.4);
pentage = new Polygon(5,80,width*0.6,height*0.6);
hexagon = new Polygon(6,80,width*0.8,height*0.8);
}
void draw(){
triangle.update();//更新多边形
square.update();
pentage.update();
hexagon.update();
background(255);
triangle.display();//呈现多边形
square.display();
pentage.display();
hexagon.display();
}
//鼠标点击事件
void mousePressed(){
triangle.randomize();
square.randomize();
pentage.randomize();
hexagon.randomize();
}
class Polygon{
int sideCount;//边数
float radius,radiusT;//半径
color c,cT;//颜色
float rtt;//旋转角度 rotation
float [] x,y; //顶点
float ctrX,ctrY; //中心位置k
float lerpStep;//控制插值的步数
float thetabfst,thetabfstT;//旋转偏差量
//构造方法
Polygon(int _sideCount, float _radius,float _ctrX,float _ctrY){
//用传入参数给自身属性赋值
sideCount = _sideCount;
radius = radiusT = _radius;
ctrX = _ctrX;
ctrY = _ctrY;
//初始化
x = new float[sideCount];
y = new float[sideCount];
c = cT = color(random(255),random(255),random(255));
thetabfst = thetabfstT = random(TWO_PI);
}
//声明一个方法
void update(){
radius = lerp(radius,radiusT,lerpStep);
c = lerpColor(c,cT,lerpStep);
thetabfst = lerp(thetabfst,thetabfstT,lerpStep);
//用for循环计算一下多边形顶点的位置,有多少条边就有多少个顶点
for(int i=0;i<sideCount;i++){
float theta = map(i,0,sideCount,thetabfst,thetabfst + TWO_PI);
x[i] = cos(theta)*radius+ctrX;
y[i] = sin(theta)*radius+ctrY;
}
}
void randomize(){ //随机参数
lerpStep = random(0.0325,0.2); //随机插值,使大小、颜色变化速度不一致
radiusT = random(60,100); //随机大小
cT = color(random(255),random(255),random(255)); //随机颜色
thetabfst += random(PI*0.25,PI*0.75);
}
void display(){
noStroke();
fill(c);
beginShape();
for(int i=0;i<sideCount;i++){
vertex(x[i],y[i]);
}
endShape();
}
}
⑥课程作业
实现:
屏幕录制2023-05-3013.43.21
int amt = 10; //图形数量
Button [] btns; //建立对象数组
void setup(){
size(800, 800, P3D);
smooth(8);
rectMode(CENTER);
btns = new Button[amt];
for(int i=0;i<amt;i++){
float theta = map(i,0,amt,0,TWO_PI);
btns[i] = new Button(theta,320,i%2,80,randColor()); //i%2,0和1周期性变化
//float _theta,float _radius,int _mode,float _diam,color _c
}
}
color randColor(){
return color(random(255),random(255),random(255));
}
void draw(){
for(int i=0;i<amt;i++){
btns[i].update();
}
background(0);
for(int i=0;i<amt;i++){
btns[i].display();
}
}
class Button{
int mode; //0-rectangular;1-circular
float x,y;
float theta,thetaIncre,radius;
float diam,diamT,diamSelf,w,wT,h,hT;
color c,cT,cSelf;
Button(float _theta,float _radius,int _mode,float _diam,color _c){
mode = _mode;
theta = _theta;
radius = _radius;
//用来画矩形
w = wT = _diam*1.25;
h = hT = _diam*0.75;
diam = diamT = diamSelf = _diam;
c = cT = cSelf = _c;
thetaIncre = .0025;
}
void update(){ //数据更新
theta += thetaIncre; //theta递增,实现转动
x = cos(theta)*radius + width * 0.5;
y = sin(theta)*radius + height * 0.5;
if(inRange()){ //鼠标在范围内外的事件
wT = diamSelf * 1.25 * 1.5;
hT = diamSelf * 0.75 * 1.5;
diamT = diamSelf*1.5;
cT = color(255);
}else{
wT = diamSelf * 1.25;
hT = diamSelf * 0.75;
diamT = diamSelf;
cT = cSelf;
}
diam = lerp(diam,diamT,0.0625);
w = lerp(w,wT,0.125);
h = lerp(h,hT,0.125);
c = lerpColor(c,cT,0.125);
}
boolean inRange(){ //判断鼠标是否在范围内
if(mode == 0){ //矩形范围判定
float boundL = x - w * .5;
float boundR = x + w * .5;
float boundT = y + h * .5;
float boundB = y - h * .5;
if(mouseX > boundL && mouseX < boundR && mouseY < boundT && mouseY > boundB) return true;
else return false;
}else{ //圆形范围判定
float d = dist(mouseX,mouseY,x,y);
if(d<diam*.5) return true;
else return false;
}
}
void display(){ //数据呈现
fill(c);
if(mode == 0) rect(x,y,w,h);
else circle(x,y,diam);
}
}
粒子系统(一)
①对象数组
实例:
Polygon [] polys;
int cols = 10,rows = 10;
void setup(){
size(800,800,P3D);
smooth(8);
polys = new Polygon[cols * rows];
for(int i=0;i<polys.length;i++){
int col = i % cols;
int row = i / cols;
int sideCount = round(random(3,6));//取随机数再取整
float radius = random(20,40);
float x = map(col,0,cols-1,width*0.1,width*0.9);
float y = map(row,0,rows-1,height*0.1,height*0.9);
polys[i] = new Polygon(sideCount,radius,x,y);
//int _sideCount, float _radius,float _ctrX,float _ctrY
}
}
void draw(){
for(int i=0;i<polys.length;i++){
polys[i].update();
}
background(255);
for(int i=0;i<polys.length;i++){
polys[i].display();
}
}
//鼠标点击事件
void mousePressed(){
for(int i=0;i<polys.length;i++){
polys[i].randomize();
}
}
class Polygon{
int sideCount;//边数
float radius,radiusT;//半径
color c,cT;//颜色
float rtt;//旋转角度 rotation
float [] x,y; //顶点
float ctrX,ctrY; //中心位置k
float lerpStep;//控制插值的步数
float thetabfst,thetabfstT;//旋转偏差量
//构造方法
Polygon(int _sideCount, float _radius,float _ctrX,float _ctrY){
//用传入参数给自身属性赋值
sideCount = _sideCount;
radius = radiusT = _radius;
ctrX = _ctrX;
ctrY = _ctrY;
//初始化
x = new float[sideCount];
y = new float[sideCount];
c = cT = color(random(255),random(255),random(255));
thetabfst = thetabfstT = random(TWO_PI);
}
//声明一个方法
void update(){
radius = lerp(radius,radiusT,lerpStep);
c = lerpColor(c,cT,lerpStep);
thetabfst = lerp(thetabfst,thetabfstT,lerpStep);
//用for循环计算一下多边形顶点的位置,有多少条边就有多少个顶点
for(int i=0;i<sideCount;i++){
float theta = map(i,0,sideCount,thetabfst,thetabfst + TWO_PI);
x[i] = cos(theta)*radius+ctrX;
y[i] = sin(theta)*radius+ctrY;
}
}
void randomize(){ //随机参数
lerpStep = random(0.0325,0.2); //随机插值,使大小、颜色变化速度不一致
//radiusT = random(60,100); //随机大小
cT = color(random(255),random(255),random(255)); //随机颜色
thetabfst += random(PI*0.25,PI*0.75);
}
void display(){
noStroke();
fill(c);
beginShape();
for(int i=0;i<sideCount;i++){
vertex(x[i],y[i]);
}
endShape();
}
}
在此基础上可实现间隔分布:
Polygon [] polys;
int cols = 10,rows = 10;
void setup(){
size(800,800,P3D);
smooth(8);
polys = new Polygon[cols * rows / 2]; //***
int polyIdx = 0; //***
//for(int i=0;i<polys.length;i++){
for(int i=0;i<cols * rows;i++){ //***
int col = i % cols;
int row = i / cols;
if(col%2==0 && row%2==0) continue; //跳过循环***
else if(col%2==1 && row%2==1) continue; //***
int sideCount = round(random(3,6));//取随机数再取整
float radius = random(20,40);
float x = map(col,0,cols-1,width*0.1,width*0.9);
float y = map(row,0,rows-1,height*0.1,height*0.9);
polys[polyIdx] = new Polygon(sideCount,radius,x,y); //***
polyIdx++; //***
//int _sideCount, float _radius,float _ctrX,float _ctrY
}
}
void draw(){
for(int i=0;i<polys.length;i++){
polys[i].update();
}
background(255);
for(int i=0;i<polys.length;i++){
polys[i].display();
}
}
//鼠标点击事件
void mousePressed(){
for(int i=0;i<polys.length;i++){
polys[i].randomize();
}
}
class Polygon{
int sideCount;//边数
float radius,radiusT;//半径
color c,cT;//颜色
float rtt;//旋转角度 rotation
float [] x,y; //顶点
float ctrX,ctrY; //中心位置k
float lerpStep;//控制插值的步数
float thetabfst,thetabfstT;//旋转偏差量
//构造方法
Polygon(int _sideCount, float _radius,float _ctrX,float _ctrY){
//用传入参数给自身属性赋值
sideCount = _sideCount;
radius = radiusT = _radius;
ctrX = _ctrX;
ctrY = _ctrY;
//初始化
x = new float[sideCount];
y = new float[sideCount];
c = cT = color(random(255),random(255),random(255));
thetabfst = thetabfstT = random(TWO_PI);
}
//声明一个方法
void update(){
radius = lerp(radius,radiusT,lerpStep);
c = lerpColor(c,cT,lerpStep);
thetabfst = lerp(thetabfst,thetabfstT,lerpStep);
//用for循环计算一下多边形顶点的位置,有多少条边就有多少个顶点
for(int i=0;i<sideCount;i++){
float theta = map(i,0,sideCount,thetabfst,thetabfst + TWO_PI);
x[i] = cos(theta)*radius+ctrX;
y[i] = sin(theta)*radius+ctrY;
}
}
void randomize(){ //随机参数
lerpStep = random(0.0325,0.2); //随机插值,使大小、颜色变化速度不一致
//radiusT = random(60,100); //随机大小
cT = color(random(255),random(255),random(255)); //随机颜色
thetabfst += random(PI*0.25,PI*0.75);
}
void display(){
noStroke();
fill(c);
beginShape();
for(int i=0;i<sideCount;i++){
vertex(x[i],y[i]);
}
endShape();
}
}
屏幕录制2023-05-3016.25.18
②运用OOP思路的粒子系统
向量:
边缘侦测:
如何实现下面的效果?
屏幕录制2023-05-3113.53.56
Particle [] pts;
void setup(){
size(800,800,P3D);
smooth(8);
pts = new Particle[300];
for(int i=0;i<pts.length;i++){ //建立每一个小粒子
pts[i] = new Particle(random(width),random(height),random(5,20));
//float initX,float initY,float _diam
}
background(0);
}
void draw(){
for(int i=0;i<pts.length;i++){
pts[i].update();
}
//每次叠加一个半透明的矩形,实现残影效果
noStroke();
fill(0,64);
rect(0,0,width,height);
for(int i=0;i<pts.length;i++){
pts[i].display();
}
}
class Particle{
PVector pos,vel;//向量
float diam;
float rtt; //rotation
color c;
Particle(float initX,float initY,float _diam){ //构造方法
pos = new PVector(initX,initY);
vel = PVector.random2D(); //让速度向量随机取一个任意方向的单位向量,但此时速度一致
vel.mult(random(0.25,5)); //随机乘一个数值,使速度不同
diam = _diam;
c = color(random(255),random(255),random(255));
rtt = random(-0.025,0.025)*PI;
}
void update(){
vel.rotate(rtt);
pos.add(vel);//v += vx;y += vy;
boundaryTest();
}
void boundaryTest(){ //碰撞检测
float leftBound = diam * .5;
float rightBound = width - diam * .5;
float topBound = diam * .5;
float bottomBound = height - diam * .5;
if(pos.x <= leftBound){
pos.x = leftBound;
vel.x *= -1;
}
else if(pos.x >= rightBound){
pos.x = rightBound;
vel.x *= -1;
}
if(pos.y <= topBound){
pos.y = topBound;
vel.y *= -1;
}
else if(pos.y >= bottomBound){
pos.y = bottomBound;
vel.y *= -1;
}
}
void display(){
strokeWeight(diam);
stroke(c);
point(pos.x,pos.y);
}
}
③课程作业
实现下方的实时互动效果
屏幕录制2023-06-0113.00.55
Spiral [] sGrid;
int cols = 10,rows = 10,amt = cols * rows;
float unitW;
void setup(){
size(800,800,P3D);
smooth(8);
unitW = width * 1.0 / cols; //*0.1是为了让width带小数,使之后计算数值更准确
sGrid = new Spiral[amt];
for(int i=0;i<amt;i++){
int col = i % cols;
int row = i / cols;
float x = map(col,0,cols-1,unitW * .5,width - unitW * .5);
float y = map(row,0,rows-1,unitW * .5,height - unitW * .5);
sGrid[i] = new Spiral(100,x,y,unitW * .5,i * TWO_PI * 0.25);
//int _res,float _ctrX,float _ctrY,float _radius,float _thetaOdst
}
}
void draw(){
for(int i=0;i<amt;i++){
sGrid[i].update();
}
background(255);
for(int i=0;i<amt;i++){
sGrid[i].display();
}
}
class Spiral{
int res; //resolution 精度,即分辨率
float thetaMax,thetaMaxT,thetaOdst; //弯曲程度,,,角度偏移值
float ctrX,ctrY; //中心位置
float radius; //半径
Spiral(int _res,float _ctrX,float _ctrY,float _radius,float _thetaOdst){
res = _res;
ctrX = _ctrX;
ctrY = _ctrY;
radius = _radius;
thetaOdst = _thetaOdst;
}
void update(){
float d = dist(mouseX,mouseY,ctrX,ctrY);
d = constrain(d,0,width*0.5);
thetaMaxT = map(d,0,width*0.5,TWO_PI*4,0);
thetaMax = lerp(thetaMax,thetaMaxT,0.0625);
}
void display(){
noFill();
stroke(0);
strokeWeight(5);
beginShape();
for(int i=0;i<res;i++){
float theta = map(i,0,res-1,0,thetaMax) + thetaOdst;
float r = map(i,0,res-1,radius,0);
float x = cos(theta) * r + ctrX;
float y = sin(theta) * r + ctrY;
vertex(x,y);
}
endShape();
}
}
粒子系统(二)
①数组列表
数组列表与数组的差异
②使用数组改写上一节的粒子系统实例
ArrayList <Particle>ptcs; //Particle [] pts;
void setup(){
size(800,800,P3D);
smooth(8);
background(0);
ptcs = new ArrayList<Particle>(); //pts = new Particle[300];
for(int i=0;i<120;i++){ //for(int i=0;i<pts.length;i++){
Particle ptc = new Particle(random(width),random(height),random(5,20));
//pts[i] = new Particle(random(width),random(height),random(5,20));
ptcs.add(ptc);
}
}
void draw(){
for(int i=0;i<ptcs.size();i++){ //for(int i=0;i<pts.length;i++){
Particle ptc = ptcs.get(i);
ptc.update();
//pts[i].update();
}
noStroke();
fill(0,64);
rect(0,0,width,height);
for(int i=0;i<ptcs.size();i++){ //for(int i=0;i<pts.length;i++){
Particle ptc = ptcs.get(i);
ptc.display();
//pts[i].display();
}
}
class Particle{
PVector pos,vel;//向量
float diam;
float rtt; //rotation
color c;
Particle(float initX,float initY,float _diam){ //构造方法
pos = new PVector(initX,initY);
vel = PVector.random2D(); //让速度向量随机取一个任意方向的单位向量,但此时速度一致
vel.mult(random(0.25,5)); //随机乘一个数值,使速度不同
diam = _diam;
c = color(random(255),random(255),random(255));
rtt = random(-0.025,0.025)*PI;
}
void update(){
vel.rotate(rtt);
pos.add(vel);//v += vx;y += vy;
boundaryTest();
}
void boundaryTest(){ //碰撞检测
float leftBound = diam * .5;
float rightBound = width - diam * .5;
float topBound = diam * .5;
float bottomBound = height - diam * .5;
if(pos.x <= leftBound){
pos.x = leftBound;
vel.x *= -1;
}
else if(pos.x >= rightBound){
pos.x = rightBound;
vel.x *= -1;
}
if(pos.y <= topBound){
pos.y = topBound;
vel.y *= -1;
}
else if(pos.y >= bottomBound){
pos.y = bottomBound;
vel.y *= -1;
}
}
void display(){
strokeWeight(diam);
stroke(c);
point(pos.x,pos.y);
}
}
③进一步复杂化粒子系统的逻辑,以及深化细节
- 添加与移除元素实例
添加都是添加在最后,索引不会发生变更
移除如果是移除中间的元素,后边的索引会发生相应的变化
- 生命值映射到视觉形态
- 两种规避规避移除元素过程中跳过后一个元素方法
1.索引控制
2.倒序遍历 - 粒子系统实例
粒子系统
ArrayList <Particle>ptcs; //声明
boolean emitting;
void setup(){
size(800,800,P3D);
smooth(8);
background(0);
blendMode(ADD); //颜色叠加效果
ptcs = new ArrayList<Particle>(); // 初始化
}
void draw(){
if(emitting){ //为true时,初始化元素,不断释放
for(int i=0;i<25;i++){ //for增加数量
Particle ptc = new Particle(mouseX,mouseY,mouseX-pmouseX,mouseY-pmouseY,random(3,8));
ptcs.add(ptc);
}
}
for(int i=0;i<ptcs.size();i++){ //调用元素
Particle ptc = ptcs.get(i);
if(ptc.dead){ //如果是死亡状态则移除
ptcs.remove(ptc);
i--; //规避移除元素过程中跳过后一个元素
}
else{ //否则继续更新
ptc.update();
}
}
background(0);
for(int i=0;i<ptcs.size();i++){ //调用元素
Particle ptc = ptcs.get(i);
ptc.display();
}
fill(#00ff00);
text("ptcs.size():" + ptcs.size() ,50,50); //显示数组列表元素数量
}
void mousePressed(){
if(mouseButton == LEFT){ //初始化元素 //监听鼠标左键按下事件
Particle ptc = new Particle(mouseX,mouseY,mouseX-pmouseX,mouseY-pmouseY,random(25,50));
ptcs.add(ptc);
}else if(mouseButton == RIGHT){//监听鼠标右键按下事件
emitting = true;
}
}
void mouseReleased(){
if(mouseButton == RIGHT){
emitting = false;
}
}
class Particle{
boolean dead;
int life,lifeSpan;//生命与寿命
PVector pos,vel;//向量
float diam,diamMax,alpha,friction;//直径,直径最大值,透明度,摩擦力
float rtt; //rotation 旋转,旋转量
color c;
Particle(float initX,float initY,float velX,float velY,float _diamMax){ //构造方法
pos = new PVector(initX,initY);
if(velX == 0 && velY == 0){ //如果鼠标没有移动随机取方向
vel = PVector.random2D(); //让速度向量随机取一个任意方向的单位向量,但此时速度一致
vel.mult(random(0.25,5)); //随机乘一个数值,使速度不同
}else{ //移动的话跟随鼠标方向
vel = new PVector(velX,velY);
vel.rotate(random(-PI*.1,PI*.1));//让方向偏移一点点
vel.mult(random(0.05,0.25));
}
diamMax = _diamMax;
c = color(random(255),random(255),random(255));
//rtt = random(-0.025,0.025)*PI;
friction = random(0.9,0.99);
lifeSpan = round(random(60,180)); //存在60-180帧
}
void update(){
updateLife();
updateAppr();
updatePos();
}
void updateLife(){ //更新生命
if(life<lifeSpan) life++; //当生命小于寿命,就让它慢慢增加
else dead = true;
}
void updateAppr(){ //更新透明度
if(life<lifeSpan * .25){ //生长期
diam = map(life,0,lifeSpan * .25,0,diamMax);
alpha = 255;
}else if(life>=lifeSpan * .25 && life<lifeSpan * .75){ //成熟期
diam = diamMax;
alpha = 255;
}else{ //衰老期
diam = diamMax;
alpha = map(life,lifeSpan * .75,lifeSpan,255,0);
}
}
void updatePos(){ //更新坐标
//vel.rotate(rtt);
vel.mult(friction);//每一帧乘一个摩擦力,速度越来越小
pos.add(vel);//v += vx;y += vy;
boundaryTest();
}
void boundaryTest(){ //碰撞检测
float leftBound = diam * .5;
float rightBound = width - diam * .5;
float topBound = diam * .5;
float bottomBound = height - diam * .5;
if(pos.x <= leftBound){
pos.x = leftBound;
vel.x *= -1;
}
else if(pos.x >= rightBound){
pos.x = rightBound;
vel.x *= -1;
}
if(pos.y <= topBound){
pos.y = topBound;
vel.y *= -1;
}
else if(pos.y >= bottomBound){
pos.y = bottomBound;
vel.y *= -1;
}
}
void display(){
strokeWeight(diam);
stroke(c,alpha);
point(pos.x,pos.y);
}
}
④课程作业
实现下方效果:
屏幕录制2023-06-0209.55.18
图4-1
ArrayList <Firework> fwList;//1.声明
void setup(){
size(800,800,P3D);
smooth(8);
blendMode(ADD); //颜色叠加效果
fwList = new ArrayList<Firework>(); //2.初始化
}
void draw(){
//4.调用元素
for(int i=0;i<fwList.size();i++){
Firework fw = fwList.get(i);
if(fw.dieout){
if(fw.gen<5) fw.explode(); //小于五代时,移除前炸一下
fwList.remove(fw);
i--;
}else{
fw.update();
}
}
background(0);
for(int i=0;i<fwList.size();i++){
Firework fw = fwList.get(i);
fw.display();
}
fill(#00ff00);
textSize(24);
text("fwList.size():" + fwList.size(),50,50);
}
void mousePressed(){
//3.初始化元素
Firework fw = new Firework(mouseX,mouseY,random(10,20),0,color(random(255),random(255),random(255)));
// float x,float y,float _diam,int _gen,color _c
fwList.add(fw);
}
class Firework{
int gen;
boolean dieout;
PVector pos,tgt;
float diam,diamT;
color c,cT;
int life,lifeSpan;
Firework(float x,float y,float _diam,int _gen,color _c){
pos = new PVector(x,y);
tgt = PVector.random2D(); //返回一个模长为1,随机方向的二维向量
tgt.mult(200);
tgt.add(pos); //见图4-1
c = _c;
cT = color(random(255),random(255),random(255));
diam = _diam;
diamT = _diam * .75;
lifeSpan = round(random(10,20));
gen = _gen;
}
void explode(){
for(int i=0;i<round(random(2,3));i++){ //for循环增加数量
Firework fw_gen = new Firework(pos.x,pos.y,diam,gen+1,c); //炸的时候传入当前颜色
fwList.add(fw_gen);
}
}
void update(){
updateLife();
diam = lerp(diam,diamT,.0625); // 慢慢变小
c = lerpColor(c,cT,.0625);
pos.lerp(tgt,0.0625); //pos.x=lerp(pos.x,tgt.x,0.0625);pos.y=lerp(pos.y,tgt.y,0.0625);
}
void updateLife(){
if(life<lifeSpan) life++;
else dieout = true;
}
void display(){
stroke(c);
strokeWeight(diam);
point(pos.x,pos.y);
}
}