Error when switching scenes through a button node - swift

I'm trying to switch scenes when you press a button, however, when I click it, nothing happens, and nothing gets printed. There are no errors, I am just wondering why it is not working.
The node that I programmed DOES show up at the correct size and location, but when you tap on it nothing goes through. Thank you for any help.
Code:
class LevelScene: SKScene, SKPhysicsContactDelegate {
var isFingerOnBlock = false
let LevelOneName = "levelOne"
override func didMove(to view: SKView) {
super.didMove(to: view)
let Pineapple = SKSpriteNode(imageNamed: "ball")
Pineapple.isUserInteractionEnabled = true
Pineapple.position = CGPoint(x: self.frame.midX - 200, y: self.frame.midY);
Pineapple.name = "pineapple"
addChild(Pineapple)
}
func touchesBegan(touches: NSSet, withEvent event: UIEvent)
{
let touch = touches.anyObject() as! UITouch
let location = touch.location(in: self)
let nodes = self.nodes(at: location)
for node in nodes
{
if node.name == "pineapple"
{
print("ceeds")
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view!
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 = .aspectFit
skView.presentScene(scene)
}
break
}
}
}
}

You have declared
func touchesBegan(touches: NSSet, withEvent event: UIEvent)
There is no built-in function with that signature, and you never call that function; therefore your function is never called.

Thanks Matt! I fixed my code: It works now..
override func touchesBegan(_ touches: Set<UITouch>,
with event: UIEvent?) {
let touch = touches.first!
let location = touch.location(in: self)
let node : SKNode = self.atPoint(location)
if(atPoint(location) == node){
if node.name == "levelOne" {
if let scene = GameScene(fileNamed:"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 = .aspectFit
skView.presentScene(scene)
}
}
}
}

Related

Changing Scenes In Spritekit, Detecting Touches on Buttons

I am attempting to change from a home scene to a game scene in Spritekit. When I run it in the simulator, there is no transition to the GameScene. I also added a print command to see if the touches on the play button were even being registered, and they were not. Here's the code. Thanks!
import SpriteKit
class HomeScene: SKScene {
var playButton: SKSpriteNode?
var gameScene: SKScene!
override func didMove(to view: SKView) {
playButton = self.childNode(withName: "startButton") as? SKSpriteNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node == playButton {
let transition = SKTransition.fade(withDuration: 1)
gameScene = SKScene(fileNamed: "GameScene")
gameScene.scaleMode = .aspectFit
self.view?.presentScene(gameScene, transition: transition)
print("touch")
}
}
}
}
The touches parameter inside of your touchesBegan(_: with:) method is of type Set<UITouch>, i.e. a set of touches. As stated in the Apple Documentation for Sets, a set is used when the order of the items in the collection are not a concern. Therefore, you cannot assume that the first item in touches is the first touch in the set. Knowing this, I recommend refactoring your code inside of HomeScene.swift to the following:
import SpriteKit
class HomeScene: SKScene {
private var playButton: SKSpriteNode?
override func sceneDidLoad() {
self.playButton = self.childNode(withName: "//starButton") as? SKSpriteNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
self.touchDown(atPoint: t.location(in: self))
}
}
func touchDown(atPoint pos: CGPoint) {
let nodes = self.nodes(at: pos)
if let button = playButton {
if nodes.contains(button) {
let gameScene = SKScene(fileNamed: "GameScene")
self.view?.presentScene(gameScene)
}
}
}
The touchDown(atPoint:) method does all the heavy lifting and touchesBegan(_: with:) is freed up to listen for touch events. Another thing to note when calling childNode(withName:):
Adding / before the name of the file starts searching for nodes beginning at the root of your file hierarchy.
Adding // before the name of the file starts searching for nodes beginning at the root of your file hierarchy, then recursively down the hierarchy.
Adding * before the name of the file acts as a character wildcard and allows you to search for file names that are wholly or partially unknown.
I think you should use the node's name instead of the node itself for comparison in the touchesBegan... try something like this:
import SpriteKit
class HomeScene: SKScene {
var playButton: SKSpriteNode?
var gameScene: SKScene!
override func didMove(to view: SKView) {
playButton = self.childNode(withName: "startButton") as? SKSpriteNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node.name == "startButton" {
let transition = SKTransition.fade(withDuration: 1)
gameScene = SKScene(fileNamed: "GameScene")
gameScene.scaleMode = .aspectFit
self.view?.presentScene(gameScene, transition: transition)
print("touch")
}
}
}
}
Hope it helps! :)

