「たった1日で基本が身に付く! Swift アプリ開発 超入門」の感想・備忘録4

スポンサーリンク
「たった1日で基本が身に付く! Swift アプリ開発 超入門」の感想・忘備録3のつづき

シューティングゲームのソース(完成版)

import SpriteKit
import GameplayKit
import CoreMotion

class GameScene: SKScene, SKPhysicsContactDelegate {
    var vc: GameViewController!
    
    private var label : SKLabelNode?
    private var spinnyNode : SKShapeNode?
    
    var node1 = SKSpriteNode()
    var enemyRate : CGFloat = 0.0  // 敵の表示倍率用変数の追加
    var enemySize = CGSize(width: 0.0, height: 0.0)  // 敵の表示サイズ用変数の追加
    var timer: Timer?

    let motionMgr = CMMotionManager()
    var accelarationX: CGFloat = 0.0
    
    var lifeLabelNode = SKLabelNode()   // LIFE表示用ラベル
    var scoreLabelNode = SKLabelNode()   // SCORE表示用ラベル
    // LIFE用プロパティ
    var life : Int = 0 {
        didSet {
            self.lifeLabelNode.text = "LIFE : \(life)"
        }
    }
    // SCORE用プロパティ
    var score : Int = 0 {
        didSet {
            self.scoreLabelNode.text = "SCORE : \(score)"
        }
    }
    
    // カテゴリビットマスクの定義
    let myCategory : UInt32 = 0b0001
    let missileCategory : UInt32 = 0b0010
    let enemyCategory : UInt32 = 0b0100
    
    override func didMove(to view: SKView) {
        var sizeRate : CGFloat = 0.0
        var node1Size = CGSize(width: 0.0, height: 0.0)
        let offsetY = frame.height / 20
        // 画面への重力設定
        physicsWorld.gravity = CGVector(dx: 0, dy: 0)
        // 以下のコードを追加することでdidBeginメソッドをGameSceneクラス内で呼び出せるようになる
        physicsWorld.contactDelegate = self
        
        // 画像ファイルの読み込み
        self.node1 = SKSpriteNode(imageNamed: "plane")
        // 自機を幅の1/5にするための倍率を求める
        sizeRate = (frame.width / 5) / self.node1.size.width
        // 自機のサイズを計算する
        node1Size = CGSize(width: self.node1.size.width * sizeRate,
                            height: self.node1.size.height * sizeRate)
        // 自機のサイズを設定する
        self.node1.scale(to: node1Size)
        // 自機の表示位置を設定する
        self.node1.position = CGPoint(x: 0, y: (-frame.height / 2) + offsetY + node1Size.height / 2)
        
        // 自機への物理ボディ、カテゴリビットマスク、衝突ビットマスクの設定
        self.node1.physicsBody = SKPhysicsBody(rectangleOf: self.node1.size)
        self.node1.physicsBody?.categoryBitMask = self.myCategory
        self.node1.physicsBody?.collisionBitMask = self.enemyCategory
        self.node1.physicsBody?.contactTestBitMask = self.enemyCategory
        self.node1.physicsBody?.isDynamic = true
        
        // シーンに自機を追加(表示)する
        addChild(self.node1)
        
        // 敵の画像ファイルの読み込み
        let tempEnemy = SKSpriteNode(imageNamed: "enemy1")
        // 敵を幅の1/5にするための倍率を求める
        enemyRate = (frame.width / 10) / tempEnemy.size.width
        // 敵のサイズを計算する
        enemySize = CGSize(width: tempEnemy.size.width * enemyRate, height: tempEnemy.size.height * enemyRate)

        // 敵を表示するメソッドmoveEnemyを1秒ごとに呼び出し
        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { _ in
            self.moveEnemy()
        })
        
        // 加速度センサーの取得間隔を設定取得処理
        motionMgr.accelerometerUpdateInterval = 0.05
        // 加速度センサーの変更値取得
        motionMgr.startAccelerometerUpdates(to: OperationQueue.current!) { (val, _) in
            guard let unwrapVal = val else {
                return
            }
            let acc = unwrapVal.acceleration
            self.accelarationX = CGFloat(acc.x)
        }

        // ライフの作成
        self.life = 3
        self.lifeLabelNode.fontName = "HelveticaNeue-Bold"
        self.lifeLabelNode.fontColor = UIColor.white
        self.lifeLabelNode.fontSize = 30
        self.lifeLabelNode.position = CGPoint(
            x: frame.width / 2  - (self.lifeLabelNode.frame.width + 20),
            y: frame.height / 2 - self.lifeLabelNode.frame.height * 3)
        addChild(self.lifeLabelNode)

