Project with SpriteView runs but animation does not show in simulator - swift

Have started exploring SpriteView and I have gotten to the point which the editor recognizes the SpriteView object and runs, but the simulator screen stays black.
The scene runs and everything in the scene editor too.
Am I missing something? Is there some sort of initialization step that I'm missing?
import SwiftUI
import SpriteKit
struct ContentView: View {
var scene: SKScene {
let scene = SKScene(fileNamed: "MyScene")!
scene.scaleMode = .aspectFit
scene.size = CGSize(width: 300, height: 400)
scene.backgroundColor = .white
return scene
}
var body: some View {
SpriteView(scene: scene)
.frame(width: 300, height: 400)
.ignoresSafeArea()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Related

Spritekit and swiftui, change scene a better way

I wrote this simple few line of code to swap between 2 scene in Swiftui using SpriteKit, I'm try to understand if there could be a (better) different way to change from one scene to another using a button.
struct ContentView: View {
#StateObject var firstscene = FirstScene()
#StateObject var secondscene = SecondScene()
#State var changeScene = false
var body: some View {
ZStack{
if changeScene {
SpriteView(scene: firstscene)
} else {
SpriteView(scene: secondscene)
}
Button {
changeScene.toggle()
} label: {
Text("Change")
}
}
}
}
// the 2 SKScene created using a sub class of SKscene
class FirstScene: SKScene, ObservableObject {
let firstScene = SKScene(fileNamed: "FirstScene")
override func didMove(to view: SKView) {
scene?.view?.presentScene(firstScene)
}
}
class SecondScene: SKScene, ObservableObject {
let secondScene = SKScene(fileNamed: "SecondScene")
override func didMove(to view: SKView) {
scene?.view?.presentScene(secondScene)
}
}
now my doubt is, I'm change the SpriteView in the contentView using a var changeScene, could be done the same things in some other way using another approach?
open to any suggestions, I'm try to understand this framework .
thanks
You are using a standard way of switching views. You could clean your code up a bit like this:
struct ContentView: View {
#State var changeScene = false
var body: some View {
ZStack{
SpriteView(scene: (changeScene ? FirstScene() : SecondScene())) // this is a terniary operator
Button {
changeScene.toggle()
} label: {
Text("Change")
}
}
}
}
The new views do not need to be #StateObjects. You can call the View Types directly in your view.
Note: I am away from my computer so this is not tested.
iOS 14, Swift 5
This is tested and comes from this article.
https://betterprogramming.pub/build-a-game-of-chess-with-spritekit-3229c23bdba0
import SwiftUI
import SpriteKit
struct ContentView: View {
#State var switcher = false
var scene: SKScene {
let scene = GameScene.shared
scene.size = CGSize(width: 256, height: 512)
scene.scaleMode = .fill
scene.backgroundColor = .red
scene.name = "red"
return scene
}
var scene2: SKScene {
let scene2 = GameScene2.shared
scene2.size = CGSize(width: 256, height: 512)
scene2.scaleMode = .fill
scene2.backgroundColor = .blue
scene2.name = "blue"
return scene2
}
var body: some View {
if switcher {
SpriteView(scene: scene)
.frame(width: 256, height: 512)
.ignoresSafeArea()
.background(Color.red)
.onAppear {
scene2.isPaused = true
}
.onDisappear {
scene2.isPaused = false
}
} else {
SpriteView(scene: scene2)
.frame(width: 256, height: 512)
.ignoresSafeArea()
.background(Color.blue)
.onAppear {
scene.isPaused = true
}
.onDisappear {
scene.isPaused = false
}
}
Button {
withAnimation(.easeInOut(duration: 1.0)) {
switcher.toggle()
}
} label: {
Text("play")
}
}
}

pass / share class with SpriteView GameScene from a SwiftUi View

I am playing around with SpriteView, which works great. However I cannot figure out how to share a class with Spritekit GameScene,that is instantiated in one my views. Sharing my class with other views works like a charm. But how can I access my gameCenterManager class from spritekit's GameScene, how do I pass the class ? I don't have any clue how to do this. I considered making a global accessible class, but its not what I want.
The goal is to be able to send and receive data from inside GameScene to update players location and etc. But the matchmaking happens inside a SwiftUI View, which is also where the class is created.
The gameCenterManger class is created like so;
struct ContentView: View {
#StateObject var gameCenterManager = GameCenterManager()
var body: some View {
...
etc
the class is set up like so :
class GameCenterManager: NSObject, ObservableObject {
//lots of stuff going on
...
}
And is shared to the GameSceneView view that will create the SpriteView, like so :
Button("Show Sheet") {
self.showingSheet.toggle()
}
.buttonStyle(RoundedRectangleButtonStyle())
.buttonStyle(ShadowButtonStyle())
.fullScreenCover(isPresented: $showingSheet, content: { GameSceneView(gameCenterManager:self.gameCenterManager)})
Finally, inside my GameSceneView, the GameScene for SpriteView is configured.
struct GameSceneView: View {
var gameCenterManager: GameCenterManager
#Environment(\.presentationMode) var presentationMode
var scene: SKScene {
let scene = GameScene()
scene.size = CGSize(width: UIScreen.main.bounds.size.width , height: UIScreen.main.bounds.size.height)
scene.scaleMode = .fill
return scene
}
var body: some View {
Button("Dismiss") {
self.presentationMode.wrappedValue.dismiss()
}
Button("send data") {
gameCenterManager.increment()
}
SpriteView(scene: scene )
.frame(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height - 200 )
.ignoresSafeArea()
.overlay(ImageOverlay(), alignment: .bottomTrailing)
}
}
Perhaps others are looking to do the same? Here below is the answer in code snippets
GameCenterManager is the class that I want to access across all views and SKScenes
In mainview:
#main
struct gameTestApp: App {
#StateObject var gameCenterManager = GameCenterManager()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(gameCenterManager)
}
}
}
In Contentview view add :
#EnvironmentObject var gameCenterManager:GameCenterManager
And where I transition to GameSceneView inside Contentview, which will load my SKScene
Button("Show Sheet") {
self.showingSheet.toggle()
}
.buttonStyle(RoundedRectangleButtonStyle())
.buttonStyle(ShadowButtonStyle())
.fullScreenCover(isPresented: $showingSheet, content: { GameSceneView()})
Then in GameSceneView add:
#EnvironmentObject var gameCenterManager:GameCenterManager
and where we load SKScene:
var scene: SKScene {
let scene = GameScene()
scene.size = CGSize(width: UIScreen.main.bounds.size.width , height: UIScreen.main.bounds.size.height)
scene.scaleMode = .fill
scene.gameCenterManager = gameCenterManager
return scene
}
Then finally in GameScene itself add:
var gameCenterManager: GameCenterManager?

How to change the background color of a SceneView 3D Object in SwiftUI

Does anybody knows how to change the color of the background of a SceneView object?
I'm trying by just placing the background attribute to the SceneView, but it doesn't change it, still with a default background.In this case I wanted to the background be green, but it's keeps gray/white
import SwiftUI
import SceneKit
struct ContentView: View {
var body: some View {
ZStack{
Color.red
SceneView(
scene: SCNScene(named: "Earth.scn"),
options: [.autoenablesDefaultLighting,.allowsCameraControl]
)
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height/2, alignment: .center)
.background(Color.green)
.border(Color.blue, width: 3)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The gray that you are seeing is from SCNScene's background property. You need to set this to UIColor.green.
struct ContentView: View {
var body: some View {
ZStack{
Color.red
SceneView(
scene: {
let scene = SCNScene(named: "Earth.scn")!
scene.background.contents = UIColor.green /// here!
return scene
}(),
options: [.autoenablesDefaultLighting,.allowsCameraControl]
)
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height/2, alignment: .center)
.border(Color.blue, width: 3)
}
}
}
Result:

My GameScene reloads whenever my Text view updates

I have a SwiftUI/SpriteKit project.
I'm using a SwiftUI SpriteView to display my GameScene.
Whenever mainData.numOfBallsInCup is incremented, it updates a Text view.
Here's the problem: When the Text view is updated, it causes GameScene to be reloaded. GameScene's sceneDidLoad() runs each time, and the scene regresses to its initial state.
My ContentView is something like this:
import SwiftUI
import SpriteKit
struct ContentView: View {
#EnvironmentObject var mainData: MainData
var body: some View {
GeometryReader { geo in
Group {
if mainData.showSpriteScene {
SpriteView(scene: GameScene(), transition: SKTransition.reveal(with: .down, duration: 1.0))
.ignoresSafeArea()
VStack {
Text("\(mainData.numOfBallsInCup)")
.foregroundColor(Color.white)
Text("BALLS")
.foregroundColor(Color.white)
}
.position(x: geo.size.width * 0.5, y: geo.size.height * 0.06)
}
}
}
}
}
How can I have my Text view updating without reloading my GameScene?
Scene is a model, and you create new scene (right in call) on each update, so SpriteView thinks it should rebuild.
Try the following:
struct ContentView: View {
#EnvironmentObject var mainData: MainData
#State private var scene = GameScene() // << here, or put in MainData !!
var body: some View {
GeometryReader { geo in
Group {
if mainData.showSpriteScene {
SpriteView(scene: scene, // << here !!
transition: SKTransition.reveal(with: .down, duration: 1.0))
.ignoresSafeArea()

How can I make background of an SKScene transparent so I can layer in a ZStack?

I'm trying to make a game using SwiftUI and SpriteKit. And I want to be able to layout the game in a ZStack with the game layered over the background image. In order to do that, I need to make the background of the SKScene transparent. But when I do that, the nodes that I add to the SKScene no longer show up.
Here's the coding that I'm using:
import SwiftUI
import SpriteKit
import GameplayKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
view.allowsTransparency = true
self.backgroundColor = .clear
view.alpha = 0.0
view.isOpaque = true
view.backgroundColor = SKColor.clear.withAlphaComponent(0.0)
if let particles = SKEmitterNode(fileNamed: "Mud") {
particles.position.x = 512
addChild(particles)
}
}
}
struct ContentView: View {
var scene: SKScene {
let scene = GameScene()
scene.size = CGSize(width: 300, height: 400)
scene.scaleMode = .fill
return scene
}
var body: some View {
ZStack {
Image("road")
.resizable()
.aspectRatio(contentMode: /*#START_MENU_TOKEN#*/.fill/*#END_MENU_TOKEN#*/)
.ignoresSafeArea()
SpriteView(scene: scene)
.ignoresSafeArea()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}
}
The background of the SKScene is transparent and I can see the image layered underneath it in the ZStack. But, everything else in the scene is also transparent so that I can't see the emitter effect at all.
How can I make just the background of the SKScene transparent?
Finally solved using custom SpriteView. The code below makes the spritekit view actually have a transparent background. The problem seems to be the swiftUI's declarative nature means you're always using their SpriteView (which has a black background). The SKScene is really the only thing being passed into SwiftUI.
I think this explains why the view settings don't have an effect as with UIKit.
The solution was to create a new SpriteView outside of the body struct, and then use it in the body struct in place of standard SpriteView(scene:scene). This allows setting the SpriteView.Options in the init() to finally set .allowsTransparency in the SwiftUI's some View that is presented.
See the code below:
// Created by Larry Mcdowell on 9/25/20.
//
import SwiftUI
import SpriteKit
import GameplayKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
// 1
self.backgroundColor = .clear
//2
view.allowsTransparency = true
if let particles = SKEmitterNode(fileNamed: "Mud"){
particles.position.x = 140
particles.position.y = 200
addChild(particles)
}
}
}
struct ContentView: View {
var mySpriteView:SpriteView
init(){
mySpriteView = SpriteView(scene: scene, transition: nil, isPaused: false, preferredFramesPerSecond: 60, options: [.allowsTransparency], shouldRender: {_ in return true})
}
let scene:GameScene = {
let scene = GameScene()
scene.size = CGSize(width: 300, height: 400)
scene.scaleMode = .fill
return scene
}()
var body: some View {
ZStack {
Image("road")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea()
mySpriteView
.ignoresSafeArea()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This was a fun project for a rainy day!!
All you need is to set the scene background to .clear, and to pass options as a parameter to the SpriteView for transparency. So for your case, it would be
SpriteView(scene: scene, options: [.allowsTransparency])
All the SKView modifications can be removed.
See my final answer on this thread for the actual solution. I decided to delete the workaround I originally included once I figured out custom SpriteViews.