I am working on a game in Xcode with swift/spritekit.
I want to add the background from the GameViewController but I have different scenes with each different backgrounds: GameScene / MenuScene / MapScene.
Now how do I detect in gameviewcontroller which scene is running so that I can add the right background to it(as UIImage)?
And how do I set Z-position(Z-index) of an UIImage?
You'll have to track which scene is present yourself. Something like this:
class GameViewController {
var mapScene: SKScene?
var menuScene: SKScene?
var gameScene: SKScene?
var currentScene: SKScene?
func addBackground() {
if currentScene === mapScene {
// ...
} else if currentScene === menuScene {
// ...
} else if currentScene === gameScene {
// ...
}
}
}
If the UIImage is inside an SKNode, then you can set its zPosition property. If it's just a UIImage in normal UIKit elements, then it's implicit based on when it was added as a subview. If you want it on top, remove it from its parent and re-add it:
view.removeFromSuperview()
self.parentView.addSubview(view)
You don't need to detect which scene is running if you simply add the appropriate background image in the scene's implementation. Also, I recommend you avoid adding a UIImage to your view, since you will need to manually remove it when you transition to a new scene. I suggest you create a sprite node with the background image as its texture and add that to your scene with a negative value for its zPosition. Also, you should consider setting the "background" sprite's anchor point and position to CGPointZero.
For example, in 'didMoveToView'
let backgroundNode = SKSpriteNode(imageNamed:"MenuSceneBackground")
backgroundNode.position = CGPointZero
backgroundNode.anchorPoint = CGPointZero
backgroundNode.zPosition = -1000
addChild(backgroundNode)
EDIT: Add the following to remove the gesture recognizers as needed
override func willMoveFromView(view: SKView) {
view.removeGestureRecognizer(swipeGesture)
}
Related
Am trying to develop a game and am not using storyboard at all. Am building it all via code.
I know how to get the safeAreaLayout info, thanks to this forum) for a UIViewController. My thought was I'd be able to attach my GameScene: SKScene to that and go from there. Found out the hard way that's not the case. I have searched so hard and have tried all the example I found find for the likes of
override func viewDidLayoutSubviews()
and
func viewDidAppear(_ animated: Bool)
And every method I've tried returns either sets of 0's, or the full height of the screen 812
I would like to think that there is a proper method for getting the proper safeArea info to the initial GameScene of my app. Is there?
Hei,
You get the 812 pixels height because that's the scene height. In SpriteKit the games are full screen, you don't need to worry about the safe areas.
From GameViewController present the scene you want to display, in my case MainScene.
if let view = self.view as! SKView?
{
// create a new scene of type Main Scene
let scene = MainScene()
// set the new (MainScene) settings
scene.scaleMode = .resizeFill
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
// present the scene
view.presentScene(scene)
}
If you want to know the size of the scene in MainScene, just get the values like this.
class MainScene: SKScene
{
override func didMove(to view: SKView)
{
let screenHeight = self.scene!.size.height
let screenWidth = self.scene!.size.width
}
}
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
}
My sprite node change position on the screen. And I need to detect when sprite move to up, move to down, move to left, move to right.
Define a variable oldPosition and keep track of the sprite position in the update: method:
var oldPosition: CGPoint?
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if Int((oldPosition?.x)!) > Int((sprite?.position.x)!) {
// sprite moves to the left
} else if Int((oldPosition?.x)!) < Int((sprite?.position.x)!) {
// sprite moves to the right
}
if Int((oldPosition?.y)!) > Int((sprite?.position.y)!) {
// sprite moves down
} else if Int((oldPosition?.y)!) < Int((sprite?.position.y)!) {
// sprite moves up
}
// keep track
oldPosition = sprite?.position
}
I hope it helps.
You’ve got two options here. As you’ve not posted any code I can’t really advise which is the best:
If you’ve sub-classed SKNode, then override the frame property to add a didSet observer clause:
override var frame : CGRect {
didSet {
// compute the change in the frame’s origin
let delta : CGPoint = CGPoint(x: frame.origin.x - oldValue.origin.x,
y: frame.origin.y - oldValue.origin.y)
// etc
}
}
See SKNode docs at https://developer.apple.com/documentation/spritekit/sknode
If you don’t want to subclass, you can use NSNotificationCenter to add an observer to your node via addObserver:selector:name:object: . See the documentation at:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html
I’d probably go for option 1.
Hope that helps.
It is my first post on this forum and I apologize in advance if I am doing something not in the right way ! :)
I am making an iphone game with Swift & SpriteKit and I am currently facing a problem. When my app is going to background it calls a function pause (cf. below) but it automatically unpause when the game resumes.
I have seen this very interesting post : Spritekit - Keep the game paused when didBecomeActive (and How to keep SpriteKit scene paused when app becomes active?) but I am stuck.
I don't know how to implement the new SKView class as my View is configured as shown in the below code...
This is how my application works :
class GameViewController: UIViewController {
var scene: GameScene!
override func viewDidLoad() {
super.viewDidLoad()
// Configure the View
let SkView = view as! SKView
SkView.multipleTouchEnabled = true
// Create and configure the scene
scene = GameScene(size: SkView.bounds.size)
scene.scaleMode = .AspectFill
// Present the scene
SkView.presentScene(scene)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("PauseWhenBackGround:"), name:"PauseWhenBackGround", object: nil)
}
func PauseWhenBackGround(notification : NSNotification) {
if scene.Pausing == false{
scene.Pause()
}
}
I've tried the following :
I added a new class which is :
class GameSceneView : SKView {
func CBApplicationDidBecomeActive() {
}
}
Then, I tried to set my view as let SkView = view as! GameSceneView but I got an error saying that I cannot cast the view to MyProjectName.GameSceneView()...
I also tried the following : let SkView! = GameSceneView() as GameSceneView! but I end up with a gray background scene...
Does anyone knows how I can implement the new SKView class to prevent the CBApplicationDidBecomeActive() bug from happening so that the game does not unpause when becoming active ?
Thank you very much in advance ! :)
I think a better way is instead of pausing the whole scene you could create a worldNode in your GameScene and add all the sprites that need to be paused to that worldNode. Its better because if you pause the scene you cannot add pause menu nodes or use touches began etc. It basically gives you more flexibility pausing a node rather than the whole scene.
First create the world node (make global property if needed)
let worldNode = SKNode()
addChild(worldNode)
Than add all the sprites you need paused to the worldNode
worldNode.addChild(sprite1)
worldNode.addChild(sprite2)
Create an enum for your different game states
enum GameState {
case Playing
case Paused
case GameOver
static var current = GameState.Playing
}
Than make a pause func in your game scene
func pause() {
GameState.current = .Paused
//self.physicsWorld.speed = 0 // in update
//worldNode.paused = true // in update
// show pause menu etc
}
And call it like you did above using NSNotification or even better using delegation.
I prefer this method way more than pausing the scene from the gameViewController and also pausing the whole scene.
Create a resume method
func resume() {
GameState.current = .Playing
self.physicsWorld.speed = 1
worldNode.paused = false
// remove pause menu etc
}
and finally add this to your update method
override func update(currentTime: CFTimeInterval) {
if GameState.current == .Paused {
self.physicsWorld.speed = 0
worldNode.paused = true
}
Spritekit sometimes tends to resume the game when the app becomes active again or when an alert such as for in app purchases is dismissed. To avoid this I always put the code to actually pause the game in the update method.
Hope this helps.
I would like to know how to remove my SKNodes when they are off screen to help my game run more smoothly.
How To Do This On Sprite Kit
Thanks So Much
Here is an easy solution in Swift 4:
class GameScene: SKScene {
let s = SKLabelNode(fontNamed: "Chalkduster")
override func didMove(to view: SKView) {
s.text = "test"
s.fontSize = 50
addChild(s)
let moveRight = SKAction.moveBy(x: 40, y: 0, duration: 0.5)
s.run(SKAction.repeatForever(moveRight))
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if ((s.parent != nil) && !intersects(s)) {
s.removeFromParent()
print("Sprite removed.")
}
}
}
You have a sprite (in this case a SKLabelNode but any sprite node will do) that is moving horizontally and you want to delete this sprite once is out of the frame bounds.
You can use the intersects function to check this and then remove that sprite from its parent. I have also checked that the sprite has a parent before removing it (by checking if s.parent is not nil) as we wish to remove the sprite only once.
https://stackoverflow.com/a/24195006/2494064
Here is a link to an answer that removes nodes that go off the top of the screen. You would just have to replicate this to cover the entire border and set all of the walls to have the same contactBitMask values.
Basically the logic is to remove the SKSpriteNodes when they contact physicsbodies that you have resting just outside the visible screen.