CoreAnimation - CAEmitterLayer粒子
简介
粒子系统表示三维计算机图形学中模拟一些特定的模糊现象的技术,而这些现象用其它传统的渲染技术难以实现的真实感的游戏图形。经常使用粒子系统模拟的现象有火、爆炸、烟、水流、火花、落叶、云、雾、雪、尘、流星尾迹或者象发光轨迹这样的抽象视觉效果等等。
一、CAEmitterLayer属性介绍
CAEmitterLayer并不是杂乱无章地发射粒子的,它发射粒子时的位置,发射的面积和面积对应的几何图形都是可以配置的,可以是一个点,或者一个方形、圆形等等。
//emitterPosition; 发射形状的中心点,默认是(0,0,0)
@property CGPoint emitterPosition;
@property CGFloat emitterZPosition;
//发射形状的大小,默认(0,0,0)依赖于`emitterShape'属性
@property CGSize emitterSize;
@property CGFloat emitterDepth;
//发射形状, 默认 kCAEmitterLayerPoint
@property(copy) NSString *emitterShape;
//发射模式, 默认kCAEmitterLayerVolume
@property(copy) NSString *emitterMode;
//渲染模式(粒子图片是如何混合的), 默认kCAEmitterLayerUnordered
@property(copy) NSString *renderMode;
1. emitterShape
为什么叫做“发射形状”呢? 看看emitterShape的枚举,你就会大概能明白
kCAEmitterLayerPoint //点
kCAEmitterLayerLine //线段
kCAEmitterLayerRectangle //矩形
kCAEmitterLayerCuboid //3D矩形
kCAEmitterLayerCircle //圆形,半径是emitterSize.width
kCAEmitterLayerSphere //球形,半径是emitterSize.width
我们可以这样理解,emitterPosition
是所选emitterShape
的中心点,例如对于矩形是对角线交点,对于圆形是圆心,对于直线是中点。而emitterSize
则决定了矩形的大小,圆形的大小,直线的长度(直线默认是没有高度的)。
2. emitterMode
emitterMode
的作用是进一步决定发射的区域是在发射形状的哪一部份。
NSString * const kCAEmitterLayerPoints; // 顶点
NSString * const kCAEmitterLayerOutline; // 轮廓,即边上
NSString * const kCAEmitterLayerSurface; // 表面,即图形的面积内
NSString * const kCAEmitterLayerVolume; // 容积,即3D图形的体积内
当我们选择Points的时候,粒子会从发射形状的“顶点”发射出来,这里顶点只是一个简单的描述,有些图形不能用顶点来描述的,例如对于圆形来说,“顶点”就是圆心。Outline是指从形状的边界上发射,surface则是从形状的表面上发射,Voloume是相对于3D形状的“球体内”或“立方体内”发射
3.renderMode
renderMode
控制着在视觉上粒子图片是如何混合的
NSString * const kCAEmitterLayerUnordered //无序的
NSString * const kCAEmitterLayerOldestFirst
NSString * const kCAEmitterLayerOldestLast
NSString * const kCAEmitterLayerBackToFront //覆盖
NSString * const kCAEmitterLayerAdditive //叠加
二、CAEmitterCell属性介绍
因为CAEmitterCell
的属性比较多,我用表格的形式列举出来:
属性 | 作用 |
---|---|
name | 粒子名称默认是nil |
birthRate | 粒子的产生速率/秒,默认是0 |
lifetime , lifetimeRange | 粒子的生命周期平均值和生命周期的范围,以秒为单位。两者默认0 |
velocity ,velocityRange | 每一个粒子的初始速度平均值和变化范围,默认值都是0 |
xAcceleration ,yAcceleration ,zAcceleration | x,y,z方向上的加速度分量,三者默认都是0 |
scale, scaleRange ,scaleSpeed | 缩放比例 :默认是1,缩放比例范围 :默认是0,缩放速度:默认是0 |
spin,spinRange | 粒子的自转是以弧度制来计算的,表示每秒钟粒子自转的弧度数。 |
emissionRange | emissionRange则决定了粒子的发散范围,同样是一个弧度值,默认是0 |
emissionLongitude | emissionLongtitude决定了粒子飞行方向跟水平坐标轴(x轴)之间的夹角,默认是0,即沿着x轴向右飞行 |
emissionLatitude | 纬度角代表了x-z轴平面上与x轴之间的夹角 |
color | 粒子的颜色,默认白色 |
redRange,greenRange,blueRange,alphaRange | 粒子颜色blue,red,green,alpha能改变的范围,默认0,它的取值范围也是0~1,颜色值不能为负数。 |
redSpeed,greenSpeed,blueSpeed,alphaSpeed | 表示对应的颜色component的变化速度,它的取值范围也是0~1 |
contents | 粒子的内容,大多都是个CGImageRef的对象,既粒子要展现的图片,默认nil |
emitterCells
CAEmitterCell
的emitterCells跟CAEmitterLayer
的一样,也是一个CAEmitterCell的队列。我们基本可以按照操作CAEmitterLayer的emitterCells一样来设置我们粒子的emitterCells。
为了方便描述,我们将从粒子上发射出来的粒子称作称作subCell
。
-
CAEmitterCell也是服从
CAMediatiming
协议的,我们通过控制subCell的beginTime来控制subCell的出现时机。subCell的beginTime是不能大于你的粒子的lifetime的。 -
如果你想控制subCell的发射方向,那么需要考虑到父粒子的emissionLongtitude的情况。无论粒子是从什么样的形状上发射出来的,当它要发射subCell的时候,subCell总是从kCAEmitterLayerPoint形状上由父粒子的中心发射出来的。父粒子的发射方向是subCell的emissionLongtitude为0时的飞行方向。
三、CAEmitterLayer动画实例
1. 粒子发射动画
直接看下面的代码:
- (void)addStarEmitter{
self.view.backgroundColor = [UIColor blackColor];
//创建粒子发射器
_emitterLayer = [CAEmitterLayer layer];
_emitterLayer.masksToBounds = YES;
_emitterLayer.frame = self.view.bounds;
[self.view.layer addSublayer:_emitterLayer];
//设置CAEmitterLayer
_emitterLayer.renderMode = kCAEmitterLayerAdditive;
_emitterLayer.emitterPosition = CGPointMake(CGRectGetMidX(self.view.bounds)+50, 150);
//创建粒子模板
CAEmitterCell *emitterCell = [[CAEmitterCell alloc]init];
emitterCell.contents = (__bridge id)[UIImage imageNamed:@"star"].CGImage;
emitterCell.contentsScale = [UIScreen mainScreen].scale;
emitterCell.color = [UIColor colorWithRed:0 green:0 blue:0 alpha:1].CGColor;
emitterCell.redRange = 1.0;
emitterCell.greenRange = 1.0;
emitterCell.blueRange = 1.0;
emitterCell.alphaRange = 0;
emitterCell.redSpeed = 0;
emitterCell.greenSpeed = 0;
emitterCell.blueSpeed = 0;
emitterCell.alphaSpeed = -0.5;
emitterCell.scale = 1;
emitterCell.scaleRange = 0.2;
emitterCell.scaleSpeed = 0.1;
emitterCell.spin = 130*M_PI/180.0;;
emitterCell.spinRange = 0;
emitterCell.emissionLatitude = 0;
emitterCell.emissionLongitude = 0;
emitterCell.emissionRange = M_PI *2;
emitterCell.lifetime = 1;
emitterCell.lifetimeRange = 0;
emitterCell.birthRate = 200;
emitterCell.velocity = 50;
emitterCell.velocityRange = 500;
emitterCell.xAcceleration = -750;
emitterCell.yAcceleration = 0;
//将粒子添加到发射器上
_emitterLayer.emitterCells = @[emitterCell];
}
2. 点赞动画
首先我们来分析一下这个动画,共有两部分:
1.点赞按钮的Scale变化
2.可用控制开始和结束的CAEmitterLayer动画
- (void)setUp{
_emitterLayer = [CAEmitterLayer layer];
_emitterLayer.name = @"emitterLayer";
//粒子发射器的形状
_emitterLayer.emitterShape = kCAEmitterLayerCircle;
//从轮廓进行发射
_emitterLayer.emitterMode = kCAEmitterLayerOutline;
_emitterLayer.renderMode = kCAEmitterLayerOldestFirst;
_emitterLayer.emitterPosition = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
_emitterLayer.emitterSize = CGSizeMake(25, 0);
_emitterLayer.masksToBounds = NO;
CAEmitterCell *cell = [[CAEmitterCell alloc]init];
cell.contents = (__bridge id)[UIImage imageNamed:@"Sparkle"].CGImage;
cell.name = @"explosion";
cell.alphaRange = 0.2;
cell.alphaSpeed = -1.0;
cell.lifetime = 0.7;
cell.lifetimeRange = 0.3;
cell.birthRate = 0;
cell.velocity = 40;
cell.velocityRange = 10;
//如果你的contents的图片太大了,可以通过设置sacle属性缩小
cell.scale = 0.05;
cell.scaleRange = 0.02;
_emitterLayer.emitterCells = @[cell];
[self.layer addSublayer:_emitterLayer];
}
接下来我们需要手动控制动画的开始和结束。还记得前面提到的 @property(copy) NSString *name
; 吗?想要手动控制动画的开始和结 束,我们必须通过 KVC 的方式设置 cell 的值才行。
[self.emitterLayer setValue:@500 forKeyPath:@"emitterCells.explosion.birthRate"];
这句话的含义:CAEmitterLayer根据自己的"emitterCells"属性找到名叫"explosion"的cell,并设置他的birthRate = 500,从而间接控制了动画的开始。
- (void)startExplosion{
self.emitterLayer.beginTime = CACurrentMediaTime();
[self.emitterLayer setValue:@500 forKeyPath:@"emitterCells.explosion.birthRate"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self stopExplosion];
});
}
- (void)stopExplosion{
//通过设置birthRate间接控制了动画的结束
[self.emitterLayer setValue:@0 forKeyPath:@"emitterCells.explosion.birthRate"];
}
你可以查看MCFireworksButton开源库。
3. 烟花爆炸效果
我们分析可以看到动画分为两个阶段:
1.粒子发射阶段。
2.粒子爆炸阶段。(这里我们需要用到上面提到的CAEmitterCell
的emitterCells
)
- (void)fireworkEmitter{
CGFloat rocketLifeTime = 1.0;
//创建粒子发射器
CAEmitterLayer *emitterLayer = [CAEmitterLayer layer];
emitterLayer.emitterPosition = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetHeight(self.view.bounds));
emitterLayer.emitterSize = CGSizeMake(CGRectGetWidth(self.view.bounds), 0);
emitterLayer.emitterShape = kCAEmitterLayerLine;
emitterLayer.emitterMode = kCAEmitterLayerOutline;
emitterLayer.renderMode = kCAEmitterLayerAdditive;
//创建粒子模板(发射阶段)
CAEmitterCell *rocket = [CAEmitterCell emitterCell];
rocket.contents = (__bridge id)[UIImage imageNamed:@"小圆球"].CGImage;
rocket.color = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1].CGColor;
rocket.scale = 0.5;
rocket.redRange = 0.7;
rocket.greenRange = 0.7;
rocket.birthRate = 1;
rocket.lifetime = rocketLifeTime + 0.02;
rocket.velocity = 500;
rocket.velocityRange = 100;
rocket.yAcceleration = 75;
rocket.emissionRange = M_PI_4;
//创建粒子模板(爆炸阶段)
CAEmitterCell *spark = [CAEmitterCell emitterCell];
spark.birthRate = 10000;
spark.scale = 0.6;
spark.velocity = 125;
spark.emissionRange = 2* M_PI;
spark.yAcceleration = 75;
spark.lifetime = 2;
spark.contents = (id)[[UIImage imageNamed:@"星"] CGImage];
spark.scaleSpeed = -0.2;
spark.greenRange = 0.4;
spark.redRange = 0.7;
spark.blueRange = 0.9;
spark.alphaSpeed =-0.25;
spark.spin = 2* M_PI;
spark.spinRange = M_PI;
//beginTime要小于 rocket.lifetime
spark.beginTime = rocketLifeTime;
emitterLayer.emitterCells = @[rocket];
rocket.emitterCells = @[spark];
[self.view.layer addSublayer:emitterLayer];
}
完整的代码可以在这查看Demo地址