Как создать игру SpriteKit в Swift 3 (часть 3)

Опубликовано: 2022-03-10
Краткое резюме ↬ Вы когда-нибудь задумывались, что нужно для создания игры SpriteKit? Кнопки кажутся более сложной задачей, чем они должны быть? Вы когда-нибудь задумывались, как сохранить настройки в игре? С появлением SpriteKit создавать игры для iOS стало еще проще. В третьей части этой серии из трех частей мы закончим нашу игру RainCat и закончим введение в SpriteKit. Если вы пропустили предыдущий урок, вы можете наверстать упущенное, получив код на GitHub. Помните, что для этого руководства требуются Xcode 8 и Swift 3.

Вы когда-нибудь задумывались, что нужно для создания игры SpriteKit? Кнопки кажутся более сложной задачей, чем они должны быть? Вы когда-нибудь задумывались, как сохранить настройки в игре? С появлением SpriteKit создавать игры для iOS стало еще проще. В третьей части этой серии из трех частей мы закончим нашу игру RainCat и закончим введение в SpriteKit.

Если вы пропустили предыдущий урок, вы можете наверстать упущенное, получив код на GitHub. Помните, что для этого руководства требуются Xcode 8 и Swift 3.

Дополнительная литература на SmashingMag: ссылка

  • Геймификация и UX: где пользователи выигрывают или проигрывают
  • Игривый UX-дизайн: создание лучшей игры
  • Сочетание UX-дизайна и психологии для изменения поведения пользователей
Дождевик, урок 3
RainCat, урок 3
Еще после прыжка! Продолжить чтение ниже ↓

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

Сегодня мы сосредоточимся на следующем:

  • хедз-ап дисплей (HUD) для подсчета очков;
  • главное меню — с кнопками;
  • варианты отключения звука;
  • возможность выхода из игры.

Еще больше активов

Ресурсы для финального урока доступны на GitHub. Снова перетащите изображения в Assets.xcassets , как мы это делали в предыдущих уроках.

Берегись!

Нам нужен способ вести счет. Для этого мы можем создать хедз-ап дисплей (HUD). Это будет довольно просто; это будет SKNode , который содержит счет и кнопку для выхода из игры. Пока же мы сосредоточимся только на счете. Мы будем использовать шрифт Pixel Digivolve, который вы можете получить на Dafont.com. Как и в случае использования изображений или звуков, которые не принадлежат вам, прочитайте лицензию шрифта перед его использованием. В нем указано, что он бесплатен для личного использования, но если вам действительно нравится шрифт, вы можете пожертвовать его автору со страницы. Вы не всегда можете сделать все сами, поэтому приятно отблагодарить тех, кто помог вам на этом пути.

Далее нам нужно добавить пользовательский шрифт в проект. Этот процесс может быть сложным в первый раз.

Загрузите и переместите шрифт в папку проекта в папку «Шрифты». Мы делали это несколько раз в предыдущих уроках, так что мы пройдем этот процесс немного быстрее. Добавьте в проект группу Fonts и файл Pixel digivolve.otf .

Теперь самое сложное. Если вы пропустите эту часть, вы, вероятно, не сможете использовать шрифт. Нам нужно добавить его в наш файл Info.plist . Этот файл находится в левой панели Xcode. Нажмите на нее, и вы увидите список свойств (или plist ). Щелкните правой кнопкой мыши список и выберите «Добавить строку».

Добавить ряд
Добавьте строку в plist .

Когда появится новая строка, введите следующее:

 Fonts provided by application

Затем в Item 0 нам нужно добавить имя нашего шрифта. plist должен выглядеть следующим образом:

Пиксельный digivolve.otf
Шрифт успешно добавлен в plist !

Шрифт должен быть готов к использованию! Мы должны провести быстрый тест, чтобы убедиться, что он работает так, как задумано. Перейдите к GameScene.swift и в sceneDidLoad добавьте следующий код вверху функции:

 let label = SKLabelNode(fontNamed: "PixelDigivolve") label.text = "Hello World!" label.position = CGPoint(x: size.width / 2, y: size.height / 2) label.zPosition = 1000 addChild(label)

Это работает?

Привет мир!
Тестируем наш SKLabelNode . о нет! Ярлык «Hello World» вернулся.

