Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. SpriteKit
Code

Создание космических захватчиков с помощью Swift и Sprite Kit: реализация игрового процесса

by
Difficulty:BeginnerLength:LongLanguages:
This post is part of a series called Create Space Invaders with Swift and Sprite Kit.
Create Space Invaders with Swift and Sprite Kit: Implementing Classes
Create Space Invaders with Swift and Sprite Kit: Finishing Gameplay

Russian (Pусский) translation by Liliya (you can also view the original English article)

Final product image
What You'll Be Creating

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

1. Перемещение захватчиков

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

Прежде чем мы это сделаем, нам нужно обновить свойство rightBounds. Первоначально он был установлен в 0, потому что нам нужно использовать size сцены для установки переменной. Мы не смогли сделать это за пределами любого из методов класса, поэтому мы обновим это свойство в методе didMoveToView(_:).

Затем реализуем метод moveInvaders ниже метода setupPlayer, который вы создали в предыдущем учебнике.

Мы объявляем переменную changeDirection, чтобы отслеживать, когда оккупанты должны менять направление, перемещаться влево или двигаться вправо. Затем мы используем метод enumerateChildNodesWithName(usingBlock:), который ищет дочерние узлы и вызывает замыкание один раз для каждого соответствующего узла, который он находит, с соответствующим именем "invader". Закрытие принимает два параметра, node - это узел, который соответствует name , а stop - указатель на логическую переменную, чтобы завершить перечисление. Мы не будем здесь использовать stop, но будем знать, для чего он используется.

Мы приводим node в экземпляр SKSpriteNode, который является подклассом, invader половину его ширины invaderHalfWidth и обновляет свою позицию. Затем мы проверяем, находится ли его позиция в границах, leftBounds и rightBounds, а если нет, мы устанавливаем changeDirection в true.

Если changeDirection правильное, мы отрицаем invaderSpeed, который изменит направление, в которое движется захватчик. Затем мы перечисляем через захватчиков и обновляем их позицию y. И наконец, мы изменим changeDirection на false.

Метод moveInvaders вызывается в методе update(_:).

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

2. Стрельба пулями

Шаг 1: fireBullet

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

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

Добавьте метод fireBullet в класс InvaderBullet в InvaderBullet.swift.

В методе fireBullet мы создаем экземпляр InvaderBullet, передавая "laser" для imageName, и поскольку мы не хотим, чтобы звук воспроизводился, мы передаем nil для bulletSound. Мы устанавливаем его position таким же, как у захватчика, с небольшим смещением по позиции y и добавляем его в сцену.

Мы создаем два экземпляра SKAction, moveBulletAction и removeBulletAction. Действие moveBulletAction перемещает пулю в определенную точку за определенную продолжительность, в то время как действие removeBulletAction удаляет ее из сцены. Вызывая sequence(_:) метода для этих действий, они будут запускаться последовательно. Вот почему я упомянул метод waitForDuration при воспроизведении звука в предыдущей части этой серии. Если вы создаете объект SKAction, вызывая playSoundFileNamed(_:waitForCompletion:) и устанавливая waitForCompletion в true, тогда продолжительность этого действия будет до тех пор, пока звук будет воспроизводиться, иначе он сразу перейдет к следующему действию в последовательности.

Шаг 2: invokeInvaderFire

Добавьте метод invokeInvaderFire ниже других методов, которые вы создали в GameScence.swift.

Метод runBlock(_:) класса SKAction создает экземпляр SKAction и немедленно вызывает замыкание, переданное методу runBlock(_:). В закрытии мы вызываем метод fireInvaderBullet. Поскольку мы вызываем этот метод в закрытии, мы должны использовать self для его вызова.

Затем мы создаем экземпляр SKAction с именем waitToFireInvaderBullet, вызывая waitForDuration(_:), передавая количество секунд ожидания перед тем, как двигаться дальше. Затем мы создаем экземпляр SKAction, invaderFire, вызывая метод sequence(_:). Этот метод принимает набор действий, которые вызывается действием invaderFire. Мы хотим, чтобы эта последовательность повторялась постоянно, поэтому мы создаем действие с именем repeatForeverAction, передаем объекты SKAction для повторения и вызываем runAction, передавая действие repeatForeverAction. Метод runAction объявлен в классе SKNode.

