Статьи

Основы SpriteKit: узлы

SpriteKit — это 2D игровой движок Apple — движок рендеринга, построенный на основе OpenGL. Он был представлен с iOS 7, и каждый последующий выпуск вносил отличные дополнения в фреймворк. Используя текстурированные спрайты, встроенный физический движок и очень мощный класс SKAction , вы можете очень быстро создавать функциональные 2D-игры.

SpriteKit имеет встроенные редакторы для сцен и частиц, узел камеры с момента выпуска iOS9 и встроенную поддержку наборов плиток с момента выпуска iOS 10. Благодаря этим новым дополнениям SpriteKit быстро становится локомотивом для создания 2D-игр.

Чтобы следовать этому уроку, просто загрузите прилагаемое репозиторий GitHub . В нем есть папка с именем ExampleProject Starter . Откройте проект в этой папке в Xcode, и вы готовы к работе!

Узлы — это фундаментальные строительные блоки SpriteKit, а SKNode — базовый класс всех узлов. Все ваши экранные активы будут SKNode или его подклассом. SKNode сами по себе SKNode не предоставляют визуального контента. Весь визуальный контент рисуется с использованием одного из нескольких предопределенных подклассов SKNode . SKNode и его подклассы имеют несколько общих свойств, которые вы можете изменить. Некоторые из наиболее важных из них следующие.

  • position ( CGPoint ): позиция узла в системе координат его родителя
  • xScale ( CGFloat ): масштабирует ширину узла множителем
  • yScale ( CGFloat ): масштабирует высоту узла множителем
  • alpha ( CGFloat ): прозрачность узла
  • zRotation ( CGFloat ): вращение Эйлера вокруг оси z (в радианах)

Одним из наиболее важных SKNode является SKScene . Это корневой узел, к которому добавляются все остальные узлы. Сам по себе SKScene не предоставляет никаких визуальных элементов, но отображает добавленные к нему узлы.

SKScenes — это корневые узлы, к которым добавляются все остальные узлы. Сцена анимирует и визуализирует контент из своих дочерних узлов. Чтобы отобразить сцену, вы добавляете ее в SKView (который является подклассом UIView и поэтому имеет многие из тех же свойств, что и UIView ).

В начальном проекте SpriteKit начальная сцена отображается при загрузке проекта. На данный момент это просто пустой черный экран. Это показано, когда GameViewController вызывает GameViewController presentScene(_:) для экземпляра представления, передавая сцену в качестве параметра:

01
02
03
04
05
06
07
08
09
10
override func viewDidLoad() {
    super.viewDidLoad()
    let scene = GameScene(size:CGSize(width: 768, height: 1024))
    let skView = self.view as!
    skView.showsFPS = false
    skView.showsNodeCount = false
    skView.ignoresSiblingOrder = false
    scene.scaleMode = .aspectFill
    skView.presentScene(scene) // Present the Scene
}

Не беспокойтесь о других вариантах сейчас; Я объясню их позже в этой серии.

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

Выберите « Файл» > « Создать» > « Файл» в меню XCode и выберите « Какао-класс касания» .

Выберите шаблон Какао Touch Touch

Убедитесь, что для Class установлено значение NewScene, а для SubclassSKScene . Нажмите Далее, а затем Создать , убедившись, что основная цель отмечена. Ниже приведен код для NewScene.swift .

1
2
3
4
5
6
import UIKit
import SpriteKit
 
class NewScene: SKScene {
 
}

Теперь у нас есть две сцены в нашем проекте, и ни одна не имеет визуального контента. Давайте добавим SKLabelNode (как и все узлы, это подкласс SKNode ). Единственной целью SKLabelNode является отображение текстовой метки.

Узлы меток, реализованные в классе SKLabelNode , используются для отображения текста в вашей игре. Вы можете использовать пользовательские шрифты, если хотите, но для наших целей мы просто придерживаемся значения по умолчанию, которое отображает белый текст и имеет значение Helvetica Neue Ultra Light, 32 пункта .

Добавьте следующее в метод didMove(to:) в GameScene.swift . Этот метод вызывается сразу после представления сцены представлением. Как правило, это то место, где вы можете настроить любые игровые ресурсы и добавить их на сцену.

1
2
3
4
override func didMove(to view: SKView) {
    let startGameLabel = SKLabelNode(text: «Start Game»)
         
}

Здесь мы создаем SKLabelNode используя удобный инициализатор init(text:) , который принимает в качестве параметра строку текста.

Простая инициализация узлов не покажет их в сцене. Чтобы отобразить узлы, вы должны вызвать метод addChild(_:) на принимающем узле, передавая SKNode который вы хотите добавить в качестве параметра.

Добавьте следующее в метод didMove(to:) .

