So I writing a program in swift, and am having trouble understanding the proper communication between GameScene and GameViewController:
Basically I have 'savefiles' which are plists, when loaded using a function "self.loadFromPlist(fileName: )" are loaded by clearing the previous array of nodes, and drawings a set of SKShapeNodes based on attributes from the dictionary elements of the loaded Plist
I needed a file browser, which I've implemented from a Cocoapod (FileBrowser), that must be called in the "GameViewController"
My Problem is this:
When calling my 'loadFromPlist' function from inside 'viewDidLoad' like this (to initialize the project) - the function works and the gamescene is drawn:
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// 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)
let gameScene = scene as! GameScene
gameScene.loadFromPlist(scoreFileName: gameScene.defaultScore)
gameScene.gameViewControllerDelegate = self
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
When I try to do same thing in my own function in the GameViewController (called view a GameviewDelete in GamScene), the code executes the line and I can follow it with the debugger into the 'loadFromPlist' function in the gamescene, it executes the for loop drawing shapes etc, but it just does nothing.
func callloadplist() {
var loadedfilename2:String!
let file = FileBrowser()
present(file, animated: true, completion: nil)
if let scene = GameScene(size: view.frame.size) as? GameScene {
var strippedfilename: String?
file.didSelectFile = { (file: FBFile) -> Void in
let loadedfilename = file.displayName
scene.filebrowserselectedfilename = loadedfilename
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let myskscene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
let gameScene = myskscene as! GameScene
let strippedfilename = String(loadedfilename.dropLast(6))
print(strippedfilename)
gameScene.loadFromPlist(scoreFileName: "output")
}
}
}
}
}
So the function call working inside of 'viewDidLoad()' and also as 'self.loadFromPlist(fileName: )' in GameScene.swift, but cannot be called from a custom method/ function I've made in GameViewControllers -- Any Insights?
Thanks
So file.didSelectFile is setting up a closure to respond to the event of the file being selected. This was then running the function called on a different instance of a 'GameScene' Object.
I was able to refine my question and answer it here:
Swift - SKSprite Kit - Change Gamescene Variables from FileBrowser Closure
Related
I want to write the iOS Game Example that uses SpritKit in Swift, which is provided with Xcode only in code. That means I don’t want to use the GameScene.sks, actions.sks and the main.storyboard. I know how to write it without storyboard, but I can’t get it working without the .sks files. Can you say what I must change or can you provide me with a full project?
You will first need to have a View Controller. You can adjust the properties how you would like them. Here is mine:
import UIKit
import SpriteKit
class GameViewController: UIViewController {
// MARK: View Controller overrides
override func viewDidLoad() {
super.viewDidLoad()
view = SKView(frame: view.bounds)
if let view = self.view as! SKView? {
// Initialise the scene
let scene = GameScene(size: view.bounds.size) // <-- IMPORTANT: Initialise your first scene (as you have no .sks)
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
// Scene properties
view.showsPhysics = false
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
Then, you create a class for your first scene. Mine is called GameScene which was initialised in the view controller. Make sure this is a subclass of SKScene. It will look something like this:
import SpriteKit
class GameScene: SKScene {
/* All Scene logic (which you could extend to multiple files) */
}
If you have any questions, let me know :)
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.
I have 2 SKScene.
GameScene
WelcomeScene.swift
I wanted my screen to load up "WelcomeScene" first instead of the default GameScene.Thus, I've edited "GameViewController.swift" accordingly. However, all I get is a blank screen, and couldn't locate what went wrong.
Here's my code in "GameViewController".
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
if let scene = SKScene(fileNamed: "WelcomeScene") {
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
If you have a corresponding SKS file created in the Scene editor then you load your scene like so...
// Load the SKScene from WelcomeScene.sks'
if let welcomeScene = WelcomeScene(fileNamed: "WelcomeScene") {
welcomeScene.scaleMode = .aspectFill
view.presentScene(welcomeScene)
}
if you do not have a corresponding SKS file created in the scene editor but would rather load the scene that was created in code use...
welcomeScene= WelcomeScene(size: skView.bounds.size)
welcomeScene.scaleMode = .aspectFill
skView.presentScene(welcomeScene)
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'm using spritekit with swift, and I'm just trying to present a new scene...but something is wrong and throwing errors. I was pretty sure this was the right syntax, and this is really throwing me for a loop.
The line
let skView = self.view as SKView
is giving me the error "SKView? is not convertible to SKView
Any advice is appreciated!
*my current code is below:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches{
let location = touch.locationInNode(self)
if(self.nodeAtPoint(location) == self.playButton)
{
var scene = PlayScene(size: self.size)
let skView = self.view as SKView
// Configure the view.
/* 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 problem is the view property on SKScene is an optional (SKView?) because an SKScene doesn't necessarily have a containing view; what's it contained in before its been presented? Nothing.
To solve your problem you need to check the scene has a view by unwrapping the view property, by using optional binding for example:
if let view = self.view {
let scene = PlayScene(size: self.size)
scene.scaleMode = .AspectFill
view.presentScene(scene)
}
You can be pretty certain your SKScene has been presented (and thus view isn't nil) if someone is pressing on it - therefore you could instead force unwrap view, like so:
let scene = PlayScene(size: self.size)
scene.scaleMode = .AspectFill
view!.presentScene(scene)
Also, it's not required that you configure the SKView again (skView.ignoresSiblingOrder = true) since this, presumably, was already done in GameViewController.
Edit:
The line let skView = self.view as! SKView is likely from GameViewController (a subclass of UIViewController). If you take a look at the documentation for UIViewController and SKScene you'll see they both have a view property:
class UIViewController: /* Superclass and Protocols */ {
var view: UIView!
// ...
}
class SKScene: SKEffectNode {
weak var view: SKView?
// ...
}
However their types differ which means the way you use them differs. In the case of SKScene see above about unwrapping. In the case of UIViewController, you need to cast to SKView with the line:
let skView = self.view as! SKView
because UIView doesn't have the methods necessary for presenting an SKScene. (For more information on casting I'd recommending you take a look at The Swift Programming Language: Type Casting)
Normally you wouldn't be allowed to cast from UIView to SKView (keep in mind that SKView is a subclass of UIView). Try the following in Playgrounds:
let view = UIView()
let skView = view as! SKView
You should get an error something along the lines of:
Could not cast value of type 'UIView' (0x1041d0eb0) to 'SKView'
(0x10da10718)
However you can cast from UIView to SKView in your GameViewController because the Custom Class of GameViewController's view has been set to SKView:
I hope that helps clear up any confusion you had.
try this :
let sKView = self.view?.scene?.view
sKView?.presentScene(scene)
Best regards,
Yassine