阅读 547

iOS:CAEmitterLayer粒子效果

说到粒子效果就要说到核心动画Core Animation,因为粒子效果所用到的特殊图层是包含在核心动画框架中的。这个特殊图层就是CAEmitterLayer。

CAEmitterLayer是CALayer的一个常用子类,CALayer的子类有很多,CAEmitterLayer就是其中之一,CAEmitterLayer是用于实现基于Core Animation的高性能粒子引擎系统。

CALayer.png
粒子系统使用到两个类CAEmitterLayer与CAEmitterCell,CAEmitterLayer是粒子发射源,用来发射粒子的,它所发射的粒子就是CAEmitterCell(当然粒子也可以发射粒子,也就是CAEmitterCell也可以发射CAEmitterCell)。可以认为CAEmitterLayer是CAEmitterCell的发射源,通过不同的参数设置就会不断的产生不同的粒子。

我们首先来快速看一下这两个类的常用属性:

CAEmitterLayer常用属性

@property(nullable, copy) NSArray<CAEmitterCell *> *emitterCells; // 用来装粒子种类的数组, 通过给数组赋值,来支持多个cell
@property float birthRate; //粒子产生系数(倍数),默认1.0
@property float lifetime; // 粒子的生命周期系数, 默认1.0
@property CGPoint emitterPosition; // 决定粒子发射位置的中心点
@property CGFloat emitterZPosition; //三维立体中的位置
@property CGSize emitterSize; // 发射源的尺寸大小
@property CGFloat emitterDepth; // 发射源的深度
@property(copy) NSString *emitterShape; // 发射源的形状,默认是点point,(还有line,rectangle,circle,cuboid,sphere).
@property(copy) NSString *emitterMode; // 发射模式,默认是volume,(还有points,outline,surface).
@property(copy) NSString *renderMode; // 渲染模式,默认是unordered,还有oldestFirst,oldestLast,backToFront,additive
@property BOOL preservesDepth; //是否需要深度(一般使用这个属性)
@property float velocity; // 粒子基本速度系数, 默认1.0
@property float scale; // 粒子缩放比例系数, 默认1.0
@property float spin; // 粒子自旋转速度系数, 默认1.0
@property unsigned int seed; // 随机数发生器
复制代码

CAEmitterLayer里面的所有属性都在这里了,但是在实际情况中可能用不到这么多的属性,下面来重点介绍一些属性:

1. CAEmitterLayer控制发射源发射位置和形状的属性

  • emitterPosition:决定发射源的中心点
  • emitterSize: 决定发射源的大小
  • emitterShape:表示粒子从什么形状发射出来,它并不是表示粒子自己的形状。

emitterShape是一个枚举类型,提供如下类型可供选择:

  • kCAEmitterLayerPoint 点形状,发射源的形状就是一个点
  • kCAEmitterLayerLine 线形状,发射源的形状是一条线
  • kCAEmitterLayerRectangle 矩形状,发射源的形状是一个矩形
  • kCAEmitterLayerCuboid 立体矩形形状(3D),发射源是一个立体矩形,这里要生效的话需要设置z方向的数据,如果不设置就同矩形状.
  • kCAEmitterLayerCircle 圆形形状,发射源是一个圆形
  • kCAEmitterLayerSphere 立体圆形(3D),三维的圆形,同样需要设置z方向数据,不设置则如同二维的圆.
  • emitterMode:发射模式,它的作用其实就是进一步决定发射的区域是在发射形状的哪一部份。
  • kCAEmitterLayerPoints 点模式,发射器是以点的形式发射粒子。发射点就是形状的某个特殊的点,比如shap是一个点的话,那么这个点就是中心点,如果是圆形,那么就是圆心。
  • kCAEmitterLayerOutline 轮廓模式,从形状的边界上发射粒子。
  • kCAEmitterLayerSurface 表面模式,从形状的表面上发射粒子。
  • kCAEmitterLayerVolume 是相对于3D形状的物体内部发射.

我们用kCAEmitterLayerLine来说明一下。当你的CAEmitterLayer的emitterSize为CGSize(10, 10)时,你的所选择的emitterPosition为CGPoint(10,10)。那么形状为“Line”的CAEmitterLayer就会在如下图紫色的直线上产生粒子,对于“Line”来说,emitterSize的高度是被忽略的。

