iOS
July 2

Введение в 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), который управляет логикой игры, обрабатывает события (например, касания) и настраивает физику. Именно с неё начинается построение любой игры.

Давайте же перейдем к коду и создадим простую сцену.

Шаг 1: Создание сцены

В Xcode выбираем New File From TemplateSpriteKit 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: генерацию препятствий, бесконечную прокрутку, обработку столкновений и многое другое. А может, даже вместе напишем небольшую игру шаг за шагом.

Спасибо за внимание и до встречи в следующих статьях!🤘

Полезные ссылки:


Telegram-канал Теплица

Обратная связь