Статьи

SpriteKit From Scratch: ограничения и действия

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

Чтобы следовать за мной, вы можете использовать проект, созданный вами в первом уроке этой серии, или загрузить свежую копию с GitHub .

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

Прежде чем мы сможем начать добавлять ограничения и действия в сцену, нам сначала нужно создать несколько классов, чтобы мы могли работать с нашими узлами в коде. Создайте новый класс PlayerNode на основе шаблона iOS> Source> Cocoa Touch Class и убедитесь, что он является подклассом SKSpriteNode .

Класс PlayerNode

Если Xcode выдает ошибку после создания класса, добавьте оператор импорта для платформы SpriteKit ниже оператора import UIKit :

1
2
import UIKit
import SpriteKit

Затем объявите следующие три свойства в классе PlayerNode . Эти свойства будут содержать ограничения, используемые для ограничения горизонтального движения автомобиля.

01
02
03
04
05
06
07
08
09
10
import UIKit
import SpriteKit
 
class PlayerNode: SKSpriteNode {
 
    var leftConstraint: SKConstraint!
    var middleConstraint: SKConstraint!
    var rightConstraint: SKConstraint!
 
}

Создайте еще один класс Touch Cocoa и назовите его MainScene , сделав его подклассом SKScene .

Класс MainScene

Вверху добавьте оператор импорта для платформы SpriteKit.

1
2
import UIKit
import SpriteKit

Создав эти классы, откройте MainScene.sks , щелкните серый фон, чтобы выбрать сцену, откройте инспектор пользовательских классов справа и установите для Custom Class значение MainScene .

Пользовательский класс MainScene

Выберите автомобиль и установите для его класса PlayerNode так же, как для сцены. Наконец, с выбранной машиной, откройте инспектор атрибутов и измените имя на Player .

Имя спрайта игрока

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

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

Ограничения, которые мы собираемся добавить, — это три, которые мы объявили в Класс PlayerNode . Эти ограничения будут использоваться для блокировки машины на трех полосах в игре.

Откройте MainScene.swift и создайте свойство для игрока типа PlayerNode! , Это свойство будет хранить ссылку на узел игрока.

1
2
3
4
5
6
7
8
import UIKit
import SpriteKit
 
class MainScene: SKScene {
 
    var player: PlayerNode!
 
}

Далее мы переопределяем метод didMoveToView(_:) класса MainScene :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
override func didMoveToView(view: SKView) {
    super.didMoveToView(view)
 
    size = view.frame.size
 
    if let foundPlayer = childNodeWithName(«Player») as?
        player = foundPlayer
    }
 
    let center = size.width/2.0, difference = CGFloat(70.0)
 
    player.leftConstraint = SKConstraint.positionX(SKRange(constantValue: center — difference))
    player.middleConstraint = SKConstraint.positionX(SKRange(constantValue: center))
    player.rightConstraint = SKConstraint.positionX(SKRange(constantValue: center + difference))
 
    player.leftConstraint.enabled = false
    player.rightConstraint.enabled = false
 
    player.constraints = [player.leftConstraint, player.middleConstraint, player.rightConstraint]
}

Давайте пройдемся по коду шаг за шагом. Метод didMoveToView(_:) вызывается всякий раз, когда сцена представлена ​​представлением. После вызова метода суперкласса didMoveToView(_:) мы didMoveToView(_:) размер сцены до того же размера, что и текущий вид. Это гарантирует, что сцена всегда заполняет размер экрана текущего устройства и масштабируется должным образом.

Мы получаем доступ к спрайту плеера, который мы добавили в редакторе сцены XCode, ища его по имени, которое мы дали ему ранее. Затем мы присваиваем это значение свойству player .

После вычисления центра сцены и указания постоянной разницы 70.0 , мы создаем ограничения спрайта. Используя метод класса positionX(_:) класса SKConstraint , мы создаем левое, среднее и правое ограничения для спрайта игрока. Этот метод требует в качестве SKRange экземпляр SKRange , который в нашем случае является диапазоном с постоянным значением. Если вы хотите взглянуть на возможные ограничения и диапазоны в SpriteKit, я рекомендую взглянуть на SKConstraint на SKConstraint и SKRange .

