Анимация символов

Приключение является игрой, это полно жизни. Листы падают от деревьев все вокруг уровня, записи факелов вне каждой полости гоблина, муха искр, когда снаряды поражают стены, лавины дыма от уничтоженных полостей, и зеленая липкая вещь извергает везде, когда умирает гоблин.

Для соответствия динамического характера среды мы создали обширные анимации для всех движущихся символов в игре — герои воина и лучника, гоблины и босс уровня. Эти символы всегда анимируются, даже когда неактивный, и у каждого есть уникальная индивидуальность. Воины идут с чванством, лучники поворачивают свои поклоны поперек, гоблины качают руки и закатывают глаза, когда они перемещаются, и босс уровня колеблется вокруг покачивания его клуба.

Как с большой частью поведения в Приключении, мы имеем дело с анимациями во время цикла обновления. Каждый раз, когда перемещение или действие происходят, который должен инициировать анимацию, мы устанавливаем флаг на соответствующем символе для запроса определенного состояния анимации. Во время цикла обновления мы проверяем этот флаг и выполняем действие для анимации через соответствующую последовательность кадров, как показано на рисунке 6-1.

Рисунок 6-1  , Разрешающий анимацию во время цикла обновления

Запрос анимации

Все анимированные символы в Приключении убывают в конечном счете от APACharacter и имейте их animated набор свойств к YES. APACharacter класс обеспечивает a requestedAnimation свойство, которое мы устанавливаем каждый раз, когда мы должны изменить текущую анимацию, такой, атаковав, идя, будучи пораженным, или смерть.

Например, мы устанавливаем требуемую анимацию в APAAnimationStateWalk каждый раз, когда мы перемещаем символ. move: метод, на основе которого мы вызываем APAPlayer набор свойств в ответ на ввод с клавиатуры, запрашивает анимацию прежде, чем применить действие для данного перемещения.

  1. Adventure: APACharacter.m -move:
  2. - (void)move:(APAMoveDirection)direction withTimeInterval:(NSTimeInterval)timeInterval {
  3. CGFloat rot = self.zRotation;
  4. SKAction *action = nil;
  5. switch (direction) {
  6. case APAMoveDirectionForward:
  7. action = [SKAction moveByX:-sinf(rot)*self.movementSpeed*timeInterval
  8. y:cosf(rot)*self.movementSpeed*timeInterval
  9. duration:timeInterval];
  10. break;
  11. ... (Handle the other movement directions)
  12. }
  13. if (action) {
  14. self.requestedAnimation = APAAnimationStateWalk;
  15. [self runAction:action];
  16. }
  17. }

Разрешение анимации

Мы разрешаем все требуемые анимации во время цикла обновления — APACharacter переопределения класса update: вызывать resolveRequestedAnimation.

  1. Adventure: APACharacter.m -resolveRequestedAnimation
  2. - (void)resolveRequestedAnimation {
  3. NSString *animationKey = nil;
  4. NSArray *animationFrames = nil;
  5. APAAnimationState animationState = self.requestedAnimation;
  6. switch (animationState) {
  7. default:
  8. case APAAnimationStateIdle:
  9. animationKey = @"anim_idle";
  10. animationFrames = [self idleAnimationFrames];
  11. break;
  12. ... (Handle other requested animations)
  13. }
  14. if (animationKey) {
  15. [self fireAnimationForState:animationState usingTextures:animationFrames withKey:animationKey];
  16. }
  17. self.requestedAnimation = self.dying ? APAAnimationStateDeath : APAAnimationStateIdle;
  18. }
10

Каждый анимированный APACharacter разделите на подклассы переопределяет различные методы кадра анимации (idleAnimationFrames, walkAnimationFrames, и т.д.) для обеспечения массива текстур для анимации. Эти текстуры предварительно загружены, как описано в Загрузке Совместно используемых Символьных Активов.

17

Как только мы запустили анимацию, мы сбрасываем requestedAnimation свойство.

Это состояние анимации сброса решает следующую анимацию, если ничто не происходит для запроса различного состояния, прежде чем завершится текущая анимация.

fireAnimationForState:usingTextures:withKey: метод использует SKAction последовательность для проигрывания кадров анимации и вызова animationHasCompleted: когда сделано.

  1. Adventure: APACharacter.m -fireAnimationForState:usingTextures:withKey:
  2. - (void)fireAnimationForState:(APAAnimationState)animationState usingTextures:(NSArray *)frames withKey:(NSString *)key {
  3. SKAction *action = [self actionForKey:key];
  4. if (action || [frames count] < 1) {
  5. return; // we already have a running animation or there aren't any frames to animate
  6. }
  7. self.activeAnimationKey = key;
  8. [self runAction:[SKAction sequence:@[
  9. [SKAction animateWithTextures:frames timePerFrame:self.animationSpeed resize:YES restore:NO],
  10. [SKAction runBlock:^{
  11. [self animationHasCompleted:animationState];
  12. }]]]
  13. withKey:key];
  14. }
