I am trying to create a "Space Invaders" game in swift, when the user touches the screen a bullet is shot from the ship, but when I try to touch it again while the bullet is moving across the screen I have an NSException and the game breaks. How do I set up the action so that there can be multiple instances of the action, so that the shooter is semi automatic. Below is my current scene controller.
import SpriteKit
class GameScene: SKScene {
let background = SKSpriteNode(imageNamed: "background")
let heroShip = SKSpriteNode(imageNamed: "heroShip")
let bullet = SKSpriteNode(imageNamed: "bullet")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
background.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame))
heroShip.position = CGPointMake(self.size.width/6.0, self.size.height/2.0)
self.heroShip.zPosition = 1.0
self.addChild(background)
self.addChild(heroShip)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
bullet.position = CGPointMake(heroShip.position.x + bullet.size.width/2, heroShip.position.y)
let action = SKAction.moveToX(self.frame.width + self.bullet.size.width, duration: 0.5)
self.addChild(bullet)
bullet.runAction(action, completion: {
self.bullet.removeAllActions()
self.bullet.removeFromParent()
})
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
If you want to have multiple bullets shot from one ship, you have to create multiple instances of a bullet. What you have now is bullet property of GameScene class which is a mistake.
You probably want to instantiate bullets dynamically in your IBAction
So try something like this:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.position = CGPointMake(heroShip.position.x + bullet.size.width/2, heroShip.position.y)
let action = SKAction.moveToX(self.frame.width + bullet.size.width, duration: 0.5)
self.addChild(bullet)
bullet.runAction(action, completion: {
bullet.removeAllActions()
bullet.removeFromParent()
})
}
and remove
let bullet = SKSpriteNode(imageNamed: "bullet")
from the top of the class
Related
I am trying to implement moving to another scene when a wheel stops rotating. The code I have is shown below. I cannot figure out how to detect when the velocity has reached 0.0?
import SpriteKit
import GameplayKit
class GameplayScene: SKScene {
var player: Player?;
override func didMove(to view: SKView) {
player = self.childNode(withName: "spinner") as! Player?;
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if atPoint(location).name == "play_button" {
spin()
}
}
}
func spin () {
let random = GKRandomDistribution(lowestValue: 20, highestValue: 90)
let r = random.nextInt()
player?.physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(self.frame.width))
player?.physicsBody?.affectedByGravity = false
player?.physicsBody?.isDynamic = true
player?.physicsBody?.allowsRotation = true
player?.physicsBody?.angularVelocity = CGFloat(r)
player?.physicsBody?.angularDamping = 1.0
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
}
So from here, I would like to execute the following when the wheel has stopped spinning:
let play_scene = Questions(fileNamed: "QuestionsScene")
play_scene?.scaleMode = .aspectFill
self.view?.presentScene(play_scene!, transition: SKTransition.doorsOpenVertical(withDuration: 1))
I have now edited the class and it looks as follows:
import SpriteKit
import GameplayKit
class GameplayScene: SKScene, SKSceneDelegate {
var player: Player?
override func didMove(to view: SKView) {
self.delegate = self
player = self.childNode(withName: "spinner") as! Player?;
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if atPoint(location).name == "play_button" {
spin()
}
}
}
func spin () {
let random = GKRandomDistribution(lowestValue: 20, highestValue: 90)
let r = random.nextInt()
player?.physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(self.frame.width))
player?.physicsBody?.affectedByGravity = false
player?.physicsBody?.isDynamic = true
player?.physicsBody?.allowsRotation = true
player?.physicsBody?.pinned = true
player?.physicsBody?.angularVelocity = CGFloat(r)
player?.physicsBody?.angularDamping = 1.0
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func didSimulatePhysics() {
if ((player?.physicsBody?.angularVelocity)! <= CGFloat(0.01))
{
print("Got it")
}
}
}
Problem is I still receive an error on the if statement within didSimulatePhysics function. The error I receive is "Thread 1: EXC_BAD_INSTRUCTION...."
Your wheel's SKPhysicsBody has a built-in property, angularVelocity, that tells you how fast it's spinning. You're already using it when you set it to r to start the wheel spinning.
To watch angularVelocity you can use didSimulatePhysics(). It gets called once every frame, right after the physics calculations are done. That will look something like this:
func didSimulatePhysics() {
if wheelIsSpinning && angularVelocity != nil && angularVelocity! <= CGFloat(0.001) {
wheelIsSpinning = false
// wheel has stopped
// add your code here
}
}
Due to the vagaries of physics modeling, the angularVelocity might never be exactly zero. So instead we watch for it to be less than some arbitrary threshold, in this case 0.001.
You don't want it to execute every frame when the wheel isn't moving, so I added a property wheelIsSpinning to keep track. You'll need to add it as an instance property to GameplayScene, and set it to true in spin().
I'm unable to move the sprite even after I set the class on the Sprite in the GameScene. Below is my code for the GameplayScene class. I did not include my Player class because the code is really basic. (just a movePlayer bool function with a if statement that moves based on the x position)
import SpriteKit
class GameplayScene: SKScene {
var player: Player? // created new varible player for the scene, inherited the player class
var canMove = false // created new varible for letting player move; false by default
var moveLeft = false // created new varible to move player left; false by default
var center: CGFloat? // created new varible to set center of screen
override func didMoveToView(view: SKView) {
center = (self.scene?.size.width)! / (self.scene?.size.height)! //calculates center of screen
player.self?.childNodeWithName("Player") as? Player! // adds node "Player" within Player class(Player!)
}
override func update(currentTime: NSTimeInterval) {
managePlayer() //constantly checks if player can/is moving
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self) // gets location inside scene
if location.x > center { // if statement to check if user to taping screen on the right
moveLeft = false //moves player right
} else {
moveLeft = true
}
}
canMove = true
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
canMove = false //says if player is not touching screen, the player will not move
}
func managePlayer() {
if canMove {
player?.movePlayer(moveLeft) //takes the Player varible(which has the class), then grabs the move player func, then checks argument moveLeft
}
}
}
add player = self.childNode(withName: "Player") as? Player to func didMove in GameplayScene.swift
override func didMove(to view: SKView) {
print("didmove")
center = (self.scene?.size.width)! / (self.scene?.size.height)!
player = self.childNode(withName: "Player") as? Player
}
I am trying to delete an image that is moving by touch and only that image. I spawn multiple images of that same image and I only want to delete the ones that are being touched. How can I do this? Any ideas? I am running IOS app Game and SpriteKit.
import SpriteKit
`class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let myLabel = SKLabelNode(fontNamed:"Chalkduster")
myLabel.text = "Hello, World!"
myLabel.fontSize = 45
myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
self.addChild(myLabel)
}
func generateNode() {
let Bullet = SKSpriteNode(imageNamed: "Bullets")
Bullet.name = "generatedNode"
}
func touchesCancelled(touches: NSSet, withEvent event: UIEvent?) {
let touch = touches.anyObject() as! UITouch?
if let location = touch?.locationInNode(self)
{
for Bullet in self.nodesAtPoint(location)
{
if Bullet.name == "generatedNode"
{
Bullet.removeFromParent()
}
}
}
}
}
func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
My problem is that when I tried to move to my next Scene using self.scene?.presentScene() an error message pops up and says 'presentScene is not a member of SKScene'.
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
backgroundColor = SKColor.blackColor()
let start = SKLabelNode(fontNamed: "Chalkduster")
start.position = CGPoint (x: self.frame.width/2-75, y: self.frame.height/2+100)
start.fontSize = 35
start.text = "Start"
addChild(start)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
let MyScene = SecondScene(size: self.size)
MyScene.scaleMode = scaleMode
self.scene?.presentScene(MyScene)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
presentScene is a method of SKView, not SKScene.
So to load a new scene you should write something like this
class GameScene: SKScene {
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let myScene = SecondScene(size: self.size)
myScene.scaleMode = scaleMode
self.view?.presentScene(myScene)
}
}
Transition
You can also present the new scene with a transition effect. In this case one this method and pass a SKTransition as second param.
self.view?.presentScene(myScene, transition: SKTransition.crossFadeWithDuration(1))
In the example above I am using a Cross Fade transition having a duration of 1 second. But there are many other transitions to choose from.
Hi than you for looking at this i was wondering why my code in swift sprite kit won't work i can run it but when i click on the button it does not work(it does not go to the scene) but when i add a println to the if statement and then i click it it does run the println. Thank you in advance!
Here is my code for my first scene:
//
// GameScene.swift
// Pocket Rocket
//
// Created by Lucas Farleigh on 11/11/2014.
// Copyright (c) 2014 Lucas Farleigh. All rights reserved.
//
import SpriteKit;
class GameScene: SKScene {
let background = SKSpriteNode(imageNamed:"background")
let playButton = SKSpriteNode(imageNamed: "playbutton")
override func didMoveToView(view: SKView) {
playButton.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidX(self.frame))
background.yScale = 2
background.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidX(self.frame))
self.addChild(background)
self.addChild(playButton)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
//making the scene vars
var scene = PlayScene(size: self.size)
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
let skview = self.view as SKView!
scene.size = skview.bounds.size
playButton.name = "PB"
if node.name == "PB"{
println("it worked")
skview.presentScene(scene)
}
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
And my second scene(playscene)
//
// PlayScene.swift
// Pocket Rocket
//
// Created by Lucas Farleigh on 14/11/2014.
// Copyright (c) 2014 Lucas Farleigh. All rights reserved.
//
import SpriteKit
import UIKit
import Foundation
class PlayScene:SKScene{
let background = SKSpriteNode(imageNamed: "background")
let bar1 = SKSpriteNode(imageNamed:"Bar1")
let bar2 = SKSpriteNode(imageNamed:"Bar2")
let bar3 = SKSpriteNode(imageNamed:"Bar3")
let bar4 = SKSpriteNode(imageNamed:"Bar4")
let bar5 = SKSpriteNode(imageNamed:"Bar5")
let bar6 = SKSpriteNode(imageNamed:"Bar6")
override func didMoveToView(view: SKView) {
background.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidX(self.frame))
addChild(background)
background.yScale = 2
Note that you are creating background and PlayButton nodes twice: once as a class property, and a second time as a local variable in didMoveToView. It is the local version that you are then adding to the scene as a child view. If you want to be able to test against these nodes in other methods in your class, get rid of the local declarations in didMoveToView. You can still fill in the other information about these nodes, just remove the let statements.
Then in your touchesBegan you want to get the location of the touch in the scene by passing self to touch.locationInNode(self) and then retrieve the node that corresponds to that touch:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if node == Play_Button {
println("Hello!!!!")
}
}
}
A second way to accomplish the PlayButton would be to give the node a name when you create it:
Play_Button.name = "playButton"
and then your touchesBegan would look like this:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if node.name == "playButton" {
println("Hello!!!!")
}
}
}
I agree with vacawama's answer (I use the naming option) but I'd like to add a little on my approach to buttons in SK. Usually you either have a background and a label or just a PNG image with both background and label.
When using nodeAtPoint on the label you might not have enough area. If instead you use the background area to detect taps then you might have conflicts with the label on top of the node.
To solve this I create a third element (usually an SKShapeNode) on top of both, transparent and a little bigger to make it easier for the user to tap on it.
To make sure you are positioning these tap areas on top of the buttons remember to use a higher zPosition