Шаг 3: fireInvaderBullet

Добавьте метод fireInvaderBullet ниже метода invokeInvaderFire, введенного на предыдущем шаге.

В этом методе мы называем метод, называемый randomElement, который возвращает случайный элемент из массива invadersWhoCanFire, а затем вызывает его метод fireBullet. К сожалению, нет встроенного метода randomElement в структуре Array. Однако мы можем создать расширение Array для предоставления этой функции.

Шаг 4: Реализация randomElement

Откройте File> New> File ... и выберите Swift File. Мы делаем что-то другое, чем раньше, поэтому просто убедитесь, что вы выбираете Swift File, а не Cocoa Touch Class. Нажмите Next и назовите файл Утилиты. Добавьте следующий код в Utilities.swift.

Мы расширяем структуру Array, чтобы иметь метод с именем randomElement. Функция arc4random_uniform возвращает число от 0 до того, что вы проходите. Поскольку Swift неявно не конвертирует числовые типы, мы должны сами сделать преобразование. Наконец, мы возвращаем элемент массива по индексу index.

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

Шаг 5: Стрельба пулей

С этим все в порядке, теперь мы можем стрелять из пуль. Добавьте к методу didMoveToView(_:).

Если вы протестируете приложение сейчас, каждую секунду или около того вы должны увидеть, что один из захватчиков из нижней строки выстрелил в пулю.

3. Стрельба игровыми пулями

Шаг 1: fireBullet(scene:)

Добавьте в класс Player в Player.swift следующее свойство.

Мы хотим ограничить, как часто игрок может стрелять из пули. Свойство canFire будет использоваться для его регулирования. Затем добавьте следующее к методу fireBullet(scene:) в классе Player.

Сначала мы убеждаемся, что игрок способен стрелять, проверяя, установлено ли для параметра canFire значение true. Если это не так, мы немедленно возвращаемся из метода.

Если игрок может стрелять, мы устанавливаем canFire на false, чтобы они не могли сразу запустить другую пулю. Затем мы создаем экземпляр PlayerBullet, передавая "laser" для параметра imageNamed. Поскольку мы хотим, чтобы звук воспроизводился, когда игрок запускает пулю, мы передаем "laser.mp3" для параметра bulletSound.

Затем мы устанавливаем позицию пули и добавляем ее на экран. Следующие несколько строк такие же, как метод FireBullet Invader, поскольку мы перемещаем маркер и удаляем его из сцены. Затем мы создаем экземпляр SKAction, waitToEnableFire, вызывая метод waitForDuration(_:). Наконец, мы вызываем runAction, переходя в waitToEnableFire, и по завершении установки canFire возвращается к true.

Шаг 2: Стрельба игровой пулей

Всякий раз, когда пользователь прикасается к экрану, мы хотим запустить пулю. Это так же просто, как вызов fireBullet в объекте player в методе touchhesBegan(_:withEvent:) класса GameScene.

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

4. Категории столкновений

Чтобы обнаружить, когда узлы сталкиваются или контактируют друг с другом, мы будем использовать встроенный физический движок Sprite Kit. Тем не менее, поведение по умолчанию в физическом движке состоит в том, что все сталкивается со всем, когда у них есть физическое тело, добавленное к ним. Нам нужен способ отделить то, что мы хотим взаимодействовать друг с другом, и мы можем сделать это, создав категории, к которым принадлежат определенные физические тела.

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

Добавьте следующее определение структуры в класс GameScene, ниже объявления invaderNum в GameScene.swift.

Мы используем структуру CollsionCategories для создания категорий для классов Invader, Player, InvaderBullet и PlayerBullet. Мы используем битовое смещение, чтобы включить бит.

5. Столкновение игроков и захватчиков

Шаг 1: Настройка InvaderBullet для Collision

