最早接触这个概念是在去年的圣诞节的时候,那个时候手机淘宝首页出了一个页面下雪的动画,当时觉得蛮炫酷的,而且以为实现起来蛮难的,后来自己查了资料才知道“粒子特效”这个概念,今天就来简单聊聊它吧。
粒子特效
粒子系统表示三维计算机图形学中模拟一些特定的模糊现象的技术,而这些现象用其它传统的渲染技术难以实现的真实感的游戏图形。经常使用粒子系统模拟的现象有火、爆炸、烟、水流、火花、落叶、云、雾、雪、尘、流星尾迹或者象发光轨迹这样的抽象视觉效果等等。
以上定义来自维基百科,详细的解释点击这里.
在iOS系统当中的实现
粒子特效由两个部分组成,一个是发射器,主要负责粒子的生成和发射,另一个是粒子单元,用于描述粒子的状态。在iOS系统当中这两部分分别由CAEmitterLayer(发射器)和CAEmitterCell(组成)。
具体使用可以查看以下代码:
//雪花动画
- (void)snow {
//粒子发射器
CAEmitterLayer *snowEmitter = [CAEmitterLayer layer];
//粒子发射的位置
snowEmitter.emitterPosition = CGPointMake(100, 30);
//发射源的大小
snowEmitter.emitterSize = CGSizeMake(self.view.bounds.size.width, 0.0);
//发射模式
snowEmitter.emitterMode = kCAEmitterLayerOutline;
//发射源的形状
snowEmitter.emitterShape = kCAEmitterLayerLine;
//创建雪花粒子
CAEmitterCell *snowflake = [CAEmitterCell emitterCell];
//粒子的名称
snowflake.name = @"snow";
//粒子参数的速度乘数因子。越大出现的越快
snowflake.birthRate = 1.0;
//存活时间
snowflake.lifetime = 120.0;
//粒子速度
snowflake.velocity = -10; // falling down slowly
//粒子速度范围
snowflake.velocityRange = 10;
//粒子y方向的加速度分量
snowflake.yAcceleration = 2;
//周围发射角度
snowflake.emissionRange = 0.5 * M_PI; // some variation in angle
//子旋转角度范围
snowflake.spinRange = 0.25 * M_PI; // slow spin
//粒子图片
snowflake.contents = (id)[[UIImage imageNamed:@"DazFlake"] CGImage];
//粒子颜色
snowflake.color = [[UIColor redColor] CGColor];
//设置阴影
snowEmitter.shadowOpacity = 1.0;
snowEmitter.shadowRadius = 0.0;
snowEmitter.shadowOffset = CGSizeMake(0.0, 1.0);
snowEmitter.shadowColor = [[UIColor whiteColor] CGColor];
// 将粒子添加到粒子发射器上
snowEmitter.emitterCells = [NSArray arrayWithObject:snowflake];
[self.view.layer insertSublayer:snowEmitter atIndex:0];
}
//烟花动画
- (void)fireworks {
// Cells spawn in the bottom, moving up
//分为3种粒子,子弹粒子,爆炸粒子,散开粒子
CAEmitterLayer *fireworksEmitter = [CAEmitterLayer layer];
CGRect viewBounds = self.view.layer.bounds;
fireworksEmitter.emitterPosition = CGPointMake(viewBounds.size.width/2.0, viewBounds.size.height);
fireworksEmitter.emitterSize = CGSizeMake(viewBounds.size.width/2.0, 0.0);
fireworksEmitter.emitterMode = kCAEmitterLayerOutline;
fireworksEmitter.emitterShape = kCAEmitterLayerLine;
fireworksEmitter.renderMode = kCAEmitterLayerAdditive;
fireworksEmitter.seed = (arc4random()%100)+1;
// Create the rocket
CAEmitterCell *rocket = [CAEmitterCell emitterCell];
rocket.birthRate = 1.0;
rocket.emissionRange = 0.25 * M_PI; // some variation in angle
rocket.velocity = 380;
rocket.velocityRange = 100;
rocket.yAcceleration = 75;
rocket.lifetime = 1.02; // we cannot set the birthrate < 1.0 for the burst
//小圆球图片
rocket.contents = (id)[[UIImage imageNamed:@"DazRing"] CGImage];
rocket.scale = 0.2;
rocket.color = [[UIColor redColor] CGColor];
rocket.greenRange = 1.0; // different colors
rocket.redRange = 1.0;
rocket.blueRange = 1.0;
rocket.spinRange = M_PI; // slow spin
// the burst object cannot be seen, but will spawn the sparks
// we change the color here, since the sparks inherit its value
CAEmitterCell *burst = [CAEmitterCell emitterCell];
burst.birthRate = 1.0; // at the end of travel
burst.velocity = 0; //速度为0
burst.scale = 2.5; //大小
burst.redSpeed =-1.5; // shifting
burst.blueSpeed =+1.5; // shifting
burst.greenSpeed =+1.0; // shifting
burst.lifetime = 0.35; //存在时间
// and finally, the sparks
CAEmitterCell *spark = [CAEmitterCell emitterCell];
spark.birthRate = 400;
spark.velocity = 125;
spark.emissionRange = 2* M_PI; // 360 度
spark.yAcceleration = 75; // gravity
spark.lifetime = 3;
//星星图片
spark.contents = (id)[[UIImage imageNamed:@"DazStarOutline"] CGImage];
spark.scaleSpeed = -0.2;
spark.greenSpeed = -0.1;
spark.redSpeed = 0.4;
spark.blueSpeed =-0.1;
spark.alphaSpeed =-0.25;
spark.spin = 2* M_PI;
spark.spinRange = 2* M_PI;
// 3种粒子组合,可以根据顺序,依次烟花弹-烟花弹粒子爆炸-爆炸散开粒子
fireworksEmitter.emitterCells = [NSArray arrayWithObject:rocket];
rocket.emitterCells = [NSArray arrayWithObject:burst];
burst.emitterCells = [NSArray arrayWithObject:spark];
[self.view.layer addSublayer:fireworksEmitter];
}
粒子特效的叠加
通过上面的代码和注释、以及苹果的注释,我相信大部分人都已掌握了粒子效果的基本使用,下面我们来谈谈粒子特效叠加的问题。
在CAEmitterCell.h当中我们可以看到这样一段描述
/* An array containing the sub-cells of this cell, or nil (the default
* value). When non-nil each particle emitted by the cell will act as
* an emitter for each of the cell's sub-cells. The emission point is
* the current particle position and the emission angle is relative to
* the current direction of the particle. Animatable. */
@property(nullable, copy) NSArray<CAEmitterCell *> *emitterCells;
这段描述说明了当设置粒子单元(CAEmitterCell *)的emitterCells时,每个粒子会充当一个发射器的角色,发射对应粒子,形成粒子特效的叠加。
将rocket.emitterCells = [NSArray arrayWithObject:burst];
修改为rocket.emitterCells = [NSArray arrayWithObject: spark];
就可以看到如下图对应的叠加效果了。
取消叠加
看了粒子特效的叠加,你可能还会有这样一个想法,我能不能取消某个时间段的叠加,只让发射的粒子在最后的时间作为发射器产生粒子效果呢,类似于发射一颗礼炮,在礼炮到达空中时爆炸,而不是像现在这样一路炸上天呢。
实例代码的fireworks部分就实现了这样的效果,下面我们来分析一下他的思路
fireworks部分的基本思路是这样的,给发射的粒子A附加一个新的粒子效果B,B的效果是在A生命周期的结尾发射一个生命周期极短的粒子,在B粒子极短的生命周期内,发射爆炸效果的粒子C,这样看起来的效果就是A被发射出去,在其生命周期的结尾发射了一个爆炸的粒子C,用户并没有意识到B粒子的存在,从而达到取消叠加的效果。
如何在代码中实现这种取消叠加的效果
birthRate
粒子每秒钟产生粒子的个数,决定了粒子产生的间隔,而且并不是一开始就发射粒子,而是要从开始过相应的时间间隔才发送粒子。所以我们只需要将B粒子的生成间隔设定为A粒子的生存长度即可,这样就实现了在A粒子即将结束的时候发射B粒子。
fireworks部分的代码也可以这样表示
burst.birthRate = 1.0/rocket.lifetime;
取消叠加的坑
你做了取消叠加部分的操作,发现A粒子生命周期结束时并没有出现爆炸效果的粒子C,这个时候问题出现在哪里呢?
原因就是刚好在A生命周期的结尾要发射单个粒子B的时候,A died,然后B就没发射出来……
这个时候你需要将A的生命周期比原来加长一点,要比B的生成间隔时间长那么一小点,代码实现上为:
CGFloat rocketLifeTime = 1.0f;
rocket.lifetime = rocketLifeTime + 0.01f;
…
burst.birthRate = 1.0f/rocketLifeTime;
或者
burst.birthRate = 1.0f/(rocket.lifetime-0.01f);
补充:粒子属性的继承
如果发射器是粒子的话,它的相应属性,会继承给它发射出的子粒子。比如说粒子A设置了redSpeed属性,粒子B redSpeed的默认值会和A相同,除非B自己设置一个redSpeed的数值。
参考博文: