CAEmitterLayer

http://www.jianshu.com/p/197c2257f597

粒子动画

CAEmitterLayer常用属性

CAEmitterLayer并不是杂乱无章地发射粒子的,它发射粒子时的位置,发射的面积和面积对应的几何图形都是可以配置的,可以是一个点,或者一个方形、圆形等等

emitterShape:发射形状

是粒子从什么形状发射出来,它并不是表示粒子自己的形状

  • kCAEmitterLayerPoint :点
  • kCAEmitterLayerLine:线
  • kCAEmitterLayerRectangle :矩形
  • kCAEmitterLayerCircle:圆形
  • kCAEmitterLayerCuboid :正方体
  • kCAEmitterLayerSphere : 球

后两个是3D效果粒子,避而不谈。

emitterPosition:发射位置

在粒子图层上粒子的发射点(支持隐式动画),决定了粒子发射形状的中心点。

emitterSize:发射尺寸

决定了粒子发射形状的大小。

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

直线.png

我们可以这样理解,emitterPosition是所选emitterShape的中心点,例如对于矩形是对角线交点,对于圆形是圆心,对于直线是中点。而emitterSize则决定了矩形的大小,圆形的大小,直线的长度。这样说应该就够通俗易懂了。
emitterCell会以中心点抽象为一个点,在上述区域内出现。
另外,我们可以将emitterCell的速度相关的属性全部设置为0,你也可以直接注释掉他们,采用默认值,这样我们将会得到一些不会移动的粒子,因为它们没有速度,这样我们能看清楚CAEmitterLayer的形状是什么
kCAEmitterLayerPoint:

![Uploading emitterShape_line_037035.png . . .]

kCAEmitterLayerLine:

emitterShape_line.png

kCAEmitterLayerRectangle:

emitterShape_Rectangle.png

kCAEmitterLayerCircle

emitterShape_circle.png

注意:
emitterShape与emitterSize配合使用
如果是kCAEmitterLayerPoint,emitterSize没有意义。如果是kCAEmitterLayerLine,emitterSize的高度没有意义。如果是其余两种,emitterSize宽高均有意义,如果此时不设置emitterSize,与kCAEmitterLayerPoint模式效果一样。

emitterMode:发射模式

emitterMode的作用是进一步决定发射的区域是在发射形状的哪一部份,当我们看到它的枚举时,也就能大概了解了。

  • kCAEmitterLayerPoints : 点发射
  • kCAEmitterLayerOutline : 线发射
  • kCAEmitterLayerSurface:面发射
  • kCAEmitterLayerVolume:容积发射,3D图形的体内

当我们选择Points的时候,粒子会从发射形状的“顶点”发射出来,这里顶点只是一个简单的描述,有些图形不能用顶点来描述的,例如对于圆形来说,“顶点”就是圆心。Outline是指从形状的边界上发射,surface则是从形状的表面上发射,Voloume是相对于3D形状的“球体内”或“立方体内”发射,关于3D形状的问题,以后再说。
注意:
发射方式和emitterShape和emitterSize是配合使用的。当emitterShape和emitterSize共同描绘出点的时候,发射方式使用点发射,选线发射和面发射无意义,依然是点发射的效果。当emitterShape和emitterSize共同描绘出矩形时,发射方式选面发射,如果选点发射会从点发射,选线发射同理。
emitterShape与emitterSize与emitterMode共同配合使用。同选点、线、或者面即可(其实功能有重叠部分,或可不用全部设置)

renderModel:渲染模式,默认值是kCAEmitterLayerUnordered
  • kCAEmitterLayerUnordered:无序随机的
  • kCAEmitterLayerOldestFirst:最新的在上层出现
  • kCAEmitterLayerOldestLast:最新的在下层出现
  • kCAEmitterLayerBackToFront :由下层向上层涌动
  • kCAEmitterLayerAdditive :叠加显示

渲染模式就是当新的Cell出现的时候,该图层是在上一个Cell的上面还是下面。Additive为叠加模式,叠加的位置颜色会变重。

CAEmitterCell常用属性

CAEmitterLayer决定了粒子系统的发射位置,致于怎么让粒子本身酷炫起来,就需要CAEmitterCell的帮助。

(1)lifetime

粒子在系统上单纯的存在时间,单位是秒,时间到了粒子即会消失。

