CAEmitterLayer, как излучать в течение короткого времени повторно

Я играю с CAEmitterLayer, и теперь я сталкиваюсь с некоторыми проблемами:(

Мне нужен эффект короткой частицы - например, удар или взрыв - например, в одном месте (поэтому у меня есть небольшой UIView в этом месте). Как мне это сделать?

1, у меня была идея - создать emitterLayer с его частицами и установить lifeTime на 0. И когда мне это нужно, я устанавливаю для lifeTime, например, 1, и через некоторое время я могу установить его обратно на 0. - НО он ничего не делает:(

2. Вторая идея состояла в том, чтобы каждый раз создавать мне слой [CAEmitterLayer] и добавлять его в качестве подслоя слоев. Но я думаю, что произойдет, когда я повторю это, например, десять раз... У меня есть 10 подслоев с одним активным и 9 "мертвыми"? Как вообще перестать излучать? Через некоторое время у меня есть executeSelector, чтобы установить время жизни равным 0, и другой селектор с более длинным интервалом для удаления FromSuperlayer... Но это не так красиво, как мне бы хотелось:(Есть ли другой "правильный" способ?

Я думаю, что со слишком большим количеством подслоев связана моя другая проблема... Я хочу испустить только одну частицу. И когда я делаю это, это работает. Но ИНОГДА он излучает три частицы, иногда две... И это меня бесит. Когда я не останавливаю излучатель, он каждый раз дает правильное количество частиц...

Итак, вопросы...

как испускать частицы за короткое время. как с ними работать - как остановить, удалить из родительского слоя, … как испустить точное количество частиц

РЕДАКТИРОВАТЬ:

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

Затем в методе, связанном с кнопкой, я делаю это:

emitter.lifetime = 1.0;

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.9 * NSEC_PER_SEC), dispatch_get_current_queue(), ^{
    emitter.lifetime = 0;
});

ИЗМЕНЕНО и ОБНОВЛЕНО после изменения отношения @David Rönnqvist

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

6 ответов

Вы можете сделать это, настроив все один раз (не добавляйте новую ячейку эмиттера каждый раз) и установив birthRate до 0 (частицы не будут созданы). Затем, когда вы хотите создать свои частицы, вы можете установить birthRate на количество частиц в секунду, которое вы хотите создать. Через некоторое время вы установите birthRate вернуться к 0, чтобы выброс прекратился. Вы могли бы использовать что-то вроде dispatch_after() сделать эту задержку.


Некоторое время назад я сделал нечто подобное и решил это следующим образом. Следующее создаст один быстрый взрыв частиц. В следующий раз, когда вы захотите испускать частицы, вы измените частоту рождения "облака" обратно на 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"];
});

Благодаря отличному ответу Foundrys здесь я решил проблему, очень похожую на эту. Это не подразумевает сокрытие каких-либо взглядов. Вкратце, это выглядит так:

Настройте свой излучатель, как обычно, назовите ячейки излучателя и дайте им значение рождаемости, равное нулю:

-(void) setUpEmission {
  # ...
  # snip lots of config
  # ...
  [emitterCell1 setBirthrate:0];
  [emitterCell1 setName:@"emitter1"];
  [emitterCell2 setBirthrate:0];
  [emitterCell2 setName:@"emitter2"];
  emitterLayer.emitterCells = @[emitterCell1, emitterCell2];
  [self.view.layer addSublayer:emitterLayer];
}

Затем создайте метод start, который автоматически отключает излучение через некоторое время, и метод stop:

-(void) startEmission {
  [emitterLayer setValue:@600 forKeyPath:@"emitterCells.emitter1.birthRate"];
  [emitterLayer setValue:@250 forKeyPath:@"emitterCells.emitter2.birthRate"];
  [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(stopEmission) userInfo:nil repeats:NO];
}

-(void) stopEmission {
  [emitterLayer setValue:@0 forKeyPath:@"emitterCells.emitter1.birthRate"];
  [emitterLayer setValue:@0 forKeyPath:@"emitterCells.emitter2.birthRate"];
}

В этом примере я установил скорость рождения 600 и 250. И таймер отключает излучение через 0,2 секунды, но используйте то, что считаете нужным.

Оптимальным решением было бы, если бы Apple внедрила методы запуска / остановки, но если не считать этого, я считаю это удовлетворительным решением.

Я тоже искал решение и нашел эту статью.

посмотрите на эту суть частицы конфетти, и ее Stop Emitting метод.

Что это делает:

  1. Добавьте вид частиц к вашему виду дисплея.
  2. Пусть частицы бегут столько, сколько вы хотите.
  3. остановить выброс новых частиц с confettiEmitter.birthRate = 0.0;,
  4. подожди несколько секунд.
  5. удалить вид частиц.

Надеюсь, это поможет.

CAEmitter.birthRate является анимируемым. Предполагая, что вы добавили несколько CAEmitterLayerС точки зрения, вы можете сделать это, чтобы оживить спад рождаемости, а затем перезапустить через несколько секунд:

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

ОК, наконец, после нескольких часов тестирования и проб разных стилей (инициализация, удаление, настройка эмиттеров) я пришел к окончательному результату... И на самом деле это меня очень огорчает...

---Это невозможно!---

Даже когда я создаю излучатель и его частицы каждый раз, когда мне это нужно, если я настраиваю только одну частицу на излучение, это дает мне большую часть времени одну частицу... НО это не 100%, а иногда он просто излучает три частицы или две... Это случайно. И это очень плохо. Потому что это визуальный эффект...:(

В любом случае, если у кого-то есть совет, как решить эту проблему, пожалуйста, дайте мне знать...

Вы рассматривали возможность использования анимируемых свойств CALayers?

  func setEmitterProperties() {

    backgroundColor = UIColor.clearColor().CGColor
    birthRate = kStandardBirthRate
    emitterShape = kCAEmitterLayerLine
    emitterCells = [typeOneCell, typeTwoCell, typeOneCell]
    preservesDepth = false

    let birthRateDecayAnimation = CABasicAnimation()
    birthRateDecayAnimation.removedOnCompletion = true
    birthRateDecayAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
    birthRateDecayAnimation.duration = CFTimeInterval(kStandardAnimationDuration)
    birthRateDecayAnimation.fromValue = NSNumber(float: birthRate)
    birthRateDecayAnimation.toValue = NSNumber(float: 0)
    birthRateDecayAnimation.keyPath = kBirthRateDecayAnimationKey
    birthRateDecayAnimation.delegate = self

  }
  1. Свойство делегата также может быть равно нулю, если вы не хотите ничего делать по завершении, как в animationDidStop: finish:

  2. Константы kBirthRateDecayAnimationKey & kStandardAnimationDuration используют мое соглашение, а не Apple.

Другие вопросы по тегам