I am developing a game with 3 different SKScenes(scene1, scene2, scene3). In GameViewController i initialize all of them like this:
class GameViewController: UIViewController {
var hero = Hero()
var skView: SKView!
var scene1: SKScene!
var scene2: SKScene!
var scene3: SKScene!
override func viewDidLoad() {
super.viewDidLoad()
// init scenes
scene1 = SKScene(size: view.bounds.size, hero: hero)
scene2 = SKScene(size: view.bounds.size, hero: hero)
scene3 = SKScene(size: view.bounds.size, hero: hero)
scene1.scaleMode = .AspectFill
scene2.scaleMode = .AspectFill
scene3.scaleMode = .AspectFill
// set view
skView = self.view as SKView
// present the first scene
skView.presentScene(scene1)
}
My idea is to present the first scene at first and present(swith to) the other scene later(i.e. when hero is stronger). In each scene the hero sprite was added like this:
func addHero() {
let heroSprite = SKSpriteNode(imageNamed: "hero")
hero.sprite = heroSprite
heroSprite.position = CGPoint(x: size.width/4, y: size.height/2)
addChild(heroSprite)
}
And in update method, hero position is updated by touching.
func update() {
if touching {
hero.position++
}
}
Hero class looks like this:
class Hero {
var sprite: SKSpriteNode?
}
The problem is:
The hero is movable by touching when only the first scene(scene1) is initialized. Which means, the hero is not movable any more with code above.
Can anyone give me some advice what have i done wrong? Thanks in advance!
PS: The complete codes can be found in Github.
The issue is solved by another related question: Sprites must be cleaned before switch scenes with SpriteKit and Swift?
By switching scenes, be sure not to add the sprites twice by checking the init status. For example in "didMoveToView", "init" or "setup" method:
if isInit {
do nothing
} else {
add sprites
isInit = true
}
Related
so I have a menu screen in my app. Swift, SpriteKit, iOS 7-10,11,12,13..
All I want is a user to tap the button on my screen, and have it move them to another screen. This works in other references in my code. However All I did was change the first loading scene in the GameViewController to my own swift file, and this is how the code looks:
import Foundation
import SpriteKit
import GameplayKit
import UIKit
class FrontMenu : SKScene {
var play = SKSpriteNode(imageNamed: "pbut")
var credit = SKSpriteNode()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node : SKNode = self.atPoint(location)
if node.name == "play" {
print("play tapped.")
// this is where I would have placed the transition, but this did not work.
}
else if node.name == "cred" {
NSLog("credits tapped")
}
if location.x <= -90 && location.y >= -160 {
NSLog("Area hit for play")
// This area is still not detected within the app, and there is no button function.
let gameScene = GameScene(fileNamed: "GameScene")
gameScene?.scaleMode = .aspectFill
self.view?.presentScene(gameScene!, transition: SKTransition.fade(withDuration: 0.86))
Basically that does that. You can see where I implemented the transition. Am I missing an import? Am I missing something to change to the overall Plist file? or Maybe something else within GameViewController.swift, I changed the first loading swift file, so that it does load this code first in the app. However it does not detect the touches.
Thank you for your help!
It looks like you are missing code that would set the name for your node. Make sure that in your didMove function that you are setting the name of the node and adding it to the scene.
//inside scene
override func didMove(to view: SKView) {
play.name = "play"
self.addChild(play)
}
The other thing you have to make sure of is that you created a SpriteKit scene file in your project and set the custom class to be FrontMenu
As for switching the file in your GameViewController, you should just be using the name of the file and not the file extension for fileNamed::
//inside GameViewController
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'MneuScene.sks'
if let scene = SKScene(fileNamed: "MenuScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .resizeFill
// Present the scene
view.presentScene(scene)
}
}
}
I'm making a game using spritekit. I'm using a joystick in my game which is declared in a separate SKNode class called 'joystick'.
In this class, I add a UIGesturerecongiser to the view from the class. In the constructor method, one of the parameters is a SKView which is passed from the game scene.
Constructor Method in Joystick class:
init(colour: UIColor, position: CGPoint, skView: SKView) {
//constructor method
self.colour = colour;
self.parentposition = position;
self.view = skView;
super.init();
//setup properties
//user interaction is needed to allow touches to be detected
self.isUserInteractionEnabled = true;
//setup
setup();
}
In the games scene, I initialise the class like this:
class GameScene: SKScene {
func setupJoyStick() {
let joystick1 = Joystick(colour: UIColor.red, position: CGPoint(x: screenwidth / 3, y: screenwidth / 10 * 1.5), skView: self.view!)
self.addChild(joystick1)
}
}
Error:
When I run my app, I get an error because the 'self.view' return nil and because it is forced to unwrap, it causes a fatal error.
Where View is defined:
if let scene = GKScene(fileNamed: "GameScene") {
// Get the SKScene from the loaded GKScene
if let sceneNode = scene.rootNode as! GameScene? {
// Copy gameplay related content over to the scene
sceneNode.entities = scene.entities
sceneNode.graphs = scene.graphs
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = .aspectFill
sceneNode.size = view.bounds.size
// Present the scene
if let view = self.view as! SKView? {
view.presentScene(sceneNode)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
Additional Info:
I'm using:
Xcode 9.2
Swift 4
Sprite Kit
I'm testing it on:
Iphone 6s
Latest version of IOS (non-beta), latest public release.
Can someone please explain why this is happening and how to fix this problem.
Thanks in advance, any help would be appreciated.
Looks like you're trying to get view property of SKScene but it's nil. It's because you didn't presented SKScene.
Unfortunately I haven't worked with SpriteKit but you can find info about view property here and about SKScene here.
The relevant code is copied below and I also put a simple test project on Github to demonstrate this situation:
https://github.com/prinomen/viewPresentSceneTest
I have a GameViewController and a GameScene class. I try to set the scene with SKScene(fileNamed: "GameScene") but that call is not working because the scene does not appear and the print statement after that line is not being called.
I know I could use a different way to set the scene, like this:
let scene = GameScene()
But I'm trying to understand SpriteKit and it bothers me that the code below does not work. In another project I was able to successfully set the scene using SKScene(fileNamed: "GameScene") like in the code below.
Does anyone know why it is not working in this project?
GameViewController.swift
import SpriteKit
class GameViewController: UIViewController {
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
print("SKScene was set")
// Set the scale mode fit the window:
scene.scaleMode = .aspectFill
// Size our scene to fit the view exactly:
scene.size = view.bounds.size
// Show the new scene:
view.presentScene(scene)
}
}
}
}
GameScene.swift
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let logoText = SKLabelNode(fontNamed: "AvenirNext-Heavy")
logoText.text = "Game Scene"
logoText.position = CGPoint(x: 0, y: 100)
logoText.fontSize = 60
self.addChild(logoText)
}
}
I believe you need an .sks file to load scenes like that. You probably deleted it from this project, but still kept it around in the other one.
Here's what the documentation says:
The name of the file, without a file extension. The file must be in
the app’s main bundle and have a .sks filename extension.
I have created a UI elements on main.storyboard which i require to be hidden until the game is over and the once the player tap the screen to dismiss. Main.storyboard is linked to GameViewController therefor all my IBOutlets and IBActions are in there and all my game code is in GameScene. How can i link the view controller to the scene for that the popup image and buttons only appear when it is game over. Would greatly appreciate some help, I have been stuck on this for quite some time now.
This seems to be quite a common problem people have with SpriteKit games so lets go through the difference between SpriteKit games and UIKit apps.
When you make a regular UIKit app, e.g. YouTube, Facebook, you would use ViewControllers, CollectionViews, Views etc for each screen/menu that you see (Home screen, Channel screen, Subscription channel screen etc). So you would use UIKit APIs for this such as UIButtons, UIImageViews, UILabels, UIViews, UICollectionViews etc. To do this visually we would use storyboards.
In SpriteKit games on the other hand it works differently. You work with SKScenes for each screen that you see (MenuScene, SettingsScene, GameScene, GameOverScene etc) and only have 1 ViewController (GameViewController). That GameViewController, which has a SKView in it, will present all your SKScenes.
So we should add our UI directly in the relevant SKScenes using SpriteKit APIs such as SKLabelNodes, SKSpriteNodes, SKNodes etc. To do this visually we would use the SpriteKit scene level editor and not storyboards.
So the general logic would be to load your 1st SKScene as usual from the GameViewController and than do the rest from within the relevant SKScenes. Your GameViewController should basically have next to no code in it beyond the default code. You can also transition from 1 scene to another scene very easily (GameScene -> GameOverScene).
If you use GameViewController for your UI it will get messy really quickly if you have multiple SKScenes because UI will be added to GameViewController and therefore all SKScenes. So you would have to remove/show UI when you transition between scenes and it would be madness.
To add a label in SpriteKit it would be something like this
class GameScene: SKScene {
lazy var scoreLabel: SKLabelNode = {
let label = SKLabelNode(fontNamed: "HelveticaNeue")
label.text = "SomeText"
label.fontSize = 22
label.fontColor = .yellow
label.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
return label
}()
override func didMove(to view: SKView) {
addChild(scoreLabel)
}
}
To make buttons you essentially create a SKSpriteNode and give it a name and then look for it in touchesBegan or touchesEnded and run an SKAction on it for animation and some code after.
enum ButtonName: String {
case play
case share
}
class GameScene: SKScene {
lazy var shareButton: SKSpriteNode = {
let button = SKSpriteNode(imageNamed: "ShareButton")
button.name = ButtonName.share.rawValue
button.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
return button
}()
override func didMove(to view: SKView) {
addChild(shareButton)
}
/// Touches began
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node = atPoint(location)
if let nodeName = node.name {
switch nodeName {
case ButtonName.play.rawValue:
// run some SKAction animation and some code
case ButtonName.share.rawValue:
let action1 = SKAction.scale(to: 0.9, duration: 0.2)
let action2 = SKAction.scale(to: 1, duration: 0.2)
let action3 = SKAction.run { [weak self] in
self?.openShareMenu(value: "\(self!.score)", image: nil) // image is nil in this example, if you use a image just create a UIImage and pass it into the method
}
let sequence = SKAction.sequence([action1, action2, action3])
node.run(sequence)
default:
break
}
}
}
}
}
To make this even easier I would create a button helper class, for a simple example have a look at this
https://nathandemick.com/2014/09/buttons-sprite-kit-using-swift/
You can also check out Apple's sample game DemoBots for a more feature rich example.
This way you can have things such as animations etc in the helper class and don't have to repeat code for each button.
For sharing, I would actually use UIActivityController instead of those older Social APIs which might become deprecated soon. This also allows you to share to multiple services via 1 UI and you will also only need 1 share button in your app. It could be a simple function like this in the SKScene you are calling it from.
func openShareMenu(value: String, image: UIImage?) {
guard let view = view else { return }
// Activity items
var activityItems = [AnyObject]()
// Text
let text = "Can you beat my score " + value
activityItems.append(text as AnyObject)
// Add image if valid
if let image = image {
activityItems.append(image)
}
// Activity controller
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
// iPad settings
if Device.isPad {
activityController.popoverPresentationController?.sourceView = view
activityController.popoverPresentationController?.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
activityController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.init(rawValue: 0)
}
// Excluded activity types
activityController.excludedActivityTypes = [
UIActivityType.airDrop,
UIActivityType.print,
UIActivityType.assignToContact,
UIActivityType.addToReadingList,
]
// Present
view.window?.rootViewController?.present(activityController, animated: true)
}
and then call it like so when the correct button was pressed (see above example)
openShareMenu(value: "\(self.score)", image: SOMEUIIMAGE)
Hope this helps
create reference of GameViewController in GameScene class like this way
class GameScene: SKScene, SKPhysicsContactDelegate {
var referenceOfGameViewController : GameViewController!
}
in GameViewController pass the reference like this way
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = GameScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
scene.referenceOfGameViewController = self
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
by using this line you can pass reference to GameScene class
scene.referenceOfGameViewController = self
Now In GameScene class you can access all the variable of GameViewController like this way
referenceOfGameViewController.fbButton.hidden = false
referenceOfGameViewController.gameOverPopUP.hidden = false
I am having trouble figuring out how to change view controllers when your player collided with an object.
I want to like a menu to pop-up displaying a menu button and a replay button, also so extra buttons that are not important at this moment of time. I am not sure how some of those end of game menus are made, I am thinking switching view controllers, if you know exactly how they are made please tell me.
This is the code I have at the moment, and the only thing it does is display a label that the game is over and when that label is tapped the game will restart:
import Foundation
import AVFoundation
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var movingGround: PPMovingGround!
var square1: PPSquare1!
var wallGen: PPWallGen!
var diamondGen: PPDiamondGen!
var isStarted = false
var isGameOver = false
var isDiamondContact = false
var playerNode: SKNode!
override func didMoveToView(view: SKView) {
//code that is not important was deleted
func collisionWithDiamond() {
isDiamondContact = true
}
func restart() {
let newScence = GameScene(size: view!.bounds.size)
newScence.scaleMode = .AspectFill
view!.presentScene(newScence)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if isGameOver {
restart()
} else {
square1.flip()
}
}
override func update(currentTime: CFTimeInterval) {
}
// MARK: - SKPhysicsContactDelegate
func didBeginContact(contact: SKPhysicsContact) {
if !isGameOver {
gameOver()
} else {
!isDiamondContact
collisionWithDiamond()
}
}
Note: I have deleted code that is unrelated or not necessary.
Updates:
Link to a game play of a game: https://www.youtube.com/watch?v=WUibTETfEQY
SKIP TO 2:32 TO SEE THE GAME OVER SCREEN
Link to image of game over screen: Image
(I was unable to post an image here because I don't have the required 10 rep points yet.)
// Edited Answer
This will be the easiest. Create a new GameOverScene.swift that is a SKScene. Then customize that scene however you want with background image, SKLabelNodes for buttons. Checkout creating buttons in skview to point to different scenes
When the game ends in GameScene,
let gameOverScene: GameOverScene = GameOverScene(size: self.size)
self.view!.presentScene(gameOverScene, transition: SKTransition.doorsOpenHorizontalWithDuration(1.0))
Here is a project that has this implemented, http://www.raywenderlich.com/76741/make-game-like-space-invaders-sprite-kit-and-swift-tutorial-part-2
// First Answer -----------------------------------------------
If you want to switch viewControllers, you will have to present the new viewController like this or with segue,
self.view?.window?.rootViewController?.presentViewController(newView, animated: true, completion: nil)
self.view?.window?.rootViewController?.performSegueWithIdentifier("id", sender: AnyObject)
Otherwise create a SKView and add buttons, then add it to the scene when game is over or add it before, hide it, then show it. Once user picks a choice, remove it or hide it with,
SKView.hidden = false
SKView.hidden = true
Add SKView with,
self.view?.addSubview(SKView)
Simple SKView overlay,
let view1 = SKView(frame: CGRectMake(0, 0, 200, 200))
view1.center = self.view!.center
self.view?.addSubview(view1)
If the game is over and you want to present a selective menu, you could present a UIAlertView that presents the user with whatever options that you want. From there they could restart the game, or they could choose to go to some other view like one that manages their player stats or something ( I don't know what exactly your game is).