3

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

Мы не должны делать ничего для хранения игры анимации — Набор Sprite выполняет действие и заботится о пробежке кадров автоматически во время цикла обновления.

Завершение анимации

Когда анимация завершается, APACharacter класс получает animationHasCompleted: обратный вызов. Если символ просто умер и сброс различных свойств, мы используем этот обратный вызов для выполнения общей работы очистки, такой как постепенное исчезновение теневого блоба.

animationHasCompleted: метод поочередно вызывает animationDidComplete:, который переопределяется подклассами для выполнения символьно-специфичной работы. APAHeroCharacter когда герой умирает, а также запустить снаряд, класс, например, использует этот метод для выполнения задач, необходимых.

  1. Adventure: APAHeroCharacter.m -animationComplete
  2. - (void)animationDidComplete:(APAAnimationState)animationState {
  3. switch (animationState) {
  4. case APAAnimationStateDeath:{
  5. APAMultiplayerLayeredCharacterScene * __weak scene = [self characterScene];
  6. SKEmitterNode *emitter = ... (load, configure, and run the death emitter)
  7. APACharacter * __weak weakSelf = self;
  8. [self runAction:[SKAction sequence:@[
  9. [SKAction waitForDuration:4.0f],
  10. [SKAction runBlock:^{
  11. [scene heroWasKilled:weakSelf];
  12. }],
  13. [SKAction removeFromParent],
  14. ]]];
  15. break;
  16. case APAAnimationStateAttack:
  17. [self fireProjectile];
  18. break;
  19. default:
  20. break;
  21. }
  22. }
5

Мы отмечаем переменные как __weak избегать любой возможности цикла сильной ссылки при получении scene и self в блоке действия.

18

Мы ожидаем, пока анимация атаки не завершается прежде, чем запустить снаряд, дать иллюзию воина, бросающего молоток или лучника, стреляющего из поклона.

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

Увольнение снарядов

APAHeroCharacter когда анимация атаки завершается, и, класс ответственен за увольнение снаряда APAWarrior и APAArcher предоставьте подходящий снаряд вместе с присоединенным эмиттерным узлом для подчеркивания перемещения. Каждый снаряд героя имеет ограниченную жизнь — он начинает постепенно исчезать после 0,6 секунд и исчезает полностью спустя 1 секунду после увольнения. Мы выполняем множество действий с каждым снарядом, чтобы обработать перемещение, постепенно исчезнуть, удаление, и также играть звук увольнения.

  1. Adventure: APAHeroCharacter.m -fireProjectile
  2. - (void)fireProjectile {
  3. // ... (Copy and configure the hero-specific projectile)
  4. // ... (Create an emitter for the projectile)
  5. CGFloat rot = self.zRotation;
  6. [projectile runAction:[SKAction moveByX:-sinf(rot)*kHeroProjectileSpeed*kHeroProjectileLifetime
  7. y:cosf(rot)*kHeroProjectileSpeed*kHeroProjectileLifetime
  8. duration:kHeroProjectileLifetime]];
  9. [projectile runAction:[SKAction sequence:@[[SKAction waitForDuration:kHeroProjectileFadeOutTime],
  10. [SKAction fadeOutWithDuration:kHeroProjectileLifetime - kHeroProjectileFadeOutTime],
  11. [SKAction removeFromParent]]]];
  12. [projectile runAction:[self projectileSoundAction]];
  13. projectile.userData = [NSMutableDictionary dictionaryWithObject:self.player forKey:kPlayer];
  14. }
13

Мы создаем совместно используемое projectileSoundAction во время загрузки сцены, так, чтобы сам звук был предварительно загружен от диска. Если бы мы не предварительно загружали звук, то мы видели бы отброшенные кадры, в то время как файл был загружен в первый раз, когда снаряд был запущен.

15

Мы используем узел userData словарь для отслеживания проигрыватель, ответственный за увольнение снаряда.

Если снаряд поражает врага — различный вражеский класс, мы используем эту информацию для увеличения счета того проигрывателя collidedWith: вызов методов addToScore:afterEnemyKillWithProjectile: на сцене, которая ответственна за увеличение счета к соответствующему проигрывателю.