Статьи

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

Конечный продукт
Что вы будете создавать

В первой части этой серии мы изучили основы инфраструктуры Sprite Kit и реализовали начальный экран игры. В этом уроке мы реализуем основные классы игры.

Swift является объектно-ориентированным языком, и мы воспользуемся этим, разделив все игровые объекты на их собственные классы. Мы начнем с реализации класса 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 позже в этом уроке.

На этом этапе мы добавим захватчиков в 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 захватчиков, выровненных по центру сцены.

Создайте новый класс касания какао с именем 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 .

Другие методы будут реализованы позже в этом руководстве.

Объявите постоянное свойство с именем 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()
}

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

Создайте новый класс касания какао с именем 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 , то действие длилось бы столько, сколько длился звуковой файл.

Создайте новый класс касания Какао с именем 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:) . Однако будет гораздо больше смысла, почему он настроен таким образом, когда мы добавим код для обнаружения столкновений.

Создайте новый класс касания какао с именем 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)
    }
     
}

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