Button is still working even when removeFromParent() in Swift SpriteKit is called

In the code below if I press the removeButton the button would disappear but it's still working. Maybe I should make the if statement false but I don't know how.
class GameScene : SKScene {
var button : SKSpriteNode!
var removeButton : SKSpriteNode!
var image : SKSpriteNode!
override func didMove(to view: SKView) {
createButton()
createRemoveButton()
}
func createButton() {
button = SKSpriteNode(imageNamed: "button")
button.position = CGPoint(x: -300,y: 0)
self.addChild(button)
}
func createRemoveButton() {
removeButton = SKSpriteNode(imageNamed: "removeButton")
removeButton.position = CGPoint(x: 300,y: 0)
self.addChild(removeButton)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if button.contains(touchLocation) {
image = SKSpriteNode(imageNamed: "image")
image.position = CGPoint(x: 0,y: 300)
self.addChild(image)
}
if removeButton.contains(touchLocation) {
button.removeFromParent()
}
}
override func update(_ currentTime: TimeInterval) {
}
}
You need to understand ARC (Automatic Reference Counting) and what it takes to retain an instance.
In your case you are retaining button, which means it does not delete from memory.
Now when you remove the button from the parent, the button's frame is still exactly the same, the only thing different is button.parent is now nil
When you call button.contains(touchLocation), this is going to pass because you are not concerned with whether or not button is on the scene, all you are doing is checking if the touchLocation is within the frame of the button.
The quickest fix is to check the parent:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if button.parent == self && button.contains(touchLocation) {
image = SKSpriteNode(imageNamed: "image")
image.position = CGPoint(x: 0,y: 300)
self.addChild(image)
}
if removeButton.contains(touchLocation) {
button.removeFromParent()
}
}
In reality though, you need to learn how to better manage your resources. I would recommend trying to find tutorials about how ARC works.
It is normal because you dont use the good button. You use button instead of removeButton.
You should do:
if removeButton.contains(touchLocation) {
removeButton.removeFromParent()
}
Now it should work correctly

How to change button images from another class?

I´m new to coding and I want to know how to change the buton image in the menu class from the setting class.
Here is the menu class:
class Mainmenu: SKScene{
var ratingButton = SKSpriteNode(imageNamed: "RatingButton1")
}
In my setting class I want to change this image to "RatingButton2" just by clicking a button.
Here is the setting class:
class Settings: SKScene {
override func didMove(to view: SKView) {
self.backgroundColor = SKColor.white
let DCButton = SKSpriteNode(imageNamed: "ChangeButton")
DCButton.position = CGPoint(x: self.size.width * 0.2, y: self.size.height * 0.8)
DCButton.setScale(0.53)
self.addChild(DCButton)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let locationUser = touch.location(in: self)
if atPoint(locationUser) == DCButton {
//Change the button image here
}
}
}
}
Swift 3+ Use Notificationcenter to update your button image.
// Define identifier
let notificationName = Notification.Name("NotificationIdentifier")
// Register to receive notification in Mainmenu
NotificationCenter.default.addObserver(self, selector: #selector(receivedNotification), name: notificationName, object: nil)
// Post notification into Settings write this line into "touchesBegan" method
NotificationCenter.default.post(name: notificationName, object: nil)
// Stop listening notification if required.
NotificationCenter.default.removeObserver(self, name: notificationName, object: nil)
Notification Handler
func receivedNotification(notification: Notification){
// Set your button image here...
}
I would not recommend using NSNotificationCenter for this task, it adds a lot of overhead for something that will be rarely used.
Now unfortunately I do not know how your layout is structured, so I am not sure what answer is best for you.
If Menu and Settings are both on the scene at the same time, then you can search the scene to find the button you are looking for:
class Mainmenu: SKScene{
var ratingButton = SKSpriteNode(imageNamed: "RatingButton1")
ratingButton.name = "ratingButton"
}
class Settings: SKScene {
override func didMove(to view: SKView) {
self.backgroundColor = SKColor.white
let DCButton = SKSpriteNode(imageNamed: "ChangeButton")
DCButton.position = CGPoint(x: self.size.width * 0.2, y: self.size.height * 0.8)
DCButton.setScale(0.53)
self.addChild(DCButton)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let locationUser = touch.location(in: self)
if atPoint(locationUser) == DCButton {
let button = scene.childNode(withName:"//ratingButton") as! SKSpriteNode
button.texture = SKTexture(imageNamed:"RatingButton2")
}
}
}
}
If it is not on the scene and you are presenting it, then you want to use userData to assign what your button is before hand
let menu = Mainmenu(...)
menu.userData = ["ratingButton":SKTexture(imageNamed:textureName)] //texture name is either Ratingbutton1 or RatingButton2 depending on state
self.view.presentScene(menu)
Then in the viewDidLoad, just read the userData
func viewDidLoad(...)
{
ratingButton.texture = userData?["ratingButton"]
}

