Is there a way I can add more than one sprite in SpriteKit globally? - swift

I am struggling with one issue. Global declaration of my sprite so that I can interact with it. In this game, I have created a local sprite called enemy featured below:
func spawnEnemy() {
let enemy = SKSpriteNode(imageNamed: "as")
let yPosition = CGFloat(frame.maxY - enemy.size.height)
let getXvalue = GKRandomDistribution(lowestValue: Int(frame.minX + enemy.size.width), highestValue: Int(frame.maxX - enemy.size.width))
let xPosition = CGFloat(getXvalue.nextInt())
enemy.position = CGPoint(x: xPosition, y: yPosition)
enemy.name = "asteroid"
enemy.zPosition = 100
addChild(enemy)
let animationDuration:TimeInterval = 6
var actionArray = [SKAction]()
actionArray.append(SKAction.move(to: CGPoint(x: xPosition, y: 0), duration: animationDuration))
actionArray.append(SKAction.self.removeFromParent())
enemy.run(SKAction.sequence(actionArray))
}
I want to tap the enemy to make it disappear from the screen. The variable is declared locally and not globally so the touchesBegan function does not "see" enemy. However, when I move the statement:
let enemy = SKSpriteNode(imageNamed: "as")
outside of local declaration and into global. It works until the code tries to spawn in another enemy and i get an error of "Tried to add an SKNode who already has a parent" This is the code I have running in my view did load:
run(SKAction.repeatForever(SKAction.sequence([SKAction.run{self.spawnEnemy()
}, SKAction.wait(forDuration: 1.0)])))
Every time it spawns a new enemy it crashes and says that the SKNode already has a parent which i understand. However, for my game to function I need the player to be able to touch the individual instance of that enemy and remove it. Hence my code for
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
if let location = touch?.location(in:self) {
let nodesArray = self.nodes(at:location)
if nodesArray.first?.name == "asteroid" {
print("Test")
enemy.removeFromParent()
print("Test Completed")
}
}
}
Now the error says unresolved use of "enemy" because the enemy is not global. I have been going back and forth on this issue for quite some time. If anyone has any potential solution or work around I would be very grateful, and thank you for your help.

Move your enemies to their own class and handle the touch for each of those enemies in their own class. This cleans up your GameScene and keeps your code more organized. You can now add as many instances of enemy as you want.
FYI not related to this question but somethings to consider after you get this working
when game over or level change or win make sure you have a clean up function to remove all enemies
you should strongly consider recycling your objects vs creating them on the fly...better performance
try to separate as much code to your objects class as possible
class enemy: SKSpriteNode {
init() {
super.init(texture: nil, color: .clear, size: CGSize.zero)
setup()
}
func setup() {
isUserInteractionEnabled = true
name = "asteroid"
zPosition = 100
let image = SKSpriteNode(imageNamed: "as")
imagine.zPosition = 1
addChild(image)
self.size = image.size
animate()
}
func animate() {
let animationDuration: TimeInterval = 6
let move = SKAction.move(to: CGPoint(x: xPosition, y: 0), duration: animationDuration)
let remover = SKAction.self.removeFromParent()
run(SKAction.sequence(move, remover))
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
removeFromParent()
}
}
class GameScene: SKScene {
override func didMove(to view: SKView) {
let sequence = SKAction.sequence([SKAction.run{ self.spawnEnemy()
}, SKAction.wait(forDuration: 1.0)])
run(SKAction.repeatForever(sequence))
}
func spawnEnemy() {
let enemy = Enemy()
let yPosition = CGFloat(frame.maxY - enemy.size.height)
let getXvalue = GKRandomDistribution(lowestValue: Int(frame.minX + enemy.size.width), highestValue: Int(frame.maxX - enemy.size.width))
let xPosition = CGFloat(getXvalue.nextInt())
enemy.position = CGPoint(x: xPosition, y: yPosition)
addChild(enemy)
}
}

Related

SpriteKit creating a button issues

