UIViewController 時代
SwiftUI が出る前の時代、SceneKit と SpriteKit を同時に使うために、
SCNView の overlaySKScene に SKScene をセットしていました。
SwiftUI 時代
時は、iOS 13, SwiftUI の時代、世の民は UIViewController などという古の様式を捨て去った。
さらに iOS 14 から以下のように UIViewRepresentable などを使わなくても直接、SKScene, SCNScene を SwiftUI の View として読み込めるようになった。
SpriteView – A SwiftUI view that renders a SpriteKit scene.
https://developer.apple.com/documentation/spritekit/spriteview
SceneView – A SwiftUI view for displaying 3D SceneKit content.
https://developer.apple.com/documentation/scenekit/sceneview
これ、使ってみるとわかるんですが、SKView と SCNView が不要になります。
overlaySKScene も不要です。なぜなら SpriteView と SceneView を ZStack で重ねればいいから。
いやはや、これで SpriteKit と SceneKit のコードが分離されて、わかりやすくなるような、ならないような。
少なくとも SKView, SKScene, SCNView, SCNScene の4つが、SKScene, SCNScene の2つだけに集中すればよいので、理解しやすくなると思います!
これから本格的に実装していくうちに何か不便な点が発生するかもしれませんし、しないかもしれません。
とりあえず、UIViewRepresentable を使わない、最低限の SpriteKit + SceneKit on Pure SwiftUI のコードを以下に載せます。
これを contentView.swift にコピペするだけで実行できると思います。
ポイントは GameObject という NSObject のインスタンスを作り、そこの中で SKScene と SCNScene を生成しています。
この GameObject を介することにより、SceneKit を SpriteKit の連携もしやすくなります。
import SwiftUI import SceneKit import SpriteKit struct ContentView: View { @StateObject var gameObject = GameObject() var body: some View { ZStack() { SceneView(scene: gameObject.scnScene, options: [.allowsCameraControl], delegate: gameObject) Button(action: { gameObject.showsStatistics = true gameObject.debugOptions = [.renderAsWireframe, .showBoundingBoxes] }) { Text("Debug").padding(.top, 300) } SpriteView(scene: gameObject.skScene, options: [.allowsTransparency]) } } } class GameObject: NSObject, SCNSceneRendererDelegate, ObservableObject { var showsStatistics: Bool = false var debugOptions: SCNDebugOptions = [] var scnScene: SCNScene = { // create a new scene // let scene = SCNScene(named: "art.scnassets/ship.scn")! let scene = GameScene() return scene }() var skScene: SKScene = { let scene = SKScene() scene.backgroundColor = .clear return scene }() func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) { renderer.showsStatistics = self.showsStatistics renderer.debugOptions = self.debugOptions } } class GameScene: SCNScene { override init() { super.init() self.background.contents = UIColor.black let cg = SCNCapsule(capRadius: 2.0, height: 6.0) cg.firstMaterial?.diffuse.contents = UIColor.red cg.firstMaterial?.specular.contents = UIColor.white let cn = SCNNode(geometry: cg) cn.position = SCNVector3(x: 0.0, y: 50.0, z: 20.0) cn.rotation = SCNVector4(x: 0, y: 0, z: 1, w: Float.pi / 6.0) cn.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil) self.rootNode.addChildNode(cn) let cameraNode = SCNNode() cameraNode.camera = SCNCamera() cameraNode.position = SCNVector3(0, 20, 100) self.rootNode.addChildNode(cameraNode) let omniLight = SCNNode() omniLight.light = SCNLight() omniLight.light?.type = .omni omniLight.position = SCNVector3(10, 10, 50) self.rootNode.addChildNode(omniLight) let floorGeo = SCNFloor() let floorNode = SCNNode(geometry: floorGeo) floorGeo.firstMaterial?.diffuse.contents = UIColor.green floorNode.position.y = -1 floorNode.physicsBody = SCNPhysicsBody(type: .static, shape: nil) self.rootNode.addChildNode(floorNode) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
それではよい SwiftUI ライフを!
SwiftUIを学ぶのにおすすめの本
SwiftUI 徹底入門
SwiftUIではじめるiPhoneアプリプログラミング入門
iOS/macOS UIフレームワーク SwiftUIプログラミング
コメント