        // スコアの表示
        self.score = 0
        self.scoreLabelNode.fontName = "HelveticaNeue-Bold"
        self.scoreLabelNode.fontColor = UIColor.white
        self.scoreLabelNode.fontSize = 30
        self.scoreLabelNode.position = CGPoint(
            x: -frame.width / 2  + self.scoreLabelNode.frame.width ,
            y: frame.height / 2 - self.scoreLabelNode.frame.height * 3)
        addChild(self.scoreLabelNode)
    }
    /// シーンの更新
    override func didSimulatePhysics() {
        let pos = self.node1.position.x + self.accelarationX * 30
        if pos > frame.width / 2 - self.node1.frame.width / 2 { return }
        if pos < -frame.width / 2 + self.node1.frame.width / 2 { return }
        self.node1.position.x = pos
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // 画像ファイルの読み込み
        let missile = SKSpriteNode(imageNamed: "missile")
        // ミサイルの発射位置の作成
        let missilePos = CGPoint(x: self.node1.position.x,
                                 y: self.node1.position.y +
                                    (self.node1.size.height / 2) -
                                    (missile.size.height / 2))
        // ミサイル発射位置の設定
        missile.position = missilePos
        
        // ミサイルの物理ボディ、カテゴリビットマスク、衝突ビットマスクの設定
        missile.physicsBody = SKPhysicsBody(rectangleOf: missile.size)
        missile.physicsBody?.categoryBitMask = self.missileCategory
        missile.physicsBody?.collisionBitMask = self.enemyCategory
        missile.physicsBody?.contactTestBitMask = self.enemyCategory
        missile.physicsBody?.isDynamic = true
        
        // シーンにミサイルを表示する
        addChild(missile)
        
        // 指定した位置まで0.5秒で移動する
        let move = SKAction.moveTo(y: frame.height / 2 - missile.size.height, duration: 0.5)
        // 親からノードを削除する
        let remove = SKAction.removeFromParent()
        // アクションを連続して実行する
        missile.run(SKAction.sequence([move, remove]))
    }
    
    /// 敵を表示するメソッド
    func moveEnemy() {
        let enemyNames = ["enemy1", "enemy2", "enemy3"]
        let idx = Int.random(in: 0 ..< 3)
        let selectedEnemy = enemyNames[idx]
        let enemy = SKSpriteNode(imageNamed: selectedEnemy)
        
        // 敵のサイズを設定する
        enemy.scale(to: enemySize)
        // 敵のx方向の位置を生成する
        let xPos = (frame.width / CGFloat.random(in: 1...5)) - frame.width / 2
        // 敵の位置を設定する
        enemy.position = CGPoint(x: xPos, y: frame.height / 2)
        
        // 敵への物理ボディ、カテゴリビットマスクの設定
        enemy.physicsBody = SKPhysicsBody(rectangleOf: enemy.size)
        enemy.physicsBody?.categoryBitMask = self.enemyCategory
        enemy.physicsBody?.isDynamic = true
        
        // シーンに敵を表示する
        addChild(enemy)
        
        // 指定した位置まで2.0秒で移動させる
        let move = SKAction.moveTo(y: -frame.height / 2, duration: 2.0)
        // 親からノードを削除する
        let remove = SKAction.removeFromParent()
        // アクションを連続して実行する
        enemy.run(SKAction.sequence([move, remove]))
    }
    
    func didBegin(_ contact: SKPhysicsContact) {
        // 炎のパーティクルの読み込みと表示
        let explosion = SKEmitterNode(fileNamed: "explosion")
        explosion?.position = contact.bodyA.node?.position ?? CGPoint(x: 0, y: 0)
        addChild(explosion!)
        
        // 炎のパーティクルアニメーションを0.5秒表示して削除
        self.run(SKAction.wait(forDuration: 0.5)) {
            explosion?.removeFromParent()
        }
        
        // ミサイルが敵に当たった時の処理
        if contact.bodyA.categoryBitMask == missileCategory ||
            contact.bodyB.categoryBitMask == missileCategory {
            self.score += 10
        }

        // 自機が爆発した時の処理
        if contact.bodyA.categoryBitMask == myCategory ||
            contact.bodyB.categoryBitMask == myCategory {
            // ライフを1つ減らす
            self.life -= 1
            
            // 1秒後に restart を実行
            self.run(SKAction.wait(forDuration: 1)) {
                self.restart()
            }
        }
        
        // 衝突したノードを削除する
        contact.bodyA.node?.removeFromParent()
        contact.bodyB.node?.removeFromParent()
    }
    // リスタート処理
    func restart() {
        // ライフが0以下の場合
        if self.life <= 0 {
            for child in self.children {
                child.isPaused = true
            }
            self.timer?.invalidate()
            // START画面に戻る
            self.run(SKAction.wait(forDuration: 1)) {
                self.vc.dismiss(animated: true, completion: nil)
            }
        }
        else {
            // ライフが1以上なら自機を再表示
            addChild(self.node1)
        }
    }
}
import UIKit
import SpriteKit
import GameplayKit

class GameViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        if let view = self.view as! SKView? {
            // Load the SKScene from 'GameScene.sks'
            if let scene = SKScene(fileNamed: "GameScene") {
                // Set the scale mode to scale to fit the window
                scene.scaleMode = .aspectFill
                
                (scene as! GameScene).vc = self
                
                // Present the scene
                view.presentScene(scene)
            }
            
            view.ignoresSiblingOrder = true
            
            view.showsFPS = true
            view.showsNodeCount = true
        }
    }

    override var shouldAutorotate: Bool {
        return true
    }

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        if UIDevice.current.userInterfaceIdiom == .phone {
            return .allButUpsideDown
        } else {
            return .all
        }
    }

    override var prefersStatusBarHidden: Bool {
        return true
    }
}

コメント