I'm playing around with CAEmitterLayer and I face some problems now :(
I need a short particle effect - like a hit or explosion - at one place for example (so I have small UIView at this place). How should I do that?
1, I had an idea - create the emitterLayer with it's particles and set the lifeTime to 0. And when I need it I set the lifeTime to 1 for example and after awhile I can set it back to 0. - BUT it's not doing anything :(
2, The second idea was to create [CAEmitterLayer layer] every time I need it and add it as a layers sublayer. But I'm thinking what happen when I repeat it for example ten times… I have 10 sublayers with one acive and 9 "dead"? How to stop emitting in general? I have performSelector after some time to set the lifetime to 0 and other selector with longer interval to removeFromSuperlayer… But it's not so pretty as I would like to have it :( is there another "proper" way?
I think with too many sublayers is related my other problem… I want to emit just one particle. And when I do it it works. But SOMETIMES it emit three particles, sometimes two… And it makes me mad about that. When I don't stop emitter it's giving every time the correct number of particles...
So the questions…
how to emit particles for a short time. how to work with them - like stop, remove from parent layer, … how to emit just exact number of particles
EDIT:
emitter = [CAEmitterLayer layer];
emitter.emitterPosition = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2);
emitter.emitterMode = kCAEmitterLayerPoints;
emitter.emitterShape = kCAEmitterLayerPoint;
emitter.renderMode = kCAEmitterLayerOldestFirst;
emitter.lifetime = 0;
particle = [CAEmitterCell emitterCell];
[particle setName:@"hit"];
particle.birthRate = 1;
particle.emissionLongitude = 3*M_PI_2;//270deg
particle.lifetime = 0.75;
particle.lifetimeRange = 0;
particle.velocity = 110;
particle.velocityRange = 20;
particle.emissionRange = M_PI_2;//PI/2 = 90degrees
particle.yAcceleration = 200;
particle.contents = (id) [[UIImage imageNamed:@"50"] CGImage];
particle.scale = 1.0;
particle.scaleSpeed = -0.5;
particle.alphaSpeed = -1.0;
emitter.emitterCells = [NSArray arrayWithObject:particle];
[(CAEmitterLayer *)self.view.layer addSublayer: emitter];
Then in method linked with button I do this:
emitter.lifetime = 1.0;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.9 * NSEC_PER_SEC), dispatch_get_current_queue(), ^{
emitter.lifetime = 0;
});
EDITED and UPDATED after changing to @David Rönnqvist attitude
CAEmitterCell *dustCell = [CAEmitterCell emitterCell];
[dustCell setBirthRate:1];
[dustCell setLifetime:1.5];
[dustCell setName:@"dust"];
[dustCell setContents:(id) [[UIImage imageNamed:@"smoke"] CGImage]];
[dustCell setVelocity:50];
[dustCell setEmissionRange:M_PI];
// Various configurations for the appearance...
// This is the only cell with configured scale,
// color, content, emissionLongitude, etc...
[emitter setEmitterCells:[NSArray arrayWithObject:dustCell]];
[(CAEmitterLayer *)self.view.layer addSublayer:emitter];
// After one burst, change the birth rate of the cloud to 0
// so that there is only one burst per side.
double delayInSeconds = 0.5; // One cloud will have been created by now, but not two
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
[emitter setLifetime:0.0];
[emitter setValue:[NSNumber numberWithFloat:0.0]
forKeyPath:@"emitterCells.dust.birthRate"];
});
You can do this by configuring everything once (don't add a new emitter cell every time) and setting the birthRate
to 0 (no particles will be created). Then when you want to create your particles you can set the birthRate
to the number of particles per second that you want to create. After a certain time you set the birthRate
back to 0 so that the emission stops.
You could use something like dispatch_after()
to do this delay.
I did something similar a while back and solved it like this. The following will create one quick burst of particles. The next time you want the particles to emit, you change the birthRate of the "cloud" back to 1.
CAEmitterCell *dustCell = [[CAEmitterCell alloc] init];
[dustCell setBirthRate:7000];
[dustCell setLifetime:3.5];
// Various configurations for the appearance...
// This is the only cell with configured scale,
// color, content, emissionLongitude, etc...
CAEmitterCell *dustCloud = [CAEmitterCell emitterCell];
[dustCloud setBirthRate:1.0]; // Create one cloud every second
[dustCloud setLifetime:0.06]; // Emit dustCells for 0.06 seconds
[dustCloud setEmitterCells:[NSArray arrayWithObject:dustCell]];
[dustCloud setName:@"cloud"]; // Use this name to change the birthRate later
[dustEmitter setEmitterPosition:myPositionForDustEmitter];
[rightDustEmitter setEmitterCells:[NSArray arrayWithObject:dustCloud]];
// After one burst, change the birth rate of the cloud to 0
// so that there is only one burst per side.
double delayInSeconds = 0.5; // One cloud will have been created by now, but not two
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
// For some reason, setting the birthRate of the "cloud" to 0
// has a strange side effect that when you set it back to 1 all
// the missed emissions seems to happen at once during the first
// emission and then it goes back to only emitting once per
// second. (Thanks D33 for pointing this out).
// By instead changing the birthRate of the "dust" particle
// to 0 and then back to (in my case) 7000 gives the visual
// effect that I'm expecting. I'm not sure why it works
// this way but at least this works for me...
// NOTE: This is only relevant in case you want to re-use
// the emitters for a second emission later on by setting
// the birthRate up to a non-zero value.
[dustEmitter setValue:[NSNumber numberWithFloat:0.0]
forKeyPath:@"emitterCells.cloud.emitterCells.dust.birthRate"];
});
CAEmitter.birthRate
is animatable. Assuming you've added a few CAEmitterLayer
s to the view, you can do this to animate the decay of the birthrate and then re-start after a few seconds:
- (void) startConfetti{
for (CALayer *emitterLayer in self.layer.sublayers) {
if ([emitterLayer isKindOfClass: [CAEmitterLayer class]]) {
((CAEmitterLayer *)emitterLayer).beginTime = CACurrentMediaTime();
((CAEmitterLayer *)emitterLayer).birthRate = 6;
// Decay over time
[((CAEmitterLayer *)emitterLayer) removeAllAnimations];
[CATransaction begin];
CABasicAnimation *birthRateAnim = [CABasicAnimation animationWithKeyPath:@"birthRate"];
birthRateAnim.duration = 5.0f;
birthRateAnim.fromValue = [NSNumber numberWithFloat:((CAEmitterLayer *)emitterLayer).birthRate];
birthRateAnim.toValue = [NSNumber numberWithFloat:0.0f];
birthRateAnim.repeatCount = 0;
birthRateAnim.autoreverses = NO;
birthRateAnim.fillMode = kCAFillModeForwards;
[((CAEmitterLayer *)emitterLayer) addAnimation:birthRateAnim forKey:@"finishOff"];
[CATransaction setCompletionBlock:^{
((CAEmitterLayer *)emitterLayer).birthRate = 0.f;
[self performSelector:@selector(startConfetti) withObject:nil afterDelay:10];
}];
[CATransaction commit];
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With