Add SpriteNodes and Remove Based on Time or click using SpriteKit - swift

I am new to swift and looking to build my first basic game. The game I have in mind involves sprites generating at random and then disappearing based on time or a click if the click is within the time allocated. So far I have created the basic framework and am still messing around with design. My problem comes in where I can't seem to remove the sprite based on time (its generating fine). Any help is appreciated and thanks in advance😊
Below is the framework I've built up so far.
import SpriteKit
var one = SKSpriteNode()
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let myFunction = SKAction.runBlock({()in self.addOne()})
let wait = SKAction.waitForDuration(5)
let remove = SKAction.runBlock({() in self.removeOne()})
self.runAction(SKAction.sequence([myFunction, wait, remove]))
}
func addOne() {
let oneTexture = SKTexture(imageNamed: "blue button 10.png")
let one = SKSpriteNode(texture: oneTexture)
one.position = CGPoint(x: CGRectGetMidX(self.frame) - 100, y: CGRectGetMidY(self.frame) + 250)
one.zPosition = 1
self.addChild(one)
}
func removeOne() {
one.removeFromParent()
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}

It doesn't disappear because your create a new SpiteNode, but try to remove the old one, do it like this:
var one : SKSpriteNode! //instead of creating it without data, just define the type(not necessary, but I would do it)
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let myFunction = SKAction.runBlock({()in self.addOne()})
let wait = SKAction.waitForDuration(5)
let remove = SKAction.runBlock({() in self.removeOne()})
self.runAction(SKAction.sequence([myFunction, wait, remove]))
}
func addOne() {
let oneTexture = SKTexture(imageNamed: "blue button 10.png")
one = SKSpriteNode(texture: oneTexture) //removed the let, so you dont create a new "one"
one.position = CGPoint(x: CGRectGetMidX(self.frame) - 100, y: CGRectGetMidY(self.frame) + 250)
one.zPosition = 1
self.addChild(one)
}
func removeOne() {
one.removeFromParent()
}
}

Related

Trying to get a bunch of images to show in succession when touching screen on Swift

I put the different images to show in succession in the assets.xcassets folder as shooter, shooter1, shooter2 and so on but whenever i touch the screen the animation/image shown in the view doesn't change?
Here is my code:
import UIKit
import SpriteKit
class ShooterScene: SKScene
{
var score = 0
var enemyCount = 10
var shooterAnimation = [SKTexture]()
override func didMove(to view: SKView)
{
self.initShooterScene()
}
func initShooterScene()
{
let shooterAtlas = SKTextureAtlas(named: "shooter") // referencing shooter.atlas
for index in 1...shooterAtlas.textureNames.count
{
let imgName = "shooter\(index)"
shooterAnimation += [shooterAtlas.textureNamed(imgName)]
}
}
//Animate the shooter
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let shooterNode = self.childNode(withName: "shooterNode") as? SKSpriteNode
{
let animation = SKAction.animate(with: shooterAnimation, timePerFrame: 0.1)
shooterNode.run(animation)
}
}
}
The sprite node that I use in the view has the name shooterNode but the image doesn't seem to change at all. Any help would be great
edit:
console output:
2020-01-23 17:47:59.470204-0500 Tutorial 31 - Introduction to Sprike
Kit[5666:203542] Metal API Validation Enabled
2020-01-23 17:47:59.715838-0500 Tutorial 31 - Introduction to Sprike
Kit[5666:203542] SKView: ignoreRenderSyncInLayoutSubviews is NO. Call
_renderSynchronouslyForTime without handler
2020-01-23 17:48:11.764763-0500 Tutorial 31 - Introduction to Sprike
Kit[5666:203964] XPC connection interrupted
2020-01-23 17:48:11.765810-0500 Tutorial 31 - Introduction to Sprike
Kit[5666:203968] [connection] Connection interrupted: will attempt to
reconnect
2020-01-23 17:48:11.765887-0500 Tutorial 31 - Introduction to Sprike
Kit[5666:205206] [ServicesDaemonManager] interruptionHandler is
called. -[FontServicesDaemonManager connection]_block_invoke
Message from debugger: Terminated due to signal 15
Here's your code modified. It works for me. Without more code, I don't know how you are setting up your shooter node so I can't replicate your issue.
class ShooterScene: SKScene {
var textures = [SKTexture]()
//Also serves as the name of the texture the node uses.
let NODE_NAME:String = "shooter"
override init(size: CGSize) {
super.init(size: size)
//Better the setup scene in intializer, because didMove(to view) is called multiple times.
//You only want to call the setup method once.
self.setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setup() {
//Node setup
let shooterNode = SKSpriteNode(imageNamed: NODE_NAME)
shooterNode.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
shooterNode.name = NODE_NAME
addChild(shooterNode)
//Create animation collection
let atlas = SKTextureAtlas(named: NODE_NAME)
//Arrays start at index zero so we need to subtract 1 from the total textures count
let upperBound = atlas.textureNames.count - 1
for index in 1...upperBound {
let textureName = "\(NODE_NAME)\(index)"
textures += [atlas.textureNamed(textureName)]
}
}
//Animate the shooter when screen pressed.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let shooterNode = self.childNode(withName: NODE_NAME) as? SKSpriteNode else { return }
//For animations that are around 60 Frames per second
let FPS:Double = 1/60
let animation = SKAction.animate(with: self.textures, timePerFrame: FPS)
shooterNode.run(animation)
}
}

