I'm playing around with CAEmitterCell, but most effects are very fast effects like the firework.
But I want to have a slow "stars" effect like you see here on www.findyourwaytooz.com
How can I do that with CAEmitterCell?
Thank you :)
I have a project that uses the following setup for the emitter, and it pretty accurately mimics what I think you mean:
//set ref to the layer
starsEmitter = (CAEmitterLayer*)self.layer; //2
//configure the emitter layer
starsEmitter.emitterPosition = CGPointMake(160, 240);
starsEmitter.emitterSize = CGSizeMake(self.superview.bounds.size.width,self.superview.bounds.size.height);
NSLog(#"width = %f, height = %f", starsEmitter.emitterSize.width, starsEmitter.emitterSize.height);
starsEmitter.renderMode = kCAEmitterLayerPoints;
starsEmitter.emitterShape = kCAEmitterLayerRectangle;
starsEmitter.emitterMode = kCAEmitterLayerUnordered;
CAEmitterCell* stars = [CAEmitterCell emitterCell];
stars.birthRate = 0;
stars.lifetime = 10;
stars.lifetimeRange = 0.5;
stars.color = [[UIColor colorWithRed:255 green:255 blue:255 alpha:0] CGColor];
stars.contents = (id)[[UIImage imageNamed:#"particle.png"] CGImage];
stars.velocityRange = 500;
stars.emissionRange = 360;
stars.scale = 0.2;
stars.scaleRange = 0.1;
stars.alphaRange = 0.3;
stars.alphaSpeed = 0.5;
[stars setName:#"stars"];
//add the cell to the layer and we're done
starsEmitter.emitterCells = [NSArray arrayWithObject:stars];
I uploaded the sample project to GitHub: SimpleCAEmitterLayer
Related
I'm trying to reproduce pieces of small paper falling from top effect, using CAEmitterLayer & CAEmitterCell.
So far, I got the 2D animation of it, But I'm having difficulty to make each cell to rotate when falling.
How to I apply random rotation on each particle? I tried with 3D Transform with no success so far.
This is what I got:
-(void) configureEmitterLayer {
self.emitterLayer = [CAEmitterLayer layer];
self.emitterLayer.emitterPosition = CGPointMake(self.backgroundContainer.bounds.size.width /2, 0);
self.emitterLayer.emitterZPosition = 10;
self.emitterLayer.emitterSize = self.backgroundContainer.bounds.size;
self.emitterLayer.emitterShape = kCAEmitterLayerLine;
CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];
emitterCell.contents = (__bridge id)([UIImage imageWithColor:[UIColor whiteColor]].CGImage);
emitterCell.lifetime = CGFLOAT_MAX;
emitterCell.lifetimeRange = 4.0;
emitterCell.birthRate = 2.5;
emitterCell.color = [[UIColor colorWithRed:1.0f green:1.0 blue:1.0 alpha:1.0] CGColor];
emitterCell.redRange = 1.0;
emitterCell.blueRange = 1.0;
emitterCell.greenRange = 1.0;
emitterCell.alphaRange = 0.3;
emitterCell.velocity = 10;
emitterCell.velocityRange = 3;
emitterCell.emissionRange = (CGFloat) M_PI_2;
emitterCell.emissionLongitude = (CGFloat) M_PI;
emitterCell.yAcceleration = 1;
emitterCell.zAcceleration = 4;
emitterCell.spinRange = 2.0;
emitterCell.scale = 7.0;
emitterCell.scaleRange = 4.0;
self.emitterLayer.emitterCells = [NSArray arrayWithObject:emitterCell];
[self.backgroundContainer.layer addSublayer:self.emitterLayer];
}
This library doesn't use the emitter framework but is worth looking at for inspiration. UIDynamics is attached to individual objects to achieve the desired effect.
iOS confetti example
I successfully added a CAEmitter to my UIView inside a subview, but the particles emitted by that layer extend outside that view. Please see code below for the cookiecutter code and parameters. My question is how I can prevent the generated particles from exiting the parent view?
I've tried initializing the subview with a certain frame, and even resizing the bounds. In both cases, the emitted particles start inside the bounds but fly outside the view frame, seemingly without bound. They don't disappear until I limit the lifetime, or until they reach the end of the screen. Is there a way to prevent emitters from interacting with objects outside the view?
CAEmitterLayer *emitterLayer = (CAEmitterLayer*)self.layer;
emitterLayer.name = #"emitterLayer";
emitterLayer.emitterPosition = CGPointMake(self.frame.size.width/2, 0);
emitterLayer.emitterZPosition = 0;
emitterLayer.emitterSize = CGSizeMake(1.00, 1.00);
emitterLayer.emitterDepth = 0.00;
emitterLayer.emitterShape = kCAEmitterLayerCuboid;
emitterLayer.renderMode = kCAEmitterLayerAdditive;
emitterLayer.seed = 3534563912;
// Create the emitter Cell
CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];
emitterCell.name = #"StatSap";
emitterCell.enabled = YES;
emitterCell.contents = (id)[[UIImage imageNamed:#"mysprite.png"] CGImage];
emitterCell.contentsRect = CGRectMake(0.00, 0.00, 1.00, 1.00);
emitterCell.magnificationFilter = kCAFilterTrilinear;
emitterCell.minificationFilter = kCAFilterLinear;
emitterCell.minificationFilterBias = 0.00;
emitterCell.scale = 1.00;
emitterCell.scaleRange = 0.00;
emitterCell.scaleSpeed = -0.24;
emitterCell.color = [[UIColor colorWithRed:1.00 green:0.28 blue:0.26 alpha:1.00] CGColor];
emitterCell.redRange = 1.00;
emitterCell.greenRange = 0.34;
emitterCell.blueRange = 0.31;
emitterCell.alphaRange = 0.46;
emitterCell.redSpeed = 2.03;
emitterCell.greenSpeed = 0.00;
emitterCell.blueSpeed = 0.00;
emitterCell.alphaSpeed = 0.00;
emitterCell.lifetime = 2.65;
emitterCell.lifetimeRange = 1.02;
emitterCell.birthRate = 267;
emitterCell.velocity = 155.32;
emitterCell.velocityRange = 25.00;
emitterCell.xAcceleration = 0.00;
emitterCell.yAcceleration = 911.00;
emitterCell.zAcceleration = 0.00;
// these values are in radians, in the UI they are in degrees
emitterCell.spin = -0.175;
emitterCell.spinRange = 12.566;
emitterCell.emissionLatitude = 0.000;
emitterCell.emissionLongitude = 0.000;
emitterCell.emissionRange = 6.283;
emitterLayer.emitterCells = #[emitterCell];
Have you made sure that the superview of the emitter has clipsToBounds = YES?
I am following this tutorial "http://www.raywenderlich.com/6063/uikit-particle-systems-in-ios-5-tutorial". everything works but I want that effect to go on my image view.
please help where should I make changes
this is code I have in my view controller on which I am showing animation
-(IBAction)ClickOn:(id)sender
{
[fireView startAnimation];
[fireView setIsEmitting:YES];
[self.view bringSubviewToFront:fireView];
[self.view sendSubviewToBack:sample];
}
and this is code in library view class
-(void)startAnimation
{
NSLog(#"awakeFromNib");
//set ref to the layer
fireEmitter = (CAEmitterLayer*)self.layer; //2
//configure the emitter layer
fireEmitter.emitterSize = CGSizeMake(20, 20);
CAEmitterCell* fire = [CAEmitterCell emitterCell];
fire.birthRate = 0;
fire.lifetime = 2.0;
fire.lifetimeRange = 1.5;
fire.color = [[UIColor colorWithRed:236 green:237 blue:237 alpha:0.1] CGColor];
fire.contents = (id)[[UIImage imageNamed:#"Particles_fire.png"] CGImage];
[fire setName:#"fire"];
fire.yAcceleration =
fire.velocity = 0.1;
fire.velocityRange = 80;
fire.emissionRange = 80;
fire.scale = 1.0;
fire.scaleSpeed = 0.1;
fire.spin = 40.5;
CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:#"emitterPosition"];
ba.fromValue = [NSValue valueWithCGPoint:CGPointMake(150, 400)];
ba.toValue = [NSValue valueWithCGPoint:CGPointMake(300,0)];
ba.duration = 6;
ba.autoreverses = NO;
[fireEmitter addAnimation:ba forKey:nil];
fireEmitter.renderMode = kCAEmitterLayerPoints;
//add the cell to the layer and we're done
fireEmitter.emitterCells = [NSArray arrayWithObject:fire];
fireEmitter.zPosition = 400.0;
}
basically i just want animation to go above uiimageview or uiview, currently it is going behind it
I'm simply adding a UIView to my storyboard view controller and then creating a UIView class to display a particle effect. I change the UIView's class name to that of the custom class I created. The code I'm using in the UIView class does not display the particle effect I expected. The code is as follows:
- (void)drawRect:(CGRect)rect
{
// Drawing code
CGRect viewBounds = rect;
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;
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 deg
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;
// putting it together
fireworksEmitter.emitterCells = [NSArray arrayWithObject:rocket];
rocket.emitterCells = [NSArray arrayWithObject:burst];
burst.emitterCells = [NSArray arrayWithObject:spark];
[self.layer addSublayer:fireworksEmitter];
[self setNeedsDisplay];
}
1) Remove all of this code from drawRect, and put it into the UIView subclass init method, or, since you're using storyboard, put it into awakeFromNib:
- (void)awakeFromNib {
[super awakeFromNib];
CGRect viewBounds = rect;
fireworksEmitter = (CAEmitterLayer*)self.layer;
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
CAEmitterCell* burst = [CAEmitterCell emitterCell];
burst.birthRate = 1.0; // at the end of travel
burst.velocity = 0;
burst.scale = 2.5;
burst.redSpeed =-1.5; // shifting
burst.blueSpeed =+1.5; // shifting
burst.greenSpeed =+1.0; // shifting
burst.lifetime = 0.35;
CAEmitterCell* spark = [CAEmitterCell emitterCell];
spark.birthRate = 400;
spark.velocity = 125;
spark.emissionRange = 2* M_PI; // 360 deg
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;
fireworksEmitter.emitterCells = [NSArray arrayWithObject:rocket];
rocket.emitterCells = [NSArray arrayWithObject:burst];
burst.emitterCells = [NSArray arrayWithObject:spark];
}
Your code looks fine, so here are a few suggestions
you don't show where you are creating fireworksEmitter. If you do not alloc and init it somewhere, you will get a blank screen with no errors or warnings.
if you do not have (alpha-channel) assets named "DazRing" and "DazStarOutline" (check spelling), you will get a blank screen with no errors or warnings
Although this code will run from inside drawRect, this is not an appropriate place for it. You are not doing any drawing (directly) - rather you are setting up and adding a layer. And you certainly shouldn't be calling setNeedsDisplay from inside drawRect. This code will work just as well or better if you put it (for example) in awakeFromNib.
I am using generic code (from the iOS Fireworks demo) in a slightly changed way. I have the following in a subclass of UIView. What I want is to make the firework appear at the point the user touches (not hard) and play out for the length of the CAEmitterLayer/CAEmitterCells 'lifetime'. Instead, this is immediately starting when i add it to addSublayer -- like I am sure it is meant to. However, I would like to use it in a slightly different way. Is there a way that I can change this so there is a CATransaction with a completion block (to removeFromSuperlayer) or something like that? Any ideas are welcomed.
#import "FireworksView.h"
#implementation FireworksView
- (void)launchFirework{
//Load the spark image for the particle
CAEmitterLayer *mortor = [CAEmitterLayer layer];
mortor.emitterPosition = CGPointMake(self.bounds.size.width/2, self.bounds.size.height*(.75));
mortor.renderMode = kCAEmitterLayerAdditive;
//Invisible particle representing the rocket before the explosion
CAEmitterCell *rocket = [CAEmitterCell emitterCell];
rocket.emissionLongitude = -M_PI / 2;
rocket.emissionLatitude = 0;
rocket.lifetime = 1.6;
rocket.birthRate = 1;
rocket.velocity = 400;
rocket.velocityRange = 100;
rocket.yAcceleration = 250;
rocket.emissionRange = M_PI / 4;
rocket.color = CGColorCreateCopy([UIColor colorWithRed:.5 green:.5 blue:.5 alpha:.5].CGColor);
rocket.redRange = 0.5;
rocket.greenRange = 0.5;
rocket.blueRange = 0.5;
//Name the cell so that it can be animated later using keypath
[rocket setName:#"rocket"];
//Flare particles emitted from the rocket as it flys
CAEmitterCell *flare = [CAEmitterCell emitterCell];
flare.contents = (id)[UIImage imageNamed:#"tspark.png"].CGImage;
flare.emissionLongitude = (4 * M_PI) / 2;
flare.scale = 0.4;
flare.velocity = 100;
flare.birthRate = 45;
flare.lifetime = 1.5;
flare.yAcceleration = 350;
flare.emissionRange = M_PI / 7;
flare.alphaSpeed = -0.7;
flare.scaleSpeed = -0.1;
flare.scaleRange = 0.1;
flare.beginTime = 0.01;
flare.duration = 0.7;
//The particles that make up the explosion
CAEmitterCell *firework = [CAEmitterCell emitterCell];
firework.contents = (id)[UIImage imageNamed:#"tspark.png"].CGImage;
firework.birthRate = 9999;
firework.scale = 0.6;
firework.velocity = 130;
firework.lifetime = 2;
firework.alphaSpeed = -0.2;
firework.yAcceleration = 80;
firework.beginTime = 1.5;
firework.duration = 0.1;
firework.emissionRange = 2 * M_PI;
firework.scaleSpeed = -0.1;
firework.spin = 2;
//Name the cell so that it can be animated later using keypath
[firework setName:#"firework"];
//preSpark is an invisible particle used to later emit the spark
CAEmitterCell *preSpark = [CAEmitterCell emitterCell];
preSpark.birthRate = 80;
preSpark.velocity = firework.velocity * 0.70;
preSpark.lifetime = 1.7;
preSpark.yAcceleration = firework.yAcceleration * 0.85;
preSpark.beginTime = firework.beginTime - 0.2;
preSpark.emissionRange = firework.emissionRange;
preSpark.greenSpeed = 100;
preSpark.blueSpeed = 100;
preSpark.redSpeed = 100;
//Name the cell so that it can be animated later using keypath
[preSpark setName:#"preSpark"];
//The 'sparkle' at the end of a firework
CAEmitterCell *spark = [CAEmitterCell emitterCell];
spark.contents = (id)[UIImage imageNamed:#"tspark.png"].CGImage;
spark.lifetime = 0.05;
spark.yAcceleration = 250;
spark.beginTime = 0.8;
spark.scale = 0.4;
spark.birthRate = 10;
preSpark.emitterCells = [NSArray arrayWithObjects:spark, nil];
rocket.emitterCells = [NSArray arrayWithObjects:flare, firework, preSpark, nil];
mortor.emitterCells = [NSArray arrayWithObjects:rocket, nil];
[self.layer addSublayer:mortor];
}
The answer to this is, using CAEmitter, there is NO WAY -- delegate, etc -- to stop the emitter when it ends the cycle. The only thing you can do is gracefully remove it from the layer when you think it should be removed.
Ok, so I was able to sort of achieve this by creating an animation with delegate that fired along with the emitter. in the animationDidStop i made a for loop that went through my view hierarchy and looked for emitters and removed them. it's buggy and I still want a real solution, but this works for now.
for (CALayer *layer in _plusButton.layer.sublayers) {
if (layer.class == [CAEmitterLayer class]) {
[layer removeFromSuperlayer];
}
}