1
2
3
4
override func didMove(to view: SKView) {
    let startGameLabel = SKLabelNode(text: «Start Game»)
    addChild(startGameLabel)
}

Метод addChild(_:) не является эксклюзивным для SKScene , но является методом SKNode . Это позволяет построить сложную иерархию узлов, известную как «дерево узлов». Например, предположим, что у вас есть игровой персонаж, и вы хотели раздвинуть его руки и ноги отдельно. Вы можете создать экземпляр SKNode а затем добавить каждую отдельную деталь в качестве дочернего элемента этого SKNode (содержащий узел известен как родительский узел). Это даст вам возможность перемещать персонажа как единое целое, перемещая родительский SKNode , но также позволит вам перемещать каждую отдельную часть по отдельности.

Другим важным методом добавления узлов является метод insertChild(_:at:) , который вставляет дочерний элемент в определенную позицию в списке дочерних узлов insertChild(_:at:) узла. Когда вы добавляете дочерний элемент к узлу, он поддерживает упорядоченный список дочерних элементов, на который ссылается чтение свойства дочернего элемента. Это важно при добавлении нескольких узлов к родительскому узлу, так как порядок добавления узлов влияет на некоторые аспекты обработки сцены, включая тестирование попаданий и рендеринг.

Чтобы удалить узел, вы вызываете метод removeFromParent() на узле, который хотите удалить.

Теперь, когда мы рассмотрели добавление и удаление узлов, мы можем перенести фокус обратно на пример проекта. Если вы помните, мы только что добавили SKLabelNode в GameScene . Если вы протестируете сейчас, вы увидите только половину текста в левом нижнем углу экрана.

Тест, показывающий пустой экран с текстом наполовину в левом нижнем углу экрана

Почему только половина текста отображается? Сейчас самое время поговорить о системе координат и позиционирования SpriteKit.

По умолчанию система координат SpriteKit размещает ( 0,0 ) в левом нижнем углу экрана. Также по умолчанию SpriteKit размещает узлы таким образом, чтобы они располагались в ( 0,0 ). И все же, хотя … почему мы видим только половину текста? Это связано с тем, что по умолчанию текстовая метка центрируется по горизонтали относительно источника узла метки, что составляет ( 0,0 ). Ниже изображение, которое показывает, как работает система координат узла.

A координаты x и y

Начало узла находится в точке ( 0,0 ), положительная координата x перемещается вправо, а положительная координата y поднимается по экрану. Помните, что SKScene является узлом, и, следовательно, его источник также ( 0,0 ).

Теперь, когда мы узнали, что система координат SpriteKit работает и как она размещает узлы, мы можем переместить SKLabelNode в другое положение, чтобы мы могли видеть весь текст. Добавьте следующее в метод didMove(to:) в GameScene.swift .

1
2
3
4
5
override func didMove(to view: SKView) {
    let startGameLabel = SKLabelNode(text: «Start Game»)
    startGameLabel.position = CGPoint(x: size.width/2, y: size.height/2)
    addChild(startGameLabel)
}

Здесь мы размещаем метку в центре сцены. Свойство position имеет тип CGPoint , у которого есть значения x и y, которые представляют одну точку в сцене.

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

Тест с меткой, расположенной в центре экрана

В настоящее время NewScene — это просто пустая сцена. Давайте также добавим к нему метку, и тогда мы сможем научиться переключаться между сценами. Перед вами стоит задача: прежде чем читать дальше, попробуйте добавить в NewScene ярлык с NewScene « Вернуться назад ». Мое решение ниже.

Первое, что нам нужно сделать, это добавить метод didMove(to:) . Добавьте следующее в NewScene.swift .

1
2
3
4
5
6
7
class NewScene: SKScene {
     
    override func didMove(to view: SKView) {
         
    }
     
}

Далее нам нужно добавить метку. Добавьте следующее в метод didMove(to:) который вы добавили выше.

1
2
3
4
5
override func didMove(to view: SKView) {
    let goBackLabel = SKLabelNode(text: «Go Back»)
    goBackLabel.position = CGPoint(x: size.width/2, y: size.height/2)
    addChild(goBackLabel)
}

Это добавляет метку в NewScene с текстом « Вернуться ». Далее мы реализуем функциональность, которую предлагает этот ярлык, — мы будем реагировать на сенсорные события, переключая сцены.

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

Чтобы зарегистрировать обработчики сенсорных событий в вашей игре, вы должны реализовать представление touchesBegan(_:with:) . Добавьте следующее в GameScene.swift:

1
2
3
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    print(«YOU TOUCHED»)
}