SpriteKit: How to identify the previous scene the user is coming from?

I have a GameOver scene with a button to a Settings scene. (Code shown below) I want to add a button on the MainMenu scene to do the same, send the user to the Settings scene but I want just one “Back” button on the Settings scene. I'm assuming this can be accomplished with an IF statement. If the user comes from A scene move to B else if the user comes from X scene move to Y scene.
How can I tell in SpriteKit which scene is the user coming from?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let nodeUserTapped = atPoint(pointOfTouch)
if nodeUserTapped.name == "BackButton" {
let sceneToMoveTo = SKScene(fileNamed: "GameOverScene")
let gameTransition = SKTransition.fade(withDuration: 0.5)
self.view!.presentScene(sceneToMoveTo!, transition: gameTransition)
}
}
}
In this case you can adopt various solutions to achieve what you wish.
I want to show you just 2 example, a rapid and simple way and a more custom way.
1: - You can use the userData instance property of your next scene to store the current scene, something like:
sceneToMoveTo.userData = NSMutableDictionary()
sceneToMoveTo.userData?.setObject("MenuScene", forKey: "previousScene" as NSCopying)
and when you are in the next scene you can ask to this NSMutableDictionary who is the previous scene as:
guard let previousValue = self.userData?.value(forKey: "previousScene") else { return }
switch previousValue as! String {
case "MenuScene":
//do whatever you want if the previous scene was MenuScene
default:
break
}
2: - You can build an enumeration that contain a list of the scenes where you want to move.
After that, each scene contain a simple var to store the previous scene: it's default is .None but you should fill it when you call the new scene. When you arrive to the SettingsScene and press your back button you can ask to this previousScene var who is the previous scene and decide, through a switch statement, where to go.
// GAMEOVER SCENE
import SpriteKit
enum Scenes : Int {
case MainMenu = 0
case Settings = 1
case GameOver = 2
case None = 3
}
class GameOver: SKScene {
var previousScene:Scenes = .None
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let nodeUserTapped = atPoint(pointOfTouch)
if nodeUserTapped.name == "settingsBtn" {
if let sceneToMoveTo = SKScene(fileNamed: "SettingsScene") {
let gameTransition = SKTransition.fade(withDuration: 0.5)
(sceneToMoveTo as! SettingsScene).previousScene = .GameOver
self.view?.presentScene(sceneToMoveTo, transition: gameTransition)
}
}
}
}
}
// SETTINGS SCENE
import SpriteKit
class SettingsScene: SKScene {
var previousScene:Scenes = .None
override func didMove(to view: SKView) {
print("Current scene loaded: \(type(of:self))")
print("Previous scene was: \(previousScene)")
let label = SKLabelNode.init(text: "BackButton")
addChild(label)
label.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
label.name = "backBtn"
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let nodeUserTapped = atPoint(pointOfTouch)
if nodeUserTapped.name == "backBtn" {
switch previousScene {
case .MainMenu: // go back to menu
if let sceneToMoveTo = SKScene(fileNamed: "MenuScene") {
let gameTransition = SKTransition.fade(withDuration: 0.5)
(sceneToMoveTo as! MenuScene).previousScene = .Settings
self.view?.presentScene(sceneToMoveTo, transition: gameTransition)
}
break
case .GameOver: // re-launch a new game
print("The game was ended")
if let sceneToMoveTo = SKScene(fileNamed: "GameScene") {
let gameTransition = SKTransition.fade(withDuration: 0.5)
(sceneToMoveTo as! GameScene).previousScene = .Settings
self.view?.presentScene(sceneToMoveTo, transition: gameTransition)
}
default:
break
}
}
}
}
}
//MENU SCENE
import SpriteKit
class MenuScene: SKScene {
var previousScene:Scenes = .None
override func didMove(to view: SKView) {
print("Current scene loaded: \(type(of:self))")
print("Previous scene was: \(previousScene)")
}
}
userData is your friend here, it is a dictionary that can be found on any type of SKNode. All you need to do is store the current scene you are in into the userData, and make the transition. This will keep your current scene in memory. Since your current scene is in memory and is not a part of the new scenes node tree, no update method will be called on it. Essentially you have a frozen scene state. Then when you are ready to return to the scene, just pull the instance out of the user data, and present it. Viola, you have transitioned back to your scene the moment that you have left it previously.
Basically, if you want the scene to resume where it left off, do the following:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let nodeUserTapped = atPoint(pointOfTouch)
if nodeUserTapped.name == "SettingButton" {
let sceneToMoveTo = SKScene(fileNamed: "SettingScene")
sceneToMoveTo.userData = ["previousScene":self.scene]
let gameTransition = SKTransition.fade(withDuration: 0.5)
self.view!.presentScene(sceneToMoveTo!, transition: gameTransition)
}
}
}
Then, at any point you want to return to the previous scene, just add the following.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let nodeUserTapped = atPoint(pointOfTouch)
if nodeUserTapped.name == "BackButton" {
if let userData = self.userData, let previousScene = userData["previousScene"] as? SKScene
{
let gameTransition = SKTransition.fade(withDuration: 0.5)
self.view!.presentScene(previousScene, transition: gameTransition)
}
}
}
}

