Is this the right way to start and stop a particle system - ios

I'm currently practicing with particle systems and I was wondering if the following code is the right way to stop and start a particle when a button is tapped?
The code works fine, I touch the start button and the particle starts, I touch the stop button and the particle stops but I'm not sure if removeFromSuperLayer is the right method to use. As I said, the code does what I need but I just want to make sure that the particle won't keep running in the background even after calling removeFromSuperLayer and end-up wasting resources.
- (IBAction)stopAnimation:(id)sender
{
[emitterLayer removeFromSuperlayer];
}
- (IBAction)startAnimation:(id)sender
{
[self particle];
}
-(void) particle
{
emitterLayer = [CAEmitterLayer layer];
emitterLayer.emitterPosition = CGPointMake(50 ,50);
emitterLayer.emitterZPosition = 10;
emitterLayer.emitterSize = CGSizeMake(10,10);
emitterLayer.emitterShape = kCAEmitterLayerSphere;
CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];
emitterCell.scale = 0.1;
emitterCell.scaleRange = 0.2;
emitterCell.emissionRange = (CGFloat)M_PI_2;
emitterCell.lifetime = 10;
emitterCell.birthRate = 5;
emitterCell.velocity = 20;
emitterCell.velocityRange = 50;
emitterCell.yAcceleration = 0;
emitterCell.contents = (id)[[UIImage imageNamed:#"particleImage.png"] CGImage];
emitterLayer.emitterCells = [NSArray arrayWithObject:emitterCell];
[self.view.layer addSublayer:emitterLayer];
}
Thanks a lot

You could use a method in which you put the following:
- (void)stopEmitting
{
self.emitterCell.birthRate = 0.0f;
}
With this you should be able to stop the emitting, without having to remove and re-create the layer each time when the start button is pressed.
To start again, simply do:
- (void)startEmitting
{
self.emitterCell.birthRate = <VAlUE HERE (greater than 0)>;
}
Hope this helps.

Rather than change the birthRate of the each of the emitterCells you can change the birthrate of the emitterLayer itself.
- (void)stopEmitting
{
self.emitterLayer.birthRate = 0;
}
- (void)startEmitting
{
self.emitterLayer.birthRate = 1;
}

It's funny but just modifying the birthRate like this self.emitterCell.birthRate = 0.0f; in a method doesn't stop the emitterCell, in fact it looks like if it appends instead of stopping it, in other words if I change it to self.emitterCell.birthRate = 100; it adds 100 more particles to the existing particles. Lucky I found the solution.
I basically had to give my emitterCell a name emitterCell.name = #"_myCell"; and then in my stop method modify it like this [emitterLayer setValue:[NSNumber numberWithInteger:0] forKeyPath:#"emitterCells._myCell.birthRate"]; and it worked.
This is what I did that worked. This is assuming you already have an image in your project named myImage.
#import "SpriteViewController.h"
#implementation SpriteViewController
CAEmitterLayer *emitterLayer;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)stopAnimation:(id)sender
{
[emitterLayer setValue:[NSNumber numberWithInteger:0] forKeyPath:#"emitterCells._myCell.birthRate"]; // new code
}
- (IBAction)startAnimation:(id)sender
{
[self particle];
}
-(void) particle
{
emitterLayer = [CAEmitterLayer layer];
emitterLayer.emitterPosition = CGPointMake(50 ,50);
emitterLayer.emitterZPosition = 10;
emitterLayer.emitterSize = CGSizeMake(10,10);
emitterLayer.emitterShape = kCAEmitterLayerSphere;
CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];
emitterCell.name = #"_myCell";// new code
emitterCell.scale = 0.1;
emitterCell.scaleRange = 0.2;
emitterCell.emissionRange = (CGFloat)M_PI_2;
emitterCell.lifetime = 10;
emitterCell.birthRate = 5;
emitterCell.velocity = 20;
emitterCell.velocityRange = 50;
emitterCell.yAcceleration = 0;
emitterCell.contents = (id)[[UIImage imageNamed:#"myImage.png"] CGImage];
emitterLayer.emitterCells = [NSArray arrayWithObject:emitterCell];
[self.view.layer addSublayer:emitterLayer];
}
#end

Related

Particle system looks different on iOS 6 and ios7

I have a couple of particle systems on my app that I checked originally on an iPad 3 with ios7. The particles looked and behaved like I expected them, but when I test on an iPad2 with ios6. The particles are displaced up and are smaller. I have a subclass for the particles. Here's the code for one of my particle systems. I was wandering if the CGREctMake is positioning the particle in a different area on a non retina display, but that doesn't happen when I position other things on the display.
Code to call the particles.
- (void)ShowSteamEffect
{
//Create view for Steam particle effect.
CGRect SteamFrame = CGRectMake(790, 380, 100, 100);
//Show Steam effect
ShowSteam = [[SteamEffect alloc]initWithFrame:SteamFrame];
ShowSteam.hidden = NO;
[self.view insertSubview:ShowSteam aboveSubview:imgComputerLights];
}
Steam subclass.
#import "SteamEffect.h"
#implementation SteamEffect
{
CAEmitterLayer* SteamEmitter;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
SteamEmitter = (CAEmitterLayer*) self.layer;
//SteamEmitter.emitterPosition= CGPointMake(0, 0);
//SteamEmitter.emitterSize = CGSizeMake(10,10);
CAEmitterCell* Steam = [CAEmitterCell emitterCell];
Steam.birthRate = 50;
Steam.spin = .6;
Steam.lifetime = 1.7;
Steam.alphaRange = 0.2;
Steam.alphaSpeed = 0.2;
Steam.contents = (id)[[UIImage imageNamed:#"Steam1.png"]CGImage];
Steam.velocity = 30;
Steam.velocityRange = 50;
Steam.emissionLongitude = -60;
Steam.emissionRange = M_1_PI;
Steam.scale = .2;
Steam.scaleSpeed = .5;
Steam.yAcceleration = -200;
SteamEmitter.renderMode = kCAEmitterLayerBackToFront;
SteamEmitter.emitterShape = kCAEmitterLayerCircle;
SteamEmitter.emitterCells = #[Steam];
}
return self;
}
-(void)didMoveToSuperview
{
[super didMoveToSuperview];
if (self.superview==nil) return;
[self performSelector:#selector(disableEmitterCell) withObject:nil afterDelay:0.5];
}
-(void)disableEmitterCell
{
[SteamEmitter setValue:#0 forKeyPath:#"birthRate"];
}
+ (Class) layerClass
{
//tell UIView to use the CAEmitterLayer root class
return [CAEmitterLayer class];
}
- (void) setEmitterPosition:(CGPoint)pos
{
SteamEmitter.emitterPosition = pos;
}
- (void) toggleOn:(bool)on
{
//SteamEmitter.birthRate = on? 300 : 0;
}

UIKit Particle Systems in ios7. Number of particles increased

The particle system on ios7 seem to be working different, than on ios6 and ios5. The number of particles increased.
The same issue happens with all particle effects in the app.
The only working solution is to check, if it is ios7 and decrease the particle birth rate. Is there a better solution?
The code of particle emitter view.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
//initialize the emitter
_emitter = (CAEmitterLayer*)self.layer;
_emitter.emitterPosition = CGPointMake(self.bounds.size.width /2, self.bounds.size.height/2 );
_emitter.emitterSize = self.bounds.size;
_emitter.emitterMode = kCAEmitterLayerAdditive;
_emitter.emitterShape = kCAEmitterLayerRectangle;
}
return self;
}
- (void)didMoveToSuperview
{
//Check if parent is initialized
[super didMoveToSuperview];
if (self.superview==nil) return;
//Load png
UIImage* texture = self.explosionImage; //[UIImage imageNamed:#"particle.png"];
NSAssert(texture, #"particle.png not found");
//Create a new emitter cell
CAEmitterCell* emitterCell = [CAEmitterCell emitterCell];
//Set the cell’s contents property to the texture you loaded
emitterCell.contents = (__bridge id)[texture CGImage];
//Name the cell “cell”
emitterCell.name = #"cell";
//Parameters
emitterCell.birthRate = self.birthRate;// 40;//1000
emitterCell.lifetime = 2.8;
emitterCell.lifetimeRange = 0.7;
//Set the cell’s color to randomly vary its components
if (!self.explosionColor) {
emitterCell.blueRange = 0.33;
emitterCell.blueSpeed = -0.33;
emitterCell.redRange = 0.33;
emitterCell.redSpeed = -0.33;
emitterCell.greenRange = 0.33;
emitterCell.greenSpeed = -0.33;
}
//Explosion color
if (self.explosionColor) emitterCell.color = [self.explosionColor CGColor];
//velocity
emitterCell.velocity = IS_IPAD?40:20;//160
emitterCell.velocityRange = RANDOMF(10, 20);//15
//Alpha
emitterCell.alphaSpeed = -0.73;
emitterCell.alphaRange = 0.9;
//Scale
emitterCell.scale = IS_IPAD?0.6:0.30;//0.5
emitterCell.scaleRange = 0.6;//0.5
emitterCell.scaleSpeed = 0;
//Range
emitterCell.emissionRange = M_PI*2;//2
//Add the cell to the emitter layer
_emitter.emitterCells = #[emitterCell];
[self performSelector:#selector(disableEmitterCell) withObject:nil afterDelay:self.cellIsEmitting];
[self performSelector:#selector(removeFromSuperview) withObject:nil afterDelay:self.emittingInView];
}
When running on an iOS7, it looks as if the particle system started animating earlier than the time the emitter layer is added.
It is probably a bug, and, hopefully, will be solved in future.
For now, to get a similar behavior as on iOS6, the CAEmitterLayer's beginTime can be set to the current time:
_emitter.beginTime = CACurrentMediaTime();

CAEmitterLayer not Shown

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.

iOS CAEmitterCell firework effect

I'm wanna create an effect like firework. I know that I can use for it CAEmitterCell. I tried to download few examples, but I still in progress with this feature in iOS 5.0
Anybody know good tutorial how to create firework effect using CAEmitterCell?
I need to create something like below:
All particles are going from the center (red circle) in direction to green circles. Red circle this is initial point that will have some CGPoint value.
I have found this solution:
it's how DWFParticleView.h file looks
#import <UIKit/UIKit.h>
#interface DWFParticleView : UIView
#property (nonatomic) CGFloat time;
-(void)configurateEmmiter;
-(void)setEmitterPositionFromTouch: (UITouch*)t;
-(void)setIsEmitting:(BOOL)isEmitting andPoint:(CGPoint)point;
#end
this is how looks DWFParticleView.m
#import "DWFParticleView.h"
#import <QuartzCore/QuartzCore.h>
#implementation DWFParticleView
{
CAEmitterLayer* fireEmitter; //1
}
-(void)configurateEmmiter
{
//set ref to the layer
fireEmitter = (CAEmitterLayer*)self.layer; //2
//configure the emitter layer
fireEmitter.emitterPosition = CGPointMake(50, 50);
fireEmitter.emitterSize = CGSizeMake(5, 5);
CAEmitterCell* fire = [CAEmitterCell emitterCell];
fire.birthRate = 0;
fire.lifetime = 2.0;
fire.lifetimeRange = 0.5;
//fire.color = [[UIColor colorWithRed:0.8 green:0.4 blue:0.2 alpha:0.6] CGColor];
fire.contents = (id)[[UIImage imageNamed:#"star_icon.png"] CGImage];
[fire setName:#"fire"];
fire.velocity = 80;
fire.velocityRange = 20;
fire.emissionRange = M_PI * 2.0f;
fire.scaleSpeed = 0.1;
fire.spin = 0.5;
//add the cell to the layer and we're done
fireEmitter.emitterCells = [NSArray arrayWithObject:fire];
fireEmitter.renderMode = kCAEmitterLayerAdditive;
}
+ (Class) layerClass {
return [CAEmitterLayer class];
}
-(void)setEmitterPositionFromTouch: (UITouch*)t
{
//change the emitter's position
fireEmitter.emitterPosition = [t locationInView:self];
}
-(void)setIsEmitting:(BOOL)isEmitting andPoint:(CGPoint)point
{
fireEmitter.emitterPosition = point;
//turn on/off the emitting of particles
[fireEmitter setValue:[NSNumber numberWithInt:isEmitting?50:0]
forKeyPath:#"emitterCells.fire.birthRate"];
[self performSelector:#selector(decayStep) withObject:nil afterDelay:self.time];
}
- (void)decayStep {
[fireEmitter setValue:[NSNumber numberWithInt:0]
forKeyPath:#"emitterCells.fire.birthRate"];
}
#end
this is method one tap on screen for example that shows our effect
- (void)tap {
DWFParticleView *fireView = [[DWFParticleView alloc] init];
fireView.time = 0.3;
[fireView setUserInteractionEnabled:NO];
[fireView configurateEmmiter];
[fireView setFrame:CGRectMake(0, 0, 0, 0)];
[fireView setBackgroundColor:[UIColor redColor]];
[self.view addSubview:fireView];
}
the material I have found here: ray

How do you remove a CAEmitterLayer when it ends the lifetime of it's CAEmitter Cells -- instead of repeating until you remove it from super layer

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];
}
}

Resources