Введение в SpriteKit: учимся работать с движком на практике
В этой статье мы хотим поговорить о нативной разработке мобильных игр на iOS. А точнее познакомить вас с игровым движком SpriteKit. Разберём, что он умеет, зачем он нужен и как начать с ним работать. Вместе создадим простую игровую сцену, добавим персонажа, подключим физику и поэкспериментируем с гравитацией.
Начало пути
Мы с командой решили воссоздать классическую Flappy Bird — простую, но затягивающую игру, где игрок управляет птичкой, избегая столкновений с трубами. Несмотря на кажущуюся простоту, такая игра требует продуманной архитектуры: нужно обеспечить стабильную производительность и чёткое разделение логики. Важно было реализовать управление состояниями (меню, игра, проигрыш), физику столкновений, бесконечную прокрутку фона и генерацию труб.
Читая абзац выше, может показаться, что мы отлично разбираемся в теме и всё у нас под контролем. Но на деле всё было иначе. Опыт? Впервые. План? Почти. Время на отрицание, гнев, торг и депрессию? Не завезли. Сразу «принятие» — и поехали делать игру.
К счастью, Apple предоставляет из коробки мощный инструмент для таких задач — SpriteKit. Именно он стал нашим проводником в мир 2D-геймдева на iOS.
Что такое SpriteKit?
SpriteKit — это фреймворк от Apple, который помогает создавать 2D-игры и анимации на iOS, macOS, tvOS и iPadOS. Проще говоря, это набор инструментов, позволяющий рисовать спрайты, двигать их, наделять физикой, проигрывать звуки и управлять столкновениями.
В SpriteKit все игровые элементы, такие как персонажи, враги, фоны и прочее называются спрайтами (sprites). Эти спрайты размещаются на сцене (scene), которая служит основным контейнером для всей игровой логики и визуальных объектов.
Рассмотрим основные компоненты фреймворка:
SKScene
— сцена, на которой разворачивается игра;SKSpriteNode
— графический элемент (например, персонаж или фон);SKLabelNode
— текст на экране;SKAction
— анимации и действия (движение, вращение и прочее);SKPhysicsBody
— физическое тело для гравитации и столкновений.
Настройка Scene
В SpriteKit все игровые объекты размещаются на сцене. Это специальный класс (SKScene
), который управляет логикой игры, обрабатывает события (например, касания) и настраивает физику. Именно с неё начинается построение любой игры.
Давайте же перейдем к коду и создадим простую сцену.
В Xcode выбираем New File From Template → SpriteKit Scene.
Этот шаблон создаст .sks
-файл, визуальный редактор сцены, и вы сможете на нём размещать объекты.
Шаг 2: Указание кастомного класса
Открываем созданный .sks
-файл и в инспекторе справа и указываем имя вашего собственного класса сцены в поле Custom Class — например, GameScene
.
Шаг 3: Реализация класса сцены
Теперь создаём Swift-файл с соответствующим именем (например, GameScene.swift
) и реализуйте свой класс, унаследовав его от SKScene
.
final class GameScene: SKScene { /// Вызывается один раз, когда сцена загружена из файла или кода. /// Здесь можно инициализировать свойства, подготовить объекты, которые не зависят от view. override func sceneDidLoad() { super.sceneDidLoad() } /// Вызывается, когда пользователь прикасается к экрану. /// Здесь можно обрабатывать начало касания — например, заставить персонажа прыгнуть. override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) } /// Вызывается каждый кадр (обычно 60 раз в секунду). /// Здесь можно обновлять положение объектов, проверять столкновения, изменять состояние игры. override func update(_ currentTime: TimeInterval) { super.update(currentTime) } }
Шаг 4: Контроллер запуска игры
Затем нам необходимо создать обычный UIViewController и переопределить наше дефолтное view, на SKView
Затем нам необходимо создать UIViewController
, и переопределить у него view
, чтобы она стала SKView
.
class GameViewController: UIViewController { override func loadView() { super.loadView() view = SKView() } override func viewDidLoad() { super.viewDidLoad() startScene() } func startScene() { guard let scene = SKScene(fileNamed: "GameScene") as? GameScene else { return } let skView = view as! SKView skView.presentScene(scene) skView.showsFPS = true } }
Готово! Базовая настройка позади. Теперь можно запустить проект и посмотреть, что у нас получилось.
Скорее всего, вы увидите пустой экран. Не слишком вдохновляет, правда? Но если в правом нижнем углу появился индикатор FPS — значит всё подключено правильно и сцена действительно загружается. Поздравляю, это первый шаг!
На самом деле вы можете убрать этот индикатор, если вам он мешает. Для этого просто удалите следующую строку: skView.showsFPS = true
.
Но иногда полезно знать о свойствах, которые нам предлагает SKView из коробки, так как это может существенно облегчить дебагинг игры. Советую ознакомиться с документацией по SKView, чтобы узнать больше о его возможностях.
Добавляем объект на сцену
Настройку мы завершили, теперь самое время перейти к делу и разместить первый объект на сцене.
В качестве объекта я использую изображение асыка из нашей игры, но вы можете выбрать любое изображение в формате .png
и добавить его в Assets.xcassets
.
Чтобы добавить объект на сцену и наделить его физическими свойствами, создадим отдельный класс, унаследованный от SKSpriteNode
. Это позволит нам:
- задать текстуру (изображение);
- управлять поведением объекта;
- добавить физику: столкновения, гравитацию, вращение и т.д.
class PlayerNode: SKSpriteNode { convenience init(with image: UIImage) { let texture = SKTexture(image: image) self.init(texture: texture) } }
Как мы видим ничего сложного. Здесь мы просто создаём спрайт на основе картинки и используем её как текстуру.
Теперь перейдём в нашу сцену и обновим её:
final class GameScene: SKScene { private var player: PlayerNode! override func didMove(to view: SKView) { super.didMove(to: view) createPlayer() } private func createPlayer() { player = PlayerNode(with: UIImage(named: "asyq-sprite")!) player.position = CGPoint(x: size.width / 2, y: size.height / 2) addChild(player) } }
В функции createPlayer()
мы создаём объект, передаём ему изображение, а затем устанавливаем позицию относительно центра экрана.
Вызов происходит из didMove(to view: SKView)
— это метод жизненного цикла, аналогичный viewDidLoad()
в контроллере. Именно здесь обычно размещают начальные элементы сцены.
Собираем и запускаем проект...
Ну что ж, пока выглядит скромно, но всё работает!
Предлагаю немного поиграться с возможностями движка и попробовать немного оживить нашу сцену при помощи физики. Вернёмся в PlayerNode
и расширим его функциональность:
final class PlayerNode: SKSpriteNode { convenience init(with image: UIImage) { let texture = SKTexture(image: image) self.init(texture: texture) configureNode() } private func configureNode() { physicsBody = SKPhysicsBody() } }
И попробуем запустить наш проект снова и посмотрим что изменилось.
Уже что-то интересное, правда? Объект начал двигаться, хотя мы явно не писали код, который бы его перемещал. Почему так происходит?
Все классы, унаследованные от SKScene
, содержат свойство physicsWorld
— это движок физики, встроенный прямо в сцену. Он отвечает за гравитацию, столкновения, импульсы, трение и прочие физические эффекты.
Нас сейчас интересует его свойство: physicsWorld.gravity
. По умолчанию значение равно CGVector(dx: 0, dy: -9.8)
. Это примерное значение земного притяжения, объект начинает падать вниз по оси y
, имитируя свободное падение на Земле (~9,8 м/с²). Именно поэтому наш объект сразу падает вниз относительно оси координат.
Попробуем поиграться с этим значением и изменим поведение. Например, сделаем так, будто объект падает не в воздухе, а в воде:
override func didMove(to view: SKView) { super.didMove(to: view) physicsWorld.gravity = CGVector(dx: 0, dy: -1.0) createPlayer() }
Теперь падение стало заметно медленнее, создаётся ощущение вязкой среды.
Теперь хочется как-то взаимодействовать с нашим объектом. Например, чтобы он взлетал вверх при тапе по экрану. Для этого воспользуемся методом touchesBegan
, который уже есть в SKScene
:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) player!.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 45)) }
При касании вызывается метод touchesBegan
, где мы придаем импульс нашему объекту по оси y.
Как видим, у узла нашего объекта также есть свойство physicsBody
, которое мы можем настраивать по своему усмотрению. Через него можно задать массу, включить или отключить гравитацию, настроить вращение, а также указать маски для обработки столкновений с другими объектами на сцене.
Теперь при каждом тапе наш герой взлетает вверх — получается простая, но уже играбельная механика.
Заключение
Вот и подошла к концу наша статья о SpriteKit. Мы познакомились с базовыми возможностями движка, создали сцену, добавили персонажа, подключили физику и поэкспериментировали с гравитацией.
Конечно, это лишь малая часть того, что умеет SpriteKit. В рамках одной статьи невозможно охватить всё: от анимации и столкновений до построения полноценной игры. Но если вам интересна тема нативной разработки игр на iOS, дайте знать в комментариях!
Мы с радостью продолжим разбирать возможности SpriteKit: генерацию препятствий, бесконечную прокрутку, обработку столкновений и многое другое. А может, даже вместе напишем небольшую игру шаг за шагом.
Спасибо за внимание и до встречи в следующих статьях!🤘