Swiftでゲームを作る Spritekit

Swiftでゲームを作る
Swiftでゲーム作る場合、2Dゲームであれば、
Spritekitを使うのが一般的だと思います。
プロジェクト作成から作っていきます。
Xcode Version 14.3
![スクリーンショット 2023-07-28 9.51.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143799/dc73cd7e-a0cb-6737-9cad-b6f7deedb62f.png)
上のメニューバーからFile→New→Projectを選び
![スクリーンショット 2023-07-28 9.55.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143799/54006145-b5d7-4122-daff-0745beb2e156.png)
iosタブのAppを選択しNextボタンを押す。
![スクリーンショット 2023-07-28 9.59.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143799/48f77fa4-d157-2eca-0fdd-b51749df9141.png)
Project Nameでプロジェクト名を入力し（今回はblock breakerと入力しました。）
Nextボタンを押して保存先を決めます。

下準備、
いらない物を消す。
左のファイル一覧にある
SceneDelegate.swift
を削除。
右クリック→delete→Move To trash(ゴミ箱に移動）
同じファイル一覧にある
info（Info.plist）
Application Scene Manifest
を選択しバックスペースで削除。

ファイルを編集していきます。
ViewController.swiftのファイル名をGameViewController.swiftに変更します。
```Swift:GameViewController.swift
import UIKit
import SpriteKit

class GameViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let scene = GameScene(size: view.bounds.size)
        let skView = view as! SKView
        skView.showsFPS = true
        skView.showsNodeCount = true
        skView.showsPhysics = true
        skView.ignoresSiblingOrder = true
        scene.scaleMode = .resizeFill
        skView.presentScene(scene)
    }
 
}
```
AppDelegate.swift
AppDelegateも編集します。

```Swift:AppDelegate.swift
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window:UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
           self.window = UIWindow(frame: UIScreen.main.bounds)
           window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
           window?.makeKeyAndVisible()
           return true
       }
}
```

ストーリーボードの編集
左のファイル一覧にあるMain(Main.storyboard)を選択し
![スクリーンショット 2023-07-28 10.39.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143799/cf9bacd5-42c1-dbf9-d166-22ed16682300.png)
画像で丸した所をクリックしてclass欄のViewControllerからGameViewControllerへ変更する。

続いてViewの所画像の順番にクリックしSKViewに変更する。
![スクリーンショット 2023-07-28 10.44.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143799/d7ff72d5-5953-f881-0fe9-23c76b29f4e6.png)

ここまで下準備。
ゲームシーンを作成。
わかりやすくするためにシーンファイルを置くフォルダを作る。
![スクリーンショット 2023-07-28 10.55.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143799/d1073ede-10d7-249e-d02e-0e219d66583e.png)
半角でgameと入力し右クリックしてNew Fileを選択
![スクリーンショット 2023-07-28 11.00.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143799/903f0621-aba5-eda2-f481-40d2bec56bfa.png)
Swift Fileを選択しNext
Save As:にGameScene.swiftと入力しCreateボタンを押す。

動作テストしてみる。ブロック崩しサンプル
```Swift:GameScene.swift
import SpriteKit

class GameScene: SKScene, SKPhysicsContactDelegate {
    
    private var ball: SKShapeNode!
    private var paddle: SKSpriteNode!
    private var blocks = [SKSpriteNode]()
    
    private let ballCategory: UInt32 = 0x1 << 0
    private let paddleCategory: UInt32 = 0x1 << 1
    private let blockCategory: UInt32 = 0x1 << 2
    private let edgeCategory: UInt32 = 0x1 << 3
    
    override func didMove(to view: SKView) {
        physicsWorld.gravity = CGVector(dx: 0, dy: 0)
        physicsWorld.contactDelegate = self
        createBall()
        createPaddle()
        createBlocks()
        createPhysicsBoundaries()
        // ボールに初速を与える
    let dx: CGFloat = 200.0 // x方向の初速度
    let dy: CGFloat = 400.0 // y方向の初速度
        ball.physicsBody?.velocity = CGVector(dx: dx, dy: dy)
    }
    
    func createBall() {
        ball = SKShapeNode(circleOfRadius: 10)
        ball.fillColor = UIColor.red
        ball.position = CGPoint(x: frame.midX, y: frame.midY)
        ball.physicsBody = SKPhysicsBody(circleOfRadius: 10)
        ball.physicsBody?.categoryBitMask = ballCategory
        ball.physicsBody?.contactTestBitMask = blockCategory
        ball.physicsBody?.collisionBitMask = blockCategory | paddleCategory | edgeCategory
        ball.physicsBody?.isDynamic = true
        addChild(ball)
    }
    
    func createPaddle() {
        paddle = SKSpriteNode(color: UIColor.blue, size: CGSize(width: 100, height: 20))
        paddle.position = CGPoint(x: frame.midX, y: frame.minY + 100)
        paddle.physicsBody = SKPhysicsBody(rectangleOf: paddle.size)
        paddle.physicsBody?.categoryBitMask = paddleCategory
        paddle.physicsBody?.restitution = 1.0
        paddle.physicsBody?.collisionBitMask = ballCategory
        paddle.physicsBody?.isDynamic = false
        addChild(paddle)
    }
    
    func createBlocks() {
        let numRows = 3
        let numCols = 5
        let blockWidth = frame.width / CGFloat(numCols)
        let blockHeight: CGFloat = 20
        
        for row in 0..<numRows {
            for col in 0..<numCols {
            let block = SKSpriteNode(color: UIColor.green, size: CGSize(width: blockWidth, height: blockHeight))
                block.position = CGPoint(x: blockWidth * CGFloat(col) + blockWidth / 2, y: frame.height - CGFloat(row) * blockHeight - blockHeight / 2)
                block.physicsBody = SKPhysicsBody(rectangleOf: block.size)
                block.physicsBody?.categoryBitMask = blockCategory
                block.physicsBody?.collisionBitMask = ballCategory
                block.physicsBody?.isDynamic = false
                blocks.append(block)
                addChild(block)
            }
        }
    }
    
   override func update(_ currentTime: TimeInterval) {
           // ボールの速度を一定に保つことで停止させない
           let minSpeed: CGFloat = 300.0
           let speed = sqrt(ball.physicsBody!.velocity.dx * ball.physicsBody!.velocity.dx + ball.physicsBody!.velocity.dy * ball.physicsBody!.velocity.dy)
           if speed < minSpeed {
               let scale = minSpeed / speed
               ball.physicsBody?.velocity.dx *= scale
               ball.physicsBody?.velocity.dy *= scale
           }
       }
   
   func createPhysicsBoundaries() {
    let edge = SKNode()
        edge.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
        edge.physicsBody?.friction = 0.0
        edge.physicsBody?.restitution = 1.0
        edge.physicsBody?.categoryBitMask = edgeCategory
        addChild(edge)
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        for touch in touches {
            let location = touch.location(in: self)
            paddle.position.x = location.x
        }
    }
    
    func didBegin(_ contact: SKPhysicsContact) {
        if contact.bodyA.categoryBitMask == blockCategory || contact.bodyB.categoryBitMask == blockCategory {
            let blockNode = contact.bodyA.categoryBitMask == blockCategory ? contact.bodyA.node : contact.bodyB.node
            blockNode?.removeFromParent()
            if let index = blocks.firstIndex(of: blockNode as! SKSpriteNode) {
                blocks.remove(at: index)
            }
        }
    }
}
```