Мы отключаем левое и правое ограничения, потому что мы не хотим, чтобы они действовали на узле игрока при запуске игры. Наконец, мы присваиваем эти constraints свойству constraints узла игрока. Это свойство определено в классе SKNode .

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

Автомобиль по центру в сцене

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

Действия в SpriteKit представлены мощным классом SKAction . Действия позволяют нам легко анимировать и перемещать спрайтов в сцене. Они выполняются узлами и оцениваются API-интерфейсами SpriteKit и функционируют наряду с ограничениями и моделированием физики.

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

Аналогично тому, как узлы могут иметь дочерние узлы, есть три типа действий, которые могут иметь дочерние действия:

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

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

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

Открыть Action Editor View
Вид редактора действий

Затем прокрутите вниз в Библиотеке объектов справа и найдите элемент Move Action . Нажмите и перетащите его на временную шкалу представления редактора действий и поместите его на левый край, как показано ниже:

Добавить действие к временной шкале

Это приводит к тому, что действие начинает выполняться с 0:00 , то есть сразу после представления сцены. Если поместить его где-то еще, действие начнет выполняться после интервала времени, показанного в верхней части шкалы времени.

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

Повторяя действие «навсегда»

С выбранным действием, откройте инспектор атрибутов справа и измените значение смещения Y на 100 .

Установить значение смещения Y

Другие значения указывают на то, что при запуске автомобиля происходит немедленная анимация ( время начала ), и каждую 1 секунду ( продолжительность ) будет перемещаться на 0 пунктов в направлении X и 100 в направлении Y ( смещение ). Свойство Timing Function можно использовать для постепенного запуска и / или остановки действия. В этом случае мы используем Linear , что означает, что машина всегда движется с одинаковой скоростью.

Наконец, чтобы проверить действие, нажмите кнопку « Анимировать» в левом нижнем углу редактора сцены. Нижняя панель инструментов должна стать синей, и автомобиль должен начать движение вверх.

Действие в редакторе сцен Xcode

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

Создайте новый файл, выбрав iOS> Source> Swift File template и назовите его LaneStateMachine .

Swift Шаблон файла

Добавьте следующий код в новый файл:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import GameplayKit
 
class LaneStateMachine: GKStateMachine {
 
}
 
class LaneState: GKState {
    var playerNode: PlayerNode
 
    init(player: PlayerNode) {
        playerNode = player
    }
}
 
class LeftLane: LaneState {
    override func isValidNextState(stateClass: AnyClass) -> Bool {
        if stateClass == MiddleLane.self {
            return true
        }
 
        return false
    }
 
    override func didEnterWithPreviousState(previousState: GKState?) {
        playerNode.moveInDirection(.Left, toLane: self)
    }
}
 
class MiddleLane: LaneState {
    override func isValidNextState(stateClass: AnyClass) -> Bool {
        if stateClass == LeftLane.self ||
            return true
        }
 
        return false
    }
 
    override func didEnterWithPreviousState(previousState: GKState?) {
        if previousState is LeftLane {
            playerNode.moveInDirection(.Right, toLane: self)
        } else if previousState is RightLane {
            playerNode.moveInDirection(.Left, toLane: self)
        }
    }
}
 
class RightLane: LaneState {
    override func isValidNextState(stateClass: AnyClass) -> Bool {
        if stateClass == MiddleLane.self {
            return true
        }
 
        return false
    }
 
    override func didEnterWithPreviousState(previousState: GKState?) {
        playerNode.moveInDirection(.Right, toLane: self)
    }
}

Весь этот код использует новую платформу GameplayKit для создания конечного автомата, который представляет три линии и движение между ними в игре. Если вы хотите лучше понять, что делает этот код, ознакомьтесь с моим руководством по GameplayKit .

Затем откройте PlayerNode.swift и добавьте следующие два метода в класс PlayerNode :

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 disableAllConstraints() {
    leftConstraint.enabled = false
    middleConstraint.enabled = false
    rightConstraint.enabled = false
}
 
