Updating SKLabel text through an SKNode subclass - swift

My Goal: add one to the amount variable of the Achievements Class and then update the text so it shows on the SKLabelNode called "AmountLabel"
So I have a subclass of SKNode that shelters my SKSpriteNode that shows the emblem of the achievement and the SKLabelNodes of the title of the achievement and the amount needed to unlock the achievement As seen below:
class Achievements: SKNode {
var achievementLabel = SKLabelNode()
var achievementTitleLabel = SKLabelNode()
var achievementNode = SKSpriteNode()
var amount = 0
var neededAmount = 0
var Image: String = "locked" {
didSet {
var texture = SKTexture(imageNamed: Image)
}
}
func createAchievement() {
let tex:SKTexture = SKTexture(imageNamed: Image)
achievementNode = SKSpriteNode(texture: tex, color: SKColor.black, size: CGSize(width: 75, height: 75)) //frame.maxX / 20, height: frame.maxY / 20))
achievementNode.zPosition = -10
achievementNode.name = "AchievementNode"
amount = 0
neededAmount = 10
self.addChild(achievementNode)
self.zPosition = -11
Achievements3.append(achievementNode)
createAchievementLabels()
}
func createAchievementLabels() {
achievementTitleLabel = SKLabelNode(fontNamed: "Avenir-Black")
achievementTitleLabel.fontColor = UIColor.black;
achievementTitleLabel.fontSize = 13 //self.frame.maxY/30
achievementTitleLabel.position = CGPoint (x: 0, y: 45)
achievementTitleLabel.text = "COLLECTOR"
achievementTitleLabel.zPosition = -9
addChild(achievementTitleLabel)
achievementTitleLabel.name = "AchievementTitleLabel"
achievementLabel = SKLabelNode(fontNamed: "Avenir-Black")
achievementLabel.fontColor = UIColor.black;
achievementLabel.fontSize = 13 //self.frame.maxY/30
achievementLabel.position = CGPoint (x: 0, y: -50)
achievementLabel.text = ("\(amount) / \(neededAmount)")
achievementLabel.zPosition = -9
addChild(achievementLabel)
achievementLabel.name = "AmountLabel"
AchievementLabels.append(achievementLabel)
}
func UpdateText() {
achievementLabel.text = ("\(amount) / \(neededAmount)")
}
}
they're are then added in another class called AchievementMenu like so:
for 0...12 {
let Achievement = Achievements()
Achievement.createAchievement()
Achievement.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
Achievement.zPosition = 100
moveableArea.addChild(Achievement)
}
Then in another function in the AchievementMenu class I'm trying to enumerate through the the node tree to be able to update the label to add one to its amount variable and then updates its text like so:
func addOneToAchievementAndUpdateText() {
moveableArea.enumerateChildNodes(withName: "SKNode") {
node, stop in
let LabelAmount:Achievements = node as! Achievements
node.enumerateChildNodes(withName: "AmountLabel") {
node, stop in
LabelAmount.amount += 1
LabelAmount.UpdateText()
}
}
}
but every time i try and cast a variable as an actual class it gives me an error:
Could not cast value of type 'SKLabelNode' (0x10e5edb08) to
'Astrum.Achievements' (0x10c899438).
How do I add one to the amount variable and then update the text so it shows the new amount on the screen?

To avoid the casting error you can check the node type as:
func addOneToAchievementAndUpdateText() {
moveableArea.enumerateChildNodes(withName: "SKNode") {
node, stop in
print("- node type is: \(type(of: node))")
if node is Achievements {
let achievements = node as! Achievements
achievements.amount += 1
achievements.UpdateText()
}
}
}
Your mistake would not have appeared if you put after the line:
Achievement.zPosition = 100
this line:
Achievement.name = "SKNode"
P.S. Don't forget to use lowercase for variables to don't confused these one with class types: generally it's considered as a bad attitude..

Related

enumareteChildNodes with collision detection swift

