iOS之绘图动画动画iOS Developer

CoreAnimation - CAEmitterLayer粒子

2017-09-16  本文已影响116人  _誌念

简介

粒子系统表示三维计算机图形学中模拟一些特定的模糊现象的技术,而这些现象用其它传统的渲染技术难以实现的真实感的游戏图形。经常使用粒子系统模拟的现象有火、爆炸、烟、水流、火花、落叶、云、雾、雪、尘、流星尾迹或者象发光轨迹这样的抽象视觉效果等等。

一、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

  1. CAEmitterCell也是服从CAMediatiming协议的,我们通过控制subCell的beginTime来控制subCell的出现时机。subCell的beginTime是不能大于你的粒子的lifetime的

  2. 如果你想控制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.粒子爆炸阶段。(这里我们需要用到上面提到的CAEmitterCellemitterCells

- (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地址

上一篇下一篇

猜你喜欢

热点阅读