Використовуємо SpriteKit для створення анімації в Swift
Зараз я працюю в GameDev-компанії, яка використовує ігровий движок SpriteKit у розробці своїх проектів. У цій статті я хочу продемонструвати, як навіть у UIkit-проектах можна легко застосувати для швидкого створення анімацій.
SpriteKit — це нативний ігровий движок від Apple, представлений вперше в iOS 7 і Mac OS 10.9. Зазвичай він використовується для створення 2D-ігор. Але це не заважає йому бути хорошим інструментом при створенні анімацій, 2D-текстур і не тільки. Наприклад, на WWDC 2017 Apple розкрила, що задіяла SpriteKit у UI для Memory Debugger в Xcode.
Використання анімації в додатках дає можливість зробити їх більш привабливими для користувачів і естетично витонченими. Це сприяє кращому розумінню функціональності.
Адже гарну ефективність в роботі можна отримати тільки від естетично приємною картинки, для створення якої потрібно багато експериментувати з налаштуваннями та інтеграцією, щоб добитися ідеального результату. Ось чому, створюючи анімацію, важливо використовувати спеціальні інструменти, що дозволяють легко виконувати зміни в налаштуваннях параметрів. Давайте переконаємося самостійно, наскільки ефективно застосування SpriteKit в різних видах анімації.
SpriteKit дуже зручний для створення простих анімаційних сцен, таких як повноекранна анімація завантаження, ілюстрація в Onboarding - і Tutorial-екранах або в інших елементах інтерфейсу. Для наочності спробуємо створити анімацію завантаження, в якій будуть використовуватися 4 emoji: Tangerine, Lemon, Peach, Mango.
Налаштування сцени
Весь вміст в SpriteKit-сцені представлено об'єктом SKScene. Потім її наповнення визначається за допомогою так званих нсд (node), що дозволяють створювати ієрархію зразок застосовуваної в UIViews або CALayers . Сцена є root-нодою в ієрархії.
Для наповнення сцени використовуються певні сабклассы SKNode :
- SKSpriteNode підійде для створення контенту на базі спрайтів;
- SKLabelNode — для роботи з текстовим контентом;
- SKLightNode — для управління світлом на сцені;
- SKEmitterNode — для створення ефекту частинок, тобто диму, вогню, іскор та інших;
- SKShapeNode — для створення простих графічних елементів математичних точок, ліній і кривих;
- SKReferenceNode, SKAudioNode, SKCameraNode, SKVideoNode... Повний список можна подивитися в документації .
Додатково будуть використовуватися різні SKAction для підключення до дій нодам у вигляді переміщення, зміни розмірів або обертання.
Завдяки цим прийомам ми зможемо оживити нашу картинку і навести її елементи в рух.
Отже, приступимо!
Спробуємо почати з створення SKScene в якості базової основи для майбутньої анімації. Для цього додамо елементу форму квадрата білого кольору, виходячи з розміру контролера:
func makeScene() -> SKScene { let minimumDimension = min(view.frame.width, view.frame.height) let size = CGSize(width: minimumDimension, height: minimumDimension) let scene = SKScene(size: size) scene.backgroundColor = .white return scene }
Ми презентуємо SKScene через SKView (який є сабклассом від UIView), додавши певні маніпуляції щодо зміни розміру і центрування. Тепер можна зробити present сцени:
import UIKit import SpriteKit final class ViewController: UIViewController { private let animationView = SKView() override func viewDidLoad() { super.viewDidLoad() view.addSubview(animationView) let scene = makeScene() animationView.frame.size = scene.size animationView.presentScene(scene) } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() animationView.center.x = view.bounds.midX animationView.center.y = view.bounds.midY } }
Додавання нод
Маючи сцену для візуалізації, можна почати додавати контент.
Так як в поточному прикладі будуть використовуватися найпростіші emoji-смайлики в якості текстур, зручніше за все буде застосувати SKLabelNode (аналог UILabel в UIkit).
Отже, приступимо до створення extension для SKLabelNode, що дозволяє рендери emoji:
extension SKLabelNode { func renderEmoji(_ emoji: Character) { fontSize = 50 text = String(emoji) verticalAlignmentMode = .center horizontalAlignmentMode = .center } }
Далі, напишемо ще один з методів, щоб додати всі смайлики на сцену.
func addEmoji(to scene: SKScene) { let allEmoji: [Character] = ["?", "?", "?", "?"] let distance = floor(scene.size.width/CGFloat(allEmoji.count)) for (index, emoji) in allEmoji.enumerated() { let node = SKLabelNode() node.renderEmoji(emoji) node.position.y = floor(scene.size.height/2) node.position.x = distance * (CGFloat(index) + 0.5) scene.addChild(node) } }
Спробуємо оживити анімацію
Після початкових налаштувань можна переходити до самої захоплюючої частини задуму — безпосередньо створення анімації. Ми зробимо анімацію, змінює розмір смайликів, спочатку змусивши кожен з них зростатиме, а потім зменшуватиметься з певною затримкою в залежності від заданих параметрів:
func animateNodes(_ nodes: [SKNode]) { for (index, node) in nodes.enumerated() { // Створюємо Delay для кожної ноди в залежності від індексу let delayAction = SKAction.wait(forDuration: TimeInterval(index) * 0.2) // Анімація збільшення, потім зменшення let scaleUpAction = SKAction.scale(to: 1.5, duration: 0.3) let scaleDownAction = SKAction.scale(to: 1, duration: 0.3) // Очікування в 2 секунди, перш ніж повторити Action let waitAction = SKAction.wait(forDuration: 2) // Формуємо Sequence (послідовність) для SKAction let scaleActionSequence = SKAction.sequence([scaleUpAction, scaleDownAction, waitAction]) // Створюємо Action для повторення нашої послідовності let repeatAction = SKAction.repeatForever(scaleActionSequence) // Комбінуємо 2 SKAction: Delay і Repeat let actionSequence = SKAction.sequence([delayAction, repeatAction]) // Запускаємо підсумковий SKAction node.run(actionSequence) } }
І хоча наведений вище код є робочим, він досить складний для розуміння, якщо прибрати з нього коментарі. Однак у нас є прекрасна можливість легко виправити цей недолік, використовуючи dot syntax notation в Swift. Це допоможе істотно скоротити розмір коду, прибравши тимчасові let-присвоювання:
func animateNodes(_ nodes: [SKNode]) { for (index, node) in nodes.enumerated() { node.run(.sequence([ .wait(forDuration: TimeInterval(index) * 0.2), .repeatForever(.sequence([ .scale(to: 1.5, duration: 0.3), .scale(to: 1, duration: 0.3), .wait(forDuration: 2) ])) ])) } }
Тепер можна запустити нашу анімацію, додавши виклик написаних вище методів в makeScene().
func makeScene() -> SKScene { let minimumDimension = min(view.frame.width, view.frame.height) let size = CGSize(width: minimumDimension, height: minimumDimension) let scene = SKScene(size: size) scene.backgroundColor = .white addEmoji(to: scene) animateNodes(scene.children) return scene }
Результат:
Додамо родзинку
Отримавши дуже наочний і легко читається анімаційний код, ми можемо трохи розважитися і додати трохи рухів. Наприклад, змусити виконувати повороти на 360° з одночасною зміною розмірів. Для цього нам просто потрібно об'єднати дві дії щодо зміни розміру і поворотів в одне ціле:
func animateNodes(_ nodes: [SKNode]) { for (index, node) in nodes.enumerated() { node.run(.sequence([ .wait(forDuration: TimeInterval(index) * 0.2), .repeatForever(.sequence([ .group([ .sequence([ .scale(to: 1.5, duration: 0.3), .scale(to: 1, duration: 0.3) ]), .rotate(byAngle: .pi * 2, duration: 0.6) ]), .wait(forDuration: 2) ])) ])) } }
У результаті виходить:
Анімація текстур
Під кінець покажу, як можна з допомогою цих інструментів створювати повноцінну живу анімацію. Для цього використовую набір послідовних картинок. Кожен може взяти відео-анімацію і розбити її покадрово. Логіка приблизно така ж, як і при створенні гифок. В моєму випадку анімація буде розбита на 28 кадрів (зображень). Кожен кадр має назву «warrior_walk_00XX», де XX — число від 1 до 28. Всі зображення поміщаються в Assets проекту. Потім потрібно зібрати разом цей масив текстур:
func animationFrames(forImageNamePrefix baseImageName: String, frameCount count: Int) -> [SKTexture] { var array = [SKTexture]() for index in 1...count { let imageName = String(format: "%@%04d.png", baseImageName, index) let texture = SKTexture(imageNamed: imageName) array.append(texture) } return array }
Тепер потрібно написати анімацію руху персонажа і його напрямок. І додати виклик цієї функції під viewDidLoad.
func createSceneContents(for scene: SKScene) { let defaultNumberOfWalkFrames: Int = 28 let characterFramesOverOneSecond: TimeInterval = 1.0/TimeInterval(defaultNumberOfWalkFrames) let walkFrames = animationFrames(forImageNamePrefix: "warrior_walk_", frameCount: defaultNumberOfWalkFrames) let sprite = SKSpriteNode(texture: walkFrames.first) sprite.position = CGPoint(x: animationView.frame.midX, y: animationView.frame.midY + 60) scene.addChild(sprite) . // Анімація текстур let animateFramesAction: SKAction = .animate(with: walkFrames, timePerFrame: characterFramesOverOneSecond, resize: true, restore: false) // Анімація повороту персонажа на 90 градусів let rotate: SKAction = .rotate(byAngle: .pi/2, duration: 0.3) let newPosition: CGFloat = 100 let moveDuration: TimeInterval = 1.0 sprite.run(.repeatForever( .sequence( [.group([ // Рух вгору animateFramesAction, .moveBy(x: 0.0, y: newPosition, duration: moveDuration)]), rotate, .group([ // Рух вліво animateFramesAction, .moveBy(x: -newPosition, y: 0.0, duration: moveDuration)]), rotate, .group([ // Рух вниз animateFramesAction, .moveBy(x: 0.0, y: -newPosition, duration: moveDuration)]), rotate, .group([// Рух вправо animateFramesAction, .moveBy(x: newPosition, y: 0.0, duration: moveDuration)]), rotate]) )) }
В результаті виходить дуже живий персонаж.
Підводячи підсумки
Як можна переконатися на прикладі, застосувавши SpriteKit для анімації, нам вдалося написати дуже зрозумілий і наочний код, за яким легко експериментувати, змінюючи напрямку руху і масштабуючи елементи картинки. Безсумнівно, не можна назвати його універсальним засобом для створення анімації, оскільки у нас немає можливості застосувати такий спосіб для будь-яких видів анімації UIViews, але це може виявитися простим інструментом для окремо взятої картинки.
Безумовно, у SpriteKit є набагато більше можливостей, ніж було описано в цій статті, але, отримавши базові знання, ви самі можете трохи поекспериментувати зі створенням нескладної анімації.
Підсумковий код проекту можна подивитися в репозиторії .
При написанні статті використовувався матеріал з Swift by Sundell і Apple .
Опубліковано: 16/10/19 @ 10:00
Розділ Різне
Рекомендуємо:
Секретні техніки опрацювання вимог. Частина 1
Поради для початківця Java розробника. Підготовка до співбесіди — частина 2
C++ дайджест #20: CppCon 2019, Open Sourcing STL від MSVC
Шпаргалка з кібербезпеки для розробників
LocaleBro — локалізація Android - і iOS-додатків без зайвої роботи