SpriteKitのテンプレート for SwitfUI

# はじめに

約4年前に書いた「[【備忘録】SpriteKitのテンプレート](https://qiita.com/nak435/items/dac3c93af95ccdef725f)」は、Storyboardを使っていましたが、最近はSwiftUIを使う機会が増えたので、今回は、SwiftUI向けの「SpriteKitのテンプレート」をまとめておきます。

#### この記事を執筆したときの環境は以下の通り

- macOS : 13.4.1 (Ventura)
- Xcode : 14.3.1
- Swift : 5.8.1
- SwiftUI : 4.0 (for iOS16)


# Xcode新規プロジェクトの作成

Xcodeのメニューから「File」→「New」→「Project」の
「Game」テンプレートを選択してプロジェクトを作成すると、従来のStoryboardを使った（SwiftUIでは無い）プロジェクトになってしまうため、通常の「App」テンプレートを選択する。
続く「Choose options for your new project:」ダイアログにて以下を指定する。

- Interface : SwifuUI
- Language : Swift
- 他 : 任意


# ContentView.swiftの内容を書き換える

「ContentView.swift」を以下の内容に書き換える。
`SpriteView`（従来の`SKView`）の`Option`と`DebugOptions`は、必要により指定する。


```swift:ContentView.swift
import SwiftUI
import SpriteKit

struct ContentView: View {
    var body: some View {
        let spriteViewOptions: SpriteView.Options = [
            //.allowsTransparency,
            //.ignoresSiblingOrder,
            //.shouldCullNonVisibleNodes,
        ]
        let spriteViewDebugOptions: SpriteView.DebugOptions = [
            .showsDrawCount,
            .showsFPS,
            //.showsFields,
            .showsNodeCount,
            //.showsPhysics,
            //.showsQuadCount,
        ]
        GeometryReader {
            SpriteView(scene: GameScene(size: $0.size),
                       options: spriteViewOptions,
                       debugOptions: spriteViewDebugOptions)
            //.scaledToFill()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```

# GameScene.swiftを新規ファイルで作成する

「New File...」→「Swift File」から`GameScene.swift`を新規作成する。

「GameScene.swift」を以下の内容に書き換える。

:::note
実は、`GameScene.swift`は従来のまま。もし、Storyboardで開発した旧プロジェクトがあるなら、そこから`GameScene.swift`をコピーして使用できるはず。
:::



```swift:GameScene.swift
import SpriteKit

class GameScene: SKScene {

    enum ColliderType {
        static let xxx1: UInt32   = 1 << 0
        static let xxx2: UInt32   = 1 << 1
        static let xxx3: UInt32   = 1 << 2
        static let xxx4: UInt32   = 1 << 3
        static let xxx5: UInt32   = 1 << 4
    }

    override func didMove(to view: SKView) {
        self.physicsWorld.gravity = CGVector(dx: 0.0, dy: -2.0)
        self.physicsWorld.contactDelegate = self
        //TODO

    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        for touch in touches {
            let location = touch.location(in: self)
            let touchedNode = atPoint(location)
            switch touchedNode.name {
            case "xxx":
                //TODO
                break
            default:
                break
            }

        }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        //TODO

    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        //TODO

    }

    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        //TODO

    }


    override func update(_ currentTime: TimeInterval) {
        //TODO

    }
}

extension GameScene: SKPhysicsContactDelegate {
    func didBegin(_ contact: SKPhysicsContact) {
        //TODO

    }
}
```

SpriteKitテンプレートとしては、以上でおわり。


# おまけ

[以前の記事](https://qiita.com/nak435/items/dac3c93af95ccdef725f#おまけ22019105追記)で紹介した内容より、もっといい感じで書く方法があったので、紹介します。

_SpriteKitでノードを構築していく場合に、インスタンス生成後に各プロパティを設定するコードがだらだら続くが、これをいい感じに書く方法を見つけたので、追記しておく。_

```swift:普通の書き方
let label = SKLabelNode(fontNamed: "HelveticaNeue")
label.horizontalAlignmentMode = .center
label.verticalAlignmentMode = .center
label.text = "LABEL"
label.fontSize = 32
label.fontColor = .white
label.position = CGPoint(x: view.frame.midX, y: view.frame.midY)
addChild(label)
```

```swift:前回紹介した書き方 
let label: SKLabelNode = {
    $0.horizontalAlignmentMode = .center
    $0.verticalAlignmentMode = .center
    $0.text = "LABEL"
    $0.fontSize = 32
    $0.fontColor = .white
    $0.position = CGPoint(x: view.frame.midX, y: view.frame.midY)
    return $0
} (SKLabelNode(fontNamed: "HelveticaNeue"))
addChild(label)
```

```swift:今回紹介する書き方 
let label = SKLabelNode(fontNamed: "HelveticaNeue").apply {
    $0.horizontalAlignmentMode = .center
    $0.verticalAlignmentMode = .center
    $0.text = "LABEL"
    $0.fontSize = 32
    $0.fontColor = .white
    $0.position = CGPoint(x: view.frame.midX, y: view.frame.midY)
}
addChild(label)
```

`apply`メソッドは、次のように定義する。

```swift
public protocol Applicable { }
public extension Applicable {
    @discardableResult
    func apply(block: (Self) -> Void) -> Self {
        block(self)
        return self
    }
}

extension NSObject: Applicable { }
```

`NSObject`の**extension**として定義するため、ほとんどのオブジェクトで使うことができる。これは便利。（周知の内容かと思いますが・・・）

#### 以上