SKScene returns nil when transition method is called

In my little Xcode Project I am trying to transition scenes by when my SKLabelNode is touched it presents the next scene. For some reason in my scene transition method it makes me store it as an optional. And the optional returns false. My file names are correct and no grammatical errors in the references would be causing the problem here is my code. When I click my SKLabelNode my ios platform that is running it on recognizes the touch then the app crashes, in console saying that an optional value returned nil. How could I resolve this problem? Thanks
import SpriteKit
class GameScene: SKScene {
let next = SKScene(fileNamed: "nextScene")
override func didMoveToView(view: SKView) {
let backgroundimage = SKSpriteNode(imageNamed: "ipbg")
backgroundimage.size = CGSize(width: self.frame.size.width, height: self.frame.size.height)
backgroundimage.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height / 2)
addChild(backgroundimage)
let playButton = SKLabelNode(fontNamed: "")
playButton.name = "play"
playButton.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height / 2 + 100)
playButton.text = "Play"
let wait = SKAction.waitForDuration(2)
let run = SKAction.runBlock({
let randomNumber = Int(arc4random_uniform(UInt32(4)))
switch(randomNumber){
case (0):
playButton.fontColor = UIColor.blueColor()
case 1:
playButton.fontColor = UIColor.yellowColor()
case 2:
playButton.fontColor = UIColor.purpleColor()
case 3:
playButton.fontColor = UIColor.orangeColor()
default: print("default")
}
})
addChild(playButton)
var repeatActionForever = SKAction.repeatActionForever(SKAction.sequence([wait, run]))
runAction(repeatActionForever)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let trans = SKTransition.crossFadeWithDuration(1)
let touch = touches.first! as UITouch
let touchLocation = touch.locationInNode(self)
let touchedNode = nodeAtPoint(touchLocation)
if touchedNode.name == "play"{
scene!.view?.presentScene(next!, transition: trans)
}
}
}
func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
The code you posted does not compile so it's hard to find the error.
However I would try replacing the touchesBegan method with the following
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let trans = SKTransition.crossFadeWithDuration(1)
let touch = touches.first! as UITouch
let touchLocation = touch.locationInNode(self)
let touchedNode = nodeAtPoint(touchLocation)
if touchedNode.name == "play"{
guard let nextScene = SKScene(fileNamed: "nextScene")
else { fatalError("Could not load nextScene") }
self.view?.presentScene(nextScene, transition: trans)
}
}
There is no need to store the scene as a property just add it in the touches began method.
Also you should create the corresponding swift file and class for each scene instead of trying to initialising a .sks file with a generic SKScene object. The line ...(fileNamed: "nextScene") looks for an .sks file (Xcode visual level editor file), if you haven't created one your property will be nil.
To make it work do this:
First make sure you created a .sks file called NextScene (right click a file in your project -> New -> Resource -> SpriteKit Scene)
Secondly create a new swift file and create a new class called NextScene
class NextScene: SKScene {
}
Than the loading code in your touches label function would look like this.
override func touchesBegan... {
...
if let nextScene = NextScene(fileNamed: "NextScene") //actually init the corresponding class with the corresponding .sks file
/// scene presenting code
}
}
As you mentioned your scene property returns an optional because the fileNamed (.sks file) you specified might not exists, so check it is there. In general its better practice to safely unwrap like I do above or the default loading code in GameViewController does, instead of just force unwrapping with a ! .
If you don't use the visual level editor than just create your scene class and ignore creating the .sks file and load your scene like so
let nextScene = NextScene(size: self.size) // use current scene size
/// scene presenting code
Note: Your file names should start with capital letters

How to implement a SpriteKit timer?

