Как лучше всего организовать вызов метода после выполнения нескольких экшенов над разными спрайтами?
Один из вариантов предлагают тут stackoverflow. Смысл - вызвать метод после задержки, равной времени проигрывания анимации:
... ccTime actionDuration = 5.0; for (CCSprite *sprite in sprites) { [sprite runAction:[CCMoveTo actionWithDuration:actionDuration position:randomPoint]]; } [self performSelector:@selector(callbackMethod:) withObject:nil afterDelay:actionDuration];
Меня смущает, не получится ли тут какой-нибудь race condition? Возможна ли ситуация, при которой callbackMethod начнет выполняться раньше завершения всех анимаций?
Второй вариант - вызывать коллбэк метод N раз в CCSequence, и уже там считать, сколько раз анимация завершалась:
//организуем вызовы анимаций id moveAction = [CCMoveTo actionWithDuration: 5.0 position: randomPoint]; id actionCallFunc = [CCCallFunc actionWithTarget:self selector:@selector(animationComplete)]; self.animationCount = 0; for (CCSprite *sprite in sprites) { [sprite runAction: [CCSequence actions: moveAction, actionCallFunc, nil]]; } ... //метод, где проверяем сколько раз анимация завершилась - (void)animationComplete { @syncronized(self) { self.animationCount++; if (self.animationCount == self.sprites.count) //собственно только тут вызов нужного коллбэка [self callbackMethod]; } }
Ну тут просто чувствуется костыльность. Явно есть средство лучше. Кто какие практики использует?
@syncronized зачем? Экшены ж в том же потоке выполняются.
По идее можно только для одного спрайта делать sequence.
Если времена равны, то все экшены кончатся в одном кадре!
А вот использовать performSelector крайне не рекомендую, многократно накалывался. stopActions и cleanup отложенное выполнение селектора не затрагивают. Он запросто может выполнится на удаленных объектах уже. И вообще много чудес несет.
Сам в таких ситуациях делал по первому варианту, но с action
ccTime actionDuration = 5.0; for (CCSprite *sprite in sprites) { [sprite runAction:[CCMoveTo actionWithDuration:actionDuration position:randomPoint]]; } [self runAction: [CCSequence actionOne: [CCDelayTime actionWithDuration:actionDuration] two:[CCCallFunc/Block ....]];
Второй вариант можно улучшить, используя блок. Тогда не нужно хранить счетчик как ivar, и для каждого вызова функции, в которой находится твой код, будет создаваться новый счетчик.
id __weak this = self; // чтобы блок не ретейнил self; если self удалится во время анимаций, this станет nil __block NSUInteger animationCount = sprites.count; CCAction *actionCallBlock = [CCCallBlock actionWithBlock:^{ animationCount--; if (animationCount == 0) [this callbackMethod]; }]; for (CCSprite *sprite in sprites) { [sprite runAction:[CCSequence actions:moveAction, actionCallBlock, nil]]; }
shadowstyle
Универсальный вариант - посылка NSNotification в функции CCCallBlock:.
Подписчик принимает именованную нотификашку и производит нужные пертурбации.
Но мне кажется автор имеет немного другое.
Мне в одном приложении требовался учет того, идет ли данная анимация на объекте или он уже готов к "игровому" удалению - при этом callback было использовать не совсем желательно, поскольку много кода волоклось за ним, и переводить много переменных на __weak с проверками есть переменная или уже удалена - ломало. Поэтому я использовал простую перегрузку анимации, где в конструкторе счетчик (глобальная переменная синглтон) - инкрементировал, в деструкторе - понижал. При достижении нуля - посылал NSNotification об этом. Подписчик производил необходимые действия (проверял уровень на завершение).
alcoSHoLiK
Вариант хороший, но если приложение вдруг удалит спрайт(ы) до завершения анимации CCSequence, каллбэк не сработает.
CasDev
Да. А в варианте с таймером на self он сработает. Только вопрос -- этого ли эффекта ожидает автор? В любом случае, спасибо за уточнение.
tirinox, alcoSHoLiK, CasDev, спасибо за ответы. Использовал предложенный alcoSHoLiK вариант.
Тема в архиве.