Swift 遊戲開發之「方塊彈珠」(四)

前言

在上一篇文章中,咱們已經基本實現了「方塊彈珠」小遊戲的核心邏輯。在這篇文章中,咱們主要關注在調整遊戲 UI,讓遊戲具有基本可玩的階段。node

控制小球發射

以前咱們只是經過對小球的 physicsBody 經過調用 applyForce 設置一個初始力,讓小球在這個「初始力」的做用下進行運動,咱們已經寫死了對小球發射方向的設置,如今,咱們要對其改形成根據用戶手指在屏幕上滑動的方向進行運動。咱們先對小球進行初始化歸位,使其當用戶觸摸事件結束後再進行發射。git

class GameScene: SKScene {
    
    private var balls = [Ball]()
    
    // ...
    
    private func createContent() {
        // ...
        
        for _ in 0..<5 {
            // ...
            balls.append(ball)
            ball.position = CGPoint(x: size.width / 2, y: ground.frame.size.height + ball.frame.size.height / 2)
            // ...
        }
    // ...
}

extension GameScene {
    private func shot() {
        for (index, ball) in balls.enumerated() {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1 * Double(index)) {
                ball.physicsBody?.applyForce(CGVector(dx: 400 + CGFloat(index) * 0.1, dy: 800))
            }
        }
    }
}

extension GameScene {
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        shot()
    }
}
複製代碼

此時,運行工程,小球將出如今地面的中心位置上,而且只有當用戶的觸摸事件結束後纔會進行「發射」。須要注意的是,咱們對每個小球都加上了時延,第二個小球發射出去的前提是第一個小球已經發射出去了,第三個小球發射出去的前提是第二個小球已經發射出去了,以此類推,咱們只須要在以前統一發射的 for 循環中增長一個時延方法來控制每個小球發射出去的時機便可。github

接着,咱們來完成當小球和地面進行接觸時,把撞擊地面的小球都進行歸位。不過這裏須要注意的是,SpriteKit 爲了提升渲染速度,推薦咱們把一段時間內不須要進行繪製的節點進行移除,注意是移除不是刪除,若是咱們不這麼作,那麼這些已經被「隱藏」起來的節點一樣會被歸入 SpriteKit 的物理計算中,從而拖慢咱們的遊戲系統的響應速度。編程

所以,咱們從新修改下底部地面的與碰撞相關的三個枚舉值。swift

struct BitMask {
    // ...
    static let Ground = UInt32(0x00004)
}


// ...

private func createContent() {
    // ...
    ground.physicsBody?.collisionBitMask = BitMask.Ball
    ground.physicsBody?.categoryBitMask = BitMask.Ground
    ground.physicsBody?.contactTestBitMask = BitMask.Ball
}

// ...
複製代碼

並新增小球對地面的碰撞檢測方法。markdown

extension GameScene: SKPhysicsContactDelegate {
    func didBegin(_ contact: SKPhysicsContact) {
        
        switch contact.bodyA.categoryBitMask {
        // ...
        case BitMask.Ground:
            checkNodeIsGround(contact.bodyB.node)
        default:
            break
        }
        
        switch contact.bodyB.categoryBitMask {
        // ...
        case BitMask.Ground:
            checkNodeIsGround(contact.bodyA.node)
        default:
            break
        }
    }
}

extension GameScene {
    // ...
    private func checkNodeIsGround(_ node: SKNode?) {
        guard let ball = node as? Ball else { return }
        
        if (ball.physicsBody?.categoryBitMask == BitMask.Ball) {
            ball.removeFromParent();
        }
    }
}
複製代碼

此時運行工程,發現當全部小球到了底部後都被自動從當前 Scene 中移除了,爲了提升可玩度,咱們給第一個下落的小球加上標記,告訴用戶這是你第一個觸底小球的位置,下一次再發射小球時,將會從這個位置從新出發。app

咱們須要一個定位小球變量,用於標記第一個小球到底地面的位置。async

private func checkNodeIsGround(_ node: SKNode?) {
    guard let ball = node as? Ball else { return }
    
    // NOTE: 小球 & 發射出去
    if (ball.physicsBody?.categoryBitMask == BitMask.Ball && ball.isShot) {
        ball.removeFromParent();
        
        
        if (firstDownBall == nil || !children.contains(firstDownBall!)) {
            firstDownBall = Ball(circleOfRadius: 10)
            firstDownBall!.position = CGPoint(x: ball.position.x, y: ground.frame.size.height + ball.frame.size.height / 2 - 2)
            addChild(firstDownBall!)
            firstDownBall!.physicsBody?.isDynamic = false
        }
            
        ball.position = CGPoint(x: firstDownBall!.position.x, y: ground.frame.size.height + ball.frame.size.height / 2)
    }
}
複製代碼

這裏須要注意的是,當定位小球已經建立出來時,咱們須要把後續觸底的小球二次發射的初始位置均設置爲一致值,不然二次發射時,各個小球都會從當前觸底位置直接發射,並不理會定位小球所定位的位置。ide

其中,爲了縮減每次建立 Ball 的代碼量,能夠重寫 init 方法。oop

class Ball: SKShapeNode {
    var isShot = false
    
    
    override init() {
        super.init()
        
        initObject()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func initObject() {
        self.fillColor = .red
        self.physicsBody = SKPhysicsBody(circleOfRadius: 10)
        self.physicsBody?.categoryBitMask = BitMask.Ball
        self.physicsBody?.contactTestBitMask = BitMask.Box | BitMask.Ground
        self.physicsBody?.collisionBitMask = BitMask.Box
        self.physicsBody?.usesPreciseCollisionDetection = true;
        self.physicsBody?.linearDamping = 0
        self.physicsBody?.restitution = 1.0
    }
}
複製代碼

總結

至此!咱們已經完成了「方塊彈珠」的所有核心邏輯。固然這是核心邏輯,換句話說,你也能夠認爲這就是一般所說的 demo,能夠拿去融資的 demo,固然,咱們的這個小遊戲是確定不行的了,只是按照同等實現的功能映射到其餘的產品層面。

因此,從下篇文章開始,咱們將結合該系列的第一篇文章裏說闡述的遊戲背景,去完善並拓展咱們的「方塊彈珠」。

咱們如今完成的內容有:

  • 遊戲講解;
  • 熟悉 2D 編程;
  • 剛體碰撞與檢測;
  • 小球的發射與方塊的消除;
  • 遊戲邏輯完善。

GitHub 地址: github.com/windstormey…

相關文章
相關標籤/搜索