How to add score to SpriteKit using SKAction - swift

runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addScore),
SKAction.waitForDuration(1.0) ]) ))
func addScore() {
let scoreSprite = SKSpriteNode(imageNamed: "scoreSprite")
let actualY = random1(min: scoreSprite.size.height/2, max: size.height - scoreSprite.size.height/2)
scoreSprite.position = CGPoint(x: size.width + scoreSprite.size.width/2, y: actualY)
self.addChild(scoreSprite)
let actualDuration = random1(min: CGFloat(0.5), max: CGFloat(0.5))
let actionMove = SKAction.moveTo(CGPoint(x: -scoreSprite.size.width/2, y: actualY), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
scoreSprite.runAction(SKAction.sequence([actionMove, actionMoveDone])) }
Okay, so I want my score to increase by 1 for every second the person plays the game. I looked into using NSTimeInterval but from what I could gather, the better option is to use SKAction. The only way I could think of was to use an almost clear sprite and to move it across the screen every second. What I want to know is how to make a score increase each time the node moves across the screen, or if this is no good what a better option would be. The code I am using to move the node is above. Thanks in advance.

Try adding this to your didMoveToView method
let wait = SKAction.waitForDuration(1.0)
let incrementScore = SKAction.runBlock ({
++self.score
self.scoreLabel.text = "\(self.score)"
})
self.runAction(SKAction.repeatActionForever(SKAction.sequence([wait,incrementScore])))

I fixed it using by adding this to the end of the scoreSprite function:
if scoreSprite.position.x > endOfScreenLeft {
updateScore()
}
Then did this to update the score:
func updateScore() {
score++
scoreLabel.text = String(score)
}
End of screen left is a CGFloat:
endOfScreenLeft = (self.size.width / 2) * CGFloat(-1)

Related

My SKSpriteNode speed values are not updating properly

What I'm trying to do is update my SKSpriteNodes so I can change their scrolling speeds dynamically, however they aren't really working consistently. I didn't include the code, but I have another method with a switch case that sets the value of platformSpeed whenever the state is changed (in this case, the switch case is changed with UIButtons). In my code I have an SKSpriteNode array and a platformSpeed property that includes didSet so my value is updated properly.
In my method to create the platforms, I grouped my SpriteNodes into platformGroup then looped through them with addChild(). Not sure why it's acting this way but here's a quick video of what it looks like in action:
demonstration clip
So with the buttons I'm changing the switch case, and as you can see, not all of the nodes speeds are updating properly and some get faster than others and eventually pass them. I need them to stay equal distance between each other.
Now here's my code:
class GameScene: SKScene, SKPhysicsContactDelegate {
var platformGroup = [SKSpriteNode]()
var platformSpeed: CGFloat = 1.0 {
didSet {
for platforms in platformGroup {
platforms.speed = platformSpeed
}
}
}
let platformTexture = SKTexture(imageNamed: "platform")
var platformPhysics: SKPhysicsBody!
func createPlatforms() {
let platformLeft = SKSpriteNode(texture: platformTexture)
platformLeft.physicsBody = platformPhysics.copy() as? SKPhysicsBody
platformLeft.physicsBody?.isDynamic = false
platformLeft.scale(to: CGSize(width: platformLeft.size.width * 4, height: platformLeft.size.height * 4))
platformLeft.zPosition = 20
let platformRight = SKSpriteNode(texture: platformTexture)
platformRight.physicsBody = platformPhysics.copy() as? SKPhysicsBody
platformRight.physicsBody?.isDynamic = false
platformRight.scale(to: CGSize(width: platformRight.size.width * 4, height: platformRight.size.height * 4))
platformRight.zPosition = 20
let scoreNode = SKSpriteNode(color: UIColor.clear, size: CGSize(width: frame.width, height: 32))
scoreNode.physicsBody = SKPhysicsBody(rectangleOf: scoreNode.size)
scoreNode.physicsBody?.isDynamic = false
scoreNode.name = "scoreDetect"
scoreNode.zPosition = 40
platformGroup = [platformLeft, platformRight, scoreNode]
let yPosition = frame.width - platformRight.frame.width
let max = CGFloat(frame.width / 4)
let xPosition = CGFloat.random(in: -80...max)
let gapSize: CGFloat = -50
platformLeft.position = CGPoint(x: xPosition + platformLeft.size.width - gapSize, y: -yPosition)
platformRight.position = CGPoint(x: xPosition + gapSize, y: -yPosition)
scoreNode.position = CGPoint(x: frame.midX, y: yPosition - (scoreNode.size.width / 1.5))
let endPosition = frame.maxY + (platformLeft.frame.height * 3)
let moveAction = SKAction.moveBy(x: 0, y: endPosition, duration: 7)
let moveSequence = SKAction.sequence([moveAction, SKAction.removeFromParent()])
for platforms in platformGroup {
addChild(platforms)
platforms.run(moveSequence)
}
platformCount += 1
}
func loopPlatforms() {
let create = SKAction.run { [unowned self] in
self.createPlatforms()
platformCount += 1
}
let wait = SKAction.wait(forDuration: 1.1)
let sequence = SKAction.sequence([create, wait])
let repeatForever = SKAction.repeatForever(sequence)
run(repeatForever)
}
I think I can see what's going wrong. When you change platformSpeed, it changes the speed of all the platforms in platformGroup. And createPlatforms() is being called multiple times. Now, each time it's called you create a pair of platforms and assign these to platformGroup. Since you call the function multiple times, it's overwriting any existing values in the array. That's why changing platformSpeed only updates the speed of the latest platforms you've created---the older platforms stay the same speed because they're not in platformGroup anymore.
To fix this, my advice would be to have platformGroup store all the platforms currently on the screen. You could do this by changing
platformGroup = [platformLeft, platformRight, scoreNode]
to something like
let newNodes = [platformLeft, platformRight, scoreNode]
platformGroup += newNodes
// Alternatively, platformGroup.append(contentsOf: newNodes)
Now you need to make sure you're 1) only adding the new nodes to the scene, and 2) removing the old nodes from platformGroup when they're removed from the parent. You could do this by changing
let moveAction = SKAction.moveBy(x: 0, y: endPosition, duration: 7)
let moveSequence = SKAction.sequence([moveAction, SKAction.removeFromParent()])
for platforms in platformGroup {
addChild(platforms)
platforms.run(moveSequence)
}
to something like
let moveAction = SKAction.moveBy(x: 0, y: endPosition, duration: 7)
for node in newNodes {
let moveSequence = SKAction.sequence([
moveAction,
SKAction.removeFromParent(),
SKAction.run {
self.platformGroup.remove(node)
}
])
addChild(node)
node.run(moveSequence)
}
Now that you're keeping a track of all platforms ever made, your speed changes should be applied consistently to every platform on the screen. Hope this works!

How to get my score counter working in a SpriteKit game

I am using SpriteKit and Xcode to make a game and the counter is 'counting' but it only updates at the end of the game when you start a new one.
func adjustScore(by points: Int) {
score += points
}
func projectileDidCollideWithMonster(projectile: SKSpriteNode, monster: SKSpriteNode) {
print("Hit")
projectile.removeFromParent()
monster.removeFromParent()
monstersDestroyed += 1
adjustScore(by: 100)
if monstersDestroyed > 30 {
let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
let gameOverScene = GameOverScene(size: self.size, won: true)
view?.presentScene(gameOverScene, transition: reveal)
}
Where have I gone wrong?
answer solved but heres what is after my View Did Load:
override func didMove(to view: SKView) {
background.zPosition = -1
background.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
addChild(background)
scoreLabel = SKLabelNode(fontNamed: "Chalkduster")
scoreLabel.text = "Points = \(score)"
scoreLabel.fontSize = 20
scoreLabel.fontColor = SKColor.black
scoreLabel.position = CGPoint(x: size.width/6.2, y: size.height/1.2)
addChild(scoreLabel)
HighScoreLabel = SKLabelNode(fontNamed: "Chalkduster")
HighScoreLabel.text = "High score = \(highScore)"
HighScoreLabel.fontSize = 20
HighScoreLabel.fontColor = SKColor.black
HighScoreLabel.position = CGPoint(x: size.width/5.2, y: size.height/6)
addChild(HighScoreLabel)
NewSkinLabel = SKLabelNode(fontNamed: "Chalkduster")
NewSkinLabel.text = ""
NewSkinLabel.fontSize = 20
NewSkinLabel.fontColor = SKColor.black
NewSkinLabel.position = CGPoint(x: size.width/2, y: size.height/4.4)
addChild(NewSkinLabel)
}
You have two problems. First, you never access scoreLabel outside of didMove so the score label never changes. You have to write code to update the score label, as Alexandru demonstrated in his answer.
Your second problem is that you have two scoreLabel variables, a global var and a local scoreLabel let constant in the didMove function. When you declare scoreLabel in didMove,
let scoreLabel = SKLabelNode(fontNamed: "Chalkduster")
scoreLabel.text = "Total Coins: \(score)"
scoreLabel.fontSize = 40
scoreLabel.fontColor = SKColor.black
scoreLabel.position = CGPoint(x: size.width/2, y: size.height/1.5)
addChild(scoreLabel)
This version of scoreLabel goes away when you exit didMove. Now if you access the global scoreLabel to display the score, the label is empty. You loaded and set everything on the temporary local version.
The easiest way to fix this issue and avoid confusion is to have only one scoreLabel variable. Get rid of the global scoreLabel and declare scoreLabel as a property of the GameScene class. Then in didMove, remove the let to load scoreLabel.
scoreLabel = SKLabelNode(fontNamed: "Chalkduster")
Now the value of scoreLabel will stay around when the scene finishes loading. If you update scoreLabel in adjustScore, the score should update.
One more thing. If you have a response about things not working, you need to provide more information than "I tried this and it didn't work." That is not a helpful response. You have to describe what didn't work or else there will be a lot of back and forth trying to get the relevant information, increasing the time it takes for you to get the solution.
When the projectile hits a monster, you update the score, but you don't update the label. In this case, the label will always be equal to the initial value that you set at the start of your game
When you increment the score, you should also update the score_label.
func adjustScore(by points: Int)
{
score += points
score_label.text = "Points = \(score)"
}

Moving an object across the screen at a certain speed.(Sprite Kit)

I have an object that needs to move up and down the screen. When you first click it moves down the screen to an endpoint with a duration of 3 seconds. You can click at anytime to stop it from moving down and make it start moving up to a different endpoint, again at a duration of 3 seconds. The problem with doing it this way is if you click to move the object down but then immediately click again to move it up, the object moves up but at a very slow rate because it has a duration of 3 seconds. (I hope this makes sense) So what I want is to stop using the duration to set the pace/speed at which the object moves. Is there a way to say move to point x.y at blank speed? Thank you. (No matter where the object is and has to move to I want it to always move at the same pace.)
This is what I use to move the object right now:
func moveObject(){
let endpoint = CGPoint(x: self.size.width / 2 , y: self.size.height / 1.8888888888 )
let moveObject = SKAction.moveTo(endpoint, duration: 3.0 )
let moveObjectSequence = SKAction.sequence([moveLine])
Object.runAction(moveLineSequence)
}
Code after corrections:
func dropLine(){
if hookNumber == 1{
let endpoint = CGPoint(x: self.size.width / 2 , y: self.size.height / 1.8888888888 )
let moveLine = SKAction.moveTo(endpoint, duration: getDuration(fishLine.position,pointB:endpoint,speed:300.0))
let moveLineSequence = SKAction.sequence([moveLine])
fishLine.runAction(moveLineSequence)
}
}
func dropHook(){
if hookNumber == 1{
let endpoint = CGPoint(x: self.size.width / 2 , y: self.size.height - 2030)
let moveLine = SKAction.moveTo(endpoint, duration: getDuration(fishHook.position,pointB:endpoint,speed:300.0))
let moveLineSequence = SKAction.sequence([moveLine])
fishHook.runAction(moveLineSequence)
hookNumber = 2
}
}
func raiseLine(){
if hookNumber == 2{
let endpoint = CGPoint(x: self.size.width / 2 , y: 3050 )
let moveLine = SKAction.moveTo(endpoint, duration: getDuration(fishLine.position,pointB:endpoint,speed:300.0))
let moveLineSequence = SKAction.sequence([moveLine])
fishLine.runAction(moveLineSequence)
}
}
func raiseHook(){
if hookNumber == 2{
let endpoint = CGPoint(x: self.size.width / 2 , y: self.size.height - 3 )
let moveLine = SKAction.moveTo(endpoint, duration: getDuration(fishHook.position,pointB:endpoint,speed:300.0))
let moveLineSequence = SKAction.sequence([moveLine])
fishHook.runAction(moveLineSequence)
}
}
I think you could revise your approach based on the calculation speed.
So, if you know your speed, you can calculate how many time a node takes to arrive to a point and change your speed to a reasonable value.
// #-#-#-#-#-#-#-#-#-#-#-#-#-#-#
//MARK: - Calculate action duration btw two points and speed
// #-#-#-#-#-#-#-#-#-#-#-#-#-#-#
func getDuration(pointA:CGPoint,pointB:CGPoint,speed:CGFloat)->NSTimeInterval {
let xDist = (pointB.x - pointA.x)
let yDist = (pointB.y - pointA.y)
let distance = sqrt((xDist * xDist) + (yDist * yDist));
let duration : NSTimeInterval = NSTimeInterval(distance/speed)
return duration
}
Suppose you have your node with a speed of 150.0
Your move will be:
let moveObject = SKAction.moveTo(endpoint, duration: getDuration(node.position,pointB:endpoint,speed:150.0))
With this approach you speed dont' change and you don't have this disagreeable slowness.
P.S. Don't forgot to change the uppercase to your properties: in swift is a bad attitude, use object instead of Object.

Randomizing node movement duration

I am making a game in SpriteKit and I have a node that is moving back and forth across the screen and repeating using the code:
let moveRight = SKAction.moveByX(frame.size.width/2.8, y: 0, duration: 1.5)
let moveLeft = SKAction.moveByX(-frame.size.width/2.8, y: 0, duration: 1.5)
let texRight = SKAction.setTexture(SKTexture(imageNamed: "Drake2"))
let texLeft = SKAction.setTexture(SKTexture(imageNamed: "Drake1"))
let moveBackAndForth = SKAction.repeatActionForever(SKAction.sequence([texRight, moveRight, texLeft, moveLeft,]))
Drake1.runAction(moveBackAndForth)
I am trying to figure out what method I can use to randomize the duration. Every time moveBackandForth runs, I want it to rerun using a different duration, (within games, not between). If someone could give me some example code to try I would really appreciate it.
Also arc4Random works fine, but it doesn't randomize within the game, only between games.
When you run actions like from your example and randomize duration parameter with something like arc4Random this is actually happening:
Random duration is set and stored in action.
Then action is reused in a sequence with a given duration.
Because the action is reused as it is, duration parameter remains the same over time and moving speed is not randomized.
One way to solve this (which I prefer personally) would be to create a "recursive action", or it is better to say, to create a method to run desired sequence and to call it recursively like this :
import SpriteKit
class GameScene: SKScene {
let shape = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 20, height: 20))
override func didMoveToView(view: SKView) {
shape.position = CGPointMake(CGRectGetMidX(self.frame) , CGRectGetMidY(self.frame)+60 )
self.addChild(shape)
move()
}
func randomNumber() ->UInt32{
var time = arc4random_uniform(3) + 1
println(time)
return time
}
func move(){
let recursive = SKAction.sequence([
SKAction.moveByX(frame.size.width/2.8, y: 0, duration: NSTimeInterval(randomNumber())),
SKAction.moveByX(-frame.size.width/2.8, y: 0, duration: NSTimeInterval(randomNumber())),
SKAction.runBlock({self.move()})])
shape.runAction(recursive, withKey: "move")
}
}
To stop the action, you remove its key ("move").
I don't have any project where I can try right now.
But you might want to try this :
let action = [SKAction runBlock:^{
double randTime = 1.5; // do your arc4random here instead of fixed value
let moveRight = SKAction.moveByX(frame.size.width/2.8, y: 0, duration: randTime)
let moveLeft = SKAction.moveByX(-frame.size.width/2.8, y: 0, duration: randTime)
let texRight = SKAction.setTexture(SKTexture(imageNamed: "Drake2"))
let texLeft = SKAction.setTexture(SKTexture(imageNamed: "Drake1"))
let sequence = SKAction.sequence([texRight, moveRight, texLeft, moveLeft])
Drake1.runAction(sequence)
}]; 
let repeatAction = SKAction.repeatActionForever(action)
Drake1.runAction(repeatAction)
Let me know if it helped.

Random movement of sprite

I'm new here so i apologize if i've entered my question wrongly. That said, i'm having an issue making my sprite move into random locations around the screen. here is my code
func random() ->CGFloat{
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat, max:CGFloat) ->CGFloat{
return random()*(max-min)+min
}
dino.position = CGPoint(x: size.width + dino.size.width/2, y: actualY)
// Add the monster to the scene
addChild(dino)
// Determine speed of the monster
let actualDuration = random(min: CGFloat(3.0), max: CGFloat(4.0))
let randomNum = CGPoint(x:Int (arc4random()%1000), y:Int (arc4random()%1000))
// Create the actions
var actionMove = SKAction:CGPoint(CGPoint(x:randomNum, y:randomNum)), duration:NSTimeInterval;(actualDuration)
let actionMoveDone = SKAction.removeFromParent()
dino.runAction(SKAction.sequence([actionMove, actionMoveDone]))
thank you for helping, anything helps at this point
The function you are using is not valid. Use SKAction.moveTo instead.
dino.position = CGPoint(x: size.width + dino.size.width/2, y: 10)
// Add the monster to the scene
addChild(dino)
// Determine speed of the monster
let actualDuration = NSTimeInterval(random(min: CGFloat(3.0), max: CGFloat(4.0)))
let randomNum = CGPoint(x:Int (arc4random() % 1000), y:Int (arc4random() % 1000))
var actionMove = SKAction.moveTo(randomNum, duration: actualDuration) // Changed line
let actionMoveDone = SKAction.removeFromParent()
dino.runAction(SKAction.sequence([actionMove, actionMoveDone]))