Im attempting to use some of the code from a solution found on this page for creating a button Create Button in SpriteKit: Swift
class GameScene: SKScene {
let button = SKSpriteNode(imageNamed: "yourImgName")
override func didMoveToView(view: SKView) {
button.name = "btn"
button.size.height = 100
button.size.width = 100
button.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame) + 50)
self.addChild(button)
//Adjust button properties (above) as needed
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let positionInScene = touch!.locationInNode(self)
let touchedNode = self.nodeAtPoint(positionInScene)
if let name = touchedNode.name {
if name == "btn" {
let yourNextScene = YourNextScene(fileNamed: "YourNextScene")
self.view?.presentScene(yourNextScene!)
}
}
}
}
and the current code that I have is supposed to make the player jump when the button is pressed, but nothing is currently happening when its pressed
import SwiftUI
import SpriteKit
import UIKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
let button = SKSpriteNode(imageNamed: "playerOBJ")
let player = SKSpriteNode(imageNamed: "playerOBJ")
let playerRadius = player.frame.width / 2.0
player.position = CGPoint(x: 200, y: 500)
player.name = "Jimmy"
addChild(player)
player.physicsBody = SKPhysicsBody(circleOfRadius: playerRadius)
player.physicsBody?.allowsRotation = false
player.physicsBody?.friction = 0
player.physicsBody?.restitution = 0
player.zPosition = 100
// Button
button.name = "btn"
button.size.height = 100
button.size.width = 100
button.position = CGPoint(x: 100, y: 100)
self.addChild(button)
// Physics
physicsBody = SKPhysicsBody(edgeLoopFrom: frame.inset(by: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)))
Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { _ in
player.physicsBody?.applyForce(CGVector(dx: 100, dy: 1000))
}
func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let positionInScene = touch!.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name {
if name == "btn" {
player.physicsBody?.applyForce(CGVector(dx: 0, dy: 10000))
}
}
}
}
override func update(_ currentTime: TimeInterval) { }
}
I'm thinking that maybe this is an issue with the press not being resitered at all but I'm not fully sure
Your main problem is you put all of your code inside the didMove function. You put the touchesBegan function inside the didMove function. When the didMove function finishes, touchesBegan goes away so none of your touches are going to be handled in the game.
You also declared the button and the player as local variables inside the didMove function.
override func didMove(to view: SKView) {
let button = SKSpriteNode(imageNamed: "playerOBJ")
let player = SKSpriteNode(imageNamed: "playerOBJ")
// Rest of function omitted
}
When the didMove function finishes, the button and player go away too. You also have the same image name for the player and button.
The fix is to make the button and player variables properties of the GameScene class. Move touchesBegan out of the didMove function too.
class GameScene: SKScene {
// Declare button and player here.
var button = SKSpriteNode()
var player = SKSpriteNode()
override func didMove(to view: SKView) {
// Initialize button, player, and everything else here.
}
func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// Code in touchesBegan func goes here.
}
}

Label not showing up (Swift 4 SpriteKit)

I am making a game in Xcode with Swift 4, SpriteKit. My crash detection is working, however when I try to make text appear onto the screen when the player crashes, the text doesn't appear. My game basically has the use control a rocket ship which must dodge meteors, the player also has the ability to fire bullets. I have detected when the rocket ship hits a meteor but cannot get text to appear onto the screen. I know for a fact that the crash detection is working, because whenever I add a print statement in the didBegin function, the print statement executes. So it must be that way im displaying the label, but im not sure what I'm doing wrong with the label.
Here's 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")
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(circleOfRadius: player.size.width / 2)
player.physicsBody?.affectedByGravity = false
player.physicsBody?.isDynamic = false
player.physicsBody?.categoryBitMask = 2
player.physicsBody?.collisionBitMask = 1
player.physicsBody?.contactTestBitMask = 1
self.addChild(player)
self.addChild(stars)
self.addMeteor()
}
func didBegin(_ contact: SKPhysicsContact) {
gameOver()
}
func addMeteor() {
meteor.physicsBody = SKPhysicsBody(circleOfRadius: meteor.size.width / 2)
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
meteor.physicsBody?.categoryBitMask = 1
meteor.physicsBody?.collisionBitMask = 0
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 gameOver() {
print("Game Over!")
var gameOverLabel: SKLabelNode!
gameOverLabel = SKLabelNode(fontNamed: "Chalkduster")
gameOverLabel.text = "Game Over! You lost!"
gameOverLabel.horizontalAlignmentMode = .right
gameOverLabel.position = CGPoint(x: 0, y:0)
self.addChild(gameOverLabel)
print("Label added")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
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) {
meteor.position.y -= 6
if meteor.position.y < player.position.y - 300{
meteor.removeFromParent()
addMeteor()
}
}
}
Not sure what I'm doing wrong? I call the gameOver() function in didBegin which is supposed to be executed every time collision is detected. But nothing happens? The meteor simply goes past the rocket ship. If someone could help me that'd be awesome!
As i see you are trying to center the label on the screen, you may try to add this to your code, on gameover func:
1.Try change the position to this
gameOverLabel.position = CGPoint(x: (self.scene!.frame.width / 2) - (self.scene!.frame.width * self.scene!.anchorPoint.x) / 2, y:(self.scene!.frame.height / 2) - (self.scene!.frame.height * self.scene!.anchorPoint.y))
2.I don't know if you have something that is over the label and affecting its view, so try to:
gameOverLabel.zPosition = 10 //A number higher than any other zPosition
3.The label may be too small...try:
gameOverLabel.fontSize = 30 //Adjust it to what you want
4.Just a tip:
move the:
var gameOverLabel: SKLabelNode!
to next line after the
let meteor = SKSpriteNode(imageNamed: "meteor")
This way you will have a universal label, and do not have to regenerate the label every time.
If you do this, just a final tip...I don't know if you have a func like gameRestart() or something like this...but make sure that when you restart the game, you remove the label:
gameOverLabel.removeFromParent()
If you don't do this, when the app try to add the label again it will crash!
Hope it helps!
You are not moving any of your objects as far as your physics in concerned. You need to apply forces instead of manually moving a position. Also your player dynamic is set to false, which means that he never moves, so contacts only happen on the meteor end.