(2)lifetimeRange

以lifetime为基准的时间跨度,单位是秒。即粒子存在的时间为lifetime为中心点,以lifetimeRange为跨度的随机数,它可以配合lifetime来让粒子生命周期均匀变化,以便可以让粒子的出现和消失显得更加离散。
例如当lifetime=5,lifetimeRange=4时,实际的粒子存在时间为3-7秒。

(3)birthRate

粒子产生数量的决定参数,它表示CAEmitterLayer上每秒产生的粒子数量,birthRate是一个浮点数,即为0.1时表示每十秒产生一个粒子。

(4)contents

contents为cell的内容,类型为id,通常使用图片。用颜色创建图片的方法如下。

-(UIImage*)imageWithColor:(UIColor*)color andSize:(CGSize)size
{
    UIGraphicsBeginImageContext(size);
    CGContextRef context=UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, color.CGColor);

    CGRect rect=CGRectMake(0, 0, size.width, size.height);
    UIBezierPath*bezierPath=[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:size.width/2.0];

    CGContextAddPath(context, bezierPath.CGPath);
    CGContextFillPath(context);
    CGContextSetFillColorWithColor(context, color.CGColor);
    UIImage*theImage=UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return theImage;
}

同样contents也可以直接使用切图,代码如下:

emitterCell.contents = (__bridge id _Nullable)[[UIImage imageNamed:@"imageName"] CGImage];

使用bridge桥接NS类与CF类。
注意:contents不能为空,不能使用图片名不存在的图片。且图片大小决定cell的初始大小。

(5)color

color作为cell的背景色,与contents配合使用。
如果contents的颜色以[UIColor colorWithRed:1 green:1 blue:1 alpha:1]来创建,并且我们cell的color传入[UIColor colorWithRed:0.5 green:1 blue:1 alpha:1],那么最终颜色为[UIColor colorWithRed:0.5 green:1 blue:1 alpha:1]。
同样如果使用蓝色的contents图片,黄色的color,最终会得到绿色图片。
color和contents共同作用得出最终颜色。所以当使用contents图片控制cell时,color使用白色;当使用color控制cell时,contents使用纯白色图片(whiteColor,不可使用clearColor)。

(6)redRange,greenRange,blueRange

用于随机生成颜色,该属性为粒子生成时的颜色的容差范围,值为0~1。
例如redRange为0.1,那么当你的粒子的color对应的rgb为(10,255,255)。那么在其它值取默认值的前提下,这个粒子在CAEmitterLayer上发射出来的时候,它的rgb中的red component会均匀分布在 10正负0.1*255之间,即[0,35],颜色值不能为负。

(7)redSpeed,greenSpeed,blueSpeed

用于颜色逐步变化,属性为粒子颜色的变化速度,它的取值范围也是0~1。表示每秒钟的颜色变化率,乘以粒子生命周期即为变化范围。
例如你的粒子的颜色是rgb(0,255,255),并且你的粒子的redRange也为0 ,这表示你的粒子被发射出来的时候,它的颜色值中的红色值总是为0 。你的粒子的生命周期lifetime为10,即粒子可以存在10秒,10秒后会从layer上移除。那么当你的redSpeed取值为0.1时,你将会看到你的粒子的红色值每秒增加0.1*255,这个过程是连续不断变化,外观上看就是你的粒子越来越接近白色。当粒子到达生命结束的时候也就是10秒的时候,你的粒子的颜色也恰好变成了rgb(255,255,255)。

(8)alphaRange和alphaSpeed

同(6)、(7),控制透明度的初始范围和变化速率,可以通过此属性控制粒子的渐隐渐现。

(9) emissionLongitude

emissionLongtitude决定了粒子飞行方向跟水平坐标轴(x轴)之间的夹角,默认是0,即沿着x轴向右飞行。顺时针方向是正向,例如emissionLongtitude为0 ,则粒子顺着x轴飞行,如果你想让你的粒子发射出来后,沿着y轴向下飞行,那么emissionLongtitude就应该设置为PI/2。即90度,正如我所说的那样,顺时针方向是正方向。如果你想让你的粒子沿着y轴向上飞行,你可以将emissionLongtitude设置为 3*PI/2也可设置为 -PI/2。下图就简单展示了emissionLongtitude的几个典型取值,图中绿色箭头所指的方向就是当emissionLongtitude为箭头对应角度时的粒子飞行方向:

