Shooting automatically in a Shooter Game (Swift 4 - SpriteKit) - swift

I'm working on a game project with Xcode.
I've written the code that makes the ship shoot the projectiles, but I don't know what function to use to make the ship shoot automatically.
Could you help me? Thank you in advance!
Here's my code, from the GameScene :
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let projectile = SKSpriteNode(imageNamed: "projectile")
projectile.zPosition = 1
projectile.position = CGPoint(x: player.position.x, y: player.position.y)
projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
projectile.physicsBody?.isDynamic = true
projectile.physicsBody?.categoryBitMask = PhysicsCategory.Projectile
projectile.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
projectile.physicsBody?.collisionBitMask = PhysicsCategory.None
projectile.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(projectile)
let action = SKAction.moveTo(x: self.frame.width + projectile.size.width, duration: 0.5)
projectile.run(action, completion: {
projectile.removeAllActions()
projectile.removeFromParent()
})
}

Based on a comment by Jake I am assuming that you want the ship to fire "automatically" not repeating while holding the finger down.
You can use the update func to control the automatic shooting. in my example the update command fires every 1 second
private var updateTime: Double = 0
override func update(_ currentTime: TimeInterval) {
if updateTime == 0 {
updateTime = currentTime
}
if currentTime - updateTime > 1 {
self.shoot()
updateTime = currentTime
}
}
func shoot() {
let projectile = SKSpriteNode(imageNamed: "projectile")
projectile.zPosition = 1
projectile.position = CGPoint(x: player.position.x, y: player.position.y)
projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
projectile.physicsBody?.isDynamic = true
projectile.physicsBody?.categoryBitMask = PhysicsCategory.Projectile
projectile.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
projectile.physicsBody?.collisionBitMask = PhysicsCategory.None
projectile.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(projectile)
let action = SKAction.moveTo(x: self.frame.width + projectile.size.width, duration: 0.5)
projectile.run(action, completion: {
projectile.removeAllActions()
projectile.removeFromParent()
})
}

Related

SKAction not running when button is held