Replay button doesn't work. (Swift, SpriteKit)

Once my game ends, it displays a button for replay, but I can't understand how to let Xcode know if there is a touch after the game has ended. My replay button's code is in didBeginContact Method and is as follows:
func didBeginContact(contact: SKPhysicsContact) {
if (.....) {
..........
} else {
replayButton = SKSpriteNode(imageNamed: "ReplayButton")
replayButton.position = CGPoint(x: size.width / 1.75, y: size.height / 2.5)
replayButton.name = "replayButton"
self.addChild(replayButton)
}
New Swift file:
class button: SKSprideNode {
let replayButton = button(imageNamed: "replayButton")
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
var touch = touches as! Set<UITouch>
var location = touch.first!.locationInNode(self)
var node = self.nodeAtPoint(location)
if (node.name == "replayButton") {
let myScene = EMode(size: self.size)
let reveal = SKTransition.fadeWithDuration(2.0)
self.scene!.view(myScene, transition: reveal) //error on this line
}
}
}
You need to set the replay button's user interaction enabled to true like this:
replayButton.userInteractionEnabled = true
Then you just need to listen for when the button or scene is touched. One way to do that is make a subclass of SKSpriteNode called Button or something, and override the touchesBegan method.
EXAMPLE:
ButtonClass
class ExampleButton: SKSpriteNode {
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
//do stuff here
}
}
Button Initialization
let button = ExampleButton(imageNamed: "ButtonImage")
button.userInteractionEnabled = true