示例.png
我们可以这样理解,emitterPosition是所选emitterShape的中心点,例如对于矩形是对角线交点,对于圆形是圆心,对于直线是中点。而emitterSize则决定了矩形的大小,圆形的大小,直线的长度。 如果我把CAEmitterCell的速度属性全部设为0,此时没有速度的粒子就不会动了,再把发射模式emitterMode设为kCAEmitterLayerOutline,我们来看一下这几种发射形状的效果图:
kCAEmitterLayerPoint.png
kCAEmitterLayerLine.png
kCAEmitterLayerRectangle.png
kCAEmitterLayerCircle.png
现在我们利用同样的方法看下emitterMode的样式是什么样的,我们设置emitterShape发射形状为kCAEmitterLayerRectangle):
kCAEmitterLayerPoints.png
kCAEmitterLayerOutline.png
kCAEmitterLayerSurface.png

  • renderMode : 渲染模式,决定了粒子是以怎么一种形式进行渲染的。
  • kCAEmitterLayerUnordered 粒子是无序出现的
  • kCAEmitterLayerOldestFirst 声明时间长的粒子会被渲染在最上层
  • kCAEmitterLayerOldestLast 声明时间短的粒子会被渲染在最上层
  • kCAEmitterLayerBackToFront 粒子的渲染按照Z轴的前后顺序进行
  • kCAEmitterLayerAdditive 进行粒子混合

2. CAEmitterLayer决定发射源粒子系数的属性

  • birthRate: 种类粒子产生系数,默认1.0;需要结合单个粒子cell的产生系数,每个粒子cell的产生系数乘以这个粒子产生系数,得出每秒具体产生粒子的个数。 即:每秒粒子产生个数 = layer.birthRate * cell.birthRate ;
  • lifetime:粒子的生命周期系数,默认1.0。
  • velocity:粒子速度系数, 默认1.0。
  • scale:粒子的缩放比例系数, 默认1.0。
  • spin:自旋转速度系数, 默认1.0。计算方式同上;

3.CAEmitterLayer决定发射源粒子内容的属性

emitterCells:用来装粒子的数组。每一种粒子就是一个CAEmitterCell。

CAEmitterCell常用属性

@property(nullable, copy) NSString *name; // 粒子名字, 默认为nil,有了名字才能找到对应的粒子
@property(getter=isEnabled) BOOL enabled; 
@property float birthRate; // 粒子的产生率,默认0
@property float lifetime; // 粒子的生命周期,默认0,以秒为单位。
@property float lifetimeRange; // 粒子的生命周期的范围,默认0,以秒为单位。
@property CGFloat emissionLatitude;// 指定纬度,纬度角代表了在x-z轴平面坐标系中与x轴与z轴之间的夹角,默认0
@property CGFloat emissionLongitude; // 指定经度,经度角代表了在x-y轴平面坐标系中与x轴与y轴之间的夹角,默认0
@property CGFloat emissionRange; //发射角度范围,默认0
@property CGFloat velocity; // 速度,默认是0
@property CGFloat velocityRange; //速度范围,默认是0
@property CGFloat xAcceleration; // 在x方向上的重力加速度分量,默认是0
@property CGFloat yAcceleration;// 在y方向上的重力加速度分量,默认是0
@property CGFloat zAcceleration;// 在z方向上的重力加速度分量,默认是0
@property CGFloat scale; // 粒子在生命周期范围内的缩放比例, 默认是1
@property CGFloat scaleRange; // 缩放比例范围,默认是0
@property CGFloat scaleSpeed; // 在生命周期内的缩放速度,默认是0,负数缩小,正数放大;
@property CGFloat spin; // 粒子的平均旋转速度,默认是0
@property CGFloat spinRange; // 自旋转角度范围,弧度制,默认是0
@property(nullable) CGColorRef color; // 粒子的颜色,默认白色
@property float redRange; // 粒子的颜色red,green,blue,alpha能改变的范围,默认0
@property float greenRange;
@property float blueRange;
@property float alphaRange;
@property float redSpeed; // 粒子速度red,green,blue,alpha在生命周期内的改变的速度,默认都是0
@property float greenSpeed;
@property float blueSpeed;
@property float alphaSpeed;
@property(nullable, strong) id contents; // 粒子的内容,设置为CGImageRef的对象
@property CGRect contentsRect;//粒子内容的位置
@property CGFloat contentsScale; //粒子内容的缩放比例
@property(copy) NSString *minificationFilter;//缩小的过滤器(基本不用)
@property(copy) NSString *magnificationFilter;//放大的过滤器(基本不用)
@property float minificationFilterBias;
@property(nullable, copy) NSArray<CAEmitterCell *> *emitterCells; // 粒子里面的粒子
@property(nullable, copy) NSDictionary *style;
复制代码

接下来重点说说CAEmitterCell一些常用的属性:

1.CAEmitterCell决定粒子生命周期的属性

  • lifetime、lifetimeRange:粒子在系统上的生命周期,即存活时间,单位是秒。配合lifetimeRage来让粒子生命周期均匀变化,以便可以让粒子的出现和消失显得更加离散。
  • birthRate:粒子产生数量的决定参数,它表示CAEmitterLayer上每秒产生的粒子数量,是浮点数。对于这个数量为浮点数,在测试的时候可以灵活使用它。比如你想看粒子的运动状态,但是太多了可能会很迷糊,这时候你把birthRate 设置成0.1f,把lifetime设置时间较长,就能看到单个粒子的运动状态。

2.CAEmitterCell决定粒子内容的属性

  • contents:为CGImageRef的对象。关于contents会联想到CALayer了,在CALayer中展示静态的图片是需要用到这个属性。在CAEmitterCell上它的意义也是一样的。但是因为粒子系统可以给粒子上色,为了做出好的颜色变换效果,通常提供的图片为白色的纯色的图片,

  • name:粒子的名字,当CAEmitterLayer里面有很多个cell的时候,可以给每个cell设置好名字,要修改一些属性以达到动画效果的改变等,就可以通过KVC拿到这个cell的某个属性。

3.CAEmitterCell决定粒子颜色状态的属性

  • color:color是粒子的颜色属性,这个颜色属性的作用是给粒子上色,color 会结合contents内容的颜色来改变我们的CAEmitterCell,它的实现方法其实很简单,就是将contents自身的颜色的RGBA值 * color的RGBA值,就得到最终的粒子的颜色。我们想完全通过color来控制CAEmitterCell的颜色,那么最好就选用一张颜色值为(255,255,255),即白色的图片作为CAEmitterCell的contents。因为大家都知道在UIColor中,rgb值为255的component的值其实为1,因为白色的UIColor每个component都为1,CAEmitterCell的color中任意component乘以contents中颜色对应的component都会得到color上原来的值。
  • redRange、greenRange、blueRange、alphaRange:这些是对应的color的RGBA的取值范围,默认为0,取值范围为0~1,如果设置粒子的颜色为[[UIColor colorWithRed:0.3 green:0.5 blue:0.3 alpha:1.0]CGColor];再设置 redRange、greenRange、blueRange、alphaRange全部为0.1,那么也就是说在产生粒子时,粒子的颜色RGBA对应的取值范围是:R(0.1, 0.3),G(0.1, 0.5),B(0.1, 0.3),A(0.1, 1.0).
  • redSpeed、greenSpeed、blueSpeed、alphaSpeed:这些是对应的是粒子的RGBA的变化速度,默认为0,取值范围为0~1。表示每秒钟的RGBA的变化率,如果设置粒子颜色的RGBA,以及redSpeed,其他的属性没设置默认为0。粒子的生命周期(lifetime)为20秒,那么这个粒子从产生到消失的时间就是20秒。它的Red值为0,redSpeed为0.2,那么在粒子的这个生命周期内,粒子的每秒钟的Rde值就会增加0.2 * 255,表现在外观上的状态就是粒子颜色在不断变化,接近白色。最后粒子生命周期结束的时候,粒子的color正好是RGBA(1,1,1,1)。当然个变化的速度也可以负数,计算方式相同。

4.CAEmitterCell决定粒子运行轨迹的属性。

  • emissionLongitude:表示粒子飞行方向跟水平坐标轴(x轴)之间的夹角,默认是0,顺时针方向是正向。例如emissionLongtitude为0 ,则粒子顺着x轴飞行。如果想沿着Y轴向下飞行,那么可以设置emissionLongtitude = M_PI_2。

    平面运行方向.png

  • emissionLatitude:这个和emissionLongitude的原理是一样的,只不过是在三维平面上的x-z轴上与x轴的夹角。

  • emissionRange则决定了粒子的发射角度范围,同样是一个弧度值。表示粒子在沿着emissionLongtitude方向所形成的顶角为2倍emissionRange的扇形范围内发散。我们把emisstionLongtitude设置为M_PI_2,让粒子向下飞行,并且让emissionRange为M_PI_4,就会得到一个顶角为2 * PI/4的扇形区域,那么粒子就会在这个区域内发射

    emissionRange角度.png
    效果图.png

  • velocity、velocityRange、xAcceleration 、yAcceleration、zAcceleration:前面两个是粒子的初速度和初速度的范围,后面是三个分别是在x、y、z轴方向的加速度,当xAcceleration为正数时,粒子每秒向x轴正方向加速,为负数时则向负方向即水平向左加速。当yAcceleration为正数时,粒子向y轴的负方向加速,也是就是向下加速,否则向上加速。

  • spin,spinRange:是粒子的自转属性,表示每秒钟粒子自转的弧度数。粒子的自转是以弧度制来计算的,表示每秒钟粒子自转的弧度数。spin为正数代表粒子是顺时针旋转的,为负数的话就是逆时针旋转。如果粒子的生命周期就是10秒,那么你想让你的粒子这个生命周期内刚好自转1周,若spinRange为0,那么粒子的spin值就应该为((π/180)*360 )/10,就得到了每秒需要转动的弧度数。