方向对应数值.png
(10)emissionRange

emissionRange则决定了粒子的发散范围,同样是一个弧度值(radians),表示粒子在沿着emissionLongtitude方向所形成的顶角为2倍emissionRange的圆锥范围内发散。我们看例图:我们把emisstionLongtitude设置为-PI/2,让粒子向上飞行,并且让emissionRange为PI/4。那么按照上面的说法,我们应该能得到一个向上的,并且顶角为2 * PI/4的圆锥,结果应该如下图:

emissionRange.png
(11) emissionLatitude

粒子Z轴上的发射角度,用于三维立体效果,可以不做设置。与emissionLongitude原理相同,默认为0,在平面上向下,为PI时向上。为PI/2是垂直屏幕向外,3*PI/2时垂直屏幕向里,在屏幕上表现为粒子不动。
emissionLatitude的值为与平面的夹角,当存在值时,我们所看到的粒子运动距离实际上为该边长在平面上的垂直投影(直角边),小于velocity与lifeTime的乘积(斜边)。
emissionRange的值可能也作用于emissionLatitude,这样所得出的粒子出没区域不再是单纯扇形(拥有更大的角度,更短的半径),更具有离散性。

(12)velocity

粒子的运动速度,配合lifeTime得出粒子活动区域。默认为0,即粒子不运动。
velocity可以为负值,即沿反方向移动。

(13)velocityRange

粒子运动速度范围。例如当velocity为100,velocityRange为200时,最终速度范围为0~200。

(14)xAcceleration yAcceleration zAcceleration

这3个属性分别定义了3个坐标轴上的加速度,它们代表了不同坐标轴方向上的每秒的速度增量,与物理学上保持一致。即当加速度为正数时加速,为负数时减速,当速度减为负数时反向加速移动。

(15)spin,spinRange

粒子的自转。粒子的自转是以弧度制来计算的,表示每秒钟粒子自转的弧度数,2PI为每秒自转一周。例如你的粒子的生命周期就是10秒,那么你想让你的粒子在10秒内刚好自转1周,那么spin为2PI/10。另外,当spin为正数的时候,粒子是顺时针旋转的,为负数的话就是逆时针选转了。 spinRange就不多说了,跟其它range的意义和作用是一样的,让自转速度离散。

(16)scale,scaleRange,scaleSpeed

粒子的缩放。scale与scaleRange的值为0~1,可以更改粒子初始的大小。scaleSpeed表示粒子的变化速率,与以上其他属性含义相同。

(17)subCell

可以指定一个subcell作为cell的cells列表成员,完成粒子在完成动画消失时开始下一个动画。

一个简单的下雨动画代码
- (instancetype)initWithFrame:(CGRect)frame
{

    self = [super initWithFrame:frame];
    if (self) {
        [self addSubview:self.backgroundImageView];
        self.backgroundColor = [UIColor clearColor];
        self.layer.masksToBounds = YES;
        dispatch_async(dispatch_get_main_queue(), ^{
          [self.layer addSublayer:self.emitter];
        });
    }

    return self;
}

- (UIImageView *)backgroundImageView
{
    if (!_backgroundImageView) {
        _backgroundImageView = [[UIImageView alloc] initWithFrame:self.bounds];
        _backgroundImageView.image = [UIImage imageNamed:@"bg_rain"];
    }
    return _backgroundImageView;
}

- (CAEmitterLayer *)emitter
{
    if (!_emitter) {
        _emitter = ({

            CAEmitterLayer *emitter = [CAEmitterLayer layer];
            emitter.backgroundColor = [UIColor clearColor].CGColor;
            emitter.emitterPosition = CGPointMake(self.bounds.size.width, -40);
            emitter.emitterSize = CGSizeMake(self.bounds.size.width * 1.5, 80);
            emitter.emitterMode = kCAEmitterLayerSurface;
            emitter.emitterShape = kCAEmitterLayerRectangle;

            CAEmitterCell *midEmitterCell = [self midEmitterCell];
            CAEmitterCell *smallEmitterCell = [self smallEmitterCell];
            CAEmitterCell *largeEmitterCell = [self largeEmitterCell];

            //add the cell to the emitter layer
            emitter.emitterCells = @[ midEmitterCell, smallEmitterCell, largeEmitterCell ];
            emitter;
        });
    }

    return _emitter;
}