I am currently trying to implement a timer for my sprite kit game, but I don't get it working. The initial value of the timer always remains the same.
I am assuming I need to update the label somehow/somewhere, but I don't know HOW and WHERE :? I don't get the point. Any ideas?
Here is my code within my GameScene Class
let levelTimerLabel = SKLabelNode(fontNamed: "Chalkduster")
var levelTimerValue: Int = 500
var levelTimer = NSTimer()
func startLevelTimer() {
levelTimerLabel.fontColor = SKColor.blackColor()
levelTimerLabel.fontSize = 40
levelTimerLabel.position = CGPoint(x: size.width/2, y: size.height/2 + 350)
addChild(levelTimerLabel)
levelTimer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: Selector("levelCountdown"), userInfo: nil, repeats: true)
levelTimerLabel.text = String(levelTimerValue)
}
func levelCountdown(){
levelTimerValue--
}
I would stick to SKActions for these kind of tasks in SpriteKit due to fact that NSTimer is not affected by scene's, or view's paused state, so it might lead you into troubles. Or at least, it will require from you to implement a pause feature in order to pause your timers in certain situations, like when user pause the scene, or receive a phone call etc. Read more here about SKAction vs NSTimer vs GCD for time related actions in SpriteKit.
import SpriteKit
class GameScene: SKScene {
var levelTimerLabel = SKLabelNode(fontNamed: "ArialMT")
//Immediately after leveTimerValue variable is set, update label's text
var levelTimerValue: Int = 500 {
didSet {
levelTimerLabel.text = "Time left: \(levelTimerValue)"
}
}
override func didMoveToView(view: SKView) {
levelTimerLabel.fontColor = SKColor.blackColor()
levelTimerLabel.fontSize = 40
levelTimerLabel.position = CGPoint(x: size.width/2, y: size.height/2 + 350)
levelTimerLabel.text = "Time left: \(levelTimerValue)"
addChild(levelTimerLabel)
let wait = SKAction.waitForDuration(0.5) //change countdown speed here
let block = SKAction.runBlock({
[unowned self] in
if self.levelTimerValue > 0{
self.levelTimerValue--
}else{
self.removeActionForKey("countdown")
}
})
let sequence = SKAction.sequence([wait,block])
runAction(SKAction.repeatActionForever(sequence), withKey: "countdown")
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
//Stop the countdown action
if actionForKey("countdown") != nil {removeActionForKey("countdown")}
}
}
Sounds like you need to update the label everytime levelTimerValue is changed. The easiest way would be something like this.
func levelCountdown(){
levelTimerValue--
levelTimerLabel.text = String(levelTimerValue)
}

Why my node is invisible

I'm currently trying to make a node that can change it's texture, but it's not visible on the simulator. I know it's there, cause I set showNodesCount to true and it displays that 2 nodes are on scene. It's displaying a cannon node, but not rgyBox node, that should change it's texture if I tap on cannon node. Here is my code:
class GameScene: SKScene {
var gameOver = false
let cannon = SKSpriteNode(imageNamed: "cannon")
let rgyArray = ["redBox", "greenBox", "yellowBox"]
var rgyBlock = SKSpriteNode()
func changeRgyColor() {
var randomRGY = Int(arc4random_uniform(3))
rgyBlock.texture = SKTexture(imageNamed: rgyArray[randomRGY])
}
override func didMoveToView(view: SKView) {
/* Setup your scene here */
cannon.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
self.addChild(cannon)
rgyBlock.texture = SKTexture(imageNamed: rgyArray[1])
rgyBlock.position = CGPointMake(self.cannon.position.x, self.cannon.position.y + 20)
self.addChild(rgyBlock)
rgyBlock.zPosition = 10
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) == self.cannon {
changeRgyColor()
}
}
}
What am I doing wrong?
You need to recreate the rgyBlock by calling rgyBlock = SKSpriteNode(imageNamed: rgyArray[1]). Where it is defined at the top, it is defined as nothing. You then need to recreate it in didMoveToView. You can remove the texture set in didMoveToView for the rgyBlock.
You could do it a different way, and put imageNamed: "aTextureNameHere" into the define of rgyBlock. This will give rgyBlock a value. Then you can set the texture (as you already do) in didMoveToView. This way may be quicker and easier.

How do I make an endless horizontal background in Swift?

import SpriteKit
class GameScene: SKScene {
let bgline1 = SKSpriteNode(imageNamed: "Line.png")
let bgline2 = SKSpriteNode(imageNamed: "Line.png")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
backgroundColor = SKColor.whiteColor()
bgline1.anchorPoint = CGPointZero
bgline1.position = CGPointZero
bgline1.zPosition = 1
bgline1.size = CGSizeMake(self.frame.size.width, self.frame.size.height)
self.addChild(bgline1)
bgline2.anchorPoint = CGPointZero
bgline2.position = CGPointMake(bgline1.position.x-1, 0)
bgline2.zPosition = 1
bgline2.size = CGSizeMake(self.frame.size.width, self.frame.size.height)
self.addChild(bgline2)
}
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)
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered*/
bgline1.position = CGPoint(x: bgline1.position.x-4, y: bgline1.position.y)
bgline2.position = CGPoint(x: bgline2.position.x-4, y: bgline2.position.y)
if bgline1.position.x < -bgline1.size.width{
bgline1.position = CGPointMake(bgline2.position.x + bgline2.size.width, bgline1.position.y)
}
if bgline2.position.x < -bgline2.size.width{
bgline2.position = CGPointMake(bgline1.position.x + bgline1.size.width, bgline2.position.y)
}
}
}
This is my code for creating a scrolling horizontal endless background. It is supposed to make an endless background but it isn't. The code is not looping the first try but after that it does. I would like to know why it does not work and how should I fix it.
EDIT- Starts out with 2 nodes and a line and the fps is <20. Then the line ends and when it "runs out" the node count becomes 0 and fps becomes 60. Than after a few seconds the line pops back up and it runs perfectly like a endless scrolling background.
bgline2.position = CGPointMake(bgline1.size.width-1, 0)
This line of code somehow fixed the problem for me. I don't know how it fixed it but it did. If someone wants to explain this for me please do. I have not seen any tutorials out there that use this but it worked for me.
Rohit