Как я могу предотвратить перезапуск последовательности SKAction после декодирования?
Мое приложение - игра SpriteKit с сохранением и восстановлением состояния приложения. Когда состояние приложения сохраняется, большинство узлов в моем текущем SKScene
закодированы.
Когда узел работает SKAction
закодирован и декодирован, действие возобновится с самого начала. Это кажется стандартным SpriteKit
поведение.
Для меня это поведение наиболее заметно для SKAction sequence
, При декодировании последовательность перезапускается независимо от того, сколько действий компонента уже выполнено. Например, скажем, код для запуска последовательности выглядит следующим образом:
[self runAction:[SKAction sequence:@[ [SKAction fadeOutWithDuration:1.0],
[SKAction fadeInWithDuration:1.0],
[SKAction waitForDuration:10.0],
[SKAction removeFromParent] ]]];
Если состояние приложения сохраняется в течение 10-секундного ожидания, а затем восстанавливается, SKAction
последовательность начнется снова с начала, со вторым видимым постепенным исчезновением.
Это имеет смысл, что SKAction sequence
должно показывать поведение декодирования в соответствии с другими действиями. Однако было бы полезно сделать исключение, чтобы любые уже выполненные действия не выполнялись снова. Как я могу предотвратить перезапуск последовательности после декодирования?
2 ответа
SKAction
последовательность может быть разложена на несколько подпоследовательностей, так что после завершения определенной подпоследовательности она больше не будет работать и не будет перезапущена при декодировании.
Код
Создайте легкий, кодируемый объект, который может управлять последовательностью, разбивая ее на подпоследовательности и запоминая (при кодировании), что уже выполнено. Я написал реализацию в библиотеке на GitHub. Вот текущее состояние кода в сущности.
И вот пример (с использованием той же последовательности, что и ниже):
HLSequence *xyzSequence = [[HLSequence alloc] initWithNode:self actions:@[
[SKAction waitForDuration:10.0],
[SKAction performSelector:@selector(doY) onTarget:self],
[SKAction waitForDuration:1.0],
[SKAction performSelector:@selector(doZ) onTarget:self] ]];
[self runAction:xyzSequence.action];
Концепт
Первая идея: разбить последовательность на несколько независимых подпоследовательностей. После завершения каждой подпоследовательности она больше не будет работать и не будет закодирована, если приложение будет сохранено. Например, оригинальная последовательность, подобная этой:
[self runAction:[SKAction sequence:@[ [SKAction performSelector:@selector(doX) onTarget:self],
[SKAction waitForDuration:10.0],
[SKAction performSelector:@selector(doY) onTarget:self],
[SKAction waitForDuration:1.0],
[SKAction performSelector:@selector(doZ) onTarget:self] ]]];
можно разделить так:
[self runAction:[SKAction sequence:@[ [SKAction performSelector:@selector(doX) onTarget:self] ]]];
[self runAction:[SKAction sequence:@[ [SKAction waitForDuration:10.0],
[SKAction performSelector:@selector(doY) onTarget:self] ]]];
[self runAction:[SKAction sequence:@[ [SKAction waitForDuration:11.0],
[SKAction performSelector:@selector(doZ) onTarget:self] ]]];
Независимо от того, когда узел закодирован, методы doX
, doY
, а также doZ
будет запущен только один раз.
В зависимости от анимации продолжительность ожидания может показаться странной. Например, допустим, приложение сохраняется после doX
а также doY
запустить, в течение 1 секунды до doZ
, Тогда после восстановления приложение не запустится doX
или же doY
еще раз, но он будет ждать 11 секунд, прежде чем запустить doZ
,
Чтобы избежать, возможно, странных задержек, разбейте последовательность на цепочку зависимых подпоследовательностей, каждая из которых запускает следующую. Например, разделение может выглядеть следующим образом:
- (void)doX
{
// do X...
[self runAction:[SKAction sequence:@[ [SKAction waitForDuration:10.0],
[SKAction performSelector:@selector(doY) onTarget:self] ]]];
}
- (void)doY
{
// do Y...
[self runAction:[SKAction sequence:@[ [SKAction waitForDuration:1.0],
[SKAction performSelector:@selector(doZ) onTarget:self] ]]];
}
- (void)doZ
{
// do Z...
}
- (void)runAnimationSequence
{
[self runAction:[SKAction performSelector:@selector(doX) onTarget:self]];
}
При такой реализации, если последовательность сохраняется после doX
а также doY
затем, после восстановления, задержка до doZ
будет только 1 секунда. Конечно, это полная секунда (даже если она была наполовину истекла до кодирования), но результат вполне понятен: какое бы действие в последовательности не выполнялось во время кодирования, оно будет перезапущено, но после его завершения это будет сделано.
Конечно, делать кучу таких методов - противно. Вместо этого создайте объект диспетчера последовательностей, который при срабатывании для этого разбивает последовательность на подпоследовательности и выполняет их с учетом состояния.
Единственный способ, которым я могу думать о достижении того, чего вы хотите достичь, - это следующее.
- При запуске действия сохраняйте время в переменной. Имейте в виду, что вы захотите использовать значение currentTime, переданное в функцию обновления.
- Когда вам нужно кодировать, подсчитайте, сколько времени прошло с момента создания действия до момента кодирования.
Оттуда у вас есть два варианта: сэкономить оставшееся время, и когда вы собираетесь воссоздать действие, используйте это в своих расчетах или создайте новые действия, основываясь на том, сколько времени осталось, и закодируйте их.
Я не думаю, что SKActions действительно были предназначены для такого использования, но это может быть обходной путь, по крайней мере. Я думаю, что разработчики чаще сохраняют "состояние" своей игры на постоянство, а не пытаются сохранить реальные спрайты и действия. Это было бы то же самое с материалом UIKit. Вы не будете хранить UIViews для постоянства, у вас вместо этого будет какой-то другой объект, который будет содержать информацию для воссоздания, основанную на прогрессе пользователя. Надеюсь, что-то из этого было хоть немного полезным. Удачи.
редактировать
Чтобы дать больше информации о том, как "в теории" я бы пошел об этом, и вы правы, это хлопот.
- Подкласс SKSpriteNode
- Создайте новый метод для запуска действий на этом Sprite (например, -(void)startAction:withKey:duration:), который в конечном счете вызовет действие run с ключом.
- Когда вызывается startAction, вы сохраняете его в некоторый вид MutableArray со словарем, в котором хранится это действие, его ключ, продолжительность и startTime (по умолчанию 0). Возможно, вам даже не придется хранить это действие, только ключ, продолжительность и время начала.
- Добавьте update: метод к этому подклассу SKSpriteNode. Каждый цикл обновления вы вызываете его обновлением и проверяете, есть ли у 1 какие-либо действия время начала и 2, если эти действия все еще выполняются. Если время начала отсутствует, вы добавляете текущее время в качестве времени начала, а если оно не запущено, вы удаляете его из массива.
- Когда вы собираетесь кодировать / сохранять этот спрайт, вы используете информацию в этом массиве для определения состояния этих SKAction.
Суть этого в том, что каждый SKSpriteNode удерживает и отслеживает свой собственный SKAction в этом примере. Извините, у меня нет времени, чтобы пройти и написать код в Objective-C. Кроме того, я ни в коем случае не утверждаю или не пытаюсь предположить, что это лучше или хуже, чем ваш ответ, а скорее обращаюсь к тому, как я справлюсь с этим, если я был полон решимости сохранить состояние SKActions в соответствии с вашим вопросом. знак равно