5.CAEmitterCell子粒子的属性

emitterCells:CAEmitterCell的emitterCells跟CAEmitterLayer的一样,也是一个CAEmitterCell的数组。我们基本可以按照操作CAEmitterLayer的emitterCells一样来设置我们粒子的emitterCells。

不过在这里需要注意的是:

  1. CAEmitterCell是服从CAMediatiming协议的,我们通过控制子CAEmitterCell的beginTime来控制子CAEmitterCell的出现时机。当子CAEmitterCell的beginTime为0时,表示你的粒子从CAEmitterLayer上发射出来后就会立即开始发射子CAEmitterCell,而且子CAEmitterCell的beginTime是不能大于你的粒子的lifetime的。
  2. 无论粒子是从什么样的形状上发射出来的,当它要发射子CAEmitterCell的时候,子CAEmitterCell总是从kCAEmitterLayerPoint形状上由父粒子的中心发射出来的。
    colorBallCell.birthRate = 20.f;
    colorBallCell.lifetime = 10.f;
    colorBallCell.velocity = 40.f;
    colorBallCell.velocityRange = 100.f;
    colorBallCell.yAcceleration = 15.f;
    colorBallCell.emissionLongitude = M_PI_2; 
    colorBallCell.emissionRange = M_PI_4l;
    colorBallCell.scale = 0.2;


    CAEmitterCell*subCell=[CAEmitterCell emitterCell];
    subCell.lifetime=5;
    subCell.birthRate=3;
    subCell.contents=(id)[UIImage imageNamed:@"circle_white"].CGImage;
    subCell.velocity=60;
    subCell.emissionLongitude= - M_PI_2;
    subCell.emissionRange=M_PI_4/4;
    subCell.beginTime=5;
    subCell.scale=0.5;
    colorBallCell.emitterCells=@[subCell];

    colorBallLayer.emitterCells = @[colorBallCell];
复制代码

subCell.emissionLongitude.jpeg
父粒子的发射方向是子粒子的emissionLongtitude为0时的飞行方向。

上述内容已将CAEmitterLayer和CAEmitterCell的属性基本介绍完毕,后续会再做补充,下面我们来看一个具体的案例,把这些属性全部串联起来。

案例

1.设置CAEmitterLayer

设置CAEmitterLayer以及它的一些模式,并添加到要显示的view的图层上,当然也可以替换view的图层。这里是直接添加到控制器的view的layer上的,这个view的layer是一个calyer类型的,如果想替换掉calyer,可以自定义view,并在view里面重写如下方法即可实现替换