- (CAEmitterCell *)largeEmitterCell
{
    CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];

    emitterCell.contents = (id)[[UIImage imageNamed:@"rain_large"] CGImage];

    emitterCell.name = @"rain_large";

    UIColor *color = [UIColor colorWithWhite:1.0 alpha:1.0];
    emitterCell.color = color.CGColor;

    emitterCell.birthRate = LargeRainrate;
    emitterCell.lifetime = lifeTime(2.1);
    emitterCell.lifetimeRange = 0;
    emitterCell.velocity = 150;
    emitterCell.velocityRange = 50;

    emitterCell.emissionLongitude = 2;
    emitterCell.emissionRange = 0;


    emitterCell.xAcceleration = -1;
    emitterCell.yAcceleration = 20;
    emitterCell.alphaRange = 0.8;
    emitterCell.alphaSpeed = 0.1;

    emitterCell.scale = 0.6;
    emitterCell.scaleRange = 0.2;
    emitterCell.scaleSpeed = 0.0;


    CAEmitterCell *subCell = [self waterCirleCell];
    emitterCell.emitterCells = @[ subCell ];

    return emitterCell;
}

- (CAEmitterCell *)midEmitterCell
{
    CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];

    emitterCell.contents = (id)[[UIImage imageNamed:@"rain_mid"] CGImage];

    emitterCell.name = @"rain_mid";

    UIColor *color = [UIColor colorWithWhite:1.0 alpha:1.0];
    emitterCell.color = color.CGColor;

    emitterCell.birthRate = MidRainrate;
    emitterCell.lifetime = lifeTime(2.0);
    emitterCell.lifetimeRange = 0;
    emitterCell.velocity = 150;
    emitterCell.velocityRange = 50;

    emitterCell.emissionLongitude = 2;
    emitterCell.emissionRange = 0;


    emitterCell.xAcceleration = -1;
    emitterCell.yAcceleration = 25;
    emitterCell.alphaRange = 0.8;
    emitterCell.alphaSpeed = -0.1;

    emitterCell.scale = 0.6;
    emitterCell.scaleRange = 0.2;
    emitterCell.scaleSpeed = 0.0;

    return emitterCell;
}

- (CAEmitterCell *)smallEmitterCell
{
    CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];

    emitterCell.contents = (id)[[UIImage imageNamed:@"rain_small"] CGImage];

    emitterCell.name = @"rain_small";

    UIColor *color = [UIColor colorWithWhite:1.0 alpha:1.0];
    emitterCell.color = color.CGColor;

    emitterCell.birthRate = SmallRainrate;
    emitterCell.lifetime = lifeTime(2.0);
    emitterCell.lifetimeRange = 0;
    emitterCell.velocity = 200;
    emitterCell.velocityRange = 50;

    emitterCell.emissionLongitude = 2;
    emitterCell.emissionRange = 0;


    emitterCell.xAcceleration = -1;
    emitterCell.yAcceleration = 30;
    emitterCell.alphaRange = 0.6;
    emitterCell.alphaSpeed = -0.2;

    emitterCell.scale = 0.6;
    emitterCell.scaleRange = 0.2;
    emitterCell.scaleSpeed = 0.0;

    return emitterCell;
}


- (CAEmitterCell *)waterCirleCell
{
    //create new emitter cell
    CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];
    emitterCell.beginTime = lifeTime(2.05);
    //粒子图片
    emitterCell.contents = (__bridge id _Nullable)[[UIImage imageNamed:@"circle_small"] CGImage];
    //粒子源名称
    emitterCell.name = @"circle_small";
    //get the particles start color
    UIColor *color = [UIColor whiteColor];
    emitterCell.color = color.CGColor;
    //copy all the settings to the emitter cell
    //产生速率
    emitterCell.birthRate = 1;

    //生命周期
    emitterCell.lifetime = 2.4;

    //透明度
    emitterCell.alphaRange = 0.8;
    //透明度变化速度
    emitterCell.alphaSpeed = -0.3;

    //缩放
    emitterCell.scale = 0.4;
    emitterCell.scaleRange = 0.3;
    emitterCell.scaleSpeed = 0.4;


    return emitterCell;
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容