enumerateChildNodes not finding child nodes in sprite kit - Swift 4

I am trying to create a SpriteKit game where a ball moves across the screen. When the ball leaves the screen I would like to remove it from the parent and switch to a different scene (GameOverScene).
I am using enumerateChildNodes however it doesn't as if that is working. I am not really sure what the problem is however I think it may have something to do with the parent/child relationship...
func createBall(forTrack track: Int) {
setupTracks()
player?.physicsBody?.linearDamping = 0
player = SKSpriteNode(imageNamed: "small")
player?.name = "BALL"
player?.size = CGSize(width: 100, height: 100)
ballValue = 1
randFloat = Float(arc4random()) / Float(UINT32_MAX)
if randFloat > 0.001 {
ballSpeed = randFloat / 50
}
else {
ballSpeed = randFloat / 50
}
let ballPosition = trackArray?[track].position
player?.position = CGPoint(x: (ballPosition?.x)!, y: (ballPosition?.y)!)
player?.position.y = (ballPosition?.y)!
if ballDirection == "right" {
player?.position.x = 0
moveRight()
}
else {
player?.position.x = (self.view?.frame.size.height)!
moveLeft(speed: ballSpeed)
}
self.addChild(player!)
self.enumerateChildNodes(withName: "BALL") { (node: SKNode, nil) in
if node.position.x < -100 || node.position.x > (self.size.width) + 100 {
print("balls Out")
node.removeFromParent()
let transition = SKTransition.fade(withDuration: 1)
self.gameScene = SKScene(fileNamed: "GameOverScene")
self.gameScene.scaleMode = .aspectFit
self.view?.presentScene(self.gameScene, transition: transition)
}
}
}
I call this function twice, first in override func didMove():
override func didMove(to view: SKView) {
createHUD()
createBall(forTrack: track)
}
And second in override func touchesBegan:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.previousLocation(in: self)
let node = self.nodes(at: location).first
if node?.name == "BALL" {
currentScore += ballValue
player?.removeFromParent()
createBall(forTrack: track)
}
else {
let transition = SKTransition.fade(withDuration: 1)
gameScene = SKScene(fileNamed: "GameOverScene")
gameScene.scaleMode = .aspectFit
self.view?.presentScene(gameScene, transition: transition)
}
}
}
update:
The line self.enumerateChildNodes(withName: "BALL") { (node: SKNode, nil) in works so it is not a child parent relationship issue. The if statement is not working.
reading your code I think it's not about a solution, but a different approach. These suggestions will make your life easier:
Start with a smaller code, avoid or comment out all unneeded (like the if randFloat)
Force unwrap player = SKSpriteNode(imageNamed: "small")! with the ! because you actually want to crash if the initialization fails, then you can get rid of all ?
in touchesBegan, better use for touch in touches { as it is more simple to manage
let location = touch.previousLocation(in: self) you probably mean let location = touch.location(in: self)
When the ball leaves the screen I would like to remove it from the parent so this is something happening at some time in the game. You would like to call your self.enumerateChildNodes(withName: "BALL") { into the update call, not every time you touch the screen
If this is not enough, feel free to post the least amount of code to make a playground and let me test it for you :]

My Sprite won't perform the actions I tell it to?

I am making an app in Xcode 7 using Swift and SpriteKit. My idea is to have a spaceship that moves with the finger, or basically just follows it, yet I'm having trouble. This is the code:
import Foundation
import SpriteKit
class secondScene: SKScene {
override func didMoveToView(view: SKView) {
// var timer = NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector: Selector(spawnBullets()), userInfo: nil, repeats: true)
//buttons
let leftbutton = SKSpriteNode(imageNamed: "LeftArrow")
leftbutton.position = CGPoint(x: self.frame.size.width*0.2, y: self.frame.size.height*0.1)
leftbutton.size = CGSize(width: 220, height: 80)
leftbutton.color = SKColor.blueColor()
leftbutton.zPosition = 100
self.addChild(leftbutton)
let rightbutton = SKSpriteNode(imageNamed: "RightArrow")
rightbutton.position = CGPoint(x: self.frame.size.width*0.8, y: self.frame.size.height*0.1)
rightbutton.size = CGSize(width: 220, height: 80)
rightbutton.color = SKColor.blueColor()
rightbutton.zPosition = 100
self.addChild(rightbutton)
//spaceship
let spaceship = SKSpriteNode(imageNamed: "spaceship")
spaceship.zPosition = 100
spaceship.size = CGSize(width: 200, height: 120)
spaceship.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height*0.15)
self.addChild(spaceship)
}
let spaceship = SKSpriteNode(imageNamed: "spaceship")
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
spaceship.position.x = location.x
}
}
}
I run this and to me it seems like my spaceship should follow my finger, yet it doesn't. It just stays in the position I specifies before. I've got no errors and I'm out of ideas. Any thoughts?
Remove this line in your spaceship code in didMoveToView
let spaceship = SKSpriteNode(imageNamed: "spaceship")
so that you are actually using the global property you created above touchesBegan.
You probably also want to include/move your code in touchesMoved.
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
spaceship.position.x = location.x
}
}
Hope this helps

