XCode Version 12.4 (12D4e)
The first SKScene class in Functions.swift included the buildBackground function which I want to call in different SKScene class.
class Functions: SKScene {
func buildBackground(backgroundPath: String){
let texture = SKTexture(imageNamed: backgroundPath)
let background = SKSpriteNode(texture: texture)
background.size = CGSize(width: 512, height: 512)
background.position = CGPoint(x: 0, y: 0)
background.name = "BACKGROUND"
addChild(background)
print("Background loaded")
}
}
I'm trying to call this function in the second SKScene class in the Room.swift file
I created an instance of Functions in Room and called my function just like that:
public class Room: SKScene {
let functions = Functions()
override public func didMove(to view: SKView) {
functions.buildBackground(backgroundPath: "roomBackground")
}
}
I got "Cannot find 'Functions' in scope" on the "let functions = Functions()" line. After running a build, I got my message "Background loaded", so I guess, the function was called, however, my wallpaper SKSpriteNode has not appeared in my scene. After I tried to move buildBackground method inside Room.swift class, it all works, but there are a lot of different scenes in my project (Room1, Room2, etc.) so I don't want to copy-paste this method each file.
since you are overriding a public function, you probably need to use self :
self.functions.buildBackground(...)
Related
I'm trying to learn how to make a GameManager type class, and making individual classes for each of my GameScenes... probably the wrong thing to do, but for the sake of this question, please accept this as the way to do things.
My GameManager looks like this, having a reference to each of the scenes, that's static:
import SpriteKit
class GM {
static let scene2 = SecondScene()
static let scene3 = ThirdScene()
static let home = SKScene(fileNamed: "GameScene")
}
How do I create a SKScene programmatically, without size info, since they're in a subclass of SKScene and don't have any idea what the view size is, and I don't want them to need worry about this:
I'm doing this, but getting a EXC_BAD_Access at convenience override init()
class SecondScene: SKScene {
override init(size: CGSize){
super.init(size: size)
}
convenience override init(){
self.init()
self.backgroundColor = SKColor.red
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
}
}
As I mentioned your question is a bit vague but lets do some examples of what a GameManager class can be.
Before I start lets differentiate between calling this
let scene = StartScene(size: ...)
and this
let scene = SKScene(fileNamed: "StartScene")
The 1st method, with size, is when you create your scenes all in code and you are not using the xCode visual level editor.
The 2nd method is when you are using the Xcode level editor, so you would need to create a StartScene.sks file. Its that .sks file that it looks for in fileNamed.
Now for some game manager example, lets first imagine we have 3 SKScenes.
class StartScene: SKScene {
override func didMove(to view: SKView) { ... }
}
class GameScene: SKScene {
override func didMove(to view: SKView) { ... }
}
class GameOverScene: SKScene {
override func didMove(to view: SKView) { ... }
}
Lets say you want to transition from StartScene to GameScene, you would add this code in your StartScene at the correct spot e.g when the play button is pressed. Thats the simplest way to move from one SKScene to the next, directly from the SKScene itself.
// Code only, no xCode level editor
let gameScene = GameScene(size: CGSize(...))
let transition = SKTransition...
gameScene.scaleMode = .aspectFill
view?.presentScene(gameScene, transition: transition)
// With xCode level editor (returns an optional so needs if let
// This will need the GameScene.sks file with the correct custom class set up in the inspector
// Returns optional
if let gameScene = SKScene(fileNamed: "GameScene") {
let transition = SKTransition...
gameScene.scaleMode = .aspectFill
view?.presentScene(gameScene, transition: transition)
}
Now for some actual examples of GameManagers, Im sure you know about some of them already.
EXAMPLE 1
Lets say we want a scene loading manager. You approach with static methods will not work because a new instance of SKScene needs be created when you transition to one, otherwise stuff like enemies etc will not reset. Your approach with static methods means you would use the same instance every time and that is no good.
I personally use a protocol extension for this.
Create a new .swift file and call it SceneLoaderManager or something and add this code
enum SceneIdentifier: String {
case start = "StartScene"
case game = "GameScene"
case gameOver = "GameOverScene"
}
private let sceneSize = CGSize(width: ..., height: ...)
protocol SceneManager { }
extension SceneManager where Self: SKScene {
// No xCode level editor
func loadScene(withIdentifier identifier: SceneIdentifier) {
let scene: SKScene
switch identifier {
case .start:
scene = StartScene(size: sceneSize)
case .game:
scene = GameScene(size: sceneSize)
case .gameOver:
scene = GameOverScene(size: sceneSize)
}
let transition = SKTransition...\
scene.scaleMode = .aspectFill
view?.presentScene(scene, transition: transition)
}
// With xCode level editor
func loadScene(withIdentifier identifier: SceneIdentifier) {
guard let scene = SKScene(fileNamed: identifier.rawValue) else { return }
scene.scaleMode = .aspectFill
let transition = SKTransition...
view?.presentScene(scene, transition: transition)
}
}
Now in the 3 scenes conform to the protocol
class StartScene: SKScene, SceneManager { ... }
and call the load method like so, using 1 of the 3 enum cases as the scene identifier.
loadScene(withIdentifier: .game)
EXAMPLE 2
Lets make a game manager class for game data using the Singleton approach.
class GameData {
static let shared = GameData()
private init() { } // Private singleton init
var highscore = 0
func updateHighscore(forScore score: Int) {
guard score > highscore else { return }
highscore = score
save()
}
func save() {
// Some code to save the highscore property e.g UserDefaults or by archiving the whole GameData class
}
}
Now anywhere in your project you can say
GameData.shared.updateHighscore(forScore: SOMESCORE)
You tend to use Singleton for things where you only need 1 instance of the class. A good usage example for Singleton classes would be things such as helper classes for Game Center, InAppPurchases, GameData etc
EXAMPLE 3
Generic helper for storing some values you might need across all scenes. This uses static method approach similar to what you were trying to do. I like to use this for things such as game settings, to have them in a nice centralised spot.
class GameHelper {
static let enemySpawnTime: TimeInterval = 5
static let enemyBossHealth = 5
static let playerSpeed = ...
}
Use them like so in your scenes
... = GameHelper.playerSpeed
EXAMPLE 4
A class to manage SKSpriteNodes e.g enemies
class Enemy: SKSpriteNode {
var health = 5
init(imageNamed: String) {
let texture = SKTexture(imageNamed: imageNamed)
super.init(texture: texture, color: SKColor.clear, size: texture.size())
}
func reduceHealth(by amount: Int) {
health -= amount
}
}
Than in your scene you can create enemies using this helper class and call the methods and properties on it. This way you can add 10 enemies easily and individually manage their health etc. e.g
let enemy1 = Enemy(imageNamed: "Enemy1")
let enemy2 = Enemy(imageNamed: "Enemy2")
enemy1.reduceHealth(by: 3)
enemy2.reduceHealth(by: 1)
Its a massive answer but I hope this helps.
I have a simple app where I'm creating a shape dynamically. This shape has physics, but starts out with it's dynamics set to false (as intended).
var dot = SKSpriteNode(imageNamed: "ShapeDot.png");
override func sceneDidLoad() {
dot.name = "MyShapeDot";
dot.size = CGSize(width: 10,height: 10);
dot.position = CGPoint(x: 0, y: 0);
dot.physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(dot.size.width/2))
dot.physicsBody?.isDynamic = false;
dot.physicsBody?.allowsRotation = false;
dot.physicsBody?.pinned = false;
dot.physicsBody?.affectedByGravity = true;
//add to spritekit scene
self.addChild(dot)
}
The shape is successfully added to the .sks and the controller (I see it on the screen). Then on a tap gesture I'm calling a function to turn on dynamics for the physics sprite node.
func MyTapGesture(){
dot.physicsBody?.isDynamic = true;
}
The MyTapGesture is being called (I debugged that it triggers), but the shape doesn't become dynamic and start using gravity... Does anyone know what I'm missing???
I'm calling the MyTapGesture from my interfaceController... It's wired up as so
let gameScene = GameScene();
#IBOutlet weak var spriteTapGestures: WKTapGestureRecognizer!
#IBAction func onSpriteTap(_ sender: Any) {
NSLog("tap")
gameScene. MyTapGesture()
}
Within the MyTapGesture I've also tried print(dot) and it outputs the following:
name:'MyShapeDot' texture:[<SKTexture> 'ShapeDot.png' (128 x 128)] position:{0, 0} scale:{1.00, 1.00} size:{10, 10} anchor:{0.5, 0.5} rotation:0.00
This leads me to believe it should work and I'm calling the right reference of the class that's attached to the object. But it doesn't work. If I call MyTapGesture() within the update func of the SpriteKit class where my dot was created
override func update(_ currentTime: TimeInterval) {
MyTapGesture()
}
It works and the dynamics update! ...so for some reason my tap gesture must be calling a wrong reference or something??? So confused since the debug shows the correct data printed for the shape that I created...
To solve this - I realized that my gameScene var in my interface controller didn't have the correct reference. So I instantiated it as nil:
var gameScene : GameScene?;
And then assigned the variable in the interface controllers awake func
if let scene = GameScene(fileNamed: "GameScene") {
gameScene = scene
}
I'm following a tutorial to make a version of flappy bird. I'm using swift and this error keeps coming up. The "addChild(self.myFloor1) keeps saying expected declaration error. What did I do wrong?
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var myBackground = SKSpriteNode()
var myFloor1 = SKSpriteNode()
var myFloor2 = SKSpriteNode()
addChild(self.myFloor1)
override func didMoveToView(view: SKView) {
myBackground = SKSpriteNode(imageNamed: "background")
myBackground.anchorPoint = CGPointZero;
myBackground.position = CGPointMake(100, 0);
self.backgroundColor = SKColor(red: 80.0/255.0, green: 192.0/255.0, blue: 203.0/255.0, alpha: 1.0)
addChild(self.myBackground)
myFloor1 = SKSpriteNode(imageNamed: "floor")
myFloor2 = SKSpriteNode(imageNamed: "floor")
myFloor1.anchorPoint = CGPointZero;
myFloor1.position = CGPointMake(0, 0);
myFloor2.anchorPoint = CGPointZero;
myFloor2.position = CGPointMake(myFloor1.size.width-1, 0);
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
When you write
addChild(self.myFloor1)
You are calling a method, which must be done inside another method declaration.
Within your class declaration, the "highest level" "things" need to be declared as something: "var", "let", "func". Then within a "func", you can call your addChild method.
That's why you are getting the error: At that "class" level, it's expecting only things that you can specify what they are. Which here, you are not. You are trying to directly call that method.
What I suspect you may want, is add the viewDidLoad method and call addChild from within there. Or something like that...whatever makes sense for your view lifecycle.
Declaration refers to creating a new variable or method. The location you wrote addChild() seems as if you're creating a new variable. For example, let's look at the following simple class.
class GameScene: SKScene {
var myBackground = SKSpriteNode()
}
The variable myBackground is being declared as a new variable. You are creating a new instance of an SKSpriteNode object. SKSpriteNode is also a class. Now let's add a method to your GameScene class that prints hello. All the things you declare in the class is referred as being in the top level, which is where you create variables and functions, etc.
class GameScene: SKScene {
var myBackground = SKSpriteNode()
//This is a method/function of the class GameScene
func sayHello() {
print("Hello.")
}
//CAN'T CALL ITS OWN METHOD AT THE TOP LEVEL
sayHello()
}
To help you understand, addChild is a method/function of the SKNode class.
class SKNode: UIResponder {
func addChild(node: SKNode) {...}
}
So when you have something like you have it, it doesn't make sense because addChild is a function and you can't call a function at the top level of a class.
class GameScene: SKScene {
var myBackground = SKSpriteNode()
var myFloor1 = SKSpriteNode()
//CAN'T CALL METHOD ON TOP LEVEL OF CLASS
addChild(self.myFloor1)
}
Xcode thinks you're creating a new function called addChild, so it's expecting you to declare it by using the "func" keyword, which is why it's giving you the error, but obviously you're not creating a function call addChild, you need to call it.
You have to call addChild() inside a function/method because it calls the SKNode's addChild method.
So I have a class called GameScene which has a function called makeBackground. In another class called I created an instance of it (GameScene) and called makeBackground as follows in the didMoveToView of the new class:
var gameSceneInstance:GameScene = GameScene()
gameSceneInstance.makeBackground()
However when I run it, nothing happens!
This is the function makeBackground:
func makeBackground ()
{
let bgTxt = SKTexture(imageNamed: "bg.png")
let moveBg = SKAction.moveByX(0, y: -bgTxt.size().height, duration: 5)
let replaceBg = SKAction.moveByX(0, y: bgTxt.size().height, duration: 0)
let moveBgAnimation = SKAction.repeatActionForever(SKAction.sequence([moveBg, replaceBg]))
//-----background-----
for var i:CGFloat = 0 ; i<3; i++
{
bg = SKSpriteNode(texture: bgTxt)
bg.position = CGPoint(x: CGRectGetMidX(self.frame), y: bgTxt.size().height/2 + bgTxt.size().height * i)
bg.size.width = self.frame.width
bg.runAction(moveBgAnimation)
movingObjects.addChild(bg)
}
}
Do I have to copy and paste the makeBackground function into the new class to make it work?
Similarly, in the didMoveToView of the GameScene class, I have some sprites. Can I use them in this new class?
Thanks!
if you created an instance of the GameScene object and called the makeBackground function and nothing happened then there is something wrong with your code in the makeBackground function.
Also in another topic, instance objects which are created in a class are equeals of each other and cannot be used within each others methods unless they are specifically passed in. Perhaps you can make a method in your GameScen object with which you could pass these sprites in either as a list of sprites or as individual sprites
I am facing a problem using multiple scenes in Sprite Kit (Swift). I’ve setup 3 scene (the game scene, preferences and a help scene). I’ve defined two different SpriteNode classes, called CardItem and MenuItem. While I am using CardItem just at the game scene, I uses MenuItem at all three scenes. So when I touch at the game scene (standard scene) the CardItem SpriteNode, just the touchesBegan function from that class (in game scene) is called. But when I jump into the preferences or help scene (from the game scene), and touch on a SpriteNode MenuItem not just their touchesBegan functions is called but also the touchesBegan from the CardItem type at the game scene!!! (which still is available in the back) is called.
My question is now, is this a feature or a bug in my app?
Here my GameScene Class with the scene initialization
class GameScene: SKScene, PreferencesSceneDelegate, HelpSceneDelegate {
var prefView: SKView?
var helpView: SKView?
var activeGame: Game?
var gamePrefs = gamePreferences()
var prefManager: PreferencesManager!
func prefSceneDidFinish(myScene: PreferencesScene, command: String) {
// Remove PreferencesScene from Screen
if (command == "Abbruch") {
myScene.view!.removeFromSuperview()
setButtonTexture(command)
} else if command == "Done" {
myScene.view!.removeFromSuperview()
activeGame!.setupNewGame(self, gamePrefs: self.gamePrefs, prefManager: self.prefManager, command: 1)
} else {
println("prefSceneDidFinish: command \(command) not specified")
}
}
func helpSceneDidFinish(myScene: HelpScene) {
// Remove HelpScene from Screen
myScene.view!.removeFromSuperview()
}
override func didMoveToView(view: SKView) {
let background = Card(cardType: .background)
background.cardID = CardName.background.rawValue
background.anchorPoint = CGPoint(x: 0.5, y: 1.0)
background.position = CGPoint(x: size.width/2, y: size.height)
background.zPosition = -2
background.userInteractionEnabled = false
self.addChild(background)
//
// Preferences Initialization
//
self.prefView = SKView(frame: CGRectMake(0, 0, self.frame.width, self.frame.height))
// Create and configure PreferencesScene
prefscene = PreferencesScene(size: view.bounds.size)
prefscene.scaleMode = .AspectFill
prefManager = PreferencesManager(prefs: gamePrefs)
tmpSeriesSelection = gamePrefs.inScope // Initialize after data load fromn file
self.prefView!.presentScene(prefscene)
prefscene!.thisDelegate = self
//
// Help Initialization
//
self.helpView = SKView(frame: CGRectMake(0, 0, self.frame.width, self.frame.height))
// Create and configure HelpScene
helpscene = HelpScene(size: view.bounds.size)
helpscene.scaleMode = .AspectFill
self.helpView!.presentScene(helpscene)
helpscene!.thisDelegate = self
var helpSceneBackground = SKSpriteNode(imageNamed: "HelpSceneBackground.png")
helpSceneBackground.anchorPoint = CGPoint(x: 0.5, y: 1.0)
helpSceneBackground.position = CGPoint(x: size.width/2, y: size.height)
helpSceneBackground.zPosition = 0
helpSceneBackground.userInteractionEnabled = false
helpscene.addChild(helpSceneBackground)
activeGame = Game(scene: self, gamePrefs: gamePrefs, prefManager: prefManager)
}
Here is the code snippet (MenuItem touchesBegan) where I call the scenes
case .help :
println("Pressed Help")
var transition = SKTransition.fadeWithDuration(Double(0.0))
self.scene!.view?.addSubview(gamescene.helpView!)
case .preferences :
…
var transition = SKTransition.fadeWithDuration(Double(0.0))
self.scene!.view?.addSubview(gamescene.prefView!)
case .ok :
println("OK Selektion")
…
gamescene.prefManager.save(gamescene.gamePrefs)
prefscene.thisDelegate!.prefSceneDidFinish(prefscene, command: "Done")
Thanks in advance for your help
You are bound to run into problems when your scenes create their own SKView-instances which presents scenes, which creates views, which creates scenes etc. So, skip your prefView and helpView properties and use the scene's view property to present your scenes one after another.
view?.presentScene(scoreScene)
If (for some reason) you need to retain(1) the different scene-instances handle this in your GameViewController instead.
(1) In most cases you probably shouldn't...