Вступление
В этом уроке, третьей части серии SpriteKit From Scratch , мы подробно рассмотрим функциональность симуляции физики SpriteKit и то, как ее можно использовать в ваших 2D играх SpriteKit.
Это руководство требует, чтобы вы работали с Xcode 7.3 или выше, который включает в себя Swift 2.2 и iOS 9.3, tvOS 9.2 и OS X 10.11.4 SDK.
Чтобы продолжить, вы можете использовать проект, который вы создали в предыдущем руководстве, или загрузить свежую копию с GitHub .
Графика, используемая для игры в этой серии, может быть найдена на GraphicRiver . GraphicRiver — отличный источник для поиска иллюстраций и графики для ваших игр.
1. Физика Миров
Первая часть любого физического моделирования в SpriteKit — это свойство physicsWorld
текущей сцены. Это свойство представляет собой объект SKPhysicsWorld
который определяет такие свойства, как гравитация вашей сцены, скорость моделирования физики и контактный представитель. Физические миры также могут определять соединения между объектами, чтобы эффективно соединять несколько узлов в определенных точках.
Сила тяжести
Для игры в стиле сверху, которую мы создаем в этой серии, мы хотим изменить значение силы тяжести по умолчанию, предоставляемое SpriteKit. Гравитация по умолчанию предназначена для игры в виде спереди со значением (0, -9,8), которое имитирует гравитацию Земли, то есть 0 горизонтальное ускорение и ускорение вниз 9,8 м / с² . Для нашей игры нам нужна нулевая вертикальная гравитация, чтобы машина не начинала ускоряться вниз, как только мы установим ее физические свойства.
Откройте MainScene.sks и щелкните на сером фоне, чтобы выбрать сцену. Затем откройте инспектор атрибутов и измените Gravity, чтобы компоненты X и Y были установлены на 0 .
Важно отметить, что гравитация — единственное физическое свойство мира, которое можно изменить с помощью редактора сцены Xcode. Любые другие свойства должны быть изменены программно.
Связаться с делегатом
Чтобы игра обнаруживала столкновения между объектами, нам нужно установить свойство contactDelegate
мира физики contactDelegate
. Этот делегат может быть любым объектом, который соответствует SKPhysicsContactDelegate
протокол. Этот протокол определяет два метода, didBeginContact(_:)
и didEndContact(_:)
. Вы можете использовать эти методы для выполнения действий, основанных на объектах, которые сталкиваются в сцене.
Чтобы сохранить наш код вместе, мы собираемся сделать MainScene
свой собственный контактный делегат. Откройте MainScene.swift и отредактируйте определение класса MainScene
чтобы оно соответствовало протоколу SKPhysicsContactDelegate
.
1
2
3
4
5
6
7
8
|
import UIKit
import SpriteKit
class MainScene: SKScene, SKPhysicsContactDelegate {
…
}
|
В didMoveToView(_:)
мы устанавливаем экземпляр MainScene
как контактный делегат свойства physicsWorld
.
1
2
3
4
5
6
|
override func didMoveToView(view: SKView) {
…
physicsWorld.contactDelegate = self
}
|
Мы реализуем методы протокола SKPhysicsContactDelegate
позже. Сначала нам нужно настроить физические свойства узлов в сцене.
2. Физические тела
Любой узел в SpriteKit, для которого вы хотите каким-либо образом симулировать физику, должен иметь уникальный объект SKPhysicsBody
. Физические тела содержат несколько свойств, в том числе:
- масса
- плотность
- площадь
- трение
- скорость
В ваших играх вы также можете определить до 32 уникальных категорий, а физическое тело может быть назначено любому количеству этих категорий. Категории очень полезны для определения того, какие узлы в вашей сцене могут взаимодействовать друг с другом с точки зрения коллизий.
В физических телах эти категории представлены свойствами categoryBitMask
и collisionBitMask
, которым по умолчанию присваивается значение 0xFFFFFFFF . Это означает, что все узлы принадлежат всем категориям. Важно отметить, что в этом значении каждая шестнадцатеричная цифра F является сокращенной формой и представляет число 15 в двоичных цифрах ( 1111 ), каждая из которых соответствует одной из 32 категорий, которые вы можете использовать.
Когда два узла сталкиваются, логическая операция AND
выполняется на collisionBitMask
и categoryBitMask
первого и второго тела соответственно. Если результатом является ненулевое значение, тогда SpriteKit выполняет моделирование на двух узлах, к которым принадлежат тела.
Обратите внимание, что это вычисление AND
выполняется дважды, когда два тела меняются местами. Например:
- Расчет 1:
bodyA.collisionBitMask & bodyB.categoryBitMask
- Расчет 2:
bodyB.collisionBitMask & bodyA.categoryBitMask
Если вы не знаете, как работает оператор AND
, то вот очень простой пример, написанный на Swift:
1
2
3
4
|
let mask1 = 0x000000FF
let mask2 = 0x000000F0
let result = mask1 & mask2 // result = 0x000000F0
|
Оператор AND
определяет, какие части битовых масок являются одинаковыми, и возвращает новое значение битовой маски, содержащее совпадающие части.
Важно отметить, что эти битовые маски влияют только на сторону симуляции SpriteKit, и вы не будете уведомлены о столкновениях, обнаруженных таким образом. Это означает, что тела могут взаимодействовать друг с другом, но ни один из методов контактного делегата не вызывается.
Для того чтобы эти методы выполнялись, вы должны указать contactTestBitMask
для каждого тела, которое создает ненулевое значение, когда на них действует оператор AND
. Для всех физических тел эта битовая маска имеет значение по умолчанию 0x00000000, что означает, что вы не будете уведомлены о каких-либо столкновениях, в которых участвует физическое тело.
Физические тела, включая их различные битовые маски, можно настроить в редакторе сцены Xcode. Откройте MainScene.sks , выберите автомобиль и откройте инспектор атрибутов справа. Прокрутите вниз до раздела определения физики .
Поскольку для параметра « Тип тела» установлено значение « Нет» , ни одно из свойств, связанных с физикой, не отображается. Чтобы изменить это, нам нужно установить Body Type в значение, отличное от None . Доступны три типа телосложения:
- ограничивающий прямоугольник
- ограничивающий круг
- альфа-маска
Эти три типа телосложения являются наиболее распространенными в SpriteKit. Ограничивающий прямоугольник и Ограничивающий круг работать, создавая барьер вокруг спрайта, который будет использоваться в физических симуляциях. Это означает, что спрайт сталкивается с другим узлом всякий раз, когда его ограничивающая форма попадает в физическое тело другого узла.
Край ограничительного прямоугольника в точности соответствует размеру узла, показанному в редакторе сцены. Если вы выберете Ограничивающий круг , однако, вы видите тонкий голубой кружок, представляющий форму ограничивающего круга.
Альфа-маска работает немного по-другому и смотрит на фактическую текстуру изображения спрайта, чтобы определить края физического тела. Этот тип телосложения на данный момент является наиболее точным в SpriteKit, но он может сильно повлиять на производительность вашей игры, особенно при использовании спрайтов со сложными формами.
Для нашей игры, поскольку мы используем только один автомобильный спрайт, и наша сцена не особенно сложна, мы собираемся использовать тип кузова Альфа-маски . Не рекомендуется использовать этот тип телосложения для всех спрайтов в вашей сцене, хотя он является наиболее точным. Когда вы выбираете эту опцию в раскрывающемся меню, вы должны увидеть светло-голубую линию по краю автомобиля.
Важно отметить, что другие типы физических тел могут быть созданы программно, например, тела из объектов CGPath
а также круги и прямоугольники нестандартных размеров.
По-прежнему в инспекторе атрибутов вы должны увидеть дополнительные параметры, доступные для вас в разделе « Определение физики ». Единственное свойство, которое нам нужно изменить, — это маска контакта . Измените это на значение 1 .
Установив физический корпус автомобиля, мы можем начать создавать препятствия в игре, чтобы столкнуться с автомобилем.
3. Обнаружение столкновений
Перед тем, как мы реализуем методы протокола SKPhysicsContactDelegate
, нам нужно добавить некоторые препятствия, которых автомобиль должен избегать. Для этого мы будем каждые три секунды создавать новое препятствие перед автомобилем и расположить препятствие на случайной полосе.
Откройте MainScene.swift и добавьте оператор импорта для платформы GameplayKit, чтобы мы могли использовать генераторы случайных чисел, предоставляемые GameplayKit.
1
|
import GameplayKit
|
Затем добавьте следующий метод в класс MainScene
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
func spawnObstacle(timer: NSTimer) {
if player.hidden {
timer.invalidate()
return
}
let spriteGenerator = GKShuffledDistribution(lowestValue: 1, highestValue: 2)
let obstacle = SKSpriteNode(imageNamed: «Obstacle \(spriteGenerator.nextInt())»)
obstacle.xScale = 0.3
obstacle.yScale = 0.3
let physicsBody = SKPhysicsBody(circleOfRadius: 15)
physicsBody.contactTestBitMask = 0x00000001
physicsBody.pinned = true
physicsBody.allowsRotation = false
obstacle.physicsBody = physicsBody
let center = size.width/2.0, difference = CGFloat(85.0)
var x: CGFloat = 0
let laneGenerator = GKShuffledDistribution(lowestValue: 1, highestValue: 3)
switch laneGenerator.nextInt() {
case 1:
x = center — difference
case 2:
x = center
case 3:
x = center + difference
default:
fatalError(«Number outside of [1, 3] generated»)
}
obstacle.position = CGPoint(x: x, y: (player.position.y + 800))
addChild(obstacle)
}
|
Этот метод вызывается каждые три секунды. Давайте разберемся, чтобы увидеть, что происходит. Если текущий узел игрока скрыт, что верно, когда автомобиль сталкивается с препятствием, тогда мы аннулируем таймер и прекращаем создавать препятствия.
Мы получаем случайное число от 1 до 2 и используем его для создания узла спрайта с одним из двух спрайтов препятствий, доступных в проекте. Затем мы изменяем масштаб препятствий так, чтобы они были достаточно малы, чтобы машина могла маневрировать вокруг.
Затем мы создаем физическое тело для этого нового препятствия с кругом с радиусом 15 и маской контакта 0x00000001 . Мы устанавливаем pinned
значение true
и allowsRotation
false
чтобы препятствие оставалось на месте и не двигалось. Физическое тело тогда назначено препятствию.
Мы генерируем другое случайное число от 1 до 3, чтобы определить, в какую полосу должно быть помещено препятствие, и присвоить препятствию его расчетное положение, добавив его в сцену.
Обратите внимание, что горизонтальная разница 85 , используемая в spawnObstacle(_:)
, отличается от той, которая используется при перемещении автомобиля, 70 . Мы делаем это для того, чтобы автомобиль мог перемещаться между препятствиями.
Имея логику появления препятствий, мы можем добавить следующий код в конец метода didMoveToView(_:)
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
override func didMoveToView(view: SKView) {
…
let timer = NSTimer(timeInterval: 3.0, target: self, selector: #selector(spawnInObstacle(_:)), userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
let camera = SKCameraNode()
self.camera = camera
camera.position = CGPoint(x: center, y: player.position.y + 200)
let moveForward = SKAction.moveBy(CGVectorMake(0, 100), duration: 1.0)
camera.runAction(SKAction.repeatActionForever(moveForward))
addChild(camera)
player.xScale = 0.4;
}
|
Мы создаем таймер для выполнения spawnObstacle(_:)
каждые три секунды и добавляем его в основной цикл выполнения. Мы также создаем SKCameraNode
будет действовать как камера для сцены, и назначаем его свойству camera
этой сцены. Это приводит к визуализации сцены с точки зрения этого узла камеры. Обратите внимание, что камера расположена горизонтально и немного выше автомобиля.
Мы также добавляем идентичное действие к камере, чтобы она оставалась на одной линии с автомобилем. Мы добавляем камеру как дочерний узел сцены, как и любой другой обычный узел. И последнее, но не менее важное: мы уменьшаем автомобиль, чтобы он немного лучше подходил между препятствиями.
Последняя часть для обнаружения столкновений — это реализация одного из SKPhysicsContactDelegate
протокола SKPhysicsContactDelegate
. В нашем случае мы собираемся реализовать метод didBeginContact(_:)
поскольку мы хотим получать уведомления, как только автомобиль сталкивается с препятствием. Добавьте следующий метод в класс MainScene
.
1
2
3
4
5
6
7
|
func didBeginContact(contact: SKPhysicsContact) {
if contact.bodyA.node == player ||
player.hidden = true
player.removeAllActions()
camera?.removeAllActions()
}
}
|
Вы можете видеть, что этому методу SKPhysicsContact
один параметр SKPhysicsContact
. Этот объект содержит ключевую информацию о столкновении, включая его направление, импульс и задействованные объекты.
В этом коде нас интересует только то, какие узлы участвуют в столкновении. Мы проверяем, был ли кто-то из них автомобилем, и, если это правда, скрывают автомобиль на месте происшествия и заканчивают движение автомобиля и камеры.
Создайте и запустите свое приложение и играйте в свою игру. Вы увидите, что когда вы столкнетесь с препятствием, машина исчезнет и сцена перестанет двигаться.
Вывод
Теперь вы должны знать, как настроить физические тела для узлов вашей сцены и использовать их для симуляции реалистичной физики. Вам также должно быть удобно настроить обнаружение столкновений в вашей сцене, определив делегат контакта и назначив битовые маски проверки контакта для узлов, которые вы ожидаете столкнуться друг с другом.
В следующем руководстве по SpriteKit From Scratch мы рассмотрим более продвинутую визуальную функциональность в SpriteKit, включая системы частиц, источники света и фильтры.
Как всегда, обязательно оставляйте свои комментарии и отзывы в комментариях ниже.