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プログラミング


コメント