В первой части этой серии мы изучили основы инфраструктуры Sprite Kit и реализовали начальный экран игры. В этом уроке мы реализуем основные классы игры.
Swift является объектно-ориентированным языком, и мы воспользуемся этим, разделив все игровые объекты на их собственные классы. Мы начнем с реализации класса Invader
.
1. Реализация класса Invader
Шаг 1: Создайте класс Invader
Выберите « Создать» > « Файл …» в меню « Файл» Xcode, выберите « Какао-класс касания» в разделе « iOS»> «Источник » и нажмите « Далее» . Назовите класс Invader
и убедитесь, что он наследует от SKSpriteNode
. Убедитесь, что для языка установлено значение Swift . Введите следующий код в Invader.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
|
import UIKit
import SpriteKit
class Invader: SKSpriteNode {
var invaderRow = 0
var invaderColumn = 0
init() {
let texture = SKTexture(imageNamed: «invader1»)
super.init(texture: texture, color: SKColor.clearColor(), size: texture.size())
self.name = «invader»
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func fireBullet(scene: SKScene){
}
}
|
Класс Invader
является подклассом SKSpriteNode
учебный класс. Он имеет два свойства, invaderRow
и invaderColumn
. Захватчики выровнены по сетке, как в оригинальной игре Space Invaders . Эти два свойства дают нам простой способ отслеживать, в какой строке и столбце находится захватчик.
В init
метод, мы инициализируем экземпляр SKTexture
. Метод init(imageNamed:)
принимает изображение в качестве параметра. Затем мы вызываем инициализатор суперкласса, передавая texture
SKColor.clearColor
для параметра color
, а для параметра size
мы передаем размер текстуры. Наконец, мы устанавливаем имя "invader"
чтобы мы могли идентифицировать его позже.
Метод init
является назначенным инициализатором, что означает, что нам нужно делегировать инициализацию до назначенного инициализатора суперкласса Invader
. Вот почему мы вызываем метод init(texture:color:size:)
.
Вы можете быть удивлены, почему там есть необходимый метод init(coder:)
. SKSpriteNode
соответствует протоколу NSCoding
. Метод init(coder:)
помечается как обязательный, что означает, что каждый подкласс должен переопределить этот метод.
Мы реализуем метод fireBullet
позже в этом уроке.
Шаг 2: добавь захватчиков к сцене
На этом этапе мы добавим захватчиков в GameScene
. Откройте GameScene.swift и удалите все внутри didMoveToView(_:)
а также все внутри метода touchesBegan(_:withEvent:)
. Содержимое GameScene.swift теперь должно выглядеть следующим образом.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
|
В нашем проекте будет одна глобальная переменная invaderNum
. Эта переменная используется для отслеживания текущего уровня игры. Объявляя его как глобальную переменную, мы получаем доступ к invaderNum
разных сценах. Чтобы объявить переменную как глобальную переменную, мы объявляем ее вне класса GameScene
.
1
2
3
4
5
|
import SpriteKit
var invaderNum = 1
class GameScene: SKScene {
…
|
Затем добавьте следующие свойства в класс GameScene
.
01
02
03
04
05
06
07
08
09
10
|
class GameScene: SKScene {
let rowsOfInvaders = 4
var invaderSpeed = 2
let leftBounds = CGFloat(30)
var rightBounds = CGFloat(0)
var invadersWhoCanFire:[Invader] = []
override func didMoveToView(view: SKView) {
}
|
Свойство rowsOfInvaders
определяет количество строк захватчиков в игре, а свойство invaderSpeed
определяет скорость перемещения захватчиков. leftBounds
и rightBounds
используются для создания поля с левой и правой стороны экрана, ограничивая движение захватчиков в левом и правом направлениях. И, наконец, свойство invadersWhoCanFire — это массив, который используется для отслеживания того, какие захватчики могут запустить пулю.
Добавьте метод setupInvaders
ниже метода update(currentTime:)
в классе GameScene
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
func setupInvaders(){
var invaderRow = 0;
var invaderColumn = 0;
let numberOfInvaders = invaderNum * 2 + 1
for var i = 1;
invaderRow = i
for var j = 1;
invaderColumn = j
let tempInvader:Invader = Invader()
let invaderHalfWidth:CGFloat = tempInvader.size.width/2
let xPositionStart:CGFloat = size.width/2 — invaderHalfWidth — (CGFloat(invaderNum) * tempInvader.size.width) + CGFloat(10)
tempInvader.position = CGPoint(x:xPositionStart + ((tempInvader.size.width+CGFloat(10))*(CGFloat(j-1))), y:CGFloat(self.size.height — CGFloat(i) * 46))
tempInvader.invaderRow = invaderRow
tempInvader.invaderColumn = invaderColumn
addChild(tempInvader)
if(i == rowsOfInvaders){
invadersWhoCanFire.append(tempInvader)
}
}
}
}
|
У нас invaderRow
переменные invaderRow
и invaderColumn
которые будут использоваться для установки свойств одного и того же имени в invader. Далее мы используем двойной цикл for
чтобы выложить захватчиков на экран. Происходит много преобразования числовых типов, потому что swift неявно преобразует числа в соответствующий тип. Мы должны сделать это сами.
Сначала мы создаем новый Invader
, tempInvader
, а затем объявляем константу invaderHalfWidth
которая в два раза меньше tempInvader
.
Далее мы вычисляем xPositionStart
чтобы захватчики всегда были выровнены по середине сцены. Мы получаем половину ширины сцены и вычитаем половину ширины захватчика, поскольку точкой регистрации по умолчанию является центр (0,5, 0,5) спрайта. Затем мы должны вычесть ширину времен invaderNum
сколько бы invaderNum
равно, и добавить 10 к этому значению, поскольку между захватчиками есть 10 точек пространства. Сначала это может быть немного трудно понять, поэтому не торопитесь, чтобы понять это.
Затем мы устанавливаем свойство position
invader
, которое является GGPoint
. Мы используем немного больше математики, чтобы убедиться, что у каждого захватчика есть 10 точек между ними и что у каждой строки есть 46 точек между ними.
Мы присваиваем свойства invaderRow
и invaderColumn
и добавляем tempInvader
в сцену с помощью метода addChild(_:)
. Если это последний ряд захватчиков, мы помещаем tempInvader
в массив tempInvader
.
Метод setupInvaders
вызывается в didMoveToView(_:)
. В этом методе мы также устанавливаем для свойства backgroundColor
значение SKColor.blackColor
.
1
2
3
4
|
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.blackColor()
setupInvaders()
}
|
Если вы тестируете приложение, вы должны увидеть 4 строки по 3 захватчика. Если вы установите invaderNum
2 , вы должны увидеть 4 строки по 5 захватчиков, выровненных по центру сцены.
2. Реализация класса Player
Шаг 1: Создать класс Player
Создайте новый класс касания какао с именем Player
это подкласс SKSpriteNode
. Добавьте следующую реализацию в Player.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
|
import UIKit
import SpriteKit
class Player: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: «player1»)
super.init(texture: texture, color: SKColor.clearColor(), size: texture.size())
animate()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
private func animate(){
var playerTextures:[SKTexture] = []
for i in 1…2 {
playerTextures.append(SKTexture(imageNamed: «player\(i)»))
}
let playerAnimation = SKAction.repeatActionForever( SKAction.animateWithTextures(playerTextures, timePerFrame: 0.1))
self.runAction(playerAnimation)
}
func die (){
}
func kill(){
}
func respawn(){
}
func fireBullet(scene: SKScene){
}
}
|
Метод init
должен выглядеть знакомым. Разница лишь в том, что мы используем другое изображение для начальной настройки. В папке с изображениями есть два изображения с именами player1 и player2 , у одного включено подруливающее устройство, а у другого отключено подруливающее устройство. Мы будем постоянно переключаться между этими двумя изображениями, создавая иллюзию включения и выключения двигателя. Это то, что делает метод animate
.
В методе playerTextures
у нас есть массив playerTextures
который будет содержать текстуры для анимации. Мы добавляем объекты SKTexture
в этот массив, используя for-in
цикл и закрытый диапазон, используя оператор закрытого диапазона. Мы используем интерполяцию строк, чтобы получить правильное изображение и инициализировать экземпляр SKTexture
.
Мы объявляем константу playerAnimation
, которая вызывает метод SKAction
класса SKAction
. В этом действии мы вызываем animateWithTextures(_:timePerFrame:)
. Метод animateWithTextures(_:timePerFrame:)
принимает в качестве параметров массив текстур и количество времени, в течение которого отображается каждая текстура. Наконец, мы вызываем runAction(_:)
и передаем playerAnimation
.
Другие методы будут реализованы позже в этом руководстве.
Шаг 2: Добавление игрока в сцену
Объявите постоянное свойство с именем player
для класса GameScene
.
1
2
3
4
|
class GameScene: SKScene {
…
var invadersWhoCanFire:[Invader] = [Invader]()
let player:Player = Player()
|
Затем добавьте метод setupPlayer
ниже метода setupInvaders
.
1
2
3
4
|
func setupPlayer(){
player.position = CGPoint(x:CGRectGetMidX(self.frame), y:player.size.height/2 + 10)
addChild(player)
}
|
Вы должны быть знакомы с реализацией метода setupPlayer
. Мы устанавливаем положение player
и добавляем его на сцену. Однако мы используем новую функцию CGRectGetMidX(_:)
, которая возвращает центр прямоугольника вдоль оси x. Здесь мы используем кадр scene
.
Теперь вы можете вызвать метод setupPlayer
методе didMoveToView(_:)
.
1
2
3
4
5
|
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.blackColor()
setupInvaders()
setupPlayer()
}
|
Если вы протестируете приложение, вы увидите, что игрок добавлен в нижней части экрана с включенными движками и стрельбой.
3. Реализация классов Bullet
Шаг 1: Создайте класс Bullet
Создайте новый класс касания какао с именем Bullet
который является подклассом класса SKSpriteNode
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
import UIKit
import SpriteKit
class Bullet: SKSpriteNode {
init(imageName: String, bulletSound: String?) {
let texture = SKTexture(imageNamed: imageName)
super.init(texture: texture, color: SKColor.clearColor(), size: texture.size())
if(bulletSound != nil){
runAction(SKAction.playSoundFileNamed(bulletSound!, waitForCompletion: false))
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
|
Метод init
принимает два параметра, imageName
и bulletSound
. Второй параметр является необязательным. Плеер будет воспроизводить лазерный звук каждый раз, когда стреляет пуля. У меня нет захватчиков, делающих это в этой игре, хотя вы наверняка могли бы. Это также причина, по которой звук пули является необязательным параметром. Вы даже можете использовать разные звуки для каждого.
Первая часть должна быть знакома, хотя мы сейчас создаем texture
с тем изображением, которое было передано в качестве первого аргумента. Это позволит вам использовать разные изображения для пуль игрока и захватчиков, если вы захотите.
Если значение bulletSound
не равно nil
, мы запускаем метод playSoundFileNamed(_:waitForCompletion:)
. Этот метод принимает в качестве параметров String
, которая является именем звукового файла, включая расширение, и Bool
, waitForCompletion
. Параметр waitForCompletion
для нас не важен. Если бы он был установлен в true
, то действие длилось бы столько, сколько длился звуковой файл.
Шаг 2. Создайте класс InvaderBullet
Создайте новый класс касания Какао с именем InvaderBullet
который является подклассом класса Bullet
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
import UIKit
import SpriteKit
class InvaderBullet: Bullet {
override init(imageName: String, bulletSound:String?){
super.init(imageName: imageName, bulletSound: bulletSound)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
|
Реализация класса InvaderBullet
может не иметь особого смысла, потому что мы вызываем только метод init(imageName:bulletSound:)
суперкласса в init(imageName:bulletSound:)
. Однако будет гораздо больше смысла, почему он настроен таким образом, когда мы добавим код для обнаружения столкновений.
Шаг 3: Создайте класс PlayerBullet
Создайте новый класс касания какао с именем PlayerBullet
который также является подклассом класса Bullet
. Как видите, реализация класса PlayerBullet
идентична InvaderBullet
класса InvaderBullet
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
import UIKit
import SpriteKit
class PlayerBullet: Bullet {
override init(imageName: String, bulletSound:String?){
super.init(imageName: imageName, bulletSound: bulletSound)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
|
Вывод
В этом уроке мы создали и реализовали некоторые ключевые классы игры. Мы добавили сетку захватчиков на сцену и космический корабль, которым будет управлять игрок. Мы продолжим работу с этими классами в следующей части этой серии, в которой мы реализуем игровой процесс.