Если вы хотите проверить это сейчас, вы увидите, что ВЫ прикоснулись к консоли, когда дотронетесь до экрана. Однако обычно нам нужно знать, когда был затронут конкретный узел. Для этого нам нужен способ найти и идентифицировать узлы. Мы узнаем, как этого добиться, а затем вернемся и закончим метод touchesBegan(_:with:) .

Чтобы иметь возможность идентифицировать узел, вы используете свойство name узла и ищите в дереве узлов узел с этим именем. Свойство name узла принимает буквенно-цифровую строку без пунктуации.

Есть несколько способов поиска узла по его свойству name . Если у вас уже есть ссылка на узел, вы можете просто проверить его свойство name напрямую, что мы и сделаем в методе touchesBegan(_:with:) . Однако важно знать, как искать дерево узлов для определенного узла по имени или искать группу узлов с тем же именем.

Метод childNode(withName:) ищет дочерние узлы для конкретного имени, переданного в качестве параметра.

Метод enumerateChildNodes(withName:using:) ищет дочерние узлы и вызывает блок один раз для каждого соответствующего узла, который он находит. Этот метод используется, когда вы хотите найти все узлы с одинаковым именем.

subscript(_:) метод subscript(_:) возвращает массив узлов, которые соответствуют параметру name.

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

Теперь, когда мы знаем, как искать узлы в дереве узлов, давайте дадим нашим меткам имя.

Добавьте следующее в метод didMove(to:) в GameScene.swift .

1
2
3
4
5
6
override func didMove(to view: SKView) {
    let startGameLabel = SKLabelNode(text: «Start Game»)
    startGameLabel.name = «startgame»
    startGameLabel.position = CGPoint(x: size.width/2, y: size.height/2)
    addChild(startGameLabel)
}

Здесь мы устанавливаем startGameLabel name startgame в startgame .

Нам также нужно установить имя метки в NewScene . Добавьте следующее с помощью didMove(to:) в NewScene.swift .

1
2
3
4
5
6
override func didMove(to view: SKView) {
   let goBackLabel = SKLabelNode(text: «Go Back»)
   goBackLabel.name = «goback»
   goBackLabel.position = CGPoint(x: size.width/2, y: size.height/2)
   addChild(goBackLabel)
}

Мы устанавливаем для свойства name значение goback .

Добавьте следующее в метод touchesBegan(_:with:) в GameScene.swift .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first else {
        return
    }
         
    let touchLocation = touch.location(in: self)
    let touchedNode = self.atPoint(touchLocation)
         
    if(touchedNode.name == «startgame»){
        let newScene = NewScene(size: size)
        newScene.scaleMode = scaleMode
           
        let doorsClose = SKTransition.doorsCloseVertical(withDuration: 2.0)
        view?.presentScene(newScene, transition: doorsClose)
             
    }
}

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

Мы можем получить touchLocation в сцене из свойства location касания. Затем мы можем выяснить, какой узел был затронут, вызывая atPoint(_:) и передавая touchLocation .

Мы проверяем, touchedNode ли touchedNode имени touchedNode с именем "startgame" , и если это так, мы знаем, что пользователь коснулся метки. Затем мы создаем экземпляр NewScene и устанавливаем его свойство scalemode таким же, как у текущей сцены — это гарантирует, что сцена действует одинаково на разных устройствах. Наконец, мы создаем SKTransition и вызываем метод presentScene(_:transition:) , который представляет сцену вместе с переходом.

В классе SKTransition есть много методов класса, которые вы можете вызывать для показа различных переходов между сценами вместо немедленного показа сцены. Это обеспечивает некоторую «конфету» для конечного пользователя и делает показ новой сцены менее резким. Чтобы увидеть все доступные типы переходов, проверьте класс SKTransition в справочном руководстве .

Я не собираюсь реализовывать метод touchesBegan(_:with:) в NewScene . Почему бы вам не попробовать сделать это самостоятельно и вернуть метку обратно в GameScene используя другой тип перехода? Код будет сильно напоминать то, что мы имеем выше, только помните, что мы назвали SKLabelNode "goback" .

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

Доступно несколько других типов узлов, и мы рассмотрим их в следующем руководстве — начиная с SKSpriteNode !

Чтобы узнать больше о том, как начать использовать SpriteKit, вам также следует ознакомиться с публикацией Дэвиса Элли здесь на Envato Tuts +

  • IOS
    SpriteKit с нуля: основы
    Дэвис Элли

Кроме того, ознакомьтесь с нашими курсами SpriteKit! Они проведут вас через все этапы создания вашей первой игры SpriteKit для iOS, даже если вы никогда не программировали SpriteKit раньше.

  • стриж
    Запрограммируйте игру с боковой прокруткой с помощью Swift и SpriteKit
    Дерек Дженсен
  • Разработка игр
    Разработка игр со Swift и SpriteKit
    Дерек Дженсен