I am trying to code a word collision detection game.
The problem is that before I add rectangle as a background of my words, all of my codes are working, I can detect the collision and take action. but after add rectangle, I have to change my parents of the word and I add it as the child of background.
This the function that I create word:
func giveWords() {
randomIndex = Int.random(in: 0 ..< lastWords.count)
word = SKLabelNode(text: "\(lastWords[randomIndex])")
lastWords.remove(at: randomIndex)
word.fontSize = 17
word.name = "word"
word.fontName = "HelveticaNeue-Bold"
backgroundWord = SKShapeNode(rect: CGRect(x: 0, y: 0, width: (word.frame.width + 3), height: (word.frame.height + 2)), cornerRadius: 4)
word.physicsBody? = SKPhysicsBody(rectangleOf: self.size)
word.physicsBody?.collisionBitMask = 0
word.physicsBody?.contactTestBitMask = 0
word.physicsBody?.categoryBitMask = 1
word.physicsBody?.affectedByGravity = false
word.physicsBody?.isDynamic = false
word.position = CGPoint(x:(backgroundWord.position.x + word.frame.width/2 + 1 ), y: (backgroundWord.position.y) + 4)
let number = Int.random(in: 1 ..< 9)
backgroundWord.position = CGPoint(x: (50 * number), y: 450)
word.zPosition = 3
backgroundWord.zPosition = 3
backgroundWord.addChild(word)
addChild(backgroundWord)
}
and this is the code that I check collision:
func checkCollision() {
enumerateChildNodes(withName: "word") { (node, _) in
let word = node as! SKLabelNode
if self.basketNode.frame.intersects(word.frame) {
if self.similarWord.contains(word.text!) {
self.score += 1
self.scoreLabel.text = "\(self.score)"
self.takeWord.append(word.text!)
self.run(self.trueSound)
self.backgroundWord.removeFromParent()
} else {
self.run(self.falseSound)
self.health -= 1
self.healthLabel.text = "HP: \(self.health)"
self.backgroundWord.removeFromParent()
}
}
}
}
I tried physics collision but I did not handle, so I chose this algorithm.
After function called, self.basketNode.frame.intersects(word.frame) returns false, this means collision is not detected.
I couldn't handle why collision is not detected.
Thanks in advance!
I solved it, here is the answer and reason.
The reason behind it is, and the reason why I did not detect self.basketNode.frame.intersects(word.frame) is simple.
I tried to detect word's frame intersection but I should detect its parent which is the background. So I changed the word to the background and after that create a let that help me to access word. Here is the answer btw...
func checkCollision() {
enumerateChildNodes(withName: "word") { (node, _) in
let background = node as! SKShapeNode
if self.basketNode.frame.intersects(background.frame) {
if let word = background.childNode(withName: "string word") as? SKLabelNode {
if self.similarWord.contains(word.text!) {
self.score += 1
self.scoreLabel.text = "\(self.score)"
self.takeWord.append(word.text!)
self.run(self.trueSound)
word.removeFromParent()
background.removeFromParent()
} else {
self.run(self.falseSound)
self.health -= 1
self.healthLabel.text = "HP: \(self.health)"
word.removeFromParent()
background.removeFromParent()
}
}
}
}
btw I got helped from Apple documentation
https://developer.apple.com/documentation/spritekit/sknode/1483024-enumeratechildnodes
Thanks for your effort, I am continuing my project.

Another SKSpriteNode is removed when current one is touched

I am in developing an application using Xcode and currently I am facing a issue regarding SKSpriteNodes. When there are more than a single SKSpriteNode and when the node is touched, the touched node is not removed but the other not touched is removed. I have also noticed that when there are multiple nodes on the screen only the latest node coming from the top of the screen is removed whilst others are still moving down, although they are being touched. Can someone help identify why this has occurred and the methods of preventing such mistakes please?
For reference, I am have included the class in which the bug is in.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var gameCounter: Int = 0
private var currentLevel: Int = 0
private var debug: SKLabelNode?
private var increasedTouchArea:SKSpriteNode?
private var generatedNode:SKSpriteNode?
private var moveAndRemove:SKAction?
private var moveNode:SKAction?
// Here we set initial values of counter and level. Debug label is created here as well.
override func didMove(to view: SKView) {
print(action(forKey: "counting") == nil)
gameCounter = 0
currentLevel = 1
//backgroundColor = SKColor.gray
debug = SKLabelNode(fontNamed: "ArialMT")
debug?.fontColor = SKColor.red
debug?.fontSize = 30.0
debug?.position = CGPoint(x: frame.midX, y: frame.midY)
debug?.text = "Counter : [ \(gameCounter) ], Level [ \(currentLevel) ]"
if let aDebug = debug {
addChild(aDebug)
}
}
//Method to start a timer. SKAction is used here to track a time passed and to maintain current level
func startTimer() {
print("Timer Started...")
weak var weakSelf: GameScene? = self
//make a weak reference to scene to avoid retain cycle
let block = SKAction.run({
weakSelf?.gameCounter = (weakSelf?.gameCounter ?? 0) + 1
//Maintaining level
if (weakSelf?.gameCounter ?? 0) < 5 {
//level 1
weakSelf?.currentLevel = 1
} else if (weakSelf?.gameCounter ?? 0) >= 5 && (weakSelf?.gameCounter ?? 0) < 10 {
//level 2
weakSelf?.currentLevel = 2
} else {
//level 3
weakSelf?.currentLevel = 3
}
weakSelf?.debug?.text = "Counter : [ \(Int(weakSelf?.gameCounter ?? 0)) ], Level [ \(Int(weakSelf?.currentLevel ?? 0)) ]"
})
run(SKAction.repeatForever(SKAction.sequence([SKAction.wait(forDuration: 1), block])), withKey: "counting")
}
//Method for stopping timer and reseting everything to default state.
func stopTimer() {
print("Timer Stopped...")
if action(forKey: "counting") != nil {
removeAction(forKey: "counting")
}
gameCounter = Int(0.0)
currentLevel = 1
debug?.text = "Counter : [ \(gameCounter) ], Level [ \(currentLevel) ]"
}
//Get current speed based on time passed (based on counter variable)
func getCurrentSpeed() -> CGFloat {
if gameCounter < 30 {
//level 1
return 1.0
} else if gameCounter >= 31 && gameCounter < 49 {
//level 2
return 2.0
} else {
//level 3
return 3.0
}
}
//Method which stop generating stones, called in touchesBegan
func stopGeneratingStones() {
print("STOPPED GENERATING STONES...")
if action(forKey: "spawning") != nil {
removeAction(forKey: "spawning")
}
}
func randomFloatBetween(_ smallNumber: CGFloat, and bigNumber: CGFloat) -> CGFloat {
let diff: CGFloat = bigNumber - smallNumber
return CGFloat(arc4random() % (UInt32(RAND_MAX) + 1)) / CGFloat(RAND_MAX) * diff + smallNumber
}
//Method for generating stones, you run this method when you want to start spawning nodes (eg. didMoveToView or when some button is clicked)
func generateStones() {
print("Generating Stones...")
let delay = SKAction.wait(forDuration: 1, withRange: 0.5) //Change forDuration: delay decreases as game progresses.
//randomizing delay time
weak var weakSelf: GameScene? = self
//make a weak reference to scene to avoid retain cycle
let block = SKAction.run({
let stone: SKSpriteNode? = weakSelf?.spawnNodes(withSpeed: weakSelf?.getCurrentSpeed() ?? 0.0)
stone?.zPosition = 20
if let aStone = stone {
weakSelf?.addChild(aStone)
}
})
run(SKAction.repeatForever(SKAction.sequence([delay, block])), withKey: "spawning")
}
func spawnNodes(withSpeed stoneSpeed: CGFloat) -> SKSpriteNode? {
let nodeSize = CGSize(width: 60, height: 60) //size of shape.
let initalNodePosition = CGPoint(x: randomFloatBetween(0.0, and: (self.view?.bounds.size.width)!) - 110, y: frame.maxY)
generatedNode = SKSpriteNode(color: SKColor.green, size: nodeSize)
generatedNode?.position = initalNodePosition
moveNode = SKAction.moveBy(x: 0, y: self.view!.scene!.frame.minY + self.view!.scene!.frame.minY , duration: 5.25)
generatedNode?.isUserInteractionEnabled = false //Allows users to touch shape.
increasedTouchArea = SKSpriteNode(color: UIColor.clear, size: CGSize(width: nodeSize.width * 1.35, height: nodeSize.height * 1.35))
increasedTouchArea?.name = "generatedNode"
increasedTouchArea?.isUserInteractionEnabled = false
generatedNode?.addChild(increasedTouchArea!)
moveNode?.speed = stoneSpeed
moveAndRemove = SKAction.sequence([moveNode!, SKAction.removeFromParent()])
generatedNode?.run(moveAndRemove!, withKey: "moving")
generatedNode?.name = "generatedNode"
return generatedNode
}
func deleteNode(){
moveAndRemove = SKAction.sequence([SKAction.removeFromParent()])
generatedNode?.run(moveAndRemove!, withKey: "moving")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent? {
for touch in touches {
let locationTocuhed = touch.location(in: self)
let touchedNode : SKNode = self.atPoint(locationTocuhed)
if touchedNode.name == generatedNode?.name {
print("Node touched")
deleteNode()
}
}
if action(forKey: "counting") == nil {
print("Game started...")
startTimer()
generateStones()
} else {
print("Game paused...")
}
}
}
Your method deleteNode() runs the deletion animation on the node pointed to by generatedNode, not the node last touched:
func deleteNode(){
moveAndRemove = SKAction.sequence([SKAction.removeFromParent()])
generatedNode?.run(moveAndRemove!, withKey: "moving")
}
If you want to delete the node last touched, pass a reference to this node to the method deleteNode and then run your deletion animation in that, not generatedNode.

Removing child from parent node Swift 2

I am trying to update my score label at the end of the game. Since the variable (seems to be) outside of the scope I figured I would just remove the label and make a new one in the adjust function. I am not familiar with swift and would appreciate help. Can I just reposition or do I have to create new label? Cant figure it out. Thanks
func loadScore() {
let scoreBand = SKLabelNode(fontNamed: "Arial")
scoreBand.name = StickHeroGameSceneChildName.ScoreName.rawValue
scoreBand.text = "0"
scoreBand.position = CGPointMake(0, DefinedScreenHeight / 2 - 200)
scoreBand.fontColor = SKColor.whiteColor()
scoreBand.fontSize = 100
scoreBand.zPosition = StickHeroGameSceneZposition.ScoreZposition.rawValue
scoreBand.horizontalAlignmentMode = .Center
addChild(scoreBand)
}
func adjustScore() {
//var scoreBand = scoreBand
scoreBand.position = CGPointMake(0, DefinedScreenHeight / 2 - 100)//doesnt recognize scoreBand
}
You can reposition, just declare scoreBand as a variable of your containing class. For example:
class SomeClass: SKSpriteNode {
var scoreBand: SKLabelNode!
override init(size: CGSize) {
scoreBand = SKLabelNode(fontNamed: "Arial")
super.init(size: size)
anchorPoint = CGPointMake(0.5, 0.5)
physicsWorld.contactDelegate = self
}
func loadScore() {
scoreBand.name = StickHeroGameSceneChildName.ScoreName.rawValue
scoreBand.text = "0"
scoreBand.position = CGPointMake(0, DefinedScreenHeight / 2 - 200)
scoreBand.fontColor = SKColor.whiteColor()
scoreBand.fontSize = 100
scoreBand.zPosition = StickHeroGameSceneZposition.ScoreZposition.rawValue
scoreBand.horizontalAlignmentMode = .Center
addChild(scoreBand)
}
func adjustScore() {
scoreBand.position = CGPointMake(0, DefinedScreenHeight / 2 - 100)
}
}

How to animate a matrix changing the sprites one by one?

I´m making a little game were I have a matrix compose for SKSpriteNode and numbers, when the game its over I´m trying to make an animation were I go over the matrix changing only the sprite one by one following the order of the numbers. Look the
Board (The squares are in a Sknode and the number in other Sknode)
The Idea is change the sprite to other color and wait 2 sec after change the next but I can´t do it. I don't know how to change the sprite one by one. I make this function "RecoverMatrix()", this change the sprites but all at once, it is as if not take the wait, he change all the sprites and before wait the 2 sec.
func RecoverMatrix() {
var cont = 1
TileLayer.removeAllChildren()
numLayer.removeAllChildren()
let imageEnd = SKAction.setTexture(SKTexture(imageNamed: "rectangle-play"))
let waiting = SKAction.waitForDuration(2)
var scene: [SKAction] = []
var tiles: [SKSpriteNode] = []
while cont <= 16 {
for var column = 0; column < 4; column++ {
for var row = 0; row < 4; row++ {
if matrix[column][row].number == cont {
let label = SKLabelNode()
label.text = "\(matrix[column][row])"
label.fontSize = TileHeight - 10
label.position = pointForBoard(column, row: row)
label.fontColor = UIColor.whiteColor()
let tile = SKSpriteNode()
tile.size = CGSize(width: TileWidth - 3, height: TileHeight - 3)
tile.position = pointForBoard(column, row: row, _a: 0)
TileLayer.addChild(tile)
numLayer.addChild(label)
tiles.append(tile)
scene.append(SKAction.sequence([imageEnd, waiting]))
tile.runAction(imageEnd)
runAction(waiting)
didEvaluateActions()
}
}
}
cont++
}
for tile in tiles {
tile.runAction(SKAction.sequence(scene))
self.runAction(SKAction.waitForDuration(1))
}
}
So, I need help, I don't find the way to make this animation. I really appreciate the help. Thanks!
This is how you can run an action on every node at the same time (using a loop to loop through all the tiles):
class GameScene: BaseScene, SKPhysicsContactDelegate {
var blocks: [[SKSpriteNode]] = []
override func didMoveToView(view: SKView) {
makeBoard(4, height: 4)
colorize()
}
func makeBoard(width:Int, height:Int) {
let distance:CGFloat = 50.0
var blockID = 1
//make a width x height matrix of SKSpriteNodes
for j in 0..<height {
var row = [SKSpriteNode]()
for i in 0..<width {
let node = SKSpriteNode(color: .purpleColor(), size: CGSize(width: 30, height: 30))
node.name = "\(blockID++)"
if let nodeName = node.name {node.addChild(getLabel(withText: nodeName))}
else {
//handle error
}
node.position = CGPoint(x: frame.midX + CGFloat(i) * distance,
y: frame.midY - CGFloat(j) * distance )
row.append(node)
addChild(node)
}
blocks.append(row)
}
}
func colorize() {
let colorize = SKAction.colorizeWithColor(.blackColor(), colorBlendFactor: 0, duration: 0.5)
var counter = 0.0
let duration = colorize.duration
for row in blocks {
for sprite in row {
counter++
let duration = counter * duration
let wait = SKAction.waitForDuration(duration)
sprite.runAction(SKAction.sequence([wait, colorize]))
}
}
}
func getLabel(withText text:String) -> SKLabelNode {
let label = SKLabelNode(fontNamed: "ArialMT")
label.fontColor = .whiteColor()
label.text = text
label.fontSize = 20
label.horizontalAlignmentMode = .Center
label.verticalAlignmentMode = .Center
return label
}
}
And the result:
So basically, as I said in the comments, you can run all the actions at the same moment, it is just about when the each action will start.
You seem to imagine that runAction(waiting) means that you code pauses and waits, pausing between loops. It doesn't (and in fact there is no way to do that). Your code loops through all the loops, now, KABOOM, immediately.
Thus, all the actions are configured immediately and are performed together.

Randomize image in array in sprite kit swift.

I was wondering if someone could show me how to make it so that I can spawn random image missiles. Right now I am using one image called "meteor", I have a few more images I would like to show and randomize. I know I need to put them in an array and create an arc for random. I have done it for sound but I'm not sure how to do it for images. This is my code so far.
var lastMissileAdded : NSTimeInterval = 0.0
let missileVelocity : CGFloat = 4.0
func addMissile() {
// Initializing missile node
var missile = SKSpriteNode(imageNamed: "meteor")
missile.setScale(0.44)
// Adding SpriteKit physics body for collision detection
missile.physicsBody = SKPhysicsBody(rectangleOfSize: missile.size)
missile.physicsBody?.categoryBitMask = UInt32(obstacleCategory)
missile.physicsBody?.dynamic = true
missile.physicsBody?.contactTestBitMask = UInt32(shipCategory)
missile.physicsBody?.collisionBitMask = 0
missile.physicsBody?.usesPreciseCollisionDetection = true
missile.name = "missile"
// Selecting random y position for missile
var random : CGFloat = CGFloat(arc4random_uniform(300))
missile.position = CGPointMake(self.frame.size.width + 20, random - 20)
self.addChild(missile)
}
func moveObstacle() {
self.enumerateChildNodesWithName("missile", usingBlock: { (node, stop) -> Void in
if let obstacle = node as? SKSpriteNode {
obstacle.position = CGPoint(x: obstacle.position.x - self.missileVelocity, y: obstacle.position.y)
if obstacle.position.x < 0 {
obstacle.removeFromParent()
}
}
})
}
All you need to do is name them meteor0, meteor1 and meteor2 and use String Interpolation to create your node with your random image:
var missile = SKSpriteNode(imageNamed: "meteor\(arc4random_uniform(3))")