Проблема
Когда иерархия узлов закодирована, как это обычно бывает при сохранении состояния приложения или «сохранении игры», узлы, выполняющие SKAction
действия с блоками кода, должны обрабатываться особым образом, поскольку блоки кода не могут быть закодированы.
Пример 1: отложенный обратный вызов после анимации
Здесь убит орк. Он анимирован, чтобы исчезнуть, а затем удалить себя из иерархии узлов:
SKAction *fadeAction = [SKAction fadeOutWithDuration:3.0];
SKAction *removeAction = [SKAction removeFromParent];
[orcNode runAction:[SKAction sequence:@[ fadeAction, removeAction ]]];
Если узел орка закодирован, а затем декодирован, анимация восстановится правильно и завершится, как и ожидалось.
Но теперь пример изменен, чтобы использовать блок кода, который запускается после затухания. Возможно, код очищает состояние игры после того, как орк (наконец) мертв.
SKAction *fadeAction = [SKAction fadeOutWithDuration:3.0];
SKAction *removeAction = [SKAction removeFromParent];
SKAction *cleanupAction = [SKAction runBlock:^{
[self orcDidFinishDying:orcNode];
}];
[orcNode runAction:[SKAction sequence:@[ fadeAction, removeAction, cleanupAction ]]];
К сожалению, кодовый блок не будет кодироваться. Во время сохранения состояния приложения (или сохранения игры), если выполняется эта последовательность, будет выдано предупреждение:
SKAction: действия блока запуска не могут быть правильно закодированы, блоки Objective-C не поддерживают NSCoding.
После декодирования орк исчезнет и будет удален из родителя, но метод очистки orcDidFinishDying:
вызываться не будет.
Как лучше всего обойти это ограничение?
Пример 2: Твининг
SKAction
customActionWithDuration:actionBlock:
прекрасно подходит для твининга. Мой шаблонный код для такого рода вещей таков:
SKAction *slideInAction = [SKAction customActionWithDuration:2.0 actionBlock:^(SKNode *node, CGFloat elapsedTime){
CGFloat normalTime = (CGFloat)(elapsedTime / 2.0);
CGFloat normalValue = BackStandardEaseInOut(normalTime);
node.position = CGPointMake(node.position.x, slideStartPositionY * (1.0f - normalValue) + slideFinalPositionY * normalValue);
}];
К сожалению, customActionWithDuration:actionBlock:
не может быть закодировано. Если игра сохраняется во время анимации, она не восстановится должным образом при загрузке игры.
Опять же, как лучше всего обойти это ограничение?
Несовершенные решения
Вот решения, которые я рассматривал, но они мне не нравятся. (Тем не менее, я хотел бы прочитать ответы, которые успешно отстаивают один из них.)
Несовершенное решение: используйте в анимации
performSelector:onTarget:
, а неrunBlock:
. Это решение несовершенно, потому что вызываемому селектору нельзя передать аргументы; контекст для вызова может быть выражен только целью и именем селектора. Не хорошо.Несовершенное решение: во время кодирования удалите последовательность
SKAction
из всех соответствующих узлов и переведите состояние программы так, как если бы последовательность была завершена. В первом примере это означало бы установку узлаalpha
немедленно на0.0
, удаление узла orc из родительского и вызовorcDidFinishDying:
. Это неудачное решение как минимум по двум причинам: 1) требует специального кода обработки во время кодирования; 2) Визуально узел не сможет закончить анимацию.Несовершенное решение: во время кодирования удалите блоки кода
SKAction
из всех соответствующих узлов и воссоздайте их во время декодирования. Это нетривиально.Несовершенное решение: Никогда не используйте блоки кода
SKAction
, особенно после задержки. Никогда не полагайтесь на завершение анимации, чтобы восстановить хорошее состояние приложения. (Если вам нужно запланировать будущее событие кодируемым способом, создайте свою собственную очередь событий, не используя блоки кода.) Это решение несовершенно, потому чтоrunBlock
иcustomActionWithDuration:actionBlock:
просто чертовски полезны, и это было бы позором (и повторяющейся ловушкой). для новичков) считать их злом.