Sometime ago I was having a problem with pausing my game when didBecomeActive, then I found a solution which I thought was working, until now.
I found out that iOS automatically pauses my game (all of it) when I leave, not terminate, a game; and when coming back (didBecomeActive), it unpauses. As my point was to pause a singular layer (gameLayer), I created a boolean variable and an if condition to check if my game is paused or not.
If checkPause == false (not paused) -> it'll call a pausing function (that works great) when coming back to the game (moments after?! being unpaused by the system)
If checkPause == true (paused) -> it'll will set gameLayer.paused = false (unpaused by the system) to true (once it was paused before leaving the game)
Basically gameLayer is not being paused when coming back. It looks like iOS unpauses my game after didBecomeActive function.
I made an example project with it's code below (it's all commented and the simplest it could get)
If you want, you can download here.
import SpriteKit
class GameScene: SKScene {
//Declarations
var gameLayer = SKNode()
var pauseLayer = SKNode()
var checkPause = Bool() //checkPause == true -> gameLayer is paused | checkPause == false -> gameLayer is not paused
var enemy = SKSpriteNode()
var pauseButton = SKSpriteNode()
var playButton = SKSpriteNode()
//"Cage" objects in the screen
func cageObjects(){
//"caging" every object
let borderBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
}
//Setup
func setupPauseButton(){
//Pause
pauseButton = SKSpriteNode (imageNamed: "pause")
pauseButton.setScale(1)
pauseButton.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 1.2)
}
func setupPlayButton(){
//Play
playButton = SKSpriteNode (imageNamed: "play")
playButton.setScale(1)
playButton.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 1.2)
}
func setupEnemy(){
//Enemy
enemy = SKSpriteNode(imageNamed: "enemy")
enemy.position = CGPointMake(self.frame.width / 1, self.frame.height / 2)
enemy.name = "enemy"
enemy.setScale(0.5)
enemy.physicsBody?.affectedByGravity = false
}
//Layers
func createGameLayer(){
//pauseButton
setupPauseButton()
gameLayer.addChild(pauseButton) //add pauseButton to gameLayer
}
func createPauseLayer(){
//playButton
setupPlayButton()
pauseLayer.addChild(playButton) //add playButton to pauseLayer
}
//Spawn
func spawnEnemy(){
//Start spawning, moving and removing
let spawnEnemy = SKAction.runBlock({
() in
//Spawn enemy
self.setupEnemy()
self.gameLayer.addChild(self.enemy)
//Move left and remove when go off screen
let frameWidth = CGFloat(self.frame.width)
let moveEnemy = SKAction.moveByX(-frameWidth - 50, y: 0, duration: NSTimeInterval(0.0028 * frameWidth)) //duration: faster or slower
let removeEnemy = SKAction.removeFromParent()
var moveAndRemove = SKAction()
moveAndRemove = SKAction.sequence([moveEnemy, removeEnemy])
self.enemy.runAction(moveAndRemove)
})
//Spawn enemy each 2 seconds
let spawnEnemyDuration = SKAction.repeatActionForever(SKAction.sequence([spawnEnemy, SKAction.waitForDuration(2.0)]))
gameLayer.runAction(spawnEnemyDuration)
}
override func didMoveToView(view: SKView) {
/* Setup your scene here */
print ("didMoveToView")
registerAppTransitionObservers()
cageObjects()
checkPause = false
createGameLayer()
createPauseLayer()
self.addChild(gameLayer)
spawnEnemy()
}
//Game states
func pauseState(){
//Pause game
pauseButton.hidden = true //hide pauseButton
gameLayer.paused = true //pause gameLayer
checkPause = true //game is paused
self.addChild(pauseLayer) //add pauseLayer
}
func playState(){
pauseLayer.removeFromParent() //remove pauseLayer
//Resume game
checkPause = false //game is not paused
gameLayer.paused = false //unpause gameLayer
pauseButton.hidden = false //show pauseButton
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
//When touch buttons/screen
for touch in touches{
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if node == pauseButton{
pauseState()
}
else if node == playButton{
playState()
}
}
}
//Functions from AppDelegate
func registerAppTransitionObservers(){
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameScene.applicationDidBecomeActive), name: UIApplicationDidBecomeActiveNotification, object: nil)
}
//Just launched
func applicationDidBecomeActive(){
print("DidBecomeActive")
//gameLayer unpausing problem solving attempt
if checkPause == true{
gameLayer.paused = true
}
//Pause when game is not paused and user leave the screen OR when game is launched
else if checkPause == false{
pauseState()
}
}
}
Now I have your source, I see your problem.
You need to preserve your pause state:
class GameScene : SKScene
{
...
override var paused: Bool
{
get{
return super.paused;
}
set{
let value = self.gameLayer.paused
super.paused = newValue;
self.gameLayer.paused = value;
}
}
}
For some reason, scene paused is deciding to unpause all nodes under it
Also, you should pause your game when you leave the app, not when you come back.
//Functions from AppDelegate
func registerAppTransitionObservers(){
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameScene.applicationWillResign), name: UIApplicationWillResignActiveNotification, object: nil)
}
func applicationWillResign(){
print("WillResignActive")
pauseState()
}
And you can get rid of that check paused variable, that is redundant bloat code.
Related
I am working on a simple runner game, I have just 3 shapenodes a player node which is just a shapenode (redbox, 30 x 30) with left/right buttons basically two shapenodes.
I have managed to set flags and increment player node's position X and move it left and right with buttons in touchesBegan method and stop the movement in touchesEnded method everything works fine.
The problem is that if I touch lets say right button, the player moves to the right as expected. But if I touch and move my finger out of the button boundaries in any direction, the player keeps moving constantly as long as I touch that button again then it stops. Otherwise it keeps moving and the other button does not stop it as well.
I used touchesMoved method to stop the movement of the player when I move my finger but this does not fix the issue since touchesMoved method triggers with the slightest movement of the touch even when my finger slightly shakes.
I want the touchesMoved method called when my finger is moved off the button, not on the button.
How can I stop the movement of a sprite when touched and moved out of the node ( button ) boundaries?
You have 3 touch events:
touchesBegan; check if user tap is on one the move buttons (if yes - move player, if not - return)
touchesMoved; check if the user tap is still inside the boundaries of the move button (if yes - continue moving player, if not - stop movement)
touchesEnded; stop player movement
You can solve your problem by checking if the user finger is over the button area.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
// nothing here
let touch = touches.first
let position_in_scene = touch!.location(in: self)
check_button(position: position_in_scene)
}
func check_button(position : CGPoint)
{
if right_button.contains(position)
{
// the tap was inside button boundaries
player.move_left()
}
else
{
// the tap was outside button boundaries
player.stop()
}
}
I have this code running :
class GameScene: SKScene {
var player = SKSpriteNode()
var left = SKSpriteNode()
var right = SKSpriteNode()
var jumpbtn = SKSpriteNode()
var leftbtnPressed = false
var rightbtnPressed = false
var jump = false
var hSpeed: CGFloat = 2.0
override func didMove(to view: SKView) {
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
self.physicsBody = border
jumpbtn = self.childNode(withName: "jumpbtn") as! SKSpriteNode
player = self.childNode(withName: "r1") as! SKSpriteNode
left = self.childNode(withName: "leftbtn") as! SKSpriteNode
right = childNode(withName: "rightbtn") as! SKSpriteNode
}//didmove to view
func moveRight(){
player.position = CGPoint(x: player.position.x + hSpeed, y: player.position.y)
}
func moveLeft (){
player.position = CGPoint(x: player.position.x - hSpeed, y: player.position.y)
}
func movement (){
if rightbtnPressed == true {
moveRight()
}
if leftbtnPressed == true {
moveLeft()
}
}//movement
func CheckButton(position : CGPoint) {
if right.contains(position){
rightbtnPressed = true
}
if left.contains(position) {
leftbtnPressed = true
}
else {
leftbtnPressed = false
rightbtnPressed = false
}
}// checkbutton
func jumping (){
if jump == true {
player.physicsBody?.applyImpulse(CGVector(dx:0, dy: 5.0))
}
}// jumping
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let positionINScene = touch.location(in: self)
let touchednode = self.atPoint(positionINScene).name
if touchednode == "rightbtn"{
rightbtnPressed = true
}
if touchednode == "leftbtn"{
leftbtnPressed = true
}
if touchednode == "jumpbtn"{
jump = true
jumping()
}
}
}//touchesbegan
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let positionInScene = touch?.location(in: self)
CheckButton(position: positionInScene!)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let positionINScene = touch.location(in: self)
let touchednode = self.atPoint(positionINScene)
if touchednode.name == "rightbtn"{
rightbtnPressed = false
}
if touchednode.name == "leftbtn"{
leftbtnPressed = false
}
if touchednode.name == "jumpbtn" {
jump = false
}
}
}//touchesEnded
override func update(_ currentTime: TimeInterval) {
movement()
}
When i touch either left or right button the player starts moving as expected but the problem is while the player is moving let say to the right if i touch the jump button the player pauses moving where it should be moving and jumping
in simple words i can not move and jump at the same time
I want to build an inventory for my SpriteKit game. For this I want to go to another SKScene File which represents my inventory when I press the pause button. My problem is that when I make a transition from my InventoryScene back to my GameScene, the GameScene loads completely new. This is the transition code from my GameScene class:
func loadInventory(){
if !transitionInProgress{
transitionInProgress = true
if let scene = InventoryScene(fileNamed: "Inventory"){
Globals.InventoryGlobals.levelBeforeSwitch = currentLevel
scene.scaleMode = .aspectFill
let transition = SKTransition.push(with: .down, duration: 0.5)
self.view?.presentScene(scene, transition: transition)
}
}
}
With this code I'll go to my InventoryScene.
Now in my InventoryScene I want to go back to my GameScene with this:
func loadLevel(level: String){
if !transitionInProgress{
transitionInProgress = true
if let scene = GameScene(fileNamed: level){
scene.currentLevel = level
scene.scaleMode = .aspectFill
let transition = SKTransition.doorsOpenHorizontal(withDuration: 1)
self.view?.presentScene(scene, transition: transition)
}
}
}
The transitions are working but my problem is that the GameScene loads completely new, which is obviously because I instantiate a new GameScene. So when the player is in the middle of the level and then goes to the Inventory and back to the GameScene, the player is back at the beginning of the level. If I go from the Inventory back to the scene I want to be the GameScene as it was before (player position, enemy health, etc.)
Has anyone an idea how i can do this?
Retaining your scene is very simple, all you need to do is retain the scene with a strong reference
In your ViewController, it is as simple as storing a variable
class ViewController : UIViewController
{
var gameScene = GameScene(fileNamed:"GameScene")
}
Now for as long as your view controller is alive, your scene will be alive.
To access it, you just need to find a way to tell the MenuScene where your view controller is, then present the scene.
class ViewController : UIViewController
{
var gameScene = GameScene(fileNamed:"GameScene")
lazy var skView : SKView = self.view as! SKView
func gotoMenu()
{
let menu = MenuScene(fileNamed"MenuScene")
menu.viewController = self
skView.presentScene(menu)
}
}
class MenuScene : SKScene
{
var viewController : ViewController!
func returnToGame()
{
view.presentScene(viewcontroller.gameScene)
}
}
But, what if you don't want to use custom SKScene classes all the time, use a view controller, or would rather rely on components, why isn't there a convenient way to go back to a scene.
Well my friend, there is, and it is where userData comes into play
class GameScene : SKScene
{
func gotoMenu()
{
let menu = MenuScene(fileNamed:"MenuScene")
menu.userData = menu.userData ?? ["":Any]()
menu.userData["backToScene"] = self
view.presentScene(menu)
}
}
class MenuScene : SKScene
{
func returnToGame()
{
guard let userData = userData, let scene = userData["backToScene"] as? SKScene
view.presentScene(scene)
}
}
Since we are retaining it in the user data, we can now present the old scene anywhere we have access to the menu scene.
userData is also great in transferring inventory, of course I would create a class to manage the inventory, and just pass the reference via userData
Now, to create a menu that overlays the current scene, that is as simple as applying a new node onto your scene.
You can even use a separate SKS file to layout your menu, and overlay it:
class GameScene : SKScene
{
let menu = MenuScene(fileNamed:"MenuScene")
func overlayMenu()
{
scene.addChild(menu) //You probably want to add an SKCameraNode, and add it to the camera instead
}
override func update(currentTime: CFTimeInterval)
{
if menu.parent != nil
{
menu.update(currentTime:currentTime) //do this only when you need to have a constant update call, be sure to include additional functionality like `didFinishUpdate` in the approprate functions when needed
}
}
}
Of course now would be a good time to develop what is called a worldNode, also may be referred to as gameNode
Essentially what this node is, is the node that holds all your game elements.
This allows you to add overlay nodes that can pause your game.
Your scene hierarchy would like like this:
SKScene
--worldNode
----all nodes that belong in the game
--menuNode
----all nodes that belong on the menu
Now at any time, menu can set the worldNode's isPaused state to true, allowing the game to pause and still giving you the ability to interact with the menuNode
I do layover windows all the time in my Spritekit games, and it doesn't have to be as complicated as you are thinking. Here is how you can do it all in Spritekit without leaving the Scene.
Create a new class which is a subclass of SKSpriteNode for your InventoryDialog.
import SpriteKit
protocol InventoryDialogDelegate: class {
func close()
}
class InventoryDialog: SKSpriteNode {
private var closeButton: SKSpriteNode!
weak var delegate: InventoryDialogDelegate?
init(size: CGSize) {
super.init(texture: nil, color: .clear, size: size)
name = "inventoryDialog"
//do some background design work here
let background = SKSpriteNode(color: .white, size: self.size)
background.zPosition = 1
addChild(background)
closeButton = SKSpriteNode(texture: SKTexture(imageNamed: "closeButton"))
closeButton.position = CGPoint(x: self.size.width / 2 - closeButton.size.width / 2, y: self.size.height / 2 - closeButton.size.height / 2)
closeButton.zPosition = 2
addChild(closeButton)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if closeButton.contains(touchLocation) {
close()
}
}
func close() {
self.delegate?.close()
}
}
inside your GameScene file
class GameScene: SKScene {
var inventoryDialog: InventoryDialog!
var openButton: SKSpriteNode!
override func didMove(to view: SKView) {
openButton = SKSpriteNode(texture: SKTexture(imageNamed: "openButton"))
openButton.position = CGPoint(x: self.size.width / 2 - closeButton.size.width / 2, y: self.size.height / 2 - closeButton.size.height / 2)
openButton.zPosition = 2
addChild(openButton)
}
func displayInventoryDialog() {
backgroundBlocker = SKSpriteNode(imageNamed: "background3")
backgroundBlocker.size = self.size
backgroundBlocker.zPosition = 4999
addChild(backgroundBlocker)
inventoryDialog = InventoryDialog(size: CGSize(width: 500, height: 800))
inventoryDialog.delegate = self
inventoryDialog.zPosition = 5000
addChild(inventoryDialog)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//pause any action that you don't want running while the dialog is open
gameLayer.isPaused = true
let touch = touches.first
let touchLocation = touch!.location(in: self)
if openButton.contains(touchLocation) {
displayInventoryDialog()
}
}
}
//MARK: - InventoryDialogDelegate Methods
extension GameScene: InventoryDialogDelegate {
func close() {
//at this point you could update any GUI nesc. based on what happened in your dialog
backgroundBlocker.removeFromParent()
inventoryDialog?.removeFromParent()
gameLayer.isPaused = false
}
}
here is exactly what you wanted.
class GameScene: SKScene {
class InventoryManager: UIView {
override init(frame: CGRect) {
super.init(frame: CGRect(x: 0, y: 0, width: 300, height: 200))
//here is where you set up anything you want to display inside the view
self.backgroundColor = UIColor.green
let Button = UIButton()
Button.frame = CGRect(x: 0, y: 0, width: 40, height: 20)
Button.backgroundColor = UIColor.red
let TouchView = UIView()
TouchView.frame = self.frame
Button.center.x = TouchView.center.x
Button.center.y = TouchView.center.y
TouchView.backgroundColor = UIColor.brown
self.addSubview(TouchView)
//make sure you add any objects that you want to the TouchView and not to the self
TouchView.addSubview(Button)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
var InventoryView = InventoryManager()
var Node = SKSpriteNode()
override func didMove(to view: SKView) {
//this keeps the view centered in the screen
InventoryView.center.x = (self.view?.center.x)!
InventoryView.center.y = (self.view?.center.y)!
Node = SKSpriteNode(color: UIColor.blue, size: CGSize(width: 40, height: 40))
Node.position = CGPoint(x: self.frame.size.width / 3, y: self.frame.size.height / 3)
self.addChild(Node)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if !(self.view?.frame.contains(location))! {
InventoryView.removeFromSuperview()
}
if Node.contains(location) && InventoryView.isDescendant(of: self.view!) {
self.view?.addSubview(InventoryView)
}
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
clicking the blue button adds it to the scene again and clicking anywhere else removes it from the scene
I would like to know if anyone has a way to move a SKSpriteNode in SpriteKit Watch Game Using WKCrownDelegate. either in Y direction or X direction
Hope this Helps Others that are Starting with WatchKit.
This is my GameElement.swift:
extension GameScene {
func addPlayer() {
player = SKSpriteNode(imageNamed: "Spaceship")
player.setScale(0.15)
player.position = CGPoint(x: 5, y: -60)
player.name = “ONE”
player.physicsBody?.isDynamic = false
player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
player2 = SKSpriteNode(imageNamed: "Spaceship")
player2.setScale(0.15)
player2.position = CGPoint(x: 5, y: -60)
player2.name = “ONE”
player2.physicsBody?.isDynamic = false
player2.physicsBody = SKPhysicsBody(rectangleOf: player2.size)
addChild(player)
addChild(player2)
playerPosition = player.position
}
}
This is my GameScene.swift:
class GameScene: SKScene, SKPhysicsContactDelegate, WKCrownDelegate {
var watchParticles:SKEmitterNode!
var player:SKSpriteNode!
var player2:SKSpriteNode!
var playerPosition:CGPoint!
override func sceneDidLoad() {
self.scaleMode = SKSceneScaleMode.aspectFill
watchParticles = SKEmitterNode(fileNamed: "watchParticles")
addChild(watchParticles)
self.physicsWorld.gravity = CGVector(dx: 0 , dy: 0)
physicsWorld.contactDelegate = self
addPlayer()
}
func moveSprite(player : SKSpriteNode,moveDirection: String){
switch moveDirection {
case "UP":
print("UP")
player.childNode(withName: "ONE")?.physicsBody?.applyImpulse(CGVector(dx: 60, dy: 0))
case "DOWN":
print("DOWN")
player.childNode(withName: "ONE")?.physicsBody?.applyImpulse(CGVector(dx: -60, dy: 0))
case "STOP":
print("STOPPED")
player.childNode(withName: "ONE")?.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
default:
break
}
}
}
This is My InterfaceController.swift:
class InterfaceController: WKInterfaceController, WKCrownDelegate {
#IBOutlet var skInterface: WKInterfaceSKScene!
private var moveDirection = ""
private var game = GameScene()
private var player = GameScene()
override func awake(withContext context: Any?) {
super.awake(withContext: context)
crownSequencer.delegate = self
crownSequencer.focus()
// Configure interface objects here.
// 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
// Present the scene
self.skInterface.presentScene(scene)
crownSequencer.delegate = self
crownSequencer.focus()
// Use a value that will maintain a consistent frame rate
self.skInterface.preferredFramesPerSecond = 30
}
}
func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double) {
if rotationalDelta > 0{
moveDirection = "UP"
game.moveSprite(player: player.player, moveDirection: moveDirection)
}else if rotationalDelta < 0{
moveDirection = "DOWN"
game.moveSprite(player: player.player, moveDirection: moveDirection)
}
}
func crownDidBecomeIdle(_ crownSequencer: WKCrownSequencer?) {
moveDirection = "STOP"
game.moveSprite(player: player.player, moveDirection: moveDirection)
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}
Welcome to SO!
Ok, there is a lot to unpack so get some popcorn... I think that you are on the right track here, mostly you need to check for nil when you get errors.
First, this is wrong in your interface controller, and the part that concerned me. Here, you are just instantiating new GameScene instances, which are completely separate from the gameScene instance created by your interface controller a few lines down. Then, you were sending the crown delegate functions to these totally empty gameScenes.:
private var game = GameScene() // You are referencing nothing here, just creating a new gamescene.
private var player = GameScene() // I don't think that player is supposed to be a gamescene!
I fixed it by doing assigning the actual gameScene you want to use to the properties (so they can be used by the crown delegate).
private var game: GameScene!
lazy private var player: SKSpriteNode = self.game.player
override func awake(withContext context: Any?) {
// ... Stuff...
if let scene = GameScene(fileNamed: "GameScene") {
game = scene
This was also changed to represent the new code in your crown delegates:
game.moveSprite(player: player, moveDirection: moveDirection)
In addPlayer you were doing this:
player.physicsBody?.isDynamic = true // This needs to go AFTER you init your pb.
player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
...and I fixed it by swapping the lines.
Personally I like to do the following, to help ensure no small mistakes are made:
let pb = SKPhysicsBody(...)
pb.isDynamic = true
player.physicsBody = pb
moveSprite had a bunch of issues with it, so I'm not going to enumerate them as I did above. Check out what I did then ask me if you have any questions. Basically, you were doomed with this func from the start, because you were calling this method from the interface controller with the whacked out player values that were all nil.
Also, the .applyImpulse was giving me pretty bad controls, so I changed it to a plain adjustment of .position. There is still a small issue with coasting before the player stops, but that can be handled in another question :) (note, I only tested this on simulator.. may not be an issue on-device).
Also also, I hate errors caused by spelling mistakes in strings, so I converted this to an enum for you.
func moveSprite(player : SKSpriteNode, moveDirection: Direction) {
// This will give us an equal amount of pixels to move across the watch devices:
// Adjust this number for shorter / longer movements:
let percentageOfScreenToMovePerRotation = CGFloat(1) // One percent
let modifier = percentageOfScreenToMovePerRotation / 100
let amountToMove = self.frame.maxX * modifier
switch moveDirection {
case .UP:
player.position.x += amountToMove
case .DOWN:
player.position.x -= amountToMove
case .STOP:
break
}
}
The real moral of the story here is to check for nil. If you just use someOptional?.someMethod() all the time, then you likely will not be able to easily determine whether or not someMethod() is actually being called or not.. thus, you don't know if the problem is with the calling logic, the method, or with the object not existing, and etc.
Force unwrapping is frowned upon in production code, but IMO it is extremely valuable when first starting out--because it helps you to quickly identify errors.
Later on, you can start using things like if let and guard to help check for nil without crashing your programs, but that adds more clutter and complexity to your code when you are trying to just learn the basics of a new API and language.
And as a final tip, try to not use hard-coded strings whenever possible: put them into an enum or a constant as I have in your code:
// Because I hate string spelling erros, and you probably do too!
enum Direction {
case UP, DOWN, STOP
}
// Because I hate errors related to spelling in strings:
let names = (ONE: "ONE", TWO: "TWO")
Here are the two files in their entirety.. note, I had to comment out a few things to get it to work in my project:
GameScene:
// Because I hate string spelling erros, and you probably do too!
enum Direction {
case UP, DOWN, STOP
}
class GameScene: SKScene, SKPhysicsContactDelegate, WKCrownDelegate {
var watchParticles:SKEmitterNode!
var player: SKSpriteNode!
var player2: SKSpriteNode!
var playerPosition:CGPoint!
// Because I hate errors related to spelling in strings:
let names = (ONE: "ONE", TWO: "TWO")
func addPlayer() {
player = SKSpriteNode(color: .blue, size: CGSize(width: 50, height: 50))
// player = SKSpriteNode(imageNamed: "Spaceship")
// player.setScale(0.15)
player.position = CGPoint(x: 5, y: -60)
player.name = names.ONE
player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
player.physicsBody!.isDynamic = true // This was placed *before* pb initialzier (thus never got called)
player2 = SKSpriteNode(color: .yellow, size: CGSize(width: 50, height: 50))
// player2 = SKSpriteNode(imageNamed: "Spaceship")
// player2.setScale(0.15)
player2.position = CGPoint(x: 5, y: -60)
player2.name = names.TWO
player2.physicsBody = SKPhysicsBody(rectangleOf: player2.size)
player2.physicsBody!.isDynamic = false // This was placed *before* pb initialzier (thus never got called)
addChild(player)
addChild(player2)
playerPosition = player.position
}
override func sceneDidLoad() {
self.scaleMode = SKSceneScaleMode.aspectFill
//watchParticles = SKEmitterNode(fileNamed: "watchParticles")
//addChild(watchParticles)
self.physicsWorld.gravity = CGVector.zero
physicsWorld.contactDelegate = self
addPlayer()
}
func moveSprite(player : SKSpriteNode, moveDirection: Direction) {
// This will give us an equal amount of pixels to move across the watch devices:
// Adjust this number for shorter / longer movements:
let percentageOfScreenToMovePerRotation = CGFloat(1) // One percent
let modifier = percentageOfScreenToMovePerRotation / 100
let amountToMove = self.frame.maxX * modifier
switch moveDirection {
case .UP:
player.position.x += amountToMove
case .DOWN:
player.position.x -= amountToMove
case .STOP:
break
}
}
}
InterfaceController:
class InterfaceController: WKInterfaceController, WKCrownDelegate {
#IBOutlet var skInterface: WKInterfaceSKScene!
private var moveDirection = Direction.STOP
private var game: GameScene!
lazy private var player: SKSpriteNode = self.game.player
override func awake(withContext context: Any?) {
super.awake(withContext: context)
crownSequencer.delegate = self
crownSequencer.focus()
if let scene = GameScene(fileNamed: "GameScene") {
game = scene // VERY IMPORTANT!
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
self.skInterface.presentScene(scene)
crownSequencer.delegate = self
crownSequencer.focus()
// Use a value that will maintain a consistent frame rate
self.skInterface.preferredFramesPerSecond = 30
}
else {
fatalError("scene not found")
}
}
func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double) {
if rotationalDelta > 0{
moveDirection = .UP
game.moveSprite(player: player, moveDirection: moveDirection)
} else if rotationalDelta < 0{
moveDirection = .DOWN
game.moveSprite(player: player, moveDirection: moveDirection)
}
}
func crownDidBecomeIdle(_ crownSequencer: WKCrownSequencer?) {
moveDirection = .STOP
game.moveSprite(player: player, moveDirection: moveDirection)
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}
I'm trying to add a transition to my SpriteKit scene, but it won't work. My second scene is called "myScene." Here's my code:
func didBeginContact(contact: SKPhysicsContact) {
let collision:UInt32 = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask)
if collision == (playerCategory | crateCategory) {
NSLog("Game Over")
self.scene?.view?.paused = true
var myscene = myScene(size: self.size)
var transition = SKTransition.doorsCloseHorizontalWithDuration(0.5)
myscene.scaleMode = SKSceneScaleMode.AspectFill
self.scene!.view?.presentScene(myscene, transition: transition)
}
if (contact.bodyA.categoryBitMask == bubbleCategory) {
let node = contact.bodyB.node
//Other remove routine
node?.removeAllActions()
node?.removeFromParent()
} else if (contact.bodyB.categoryBitMask == bubbleCategory) {
let node = contact.bodyB.node
//Other remove routine
node?.removeAllActions()
node?.removeFromParent()
}
}
What's the problem here?
This will present the scene and it works fine for me every time. This will not return nil for the scene so it will transition smoothly. You can also set ignoreSiblingOrder to whatever you want with this line: skView?.ignoresSiblingOrder = true //or false
Here is my GameScene:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
self.backgroundColor = UIColor.redColor()
}
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)
println("touch occured in gamescene")
let scene:SKScene = myScene()
let skView = self.view as SKView?
skView!.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
scene.size = skView!.bounds.size
skView!.presentScene(scene)
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
I am creating a SpriteKit game and I am having a lot of trouble. The first screen is a menu, when you click the play button, the player is put into the game itself using the code:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
let location:CGPoint = touch.locationInNode(self)
if self.nodeAtPoint(location).name == "play" {
playButton.fontColor = colorPressed
var scene = PlayScene(size: self.size)
let skView = self.view! as SKView
skView.ignoresSiblingOrder = true
skView.showsFPS = false
skView.showsNodeCount = false
scene.scaleMode = .AspectFill
gameStart(scene, skView: skView)
}
}
}
func gameStart(scene:SKScene, skView:SKView) {
let scale = SKAction.scaleTo(0.0, duration: 0.17)
about.runAction(scale, completion: {
self.scene?.removeAllChildren()
self.scene?.removeFromParent()
skView.presentScene(scene)
})
}
(PlayScene is the scene for where the player plays the game) This transition works fine. The player is able to play the game fine. Next, when the player loses the game, I go to a game over scene using this code:
func lineHitBadDot(dot: SKSpriteNode) {
var sceneGG = OverScene(size: self.size)
let skViewGG = self.view! as SKView
skViewGG.ignoresSiblingOrder = true
skViewGG.showsFPS = false
skViewGG.showsNodeCount = false
sceneGG.scaleMode = .AspectFill
let scale = SKAction.scaleTo(0.0, duration: 0.17)
scoreCounter.runAction(scale, completion: {
self.scene?.removeAllChildren()
self.scene?.removeFromParent()
skViewGG.presentScene(sceneGG)
})
}
(OverScene is the game over scene) Then, the player has two options, to try again, or return to the menu scene. This uses this code:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
let location:CGPoint = touch.locationInNode(self)
if self.nodeAtPoint(location).name == "retry" {
var sceneCookie = PlayScene(size: self.size)
let skViewCookie = self.view! as SKView
skViewCookie.ignoresSiblingOrder = true
skViewCookie.showsFPS = false
skViewCookie.showsNodeCount = false
sceneCookie.scaleMode = .AspectFill
gameStart(sceneCookie, skViewD: skViewCookie)
}
else if self.nodeAtPoint(location).name == "menu" {
var sceneCookieB = GameScene(size: self.size)
let skViewCookieB = self.view! as SKView
skViewCookieB.ignoresSiblingOrder = true
skViewCookieB.showsFPS = false
skViewCookieB.showsNodeCount = false
sceneCookieB.scaleMode = .AspectFill
gameStart(sceneCookieB, skViewD: skViewCookieB)
}
}
}
func gameStartE(sceneD:SKScene, skViewD:SKView) {
let scale = SKAction.moveToY(self.size.height + 300, duration: 0.17)
homeB.runAction(scale, completion: {
self.scene?.removeAllChildren()
self.scene?.removeFromParent()
skViewD.presentScene(sceneD)
})
}
The problem arrises when going back to the menu or re-trying the game. When going back to the menu, I see a blank screen, meaning the buttons have animated away, so it is displaying the initial menu scene, instead of deallocating the old one, and creating a new one like I want it to. The same thing happens when re-trying the game, it brings back the old scene instead of deallocating it and creating a new one. I have looked all over the internet and have found no solutions to this problem. Help would be very much appreciated.
Thanks.