Введение в 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: генерацию препятствий, бесконечную прокрутку, обработку столкновений и многое другое. А может, даже вместе напишем небольшую игру шаг за шагом.
Спасибо за внимание и до встречи в следующих статьях!🤘