I've ported over this project - https://gist.github.com/cmezak/8393434 and have it working but I have a question about the differences in handling instances of a class and also initializing properly.
In GameScene.swift I have created a property with an instance to an SKSpriteNode like so:
class GameScene: SKScene {
var nodeToScroll: SKSpriteNode
init(size: CGSize) {
nodeToScroll = SKSpriteNode(imageNamed: "image")
super.init(size: size)
nodeToScroll.anchorPoint = CGPointMake(0, 0)
nodeToScroll.position = CGPointMake(0, size.height - nodeToScroll.size.height)
self.addChild(nodeToScroll)
}
}
And in GameViewController.swift I have:
var skView = SKView()
var scene = GameScene(size: CGSize())
var scrollView = UIScrollView()
class GameViewController: UIViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
skView = SKView(frame: CGRectMake(0, 0, 1024, 768))
self.view.addView(skView)
scene = GameScene(size: skView.bounds.size)
scene.scaleMode = .AspectFill
scrollView = UIScrollView(frame: skView.frame)
scrollView.hidden = true
scrollView.delegate = self
self.view.addSubview(scrollView)
scrollView.contentSize = scene.calculateAccumulatedFrame().size
skView.addGestureRecognizer(scrollView.panGestureRecognizer)
skView.presentScene(scene)
}
Like I said, this is working but my attempts to follow Swift patterns such as
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
it breaks. So my question is, how am I supposed to be handling cases where I need an instance of a scene outside of viewDidLoad? I thought this would be a simple port but now I've been staring at it so long I don't even know what I'm doing. Suggestions?
Edit:
I edited the viewController to this:
var skView: SKView!
var scene: GameScene!
var scrollView: UIScrollView!
class GameViewController: UIViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
let skView = self.view as SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
self.view.addSubview(skView)
/* Set the scale mode to scale to fit the window */
scene.size = skView.bounds.size
scene.scaleMode = .AspectFill
scrollView = UIScrollView(frame: skView.frame)
scrollView.hidden = true
scrollView.delegate = self
self.view.addSubview(scrollView)
scrollView.contentSize = scene.calculateAccumulatedFrame().size
skView.addGestureRecognizer(scrollView.panGestureRecognizer)
skView.presentScene(scene)
}
}
I don't get any warnings or errors but when I run it I get "fatal error: use of unimplemented initializer 'init(coder:)' for class 'SwiftScrollKit.GameScene'"
So then I added
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
to the GameScene class and now get 'Can't add self as subview' -- hooboy... I don't know why this is throwing me for such a loop but I just must not be grasping something here that is probably right under my nose.
You're going to be stuck until you get the scope of your instance variables corrected. In the gist you linked to, they're declared inside the #implementation { ... } block of your ViewController, but here they're declared outside the class completely, which means they're global in scope.
Inside viewDidLoad, you're redefining scene and skView in the scope of that method, hiding the global values while you do your configuration. These local variables then get deallocated at the end of viewDidLoad, so you've lost access to whatever you've set up there. (They may live on in the view hierarchy, but your global variables won't work.)
You'll want to move those global variables inside the class, and then access them using self.variableName so you don't mix them up with local variables. Other methods in the class will then also have access to what you've set up in viewDidLoad:
class GameViewController: UIViewController, UIScrollViewDelegate {
var skView: SKView!
var scene: GameScene!
var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
self.scene = // load the scene
self.skView = SKView(frame: CGRectMake(0.0, 0.0, 1024.0, 768.0))
self.view.addSubview(self.skView)
// etc
}
}
Finally, the "Can't add self as subview" error is happening because of this:
let skView = self.view as SKView
// ...
self.view.addSubview(skView)
Related
This is probably a dumb question ,but here is my situation.
I have an SKShapeNode rect that goes from left to right on the screen as follows :
// GameScene.swift
var rect = SKShapeNode()
var counter = 0{
didSet{
rect.position = CGPoint(x: CGFloat(counter) , y: frame.midY)
if CGFloat(counter) > frame.width{
counter = 0
}}}
override func update(_ currentTime: TimeInterval) {
counter = counter + 4
}
In the ViewController.swift I try to get the rect.position like this , which I know is wrong because it creates a new Instance.
//ViewController.swift
let gameScene = GameScene()
#IBAction func button(_ sender: Any) {
// gameScene.rect.position = games.frame.CGPoint(x: 200, y: 400)
print(gameScene.rect.position) // Always returns (0,0)
}
Question: How can I get rect.position in real time from the other class. So that whenever I press the button , I get the actual position of the rect?
UPDATE
On Ron's suggestion I updated the viewDidLoad method in my ViewController.swift from this :
let gameScene = GameScene()
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.spriteView {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}}
to this :
var gameScene : GameScene!
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.spriteView {
// Load the SKScene from 'GameScene.sks'
if let scene = GameScene(fileNamed: "GameScene") { // SKScene changed to GameScene
self.gameScene = scene // scene assigned to gameScene variable
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
}
INTENTION
I wanted to get the position of the moving bar when the play button was clicked.
Note that the GameScene only represents a part of the actual screen
When you first transition to your GameScene (Assuming that you are going directly from your GameViewController to your GameScene) create a class level variable for gameScene. Then when you need the information from GameScene use that same variable vs. creating a new GameScene variable
var gameScene: GameScene!
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let gameScene = GameScene(fileNamed: "GameScene") {
self.gameScene = gameScene
gameScene = .aspectFill
// Present the scene
view.presentScene(gameScene)
}
}
}
func getCoords() {
print("gameScene.rect.position \(gameScene.rect.position)")
}
I need to deallocate gamescene after the player have finished a level so that the memory is available to load another level of my game. If I don't do that, I have a crash of my app because of memory issues.
I have followed the indications given there:
Swift: Deallocate GameScene after transition to new scene?
But unfortunately, it doesn't work for me. I get an error "Could not cast value 'UIView' to 'SKView' in my GameViewController class. Here is my whole code:
import Foundation
import UIKit
import SpriteKit
class GameViewController: UIViewController {
var scene1: SKScene?
var scene2: SKScene?
var scene3: SKScene?
var skView: SKView?
func nextSceneAction1() {
scene2 = nil
scene3 = nil
skView! = self.view as! SKView
skView!.showsFPS = true
skView!.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView!.ignoresSiblingOrder = true
scene1 = TransitionSigns(size: skView!.frame.size)
scene1!.size.width = 2048
scene1!.size.height = 1536
/* Set the scale mode to scale to fit the window */
scene1!.scaleMode = .AspectFill
let transition = SKTransition.revealWithDirection(.Right, duration: 2)
scene1!.scaleMode = .AspectFill
skView!.presentScene(scene1!, transition: transition)
}
func nextSceneAction2() {
scene1 = nil
scene3 = nil
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
scene2 = TransitionSigns(size: skView.frame.size)
scene2!.size.width = 2048
scene2!.size.height = 1536
/* Set the scale mode to scale to fit the window */
scene2!.scaleMode = .AspectFill
let transition = SKTransition.revealWithDirection(.Right, duration: 2)
scene2!.scaleMode = .AspectFill
skView.presentScene(scene2!, transition: transition)
}
func nextSceneAction3() {
scene1 = nil
scene2 = nil
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
scene3 = TransitionSigns(size: skView.frame.size)
scene3!.size.width = 2048
scene3!.size.height = 1536
/* Set the scale mode to scale to fit the window */
scene3!.scaleMode = .AspectFill
let transition = SKTransition.revealWithDirection(.Right, duration: 2)
scene3!.scaleMode = .AspectFill
skView.presentScene(scene3!, transition: transition)
}
override func viewWillLayoutSubviews() {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
let scene = MainView(size: skView.frame.size)
scene.size.width = 2048
scene.size.height = 1536
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return UIInterfaceOrientationMask.AllButUpsideDown
} else {
return UIInterfaceOrientationMask.All
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidDisappear(animated: Bool) {
self.view.removeFromSuperview()
}
override func viewWillDisappear(animated: Bool){
self.view.removeFromSuperview()
}
}
I call the different functions nextSceneAction1(), nextSceneAction2(), and nextSceneAction3() from my other classes with:
let controller1 = GameViewController()
controller1.nextSceneAction1()
or
let controller2 = GameViewController()
controller2.nextSceneAction1()
When the new scene is loaded, I get this error: "Could not cast value of type 'UIView' to 'SKView'. Do you know what is wrong with this code?
By the way, I'm open with all other possibilities to clean old scene from memory before loading a new one so that there is no memory crash during the life of the app.
In general a SKScene automatically deallocates once you transition to a new scene, unless you have a memory leak, so usually you don't have to do anything in particular to free memory.
To see if your scene has deallocated you can add the deinit method
func deinit {
print("scene did deallocate")
}
If this method gets called when you change scenes you know everything correctly deallocated, if it doesn't get called you have a memory leak.
Also you code is faulty, you are creating a new GameViewController instance everytime you change scene instead of accessing the current instance.
let controller2 = GameViewController() // creates new instance of GameViewController
controller2.nextSceneAction1()
You normally only load your 1st scene from your GameViewController and than all other scene changes can be done directly in the SKScene you are in. Don't bother using the GameViewController for this.
In your current SKScene you can change to a new scene like this (e.g from GameScene to MenuScene)
class GameScene: SKScene {
...
func loadMenuScene() {
let menuScene = MenuScene(size: self.size) // use size of current scene
let transition = Some SKTransition
view?.presentScene(menuScene, withTransition: transition)
}
}
Hope this helps
I am new to swift and trying add some extra features after reading this tutorial http://www.raywenderlich.com/66877/how-to-make-a-game-like-candy-crush-part-1. What I want to do is combine with the element of digital pets, kind like the iOS game 'Best Friend'. So first, I add a main scene(picture of main scene) and several buttons, when button is pressed, the scene would change. Below are the codes I used to achieve it.
MainSceneController.swift:
import UIKit
import SpriteKit
class MainSceneController: UIViewController {
#IBOutlet weak var settingButton: UIButton!
#IBOutlet weak var storeButton: UIButton!
#IBOutlet weak var petsButton: UIButton!
#IBOutlet weak var levelButton: UIButton!
override func prefersStatusBarHidden() -> Bool {
return true
}
override func shouldAutorotate() -> Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
let mainScene = MainScene(size:CGSize(width: 320, height: 568))
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
mainScene.scaleMode = .AspectFill
skView.presentScene(mainScene)
}
#IBAction func changeScene(sender: UIButton) {
let labelText = sender.titleLabel?.text!
print(labelText)
switch (labelText!) {
case "LevelButton":
let scene = LevelScene(size:CGSize(width: 320, height: 568))
let reveal = SKTransition.fadeWithDuration(1)
scene.scaleMode = SKSceneScaleMode.AspectFill
scene.view?.presentScene(scene, transition: reveal)
case "PetsButton":
let scene = PetsScene(size:CGSize(width: 320, height: 568))
let reveal = SKTransition.fadeWithDuration(1)
scene.scaleMode = SKSceneScaleMode.AspectFill
scene.view?.presentScene(scene, transition: reveal)
default:
break
}
}
}
import SpriteKit
class PetsScene: SKScene {
override init(size: CGSize) {
super.init(size: size)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.redColor()
let mylabel = SKLabelNode(fontNamed: "Chalkduster")
mylabel.text = "PetsScene"
mylabel.fontSize = 70
mylabel.position = CGPoint(x: self.frame.size.width / 2,
y: self.frame.size.height / 2)
self.addChild(mylabel)
}
}
When I pressed the button, the scene do not change. I also use segue to achieve it. But is seems like not what I want. And I have looked for some projects source code, but I cannot find similar one. I would appreciate any advice, thanks in advance.
I am not sure if this is what you are looking for either, but it is the method I use to transition between scenes. I do not use Segues. I am not sure why, other than that I never bothered to learn how to use them well enough to default to them. I only rarely use Storyboards, even. So the below code is an example of how to transition from scene to another just in code. I usually have a GlobalFunctions.swift file that holds functions that are shared by many classes, the following would be in such a file:
func transitionToScene(node: String, sendingScene: SKScene) {
//println("Entering transition to scene method with \(node) and \(sendingScene.name)")
let transDur = 1.5
let transition = SKTransition.fadeWithColor(sendingScene.backgroundColor, duration: transDur)
var scene = SKScene()
switch node {
case splashSceneString:
scene = SplashScene(size: sendingScene.size)
case gameSceneString:
scene = GameScene(size: sendingScene.size)
case optionsSceneString:
scene = OptionsScene(size: sendingScene.size)
case menuSceneString:
scene = MenuScene(size: sendingScene.size)
default:
if general_debug == true { print("Default should not be reached. No Scene?") }
}
scene.scaleMode = SKSceneScaleMode.AspectFill
sendingScene.view!.presentScene(scene, transition: transition)
}
So this func would be called by some event. For example if you touched a button within your game/app the touchesEnded: function would do something like this:
transitionToScene("optionsSceneString", sendingScene: self.scene)
Hope this helps somewhat. Good luck!
I am completely confused as to why this leaks. After xCode 7.2 was released, the program I was working on started having memory leaks. I've stripped all of the code to the bare minimum and the leak is still here. If anyone knows how to instantiate a GameScene class with an initializer and avoid leaks please let me know.
Here is the GameViewController:
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = GameScene(size: CGSize(width: view.bounds.size.width, height: view.bounds.size.height))
let skView = view as! SKView
skView.ignoresSiblingOrder = true
skView.showsNodeCount = true
skView.showsFPS = true
skView.showsPhysics = true
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
// the rest is standard
Here is the GameScene:
import SpriteKit
class GameScene: SKScene {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(size: CGSize) {
super.init(size: size)
}
override func didMoveToView(view: SKView) {
}
}
Here is what instruments is telling me
This tells me that I am creating GameScene in a way that causes some reference look perhaps, but surely there is a way to create a GameScene and have an initializer in place.
Remove this line:
skView.showsPhysics = true
I have two views, the initial one that works fine, and a second one that I am trying to display after I transition to it from a button on the first view.
However, when I click the button, the transition happens and the screen goes blank.
Below is the viewDidLoad inside of the UIViewController class for the second view.
Also when it hits the
let sKView = view as! SKVIEW
line it spits out
Could not cast value of type 'UIView' (0x10a313eb0) to 'SKView' (0x1094d5718).
(lldb)
How do I get it to display the view?
class PlayViewController: UIViewController {
var scene2: PlayScene!
override func viewDidLoad() {
super.viewDidLoad()
let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
scene2 = PlayScene(size: skView.bounds.size)
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene2.scaleMode = .AspectFill
skView.presentScene(scene2)
}
first class
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
the second scene
import SpriteKit
class PlayScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let myLabel = SKLabelNode(fontNamed:"Chalkduster")
myLabel.text = "SCENE 2!";
myLabel.fontSize = 65;
myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
self.addChild(myLabel)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
let sprite = SKSpriteNode(imageNamed:"Spaceship")
sprite.xScale = 0.5
sprite.yScale = 0.5
sprite.position = location
let action = SKAction.rotateByAngle(CGFloat(M_PI), duration:1)
sprite.runAction(SKAction.repeatActionForever(action))
self.addChild(sprite)
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
The issue is your view controllers view is your everyday run of the mill UIView instead of an SKView. Assuming you are using a storyboard click on the view controller and then click on the view and change the class to SKView instead of UIView.
Hopefully that helps.
I am not sure what are you trying to archive, you can try change your super class from UIViewController to SpriteViewController or you can create a new SKView and append to your view.
You cannot downcast a UIView to a SKView as a SKView inherit from UIView as you can see in the apple documentation
Inherits From
NSObject
UIResponder
UIView
SKView