// 替换view的layer
+ (Class)layerClass{return [CAEmitterLayer class];}
复制代码
#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) CAEmitterLayer * colorBallLayer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.view.backgroundColor = [UIColor blackColor];
    [self setupEmitter];
    
}
- (void)setupEmitter{
    
    UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(0, 100, self.view.bounds.size.width, 50)];
    [self.view addSubview:label];
    label.textColor = [UIColor whiteColor];
    label.text = @"轻点或拖动来改变发射源位置";
    label.textAlignment = NSTextAlignmentCenter;
    
    // 1. 设置CAEmitterLayer
    CAEmitterLayer * colorBallLayer = [CAEmitterLayer layer];
    [self.view.layer addSublayer:colorBallLayer];
    self.colorBallLayer = colorBallLayer;
    
    //发射源的尺寸大小
    colorBallLayer.emitterSize = CGSizeMake(100, 10);
    //发射源的形状
    colorBallLayer.emitterShape = kCAEmitterLayerPoint;
    //发射模式
    colorBallLayer.emitterMode = kCAEmitterLayerPoints;
    //粒子发射形状的中心点
    colorBallLayer.emitterPosition = CGPointMake(self.view.layer.bounds.size.width/2, 20);
    
    // 2. 配置CAEmitterCell
    CAEmitterCell * colorBallCell = [CAEmitterCell emitterCell];
    //粒子名称
    colorBallCell.name = @"colorBallCell";
    //粒子产生率,默认为0
    colorBallCell.birthRate = 20.f;
    //粒子生命周期
    colorBallCell.lifetime = 10.f;
    //粒子速度,默认为0
    colorBallCell.velocity = 40.f;
    //粒子速度平均量
    colorBallCell.velocityRange = 100.f;
    //x,y,z方向上的加速度分量,三者默认都是0
    colorBallCell.yAcceleration = 15.f;
    //指定纬度,纬度角代表了在x-z轴平面坐标系中与x轴之间的夹角,默认0:
    colorBallCell.emissionLongitude = M_PI_2; // 向左
    //发射角度范围,默认0,以锥形分布开的发射角度。角度用弧度制。粒子均匀分布在这个锥形范围内;
    colorBallCell.emissionRange = M_PI_4; // 围绕X轴向左90度
    // 缩放比例, 默认是1
    colorBallCell.scale = 0.2;
    // 缩放比例范围,默认是0
    colorBallCell.scaleRange = 0.1;
    
    // 在生命周期内的缩放速度,默认是0
    colorBallCell.scaleSpeed = 0.02;
    // 粒子的内容,为CGImageRef的对象
    colorBallCell.contents = (id)[[UIImage imageNamed:@"circle_white"] CGImage];
    //颜色
    colorBallCell.color = [[UIColor colorWithRed:0.5 green:0.f blue:0.5 alpha:1.f] CGColor];
    // 粒子颜色red,green,blue,alpha能改变的范围,默认0
    colorBallCell.redRange = 1.f;
    colorBallCell.greenRange = 1.f;
    colorBallCell.alphaRange = 0.8;
    // 粒子颜色red,green,blue,alpha在生命周期内的改变速度,默认都是0
    colorBallCell.blueSpeed = 1.f;
    colorBallCell.alphaSpeed = -0.1f;
    
    
    
    CAEmitterCell*subCell=[CAEmitterCell emitterCell];
    subCell.lifetime=5;
    subCell.birthRate=3;
    subCell.contents=(id)[UIImage imageNamed:@"circle_white"].CGImage;
    subCell.velocity=60;
    subCell.emissionLongitude= - M_PI_2;
    subCell.emissionRange=M_PI_4/4;
    subCell.beginTime=5;
    subCell.scale=0.5;
    colorBallCell.emitterCells=@[subCell];
    // 添加
    colorBallLayer.emitterCells = @[colorBallCell];
}
复制代码

到此CAEmitterLayer和CAEmitterCell的属性配置都已完成,下面我们让发射源根据手指的点击位置进行改变:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CGPoint point = [self locationFromTouchEvent:event];
    [self setBallInPsition:point];
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CGPoint point = [self locationFromTouchEvent:event];
    [self setBallInPsition:point];
}

/**
 * 获取手指所在点
 */
- (CGPoint)locationFromTouchEvent:(UIEvent *)event{
    UITouch * touch = [[event allTouches] anyObject];
    return [touch locationInView:self.view];
}
复制代码

下面我们用一个动画来实现移动发射源到手指点击的点上:

- (void)setBallInPsition:(CGPoint)position{
    
    //创建基础动画
    CABasicAnimation * anim = [CABasicAnimation animationWithKeyPath:@"emitterCells.colorBallCell.scale"];
    //fromValue
    anim.fromValue = @0.2f;
    //toValue
    anim.toValue = @0.5f;
    //duration
    anim.duration = 1.f;
    //线性起搏,使动画在其持续时间内均匀地发生
    anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    
    // 用事务包装隐式动画
    [CATransaction begin];
    //设置是否禁止由于该事务组内的属性更改而触发的操作。
    [CATransaction setDisableActions:YES];
    //为colorBallLayer 添加动画
    [self.colorBallLayer addAnimation:anim forKey:nil];
    //为colorBallLayer 指定位置添加动画效果
    [self.colorBallLayer setValue:[NSValue valueWithCGPoint:position] forKeyPath:@"emitterPosition"];
    //提交动画
    [CATransaction commit];
}
复制代码

下面我们来看下实现的效果:

效果图.png

至此,一个简单的粒子效果的代码就写完成了,其实大部分内容都是在配置CAEmitterLayer与CAEmitterCell的属性,因为这两个类是苹果为开发者封装好的,可以直接使用,已经省去了大部分的时间,我们只需要调用属性即可。在以后的时间中,或许可以写一个OpenGL ES 的GLSL语言来实现粒子效果。

demo地址:github.com/Henry-Jeann…

关注下面的标签,发现更多相似文章
评论