Если заработает, значит, вы все сделали правильно. Если нет, то что-то не так. У Code With Chris есть более подробное руководство по устранению неполадок, но обратите внимание, что оно предназначено для более старой версии Swift, поэтому вам придется внести небольшие изменения, чтобы довести его до Swift 3.

Теперь, когда мы можем загружать пользовательские шрифты, мы можем начать с нашего HUD. Удалите ярлык «Hello World», потому что мы использовали его только для того, чтобы убедиться, что наш шрифт загружается. HUD будет SKNode , действующим как контейнер для наших элементов HUD. Это тот же процесс, который мы использовали при создании фонового узла в первом уроке.

Создайте файл HudNode.swift , используя обычные методы, и введите следующий код:

 import SpriteKit class HudNode : SKNode { private let scoreKey = "RAINCAT_HIGHSCORE" private let scoreNode = SKLabelNode(fontNamed: "PixelDigivolve") private(set) var score : Int = 0 private var highScore : Int = 0 private var showingHighScore = false /// Set up HUD here. public func setup(size: CGSize) { let defaults = UserDefaults.standard highScore = defaults.integer(forKey: scoreKey) scoreNode.text = "\(score)" scoreNode.fontSize = 70 scoreNode.position = CGPoint(x: size.width / 2, y: size.height - 100) scoreNode.zPosition = 1 addChild(scoreNode) } /// Add point. /// - Increments the score. /// - Saves to user defaults. /// - If a high score is achieved, then enlarge the scoreNode and update the color. public func addPoint() { score += 1 updateScoreboard() if score > highScore { let defaults = UserDefaults.standard defaults.set(score, forKey: scoreKey) if !showingHighScore { showingHighScore = true scoreNode.run(SKAction.scale(to: 1.5, duration: 0.25)) scoreNode.fontColor = SKColor(red:0.99, green:0.92, blue:0.55, alpha:1.0) } } } /// Reset points. /// - Sets score to zero. /// - Updates score label. /// - Resets color and size to default values. public func resetPoints() { score = 0 updateScoreboard() if showingHighScore { showingHighScore = false scoreNode.run(SKAction.scale(to: 1.0, duration: 0.25)) scoreNode.fontColor = SKColor.white } } /// Updates the score label to show the current score. private func updateScoreboard() { scoreNode.text = "\(score)" } }

Прежде чем делать что-либо еще, откройте Constants.swift и добавьте следующую строку в конец файла — мы будем использовать ее для получения и сохранения рекорда:

 let ScoreKey = "RAINCAT_HIGHSCORE"

В коде у нас есть пять переменных, относящихся к табло. Первая переменная — это фактический SKLabelNode , который мы используем для представления метки. Далее идет наша переменная для хранения текущего счета; затем переменная, которая содержит лучший результат. Последняя переменная — это логическое значение, которое сообщает нам, представляем ли мы в настоящее время высокий балл (мы используем это, чтобы установить, нужно ли нам запускать SKAction , чтобы увеличить масштаб табло и окрасить его в желтый цвет пола).

Первая функция setup(size:) предназначена только для того, чтобы все настроить. Мы настраиваем SKLabelNode так же, как и раньше. Класс SKNode по умолчанию не имеет никаких свойств размера, поэтому нам нужно создать способ установки размера для размещения нашей метки scoreNode . Мы также получаем текущий рекорд от UserDefaults . Это быстрый и простой способ сохранить небольшие фрагменты данных, но он небезопасен. Поскольку в этом примере мы не беспокоимся о безопасности, UserDefaults вполне подойдет.

В нашем addPoint() мы увеличиваем текущую переменную score и проверяем, получил ли пользователь высокий балл. Если у них высокий балл, мы сохраняем этот балл в UserDefaults и проверяем, показываем ли мы в настоящее время лучший балл. Если пользователь набрал высокий балл, мы можем анимировать размер и цвет scoreNode .

В функции resetPoints() мы устанавливаем текущий счет равным 0 . Затем нам нужно проверить, показывали ли мы высокий балл, и при необходимости сбросить размер и цвет на значения по умолчанию.

Наконец, у нас есть небольшая функция с именем updateScoreboard . Это внутренняя функция для установки оценки в scoreNode . Это вызывается как в addPoint() , так и resetPoints() .

Подключение HUD

Нам нужно проверить, правильно ли работает наш HUD. Перейдите к GameScene.swift и добавьте следующую строку под переменной foodNode в верхней части файла:

 private let hudNode = HudNode()

Добавьте следующие две строки в sceneDidLoad() вверху:

 hudNode.setup(size: size) addChild(hudNode)

Затем в функции spawnCat() сбросьте очки, если кошка упала с экрана. Добавьте следующую строку после добавления спрайта кошки в сцену:

 hudNode.resetPoints()

Затем в функции handleCatCollision(contact:) нам нужно снова сбросить счет, когда кошка попала под дождь. В операторе switch в конце функции — когда другим телом является RainDropCategory — добавьте следующую строку:

 hudNode.resetPoints()

Наконец, нам нужно сообщить табло, когда пользователь заработал очки. В конце файла в handleFoodHit(contact:) найдите следующие строки:

 //TODO increment points print("fed cat")

И замените их на это:

 hudNode.addPoint()

Вуаля!

HUD разблокирован!
HUD разблокирован!

Вы должны увидеть HUD в действии. Бегайте и собирайте еду. В первый раз, когда вы собираете еду, вы должны увидеть, как счет становится желтым и увеличивается в масштабе. Когда вы увидите, как это происходит, дайте кошке ударить. Если счет обнулится, значит, вы на правильном пути!

Рекорд!
Самый высокий балл за всю историю (… на момент написания статьи)!

Следующая сцена

Правильно, мы переходим к другой сцене! Фактически, когда все будет готово, это будет первый экран нашего приложения. Прежде чем делать что-либо еще, откройте Constants.swift и добавьте следующую строку в конец файла — мы будем использовать ее для получения и сохранения рекорда:

 let ScoreKey = "RAINCAT_HIGHSCORE"

Создайте новую сцену, поместите ее в папку «Scenes» и назовите ее MenuScene.swift . Введите следующий код в файл MenuScene.swift :

 import SpriteKit class MenuScene : SKScene { let startButtonTexture = SKTexture(imageNamed: "button_start") let startButtonPressedTexture = SKTexture(imageNamed: "button_start_pressed") let soundButtonTexture = SKTexture(imageNamed: "speaker_on") let soundButtonTextureOff = SKTexture(imageNamed: "speaker_off") let logoSprite = SKSpriteNode(imageNamed: "logo") var startButton : SKSpriteNode! = nil var soundButton : SKSpriteNode! = nil let highScoreNode = SKLabelNode(fontNamed: "PixelDigivolve") var selectedButton : SKSpriteNode? override func sceneDidLoad() { backgroundColor = SKColor(red:0.30, green:0.81, blue:0.89, alpha:1.0) //Set up logo - sprite initialized earlier logoSprite.position = CGPoint(x: size.width / 2, y: size.height / 2 + 100) addChild(logoSprite) //Set up start button startButton = SKSpriteNode(texture: startButtonTexture) startButton.position = CGPoint(x: size.width / 2, y: size.height / 2 - startButton.size.height / 2) addChild(startButton) let edgeMargin : CGFloat = 25 //Set up sound button soundButton = SKSpriteNode(texture: soundButtonTexture) soundButton.position = CGPoint(x: size.width - soundButton.size.width / 2 - edgeMargin, y: soundButton.size.height / 2 + edgeMargin) addChild(soundButton) //Set up high-score node let defaults = UserDefaults.standard let highScore = defaults.integer(forKey: ScoreKey) highScoreNode.text = "\(highScore)" highScoreNode.fontSize = 90 highScoreNode.verticalAlignmentMode = .top highScoreNode.position = CGPoint(x: size.width / 2, y: startButton.position.y - startButton.size.height / 2 - 50) highScoreNode.zPosition = 1 addChild(highScoreNode) } }

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

Приведенный выше код не делает ничего особенного; он устанавливает позиции четырех спрайтов. Мы также устанавливаем цвет фона сцены, чтобы весь фон имел правильное значение. Хорошим инструментом для генерации цветовых кодов из строк HEX для Xcode является UI Color. Приведенный выше код также устанавливает текстуры для состояний кнопок. Кнопка для запуска игры имеет нормальное состояние и нажатое состояние, тогда как кнопка звука является переключателем. Чтобы упростить работу переключателя, мы будем изменять альфа-значение звуковой кнопки при нажатии пользователем. Мы также извлекаем и устанавливаем SKLabelNode с высоким баллом.

Наш MenuScene выглядит довольно хорошо. Теперь нам нужно показать сцену при загрузке приложения. Перейдите в GameViewController.swift и найдите следующую строку:

 let sceneNode = GameScene(size: view.frame.size)

Замените его на это:

 let sceneNode = MenuScene(size: view.frame.size)

Это небольшое изменение загрузит MenuScene по умолчанию вместо GameScene .

Наша новая сцена!
Наша новая сцена! Обратите внимание на 1,0 кадра в секунду: ничего не движется, поэтому ничего обновлять не нужно.

Состояние кнопки

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

  • touchesBegan(_ touches: with event:)
  • touchesMoved(_ touches: with event:)
  • touchesEnded(_ touches: with event:)

Мы кратко рассмотрели это при обновлении зонтика, но теперь нам нужно знать следующее: какая кнопка была нажата, отпустил ли пользователь касание или щелкнул эту кнопку, и дотрагивается ли пользователь до сих пор. Здесь в игру вступает наша переменная selectedButton . Когда начинается касание, мы можем зафиксировать кнопку, которую пользователь начал нажимать, с помощью этой переменной. Если они перетаскиваются за пределы кнопки, мы можем справиться с этим и придать ей соответствующую текстуру. Когда они отпускают касание, мы можем видеть, касаются ли они еще внутри кнопки. Если да, то мы можем применить к нему соответствующее действие. Добавьте следующие строки внизу MenuScene.swift :

 override func touchesBegan(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { if selectedButton != nil { handleStartButtonHover(isHovering: false) handleSoundButtonHover(isHovering: false) } // Check which button was clicked (if any) if startButton.contains(touch.location(in: self)) { selectedButton = startButton handleStartButtonHover(isHovering: true) } else if soundButton.contains(touch.location(in: self)) { selectedButton = soundButton handleSoundButtonHover(isHovering: true) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { // Check which button was clicked (if any) if selectedButton == startButton { handleStartButtonHover(isHovering: (startButton.contains(touch.location(in: self)))) } else if selectedButton == soundButton { handleSoundButtonHover(isHovering: (soundButton.contains(touch.location(in: self)))) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { if selectedButton == startButton { // Start button clicked handleStartButtonHover(isHovering: false) if (startButton.contains(touch.location(in: self))) { handleStartButtonClick() } } else if selectedButton == soundButton { // Sound button clicked handleSoundButtonHover(isHovering: false) if (soundButton.contains(touch.location(in: self))) { handleSoundButtonClick() } } } selectedButton = nil } /// Handles start button hover behavior func handleStartButtonHover(isHovering : Bool) { if isHovering { startButton.texture = startButtonPressedTexture } else { startButton.texture = startButtonTexture } } /// Handles sound button hover behavior func handleSoundButtonHover(isHovering : Bool) { if isHovering { soundButton.alpha = 0.5 } else { soundButton.alpha = 1.0 } } /// Stubbed out start button on click method func handleStartButtonClick() { print("start clicked") } /// Stubbed out sound button on click method func handleSoundButtonClick() { print("sound clicked") }

Это простая обработка кнопок для наших двух кнопок. В touchesBegan(_ touches: with events:) мы начинаем с проверки наличия выбранных кнопок. Если мы это сделаем, нам нужно сбросить состояние кнопки до ненажатого. Затем нам нужно проверить, нажата ли какая-либо кнопка. Если один из них нажат, он покажет выделенное состояние кнопки. Затем мы устанавливаем selectedButton на кнопку для использования в двух других методах.

В touchesMoved(_ touches: with events:) мы проверяем, какая кнопка была первоначально нажата. Затем мы проверяем, находится ли текущее касание в пределах selectedButton , и оттуда обновляем выделенное состояние. Выделенное состояние startButton изменяет текстуру на текстуру нажатого состояния, где выделенное состояние soundButton имеет альфа-значение спрайта, установленное на 50%.

Наконец, в touchesEnded(_ touches: with event:) мы снова проверяем, какая кнопка выбрана, если она есть, и затем остается ли касание в пределах кнопки. Если все случаи удовлетворены, мы вызываем handleStartButtonClick() или handleSoundButtonClick() для правильной кнопки.

Время действовать

Теперь, когда у нас есть основное поведение кнопки, нам нужно событие, которое будет запускаться при нажатии кнопки. Проще всего реализовать кнопку startButton . При щелчке нам нужно только представить GameScene . Обновите handleStartButtonClick() в функции MenuScene.swift следующим кодом:

 func handleStartButtonClick() { let transition = SKTransition.reveal(with: .down, duration: 0.75) let gameScene = GameScene(size: size) gameScene.scaleMode = scaleMode view?.presentScene(gameScene, transition: transition) }

Если вы сейчас запустите приложение и нажмете кнопку, игра начнется!

Теперь нам нужно реализовать переключатель отключения звука. У нас уже есть звуковой менеджер, но нам нужно указать ему, включено или выключено отключение звука. В Constants.swift нам нужно добавить ключ, который будет сохраняться при включенном отключении звука. Добавьте следующую строку:

 let MuteKey = "RAINCAT_MUTED"

Мы будем использовать это для сохранения логического значения в UserDefaults . Теперь, когда это настроено, мы можем перейти к SoundManager.swift . Здесь мы проверим и установим UserDefaults , чтобы увидеть, включено ли отключение звука. В верхней части файла под переменной trackPosition добавьте следующую строку:

 private(set) var isMuted = false

Это переменная, которую главное меню (и все остальное, что будет воспроизводить звук) проверяет, чтобы определить, разрешен ли звук. Мы инициализируем его как false , но теперь нам нужно проверить UserDefaults , чтобы увидеть, что хочет пользователь. Замените функцию init() на следующую:

 private override init() { //This is private, so you can only have one Sound Manager ever. trackPosition = Int(arc4random_uniform(UInt32(SoundManager.tracks.count))) let defaults = UserDefaults.standard isMuted = defaults.bool(forKey: MuteKey) }

Теперь, когда у нас есть значение по умолчанию для isMuted , нам нужна возможность изменить его. Добавьте следующий код в SoundManager.swift :

 func toggleMute() -> Bool { isMuted = !isMuted let defaults = UserDefaults.standard defaults.set(isMuted, forKey: MuteKey) defaults.synchronize() if isMuted { audioPlayer?.stop() } else { startPlaying() } return isMuted }

Этот метод переключит нашу приглушенную переменную, а также обновит UserDefaults . Если новое значение не отключено, начнется воспроизведение музыки; если новое значение отключено, воспроизведение не начнется. В противном случае мы остановим воспроизведение текущего трека. После этого нам нужно отредактировать оператор if в startPlaying() .

Найдите следующую строку:

 if audioPlayer == nil || audioPlayer?.isPlaying == false {

И замените его на это:

 if !isMuted && (audioPlayer == nil || audioPlayer?.isPlaying == false) {

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

Отсюда мы можем вернуться в MenuScene.swift , чтобы закончить нашу кнопку отключения звука. Замените handleSoundbuttonClick() следующим кодом:

 func handleSoundButtonClick() { if SoundManager.sharedInstance.toggleMute() { //Is muted soundButton.texture = soundButtonTextureOff } else { //Is not muted soundButton.texture = soundButtonTexture } }

Это переключает звук в SoundManager , проверяет результат, а затем соответствующим образом устанавливает текстуру, чтобы показать пользователю, отключен звук или нет. Мы почти закончили! Нам нужно только установить начальную текстуру кнопки при запуске. В sceneDidLoad() найдите следующую строку:

 soundButton = SKSpriteNode(texture: soundButtonTexture)

И замените его на это:

 soundButton = SKSpriteNode(texture: SoundManager.sharedInstance.isMuted ? soundButtonTextureOff : soundButtonTexture)

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

Теперь, когда музыка подключена, мы можем перейти к CatSprite.swift , чтобы отключить кошачье мяуканье при включенном отключении звука. В hitByRain() мы можем добавить следующий оператор if после удаления действия ходьбы:

 if SoundManager.sharedInstance.isMuted { return }

Этот оператор вернет, отключил ли пользователь звук приложения. Из-за этого мы полностью игнорируем наши currentRainHits , maxRainHits и мяукающие звуковые эффекты.

После всего этого пришло время опробовать нашу кнопку отключения звука. Запустите приложение и проверьте, правильно ли оно воспроизводится и отключает звуки. Выключите звук, закройте приложение и снова откройте его. Убедитесь, что настройка отключения звука сохраняется. Обратите внимание, что если вы просто отключите звук и перезапустите приложение из Xcode, возможно, вы не дали достаточно времени для сохранения UserDefaults . Сыграйте в игру и убедитесь, что кошка никогда не мяукает, когда вы отключили звук.

Проверка работоспособности кнопки.

Выход из игры

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

Кнопка выхода, которую мы реализуем, будет имитировать кнопку запуска игры, которую мы добавили ранее, при этом большая часть процесса останется прежней. Изменения коснутся того, как мы обрабатываем прикосновения. Загрузите quit_button и quit_button_pressed в файл Assets.xcassets и добавьте следующий код в файл HudNode.swift :

 private var quitButton : SKSpriteNode! private let quitButtonTexture = SKTexture(imageNamed: "quit_button") private let quitButtonPressedTexture = SKTexture(imageNamed: "quit_button_pressed")

Это будет обрабатывать нашу ссылку quitButton вместе с текстурами, которые мы установим для состояний кнопки. Чтобы гарантировать, что мы случайно не обновим зонт при попытке выйти, нам нужна переменная, которая сообщает HUD (и игровой сцене), что мы взаимодействуем с кнопкой выхода, а не с зонтом. Добавьте следующий код под логической переменной showingHighScore :

 private(set) var quitButtonPressed = false

Опять же, это переменная, которую может установить только HudNode , но которую могут проверить другие классы. Теперь, когда наши переменные настроены, мы можем добавить кнопку в HUD. Добавьте следующий код в функцию setup(size:) :

 quitButton = SKSpriteNode(texture: quitButtonTexture) let margin : CGFloat = 15 quitButton.position = CGPoint(x: size.width - quitButton.size.width - margin, y: size.height - quitButton.size.height - margin) quitButton.zPosition = 1000 addChild(quitButton)

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

Кнопка выхода
Обратите внимание на новую кнопку выхода в нашем HUD.

Теперь, когда кнопка размещена, нам нужно иметь возможность взаимодействовать с ней. Прямо сейчас единственное место, где у нас есть взаимодействие в GameScene , — это когда мы взаимодействуем с umbrellaSprite . В нашем примере HUD будет иметь приоритет над зонтом, так что пользователям не нужно будет убирать зонт, чтобы выйти. Мы можем создать те же функции в HudNode.swift , чтобы имитировать сенсорные функции в GameScene.swift . Добавьте следующий код в HudNode.swift :

 func touchBeganAtPoint(point: CGPoint) { let containsPoint = quitButton.contains(point) if quitButtonPressed && !containsPoint { //Cancel the last click quitButtonPressed = false quitButton.texture = quitButtonTexture } else if containsPoint { quitButton.texture = quitButtonPressedTexture quitButtonPressed = true } } func touchMovedToPoint(point: CGPoint) { if quitButtonPressed { if quitButton.contains(point) { quitButton.texture = quitButtonPressedTexture } else { quitButton.texture = quitButtonTexture } } } func touchEndedAtPoint(point: CGPoint) { if quitButton.contains(point) { //TODO tell the gamescene to quit the game } quitButton.texture = quitButtonTexture }

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

Перейдите к GameScene.swift и замените методы touchesBegan(_ touches with event:) и touchesMoved(_ touches: with event:) следующим кодом:

 override func touchesBegan(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchBeganAtPoint(point: point) if !hudNode.quitButtonPressed { umbrellaNode.setDestination(destination: point) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchMovedToPoint(point: point) if !hudNode.quitButtonPressed { umbrellaNode.setDestination(destination: point) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchEndedAtPoint(point: point) } }

Здесь каждый метод обрабатывает все примерно одинаково. Мы сообщаем HUD, что пользователь взаимодействовал со сценой. Затем мы проверяем, фиксирует ли в данный момент касания кнопка выхода. Если его нет, то двигаем зонт. Мы также добавили touchesEnded(_ touches: with event:) для обработки окончания нажатия кнопки выхода, но мы по-прежнему не используем ее для umbrellaSprite .

Нажатие кнопки выхода не приведет к перемещению зонта, но нажатие в другом месте сделает это.

Теперь, когда у нас есть кнопка, нам нужно, чтобы она воздействовала на GameScene . Добавьте следующую строку в верхнюю часть HudeNode.swift :

 var quitButtonAction : (() -> ())?

Это общее замыкание, которое не имеет ни входа, ни выхода. Мы установим это с помощью кода в файле GameScene.swift и вызовем его, когда нажмем кнопку в HudNode.swift . Затем мы можем заменить TODO в коде, который мы создали ранее в функции touchEndedAtPoint(point:) , на это:

 if quitButton.contains(point) && quitButtonAction != nil { quitButtonAction!() }

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

Чтобы настроить закрытие quitButtonAction , нам нужно перейти к GameScene.swift . В sceneDidLoad() мы можем заменить нашу настройку HUD следующим кодом:

 hudNode.setup(size: size) hudNode.quitButtonAction = { let transition = SKTransition.reveal(with: .up, duration: 0.75) let gameScene = MenuScene(size: self.size) gameScene.scaleMode = self.scaleMode self.view?.presentScene(gameScene, transition: transition) self.hudNode.quitButtonAction = nil } addChild(hudNode)

Запустите приложение, нажмите кнопку воспроизведения, а затем нажмите кнопку выхода. Если вы вернулись в главное меню, значит, ваша кнопка выхода работает как положено. В замыкании, которое мы создали, мы инициализировали переход к MenuScene . И мы устанавливаем это закрытие для узла HUD , чтобы он запускался при нажатии кнопки выхода. Еще одна важная строка здесь, когда мы устанавливаем quitButtonAction в nil . Причина этого в том, что происходит цикл удержания. Сцена содержит ссылку на HUD, где HUD содержит ссылку на сцену. Поскольку есть ссылка на оба объекта, ни один из них не будет удален, когда придет время для сборки мусора. В этом случае каждый раз, когда мы заходим в GameScene и выходим из нее, будет создаваться еще один его экземпляр, который никогда не будет выпущен. Это плохо для производительности, и в конечном итоге у приложения закончится память. Есть несколько способов избежать этого, но в нашем случае мы можем просто удалить ссылку на GameScene из HUD, и сцена и HUD будут завершены, как только мы вернемся к MenuScene . У Кракендева есть более глубокое объяснение ссылочных типов и способов избежать этих циклов.

Теперь перейдите к GameViewController.swift и удалите или закомментируйте следующие три строки кода:

 view.showsPhysics = true view.showsFPS = true view.showsNodeCount = true

С отладочными данными игра выглядит действительно хорошо! Поздравляем: в настоящее время мы находимся в бета-версии! Проверьте окончательный код сегодня на GitHub.

Последние мысли

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

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

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

Куда пойти отсюда

Мы потратили много времени, чтобы зайти так далеко, но впереди еще много работы, которую можно проделать в этой игре. RainCat продолжает развиваться и доступен в App Store. Ниже приведен список желаний и потребностей, которые необходимо добавить. Некоторые из элементов были добавлены, в то время как другие все еще находятся на рассмотрении:

  • Добавьте значки и заставку.
  • Доработайте главное меню (упрощенное для туториала).
  • Исправьте ошибки, в том числе мошеннические капли дождя и появление нескольких продуктов.
  • Рефакторинг и оптимизация кода.
  • Меняйте цветовую палитру игры в зависимости от счета.
  • Обновите сложность на основе оценки.
  • Анимируйте кошку, когда еда находится прямо над ней.
  • Интеграция Game Center.
  • Дайте оценку (в том числе правильную оценку музыкальных треков).

Следите за GitHub, потому что эти изменения будут внесены в будущем. Если у вас есть какие-либо вопросы о коде, напишите нам по адресу [email protected], и мы обсудим его. Если некоторым темам будет уделено достаточно внимания, возможно, мы сможем написать еще одну статью, обсуждающую эту тему.

Спасибо!

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

  • Кэтрин Роу За первоначальное оформление, дизайн и редактирование, а также за публикацию статей в нашем Гараже.
  • Морган Уитон За окончательный дизайн меню и цветовые палитры (которые будут выглядеть потрясающе, как только я на самом деле реализую эти функции — следите за обновлениями).
  • Никки Кларк за прекрасные заголовки и разделители в статьях и за помощь в редактировании статей.
  • Laura Levisay За все потрясающие GIF-файлы в статьях и за то, что прислала мне GIF-файлы с милыми котиками для моральной поддержки.
  • Тому Хадсону за помощь в редактировании статей и за то, без кого эта серия вообще не была бы сделана.
  • Лани ДеГуайр за помощь в редактировании статей, это была огромная работа.
  • Джефф Мун За помощь в редактировании третьего урока и пинг-понга. Много пинг-понга.
  • Том Нельсон За то, что помог убедиться, что учебник работает должным образом.

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

Спасибо всем, кто читает это предложение, тоже.