SwiftUIでSpriteKitを使用する


モバイルゲームアプリケーション開発を学ぶにあたり、Xcode のゲームテンプレートが作成する UIKit ベースのプロジェクトではなく、現在主流の SwiftUI ベースのプロジェクトで開発を始めたかったため、調べてみました。

iOS 14以降の実装

学習を目的としたため、調べた中で最もミニマムな実装をしている次のサイトを参考にしました。

iOS の App テンプレートで、インターフェースに SwiftUI、プログラミング言語に Swift を選択したプロジェクトを作成します。

プロジェクトへ GameScene.swift という名前の Swift File を追加します。GameScene.swift を次の内容へ書き換えます。

GameScene.swift
import SpriteKit

class GameScene: SKScene {
    override func didMove(to view: SKView) {
        // something
    }
}

既存の ContentView.swift ファイルを次の内容へ書き換えます。

ContentView
import SwiftUI
// [1] SpriteKit をインポートします。
import SpriteKit

struct ContentView: View {
    // [2] GameScene を初期化し、変数へ格納します。
    var scene: SKScene {
        let scene = GameScene()
        scene.scaleMode = .resizeFill
        return scene
    }
    
    var body: some View {
        // [3] SpriteView をレンダリングします。
        SpriteView(scene: self.scene)
            .ignoresSafeArea()
    }
}

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

iOS 13以前の実装

SKView は UIKit の UIView であるため、UIViewRepresentable プロトコルを採用するコンテナを作成することで SwiftUI のビュー階層へ統合できるようです。

UIViewRepresentable プロトコルは、公式リファレンスを参照しました。

SKView を UIViewRepresentable プロトコルでラップする例は、次のサイトを参考にしました。

最もミニマルな実装のプロジェクトへ SKViewContainer.swift という名前の SwiftUI View を追加します。SKViewContainer.swift を次の内容へ書き換えます。

SKViewContainer.swift
import SwiftUI
import SpriteKit

struct SKViewContainer: UIViewRepresentable {
    typealias UIViewType = SKView
    
    var skScene: SKScene!
    
    init(scene: SKScene) {
        skScene = scene
        // 必要に応じて書き換えます。
        self.skScene.scaleMode = .resizeFill
    }
    
    func makeUIView(context: Context) -> SKView {
        let view = SKView(frame: .zero)
        // デバッグのためのコードです。
        view.showsFPS = true
        view.showsNodeCount = true
        return view
    }
    
    func updateUIView(_ view: SKView, context: Context) {
        view.presentScene(context.coordinator.scene)
    }
    
    class Coordinator: NSObject {
        var scene: SKScene?
    }
    
    func makeCoordinator() -> Coordinator {
        let coordinator = Coordinator()
        coordinator.scene = self.skScene
        return coordinator
    }
}

struct SKViewContainer_Previews: PreviewProvider {
    static var previews: some View {
        // プレビューは表示できません。
        SKViewContainer(scene: SKScene())
    }
}

既存の ContentView.swift ファイルを次の内容へ書き換えます。

ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        SKViewContainer(scene: GameScene())
    }
}

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