func moveInDirection(direction: ButtonDirection, toLane lane: LaneState) {
    disableAllConstraints()
 
    let changeInX = (direction == .Left) ?
    let rotation = (direction == .Left) ?
 
    let duration = 0.5
    let moveAction = SKAction.moveByX(CGFloat(changeInX), y: 0.0, duration: duration)
    let rotateAction = SKAction.rotateByAngle(CGFloat(rotation), duration: duration/2)
    rotateAction.timingMode = .EaseInEaseOut
    let rotateSequence = SKAction.sequence([rotateAction, rotateAction.reversedAction()])
    let moveGroup = SKAction.group([moveAction, rotateSequence])
 
    let completion = SKAction.runBlock { () -> Void in
        switch lane {
        case is LeftLane:
            self.leftConstraint.enabled = true
        case is MiddleLane:
            self.middleConstraint.enabled = true
        case is RightLane:
            self.rightConstraint.enabled = true
        default:
            break
        }
    }
     
    let sequenceAction = SKAction.sequence([moveGroup, completion])
    runAction(sequenceAction)
}

Метод disableAllConstraints() — это удобный метод для отключения ограничений узла проигрывателя.

В moveInDirection(_:toLane:) мы определяем, в каком направлении машина должна двигаться горизонтально, -70.0 при движении влево и +70.0 при движении вправо. Затем мы вычисляем правильный угол (в радианах), на который можно повернуть автомобиль при движении. Обратите внимание, что положительные числа представляют вращение против часовой стрелки.

После указания постоянной продолжительности мы создаем действия перемещения и поворота, используя moveByX(_:y:duration:) и rotateByAngle(_:duration:) соответственно. Мы создаем последовательность вращения, чтобы повернуть автомобиль назад, как это было до движения. Метод reversedAction() автоматически создает обратную операцию для вас.

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

Откройте ViewController.swift и добавьте свойство stateMachine типа LaneStateMachine! в класс ViewController .

1
2
3
4
5
6
7
class ViewController: UIViewController {
 
    var stateMachine: LaneStateMachine!
 
    …
 
}

Замените реализации viewDidLoad() и didPressButton(_:) в классе ViewController следующим:

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
36
37
38
39
40
override func viewDidLoad() {
    super.viewDidLoad()
 
    let skView = SKView(frame: view.frame)
    let scene = MainScene(fileNamed: «MainScene»)!
    skView.presentScene(scene)
    view.insertSubview(skView, atIndex: 0)
 
    let left = LeftLane(player: scene.player)
    let middle = MiddleLane(player: scene.player)
    let right = RightLane(player: scene.player)
 
    stateMachine = LaneStateMachine(states: [left, middle, right])
    stateMachine.enterState(MiddleLane)
}
 
@IBAction func didPressButton(sender: UIButton) {
    switch sender.tag {
    case ButtonDirection.Left.rawValue:
        switch stateMachine.currentState {
        case is RightLane:
            stateMachine.enterState(MiddleLane)
        case is MiddleLane:
            stateMachine.enterState(LeftLane)
        default:
            break
        }
    case ButtonDirection.Right.rawValue:
        switch stateMachine.currentState {
        case is LeftLane:
            stateMachine.enterState(MiddleLane)
        case is MiddleLane:
            stateMachine.enterState(RightLane)
        default:
            break
        }
    default:
        break
    }
}

В viewDidLoad() мы вставляем объект SKView с индексом 0, чтобы кнопки управления были видны, и мы также инициализировали SKView автомат.

В didPressButton(_:) мы didPressButton(_:) , какую кнопку нажимал пользователь, основываясь на тегах кнопок, и вводим правильную полосу, с которой автомобиль в данный момент находится.

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

Поворачивая и двигая автомобиль

Обратите внимание, что значки кнопок могут не совпадать, как показано ниже.

Несоответствующие кнопки

Чтобы это исправить, откройте каталог ресурсов ( Image.xcassets) и для каждого изображения ( Стрелка влево и Стрелка вправо ) установите Режим рендеринга в Исходное изображение .

Исходный режим рендеринга изображений

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

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

Как всегда, обязательно оставляйте свои комментарии и отзывы в комментариях ниже.