Добавьте следующий код в метод init(imageName: bulletSound:) в InvaderBullet.swift.

Существует несколько способов создания физического тела. В этом примере мы используем инициализатор init(texture:size:), который заставит обнаружение столкновения использовать форму текстуры, которую мы передаем. Существует несколько других инициализаторов, которые вы можете увидеть в ссылке класса SKPhysicsBody.

Мы могли бы легко использовать инициализатор init(rectangleOfSize:), поскольку пули имеют прямоугольную форму. В этой небольшой игре это не имеет значения. Однако имейте в виду, что использование метода init(texture:size:) может быть дорогостоящим, поскольку оно должно вычислять точную форму текстуры. Если у вас есть объекты прямоугольной или круглой формы, вы должны использовать эти типы инициализаторов, если производительность игры становится проблемой.

Для обнаружения столкновения для работы, по крайней мере одно из тел, которое вы тестируете, должно быть отмечено как динамическое. Установив для свойства usesPreciseCollisionDetection значение true, Sprite Kit использует более точное обнаружение столкновений. Установите для этого свойства значение true на маленьких, быстро движущихся телах, таких как наши пули.

Каждое тело будет принадлежать к категории, и вы определяете это, установив его categoryBitMask. Поскольку это класс InvaderBullet, мы устанавливаем его в CollisionCategories.InvaderBullet.

Чтобы сообщить, когда это тело коснулось другого тела, которое вас интересует, вы устанавливаете contactBitMask. Здесь мы хотим знать, когда InvaderBullet установил контакт с игроком, поэтому мы используем CollisionCategories.Player. Поскольку столкновение не должно вызывать никаких физических сил, мы устанавливаем collisionBitMask в 0x0.

Шаг 2: Настройка Player для столкновения

Добавьте в метод init в Player.swift следующее.

Большая часть этого должна быть знакома с предыдущего шага, поэтому я не буду перефразировать его здесь. Однако есть две отличия. Один из них заключается в том, что usePreciseCollsionDetection имеет значение false, которое является значением по умолчанию. Важно понимать, что только одно из контактирующих органов требует, чтобы это свойство было true (это была пуля). Другое отличие состоит в том, что мы также хотим знать, когда игрок связывается с захватчиком. Вы можете иметь более одной категории contactBitMask, разделяя их с помощью побитового или (|) оператора. Кроме этого, вы должны заметить, что это просто в основном противоположно InvaderBullet.

6. Invader and PlayerBullet столкновение

Шаг 1. Настройка Invader для столкновения

Добавьте метод init в Invader.swift.

Это должно иметь смысл, если вы следовали. Мы создали physicsBodycategoryBitMask и contactBitMask.

Шаг 2: Настройка PlayerBullet для столкновения

Добавьте в init(imageName:bulletSound:) в PlayerBullet.swift следующее. Опять же, реализация должна быть в настоящем времени.

7. Настройка физики для GameScene

Шаг 1. Настройка физического мира

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

Затем мы должны создать некоторые свойства на physicsWorld сцены. Введите в верхней части метода didMoveToView(_:) в GameScene.swift следующее.

Мы устанавливаем gravity свойство physicsWorld  равным 0, так что ни одно физическое тело на сцене не влияет на гравитацию. Вы также можете сделать это на основе тела, вместо того, чтобы заставить весь мир не иметь силы тяжести, установив свойство affectedByGravity. Мы также установили свойство contactDelegate в физическом мире для self- экземпляра GameScene.

Шаг 2: Внедрение SKPhysicsContactDelegate протокола

Чтобы соответствовать классу GameScene протоколу SKPhysicsContactDelegate, нам необходимо реализовать метод didBeginContact(_:). Этот метод вызывается при контакте двух тел. Реализация метода didBeginContact(_:) выглядит так.

Сначала объявляем две переменные firstBody и secondBody. Когда два объекта контактируют, мы не знаем, какое именно тело есть. Это означает, что сначала нам нужно сделать некоторые проверки, чтобы убедиться, что firstBody - это тот, у которого нижняя categoryBitMask.

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

Заключение

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





Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.