Get SKCameraNode to follow a hero Spritenode

I can't seem to figure this out. I've tried many different things and none of them seem to work. With my current code, the camera and the hero never line up and the scene seems to jump pretty far when I touch the screen. All I want to do is when I touch the screen have the hero move to the touch point and have the camera follow him. Is there some way to lock the camera to the hero spritenode?
import SpriteKit
let tileMap = JSTileMap(named: "level2.tmx")
let hero = SKSpriteNode(imageNamed: "hero")
let theCamera: SKCameraNode = SKCameraNode()
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
self.anchorPoint = CGPoint(x: 0, y: 0)
self.position = CGPoint(x: 0, y: 0)
hero.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
hero.xScale = 0.5
hero.yScale = 0.5
hero.zPosition = 2
tileMap.zPosition = 1
tileMap.position = CGPoint(x: 0, y: 0)
self.addChild(tileMap)
self.addChild(hero)
self.addChild(theCamera)
self.camera = theCamera
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
let action = SKAction.moveTo(location, duration: 1)
hero.runAction(action)
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
self.camera?.position = hero.position
}
}
The reason why you saw the scene jumped pretty far is because the scene.size doesn't equal to the screen size. I guess you might initialize your first scene like this:
// GameViewController.swift
if let scene = GameScene(fileNamed:"GameScene") {...}
That code will load GameScene.sks whose size is 1024*768 by default. But since you add your SKSpriteNode programmatically, you can initialize the scene in this way to fit the screen size:
// GameViewController.swift
// Only remove if statement and modify
let scene = GameScene(size: view.bounds.size) ...
This will solve most of the problem you have. Moreover, I suggest moving the camera node using SKAction:
override func update(currentTime: CFTimeInterval) {
let action = SKAction.moveTo(hero.position, duration: 0.25)
theCamera.runAction(action)
}
The last thing, add this line to align the camera with your hero at the start:
self.camera?.position = hero.position