I am working/practicing with SpriteKit and making a directional pad for controls. It works to move the character, except for the SKAction. I have the actions in the Actions.sks file that based off what button is pressed determines the action that is called. When I press the button the character moves fine, but when I hold it the character glides and the walking animation is stuck on the first frame until I release. What I am trying to do is have the character move if the button is pressed, and continue moving(with walking animation) when the button is held. I am trying to make it look like the gameboy era games.
class GameScene: SKScene {
var player = SKSpriteNode()
var playerSpeed: CGFloat = 0
var previousTimeInterval:TimeInterval = 0
let buttonNorth = SKSpriteNode(imageNamed: "Directional_Button")
let buttonSouth = SKSpriteNode(imageNamed: "Directional_Button")
let buttonEast = SKSpriteNode(imageNamed: "Directional_Button2")
let buttonWest = SKSpriteNode(imageNamed: "Directional_Button2")
var moveAtEndOfRelease:Bool = true
var isPressing = false
var currentState = MoveStates.n
override func didMove(to view: SKView) {
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
// Make sure you can get teh player from the scene file
if let somePlayer = self.childNode(withName: "player") as? SKSpriteNode {
player = somePlayer
// Set physics
player.physicsBody?.isDynamic = false
} else {
print("No player")
}
let widthHalf:CGFloat = self.view!.bounds.width / 2
let heightHalf:CGFloat = self.view!.bounds.height / 2
self.addChild(buttonNorth)
buttonNorth.position = CGPoint(x: -widthHalf + 80, y: -heightHalf + 100)
self.addChild(buttonSouth)
buttonSouth.position = CGPoint(x: -widthHalf + 80, y: -heightHalf + 40)
buttonSouth.yScale = -1
self.addChild(buttonWest)
buttonWest.position = CGPoint( x: -widthHalf + 30, y: -heightHalf + 70)
self.addChild(buttonEast)
buttonEast.position = CGPoint( x: -widthHalf + 130, y: -heightHalf + 70)
buttonNorth.xScale = 0.4
buttonNorth.yScale = 0.4
buttonSouth.xScale = 0.4
buttonSouth.yScale = 0.4
buttonSouth.zRotation = CGFloat(Double.pi)
buttonEast.xScale = 0.4
buttonEast.yScale = 0.4
buttonEast.zRotation = CGFloat(Double.pi)
buttonWest.xScale = 0.4
buttonWest.yScale = 0.4
}
override func update(_ currentTime: TimeInterval) {
//player.position = CGPoint(x: player.position.x + playerSpeedx, y: player.position.y + playerSpeedy)
if (isPressing) {
moveOnRelease()
}
}
func move(facing: Facing, x: CGFloat, y: CGFloat) {
let walkAnimation = SKAction(named: "walk\(facing)")!
let moveAction = SKAction.moveBy(x: x, y: y, duration: 1)
let group = SKAction.group([walkAnimation, moveAction])
// Run the actions
player.run(group)
}
func moveSide(facing: Facing, x: CGFloat) {
let walkAnimation = SKAction(named: "walk\(facing)")!
let moveAction = SKAction.moveBy(x: x, y: 0, duration: 1)
let group = SKAction.group([walkAnimation, moveAction])
// Run the actions
player.run(group)
}
func moveUpDown(facing: Facing, y: CGFloat) {
let walkAnimation = SKAction(named: "walk\(facing)")!
let moveAction = SKAction.moveBy(x: 0, y: y, duration: 1)
let group = SKAction.group([walkAnimation, moveAction])
// Run the actions
player.run(group)
}
func moveOnRelease() {
switch (currentState) {
case .n:
moveUpDown(facing: .Back, y: 1)
case .s:
moveUpDown(facing: .Front, y: -1)
case .e:
moveSide(facing: .Right, x: 1)
case .w:
moveSide(facing: .Left, x: -1)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ) {
let location = touch.location(in: self)
if (buttonNorth.frame.contains(location)) {
currentState = MoveStates.n
buttonNorth.texture = SKTexture(imageNamed: "Directional_Button_Lit")
isPressing = true
} else if (buttonSouth.frame.contains(location)) {
currentState = MoveStates.s
buttonSouth.texture = SKTexture(imageNamed: "Directional_Button_Lit")
isPressing = true
} else if (buttonEast.frame.contains(location)) {
currentState = MoveStates.e
buttonEast.texture = SKTexture(imageNamed: "Directional_Button2_Lit")
isPressing = true
} else if (buttonWest.frame.contains(location)) {
currentState = MoveStates.w
buttonWest.texture = SKTexture(imageNamed: "Directional_Button2_Lit")
isPressing = true
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if (moveAtEndOfRelease == true) {
buttonNorth.texture = SKTexture(imageNamed: "Directional_Button")
buttonSouth.texture = SKTexture(imageNamed: "Directional_Button")
buttonEast.texture = SKTexture(imageNamed: "Directional_Button2")
buttonWest.texture = SKTexture(imageNamed: "Directional_Button2")
//moveOnRelease()
isPressing = false
}
}
}
The reason it is stuck on the first frame is because the action is being called on every update i believe. You could check to see if the animation is already running, if so do not run it again.
To do this, for each run action give it a name.
So player.run(group) could be
player.run(group, withKey: "moveside"). Then, you can check to see if the action is already running or not like this if (player.action(forKey: "moveside") == nil). Finally, add in code to remove all other actions (like current movement or animations).
Putting it altogether:
if (player.action(forKey: "moveside") == nil) {
player.removeAllActions()
player.run(group, withKey: "moveside")
}

Swift SpriteKit Game gives error 'Attempted to add a SKNode which already has a parent'

I have a SpriteKit game and I am using a SKAction to move my meteor (the meteors are the enemies) all the way to the end of the screen where the player (the player is at the bottom of the screen, and they can only move on the x-axis to dodge meteors not the y) must dodge these meteors. Right I'm only trying to get one meteor on the screen at a time which moves towards the direction of the player using SKAction. There should only be on meteor at a time on the screen, but I get the error 'Attempted to add a SKNode which already has a parent'. My SKAction adds the meteor to the screen but then deletes it after the meteor has moved to its destination which is all then way to the bottom of the screen. So why am I getting this error and how can I fix it?
NOTE: The SKAction removes the meteor from the screen by using meteor.removeFromParent()
Here is my code:
import SpriteKit
import GameplayKit
import UIKit
class GameScene: SKScene, SKPhysicsContactDelegate{
let player = SKSpriteNode(imageNamed: "spaceship")
let stars = SKSpriteNode(imageNamed: "stars")
let meteor = SKSpriteNode(imageNamed: "meteor")
var scoreLabel = SKLabelNode(fontNamed: "AmericanTypewriter-Bold")
var score:Int = 0
var playerLost:Bool = false
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
print(frame.size.width)
print(frame.size.height)
stars.position = CGPoint(x:0, y:0)
stars.zPosition = 1
player.position = CGPoint(x:0, y:-320)
player.zPosition = 4
player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size)
player.physicsBody?.affectedByGravity = false
player.physicsBody?.isDynamic = false
player.physicsBody?.categoryBitMask = 2
player.physicsBody?.collisionBitMask = 2
player.physicsBody?.contactTestBitMask = 1
self.addChild(player)
self.addChild(stars)
self.addMeteor()
self.setLabel()
//spawnEnemySKAction()
}
func spawnEnemySKAction() {
let spawn = SKAction.run(addMeteor)
let waitToSpawn = SKAction.wait(forDuration: 1)
let spawnSequence = SKAction.sequence([spawn, waitToSpawn])
let spawnForever = SKAction.repeatForever(spawnSequence)
self.run(spawnForever)
}
func didBegin(_ contact: SKPhysicsContact) {
gameOver()
}
func addMeteor() {
meteor.physicsBody = SKPhysicsBody(texture: meteor.texture!, size: meteor.size)
meteor.physicsBody?.affectedByGravity = false
meteor.setScale(0.50)
meteor.position = CGPoint(x:Int(arc4random()%300),y:Int(arc4random()%600))
//meteor.position = CGPoint(x:0 , y:0)
meteor.zPosition = 4
let moveMeteor = SKAction.moveTo(y: player.position.y - 300, duration: 1.5)
let deleteMeteor = SKAction.removeFromParent()
let meteorSequence = SKAction.sequence([moveMeteor, deleteMeteor])
meteor.run(meteorSequence)
meteor.physicsBody?.categoryBitMask = 1
meteor.physicsBody?.collisionBitMask = 2
meteor.physicsBody?.contactTestBitMask = 2
self.addChild(meteor)
}
func fireBullet() {
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.position = player.position
bullet.setScale(0.5)
bullet.zPosition = 3
self.addChild(bullet)
let moveBullet = SKAction.moveTo(y: self.size.height + bullet.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([moveBullet, deleteBullet])
bullet.run(bulletSequence)
}
func setLabel() {
scoreLabel.text = "Score: " + String(score)
scoreLabel.position = CGPoint(x: 290, y: 590)
scoreLabel.zPosition = 20
}
func gameOver() {
playerLost = true
var gameOverLabel: SKLabelNode!
gameOverLabel = SKLabelNode(fontNamed: "AmericanTypewriter-Bold")
gameOverLabel.text = "Game Over! You lost! Your score was: " + String(score)
gameOverLabel.zPosition = 10
gameOverLabel.position = CGPoint(x: 0, y:0)
self.addChild(gameOverLabel)
player.removeFromParent()
meteor.removeFromParent()
stars.removeFromParent()
scoreLabel.removeFromParent()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if playerLost == false {
fireBullet()
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let previousPointOfTouch = touch.previousLocation(in: self)
let amountDragged = pointOfTouch.x - previousPointOfTouch.x
player.position.x += amountDragged
}
}
override func update(_ currentTime: TimeInterval) {
addMeteor()
if meteor.position.y < player.position.y - 300 && playerLost == false {
meteor.removeFromParent()
addMeteor()
if playerLost == false {
score += 1
}
scoreLabel.removeFromParent()
scoreLabel.text = "Score: " + String(score)
self.addChild(scoreLabel)
}
}
}
Thanks for taking the time to look at this question!
I simply had to remove the self.addChild(meteor) in the update function

My initial pipes are different from the following pipes

I'm making a game where the ball is suppose to go through some pipes, and when the player touches the pipes, the game stops. Kind of like flappy bird. The only problem I have is that the initial pipes blocks the entire screen, while the rest of the pipes are placed and randomized exactly as I want. How is this possible?
This is the ball class:
import SpriteKit
struct ColliderType {
static let Ball: UInt32 = 1
static let Pipes: UInt32 = 2
static let Score: UInt32 = 3
}
class Ball: SKSpriteNode {
func initialize() {
self.name = "Ball"
self.zPosition = 1
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.height /
2)
self.setScale(0.7)
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = ColliderType.Ball
self.physicsBody?.collisionBitMask = ColliderType.Pipes
self.physicsBody?.contactTestBitMask = ColliderType.Pipes |
ColliderType.Score
}
}
This is the Random Class:
import Foundation
import CoreGraphics
public extension CGFloat {
public static func randomBetweenNumbers(firstNum: CGFloat, secondNum:
CGFloat) -> CGFloat {
return CGFloat(arc4random()) / CGFloat(UINT32_MAX) * abs(firstNum -
secondNum) + firstNum
}
}
This is the GameplayScene:
import SpriteKit
class GameplayScene: SKScene {
var ball = Ball()
var pipesHolder = SKNode()
var touched: Bool = false
var location = CGPoint.zero
override func didMove(to view: SKView) {
initialize()
}
override func update(_ currentTime: TimeInterval) {
moveBackgrounds()
if (touched) {
moveNodeToLocation()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event:
UIEvent?) {
touched = true
for touch in touches {
location = touch.location(in:self)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event:
UIEvent?) {
touched = false
}
override func touchesMoved(_ touches: Set<UITouch>, with event:
UIEvent?) {
for touch in touches {
location = touch.location(in: self)
}
}
func initialize() {
createBall()
createBackgrounds()
createPipes()
spawnObstacles()
}
func createBall() {
ball = Ball(imageNamed: "Ball")
ball.initialize()
ball.position = CGPoint(x: 0, y: 0)
self.addChild(ball)
}
func createBackgrounds() {
for i in 0...2 {
let bg = SKSpriteNode(imageNamed: "BG")
bg.anchorPoint = CGPoint(x: 0.5, y: 0.5)
bg.zPosition = 0
bg.name = "BG"
bg.position = CGPoint(x: 0, y: CGFloat(i) * bg.size.height)
self.addChild(bg)
}
}
func moveBackgrounds() {
enumerateChildNodes(withName: "BG", using: ({
(node, error) in
node.position.y -= 15
if node.position.y < -(self.frame.height) {
node.position.y += self.frame.height * 3
}
}))
}
func createPipes() {
pipesHolder = SKNode()
pipesHolder.name = "Holder"
let pipeLeft = SKSpriteNode(imageNamed: "Pipe")
let pipeRight = SKSpriteNode(imageNamed: "Pipe")
pipeLeft.name = "Pipe"
pipeLeft.anchorPoint = CGPoint(x: 0.5, y: 0.5)
pipeLeft.position = CGPoint(x: 350, y: 0)
pipeLeft.xScale = 1.5
pipeLeft.physicsBody = SKPhysicsBody(rectangleOf: pipeLeft.size)
pipeLeft.physicsBody?.categoryBitMask = ColliderType.Pipes
pipeLeft.physicsBody?.affectedByGravity = false
pipeLeft.physicsBody?.isDynamic = false
pipeRight.name = "Pipe"
pipeRight.anchorPoint = CGPoint(x: 0.5, y: 0.5)
pipeRight.position = CGPoint(x: -350, y: 0)
pipeRight.xScale = 1.5
pipeRight.physicsBody = SKPhysicsBody(rectangleOf: pipeRight.size)
pipeRight.physicsBody?.categoryBitMask = ColliderType.Pipes
pipeRight.physicsBody?.affectedByGravity = false
pipeRight.physicsBody?.isDynamic = false
pipesHolder.zPosition = 5
pipesHolder.position.y = self.frame.height + 100
pipesHolder.position.x = CGFloat.randomBetweenNumbers(firstNum:
-250, secondNum: 250)
pipesHolder.addChild(pipeLeft)
pipesHolder.addChild(pipeRight)
self.addChild(pipesHolder)
let destination = self.frame.height * 3
let move = SKAction.moveTo(y: -destination, duration:
TimeInterval(10))
let remove = SKAction.removeFromParent()
pipesHolder.run(SKAction.sequence([move, remove]), withKey: "Move")
}
func spawnObstacles() {
let spawn = SKAction.run({ () -> Void in
self.createPipes()
})
let delay = SKAction.wait(forDuration: TimeInterval(1.5))
let sequence = SKAction.sequence([spawn, delay])
self.run(SKAction.repeatForever(sequence), withKey: "Spawn")
}
func moveNodeToLocation() {
// Compute vector components in direction of the touch
var dx = location.x - ball.position.x
// How fast to move the node. Adjust this as needed
let speed:CGFloat = 0.1
// Scale vector
dx = dx * speed
ball.position = CGPoint(x:ball.position.x+dx, y: 0)
}
}

How do I stop my timers at 0 automatically in SpriteKit?

Keep in mind this is in Sprite Kit. Basically I want a way to have my in-game upgrades on a timer that allows the user to get the upgrade after the timer has finished. The user would touch the button once to start the timer, and once it gets to zero, it stops itself. I can't get it to stop, however.
import Foundation
import SpriteKit
var timer1Cycle = 0
var timer1Time = 10000
var timer1 = Timer()
var timer1IsOn = false
class SkillPointsScene: SKScene{
func updateTimer1() {
timer1Time -= 1
timer1Label.text = " \(timer1Time) :Kepler-22b units"
}
func startTimer1(){
if timer1IsOn == false {
timer1 = Timer.scheduledTimer(timeInterval: 0.0004, target: self, selector: #selector(SkillPointsScene.updateTimer1), userInfo: nil, repeats: true)
timer1IsOn = true
}
}
func stopTimer1() {
timer1.invalidate()
timer1Time = 0
timer1Label.text = "Research Complete"
timer1IsOn = false
}
Here is the image button to start counting down from 10000:
let oneLifeImage = SKSpriteNode(imageNamed: "gun1")
let timer1Label = SKLabelNode(fontNamed: "theBoldFontt")
// runs as soon as the screen loads every time
override func didMove(to view: SKView) {
if timer1Time <= 0 {
self.stopTimer1()
}
let background = SKSpriteNode(imageNamed: "background")
background.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
background.zPosition = 0
self.addChild(background)
timer1Label.text = " "
timer1Label.fontSize = 35
timer1Label.fontColor = SKColor.white
timer1Label.position = CGPoint(x: self.size.width*0.5, y: self.size.height*0.82)
timer1Label.zPosition = 100
self.addChild(timer1Label)
oneLifeImage.setScale(1)
oneLifeImage.position = CGPoint(x: self.size.width/2, y: self.size.height*0.75)
oneLifeImage.zPosition = 2
self.addChild(oneLifeImage)
And here is where the button is touched:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
let pointOfTouch = touch.location(in: self)
let wait = SKAction.wait(forDuration: 1.5)
if oneLifeImage.contains(pointOfTouch){
if timer1Cycle == 0 {
timer1Time = 10000
}
if timer1Cycle == 1 {
timer1Time = 25000
}
if timer1IsOn == false {
startTimer1()
timer1Cycle -= 1
}
I get it to start the count down, but it never stops on its own. I don't know what to do now; I can't find any information on this because it is in Swift 3 with Sprite Kit, not the normal single view that everybody seems to know how to use.

Swift: Attemped to add a SKNode which already has a parent:

I know why I'm getting that error, but I can't figure out a way around it. I'm trying to have objects come appear and then be removed and the player should try to tap them before they're removed, but everytime the next node is about to appear it crashes. If i declare it inside its func then it all comes out but I can't tap on it...
Code:
let step = SKSpriteNode()
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
backgroundColor = UIColor.feelFreeToColor()
self.color = self.randomNumbersInt(3)
self.showBars()
self.showScore()
let spawn = SKAction.runBlock {
//self.color = self.randomNumbersInt(3)
self.showSteps()
}
let delay = SKAction.waitForDuration(1.5)
let spawnDelay = SKAction.sequence([spawn , delay])
let spawnDelayForever = SKAction.repeatActionForever(spawnDelay)
self.runAction(spawnDelayForever)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
var location = touch.locationInNode(self)
if self.nodeAtPoint(location) == step {
self.score += 1
}
}
}
func showSteps() {
let createSteps = SKAction.moveByX(0, y: -self.frame.height - 30 , duration: 10)
let removeSteps = SKAction.removeFromParent()
step.color = colors[randomNumbersInt(3)]!
step.size = CGSize(width: 275, height: 30)
step.position = CGPoint(x: self.frame.width * 0.5, y: self.frame.height * 0.75)
step.physicsBody = SKPhysicsBody(rectangleOfSize: step.size)
step.physicsBody?.categoryBitMask = Config.PhysicBodyType.Steps.rawValue
step.physicsBody?.affectedByGravity = false
step.physicsBody?.dynamic = false
step.zPosition = 1
step.runAction(SKAction.repeatActionForever(SKAction.sequence([createSteps, removeSteps])))
addChild(step)
}
In your showSteps function, declare the step node inside it, not at the top of your code, and also give it a name:
func showSteps() {
let step = SKSpriteNode()
...
step.name = "step"
step.color = colors[randomNumbersInt(3)]!
// etc
}
In your touchesBegan method, you have this if statement:
if self.nodeAtPoint(location) == step {
self.score += 1
}
You want to remove that node that you have hit, but now you can just check the name property like so:
if self.nodeAtPoint(location)?.name == "step" {
self.nodeAtPoint(location)?.removeFromParent()
self.score += 1
}
Please note that I am not super fluent in Swift, but I think you will probably need the ? in your if statement, as it might not exist (such as if you didn't tap on a specific node). Somebody more familiar with Swift is free to correct me.