In my game I am trying to add 1 to a variable score every time certain conditionals happen in my update function. The problem is that the update function is called every frame so my score variable just keeps climbing into the hundreds when it should be going up by 1 slowly. Here is my code:
override func update(_ currentTime: TimeInterval) {
if circleStart.frame.width >= self.frame.width && userTouched == false{
hasTouchedEdge = true
}
if hasTouchedEdge == true && userTouched == false {
scaleSpeed -= 0.2
}
if scaleSpeed <= 1.0 && userTouched == false {
scaleSpeed += 0.3
hasTouchedEdge = false
}
if userTouched == false{
scaleSpeed += 0.1
circleStart.setScale(CGFloat(scaleSpeed))
circleLength = circleStart.frame.width
acceptableBound1 = outerCircle.frame.width
acceptableBound2 = outerCircle.frame.width - 100
}
if userTouched == true && circleLength > acceptableBound2 && circleLength < acceptableBound1{
print("worked")
isCorrect = true
}
if userTouched == true && circleLength > acceptableBound1 {
gameOverLabel.isHidden = false
playAgain.isHidden = false
playAgain.run(fadingTapIn)
}
if userTouched == true && circleLength < acceptableBound2{
gameOverLabel.isHidden = false
playAgain.isHidden = false
playAgain.run(fadingTapIn)
}
if isCorrect == true {
score += 1
}
scoreLabel.text = String(score)
}
What do I need to do or add to have my score variable work in my desired way explained above?
Easy solution:
Add isCorrect = false after score += 1.
Good solution:
Try removing most of that code in update. Update is called nearly 60 times per second in most cases and it is not the best place to make changes for a few reasons.
Try using
override func touchesBegan(touches: Set<UITouch>,with event: UIEvent?) {
for touch in touches {
let location = touch.location
if (someNode.contains(location)) {
//This if statement isn't needed, but an example of how you can determine if a node was pressed
someFunctionToDoStuff(location)
}
}
}
or
override func touchesBegan(touches: Set<UITouch>,with event: UIEvent?) {
let touch = touches.first
let location = touch.location
if (someNode.contains(location)) {
//This if statement isn't needed, but an example of how you can determine if a node was pressed
someFunctionToDoStuff(location)
}
}
The first option may run multiple times depending on how many touches were determined. The second option will only pay attention to the first touch it notices and disregard the rest.
You can define this function however you like, just make sure to take the location of the touch if you need it:
func someFunctionToDoStuff(_ location: CGPoint) {
score += 1
// Then maybe move a node to where the press was located
ball.position = location
}
You can use touchesBegan, touchesMoved, and touchesEnded as functions.
Check out Ray Wenderlich's SpriteKit tutorials, you will learn so much! https://www.raywenderlich.com/145318/spritekit-swift-3-tutorial-beginners
Related
Making a Texas Hold'Em app. This is my first time working in sprite-kit. My current problem is that I have the cards as Color Sprite objects.
This is the GameScene with the cards. Ignore the five cards on the top. I only want the bottom two to have the flip functionality.
My goal is for the user to be able to tap one of their cards and it will flip from the back of the card, to the card in their hand and vice versa. My current strategy is using this override func touchesBegan.
The array playerOne is the cards for playerOne.
The cardOne and cardTwo are the cards for the playerOne.
FlippedOne and flippedTwo are keeping track of which card is currently flipped. This way the cards would flip individually.
This same concept is repeated if there is multiple players.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if (playerCount == 0 && turn == 0) {
let cardOne = playerOne[0].getNum()
let cardTwo = playerOne[1].getNum()
var flippedOne = 0
var flippedTwo = 0
for touch in touches {
let location = touch.location(in: self)
if (playerCardOne.frame.contains(location) && flippedOne == 0) {
playerCardOne.texture = cardTextures[cardOne]
flippedOne = 1
}
if (playerCardOne.frame.contains(location) && flippedTwo == 1) {
playerCardOne.texture = back
flippedOne = 0
}
if (playerCardTwo.frame.contains(location) && flippedTwo == 0) {
playerCardTwo.texture = cardTextures[cardTwo]
flippedTwo = 1
}
if (playerCardTwo.frame.contains(location) && flippedTwo == 0) {
playerCardTwo.texture = back
flippedTwo = 0
}
}
}
}
If there is a better way of making the cards flip on tap, I am open to ideas.
My thought on why this is not working is because it's an override function.
What currently happens is that I can get one of the cards to flip from the back to the actual card face. After which I cannot get either card to flip.
the issue with why you can't repeatedly run the code is because you are resetting the flippedOne and flippedTwo variables every time touches began gets fired
Move the variables out to the scene and it will work fine.
I also recommend converting them to Bool for simplicity
Also you don't need the frame property when checking if the spriteNode contains a location
Also brackets around the if statement aren't required in Swift (you must've come from a different language to Swift)
private var flippedOne = false
private var flippedTwo = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let location = touch!.location(in: self)
if card1.contains(location) {
if flippedOne {
card1.texture = back
flippedOne = false
}
else {
card1.texture = cardTextures[cardOne]
flippedOne = true
}
}
if card2.contains(location) {
if flippedTwo {
card2.texture = back
flippedTwo = false
}
else {
card2.texture = cardTextures[cardTwo]
flippedTwo = true
}
}
}
The if statements in the touches began func can be further simplified as such...
if card1.contains(location) {
card1.texture = flippedOne ? back : cardTextures[cardOne]
flippedOne = !flippedOne
}
if card2.contains(location) {
card2.texture = flippedTwo ? back : cardTextures[cardTwo]
flippedTwo = !flippedTwo
}
I create a game via SpriteKit. in the game every few second a ball spawn to the screen (via function) and the player has to blow them up. now, I want to check the player level via his score so if the score is bigger than 10 the spawnBall function will be executed twice (so 2 ball will spawn on the screen) an so on. I tried to to it via the update fun (that will "read" the player score and depends on the score will call the spawnBall function). Unfortunately when I do it the screen is spawn with million (or so) balls in few seconds (as I said I want it to call the function every few seconds and increase the call while the score is X). I really don't have any idea how to do it.
here is my code:
override func update(_ currentTime: CFTimeInterval) {
if (self.score <= 10){
spawnBalls()
}
if (self.score > 10 && self.score <= 20){
spawnBalls()
spawnBalls()
}
if (self.score > 20){
spawnBalls()
spawnBalls()
spawnBalls()
}
if (self.subscore == 3) {
_ = randomBallColorToBlow()
self.subscore = 0
}
}
func spawnBalls() {
let wait = SKAction.wait(forDuration: 1)
let action = SKAction.run {
self.createBall()
}
run(SKAction.repeatForever((SKAction.sequence([wait, action]))))
}
how can I do it without using the update function??
you are calling spawn balls 60 times a second by calling it in your update func.
try just checking if a certain requirement is met to upgrade to a higher spawn rate in your update but keep the calls out of the update func.
private var upgradedToLevel2 = false
private var upgradedToLevel3 = false
//called somewhere probably in a start game func
spawnBalls(duration: 1.0)
override func update(_ currentTime: CFTimeInterval) {
if (self.score > 10 && self.score <= 20) && !upgradedToLevel2 {
//this prevents the if loop from running more than once
upgradedToLevel2 = true
self.removeAction(forKey: "spawn")
spawnBalls(duration: 0.5)
}
if (self.score > 20) && !upgradedToLevel3 {
//this prevents the if loop from running more than once
upgradedToLevel3 = true
spawnBalls(duration: 0.33)
}
}
func spawnBalls(duration: Double) {
let wait = SKAction.wait(forDuration: duration)
let action = SKAction.run { self.createBall() }
let repeater = SKAction.repeatForever(SKAction.sequence([wait, action]))
run(repeater, withKey: "spawn")
}
As stated, you are spawning your balls multiple times and need to break it up. I would recommend keeping track of level using an Int instead of a bool to be able to handle an "infinite" amount of level ups without making an "infinite" amount of boolean variables
private var nextLevel = 0 //Level 0 allows us to spawn a ball on startup, so no need to call spawnBalls anywhere else
override func update(_ currentTime: CFTimeInterval) {
if (self.score > 10 * nextLevel){
self.removeAction(forKey: "spawn") //this prevents the if loop from running more than once
nextLevel += 1
spawnBalls(count:nextLevel,forDuration:1) //You can change the 1 here if you want to spawn balls at a faster speed, I would recommend a variable that uses nextLevel in a forumula
}
}
func spawnBalls(count:Int, forDuration duration:TimeInterval) {
let range = 0..<count
let wait = SKAction.wait(forDuration: duration)
let action = SKAction.run {range.forEach{self.createBall()}}
let repeater = SKAction.repeatForever(SKAction.sequence([wait, action]))
removeAction(forKey:"spawn")
run(repeater, withKey: "spawn")
}
It's the third time I'm looking for help in this question.
I have a class name BallNode of kind SKShapeNode. Inside my code I also have a function the spawn ball every 3 second from the top side of the screen. Now, I want to set a function that locate the ball position every 1 second, and so, if the ball.position.y > 200 to print a message to the console.
The purpose of this is that if any ball will be at this position (not while it's falling down) the I will call another function. I tried to do it via SKAction, update(_ currentTime: CFTimeInterval), Timer but I didn't succeed and I really have no idea what to do...
update - my current code:
var timeType1: CFTimeInterval = 0.0
var timeType2: CFTimeInterval = 2.0
override func update(_ currentTime: CFTimeInterval) {
if (currentTime - timeType1 > timeType2){
print("time")
self.checkBallsPosition()
self.timeType1 = currentTime
}
self.enumerateChildNodes(withName: "color.BallNode") { node, _ in
self.checkBallsPosition()
}
}
func checkBallsPosition() {
self.enumerateChildNodes(withName: "BALL") { (node: SKNode, nil) in
let x = self.createTopBorder()
x.isHidden = true
let wait2 = SKAction.wait(forDuration: 1)
let action2 = SKAction.run {
let position = Double(node.position.y)
if position < 200 {
}
else if position > 200 {
print("bolbo")
node.removeFromParent()
}
}
self.run(SKAction.sequence([wait2,action2]))
}
}
thats what I try do to so far, as I said the problem is that I want to get the ball last position. because the ball fall down the screen the last position should be when it touch the bottom border of the screen or when it touches another ball. if I set it at update I get the ball position every frame or (as I did) every second - but not the last. another problem is that the ball position can always change depends on another balls (when collision occurs).
update #2 - another functions:
func spawnBalls() {
let wait = SKAction.wait(forDuration: 3)
let action = SKAction.run {
self.createBall()
}
run(SKAction.repeatForever((SKAction.sequence([wait, action]))))
}
func createBall(){
let ball = BallNode(radius: 65)
print(ball.Name)
print(ball._subName!)
ball.position.y = ((frame.size.height) - 200)
let ballXPosition = arc4random_uniform(UInt32(frame.size.width))
ball.position.x = CGFloat(ballXPosition)
ball.physicsBody?.categoryBitMask = PhysicsCategory.ball
ball.physicsBody?.collisionBitMask = PhysicsCategory.ball
ball.physicsBody?.contactTestBitMask = PhysicsCategory.topBorder
ball.delegate = self
addChild(ball)
}
You did not post the entire code you are using. Not knowing how you are spawning your balls, how is the hierarchy and how you are moving them, I can't reproduce it here.
I am not sure if I understand it right, but I believe it might be simpler than what you are doing. See if the code below helps you, I am making the balls fall with SKAction (you can do it with physics also), and checking when they go below zero, when I remove them.
private let ballSpeed : CGFloat = 400
private let ballRadius : CGFloat = 10
private func spawnBall(atPoint pos : CGPoint){
let b = SKShapeNode(circleOfRadius: ballRadius)
b.name = "ball"
b.position = pos
addChild(b)
b.run(SKAction.moveTo(y: -size.height, duration: TimeInterval((pos.y+size.height)/400)))
}
override func update(_ currentTime: TimeInterval) {
super.update(currentTime)
enumerateChildNodes(withName: "ball") { (node, _) in
if node.position.y < 0 {
node.removeFromParent()
}
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let pos = touch.location(in: self)
spawnBall(atPoint: pos)
}
}
I’m working with Xcode 8 and Swift 3, trying to display a “Resume Game” button on my scene after pausing the game but since everything stops, I have not found a way to properly do so. I followed Mike's advice on this page “Tap To Resume” Pause text SpriteKit, but that didn't help.
When my pause button is tapped, it runs the pause function with the following code:
func pauseGame() {
self.isPaused = true
self.physicsWorld.speed = 0
self.speed = 0.0
self.scene?.view?.isPaused = true
}
It may seem a little overkill but that works. If I remove
self.scene?.view?.isPaused = true
from my pause function, I'm able to display the tap to Resume Button but I can still interact with some of the SpriteKit nodes in the scene. I'm working on a space shooter game so the user can still move the spceship and tap to fire although the bullets don't travel until I resume the scene.
I thought about adding a boolean "true" to the pause function and adding an IF statement to the firing and the moving above but that seems to me like complicating things a bit.
Any suggestions on how I can display the Resume button when pausing the scene?
You can't interact with any nodes on your scene because you've paused it entirely, which pauses all its children, which is everything on the scene. To avoid this, pause only certain SKNodes (layers).
Add certain nodes to different SKNodes so instead of pausing the entire scene, you can only pause the the layer (SKNode) that you wish to pause (gameLayer). It would look something like this:
Initialize the nodes
let gameLayer = SKNode()
let pauseLayer = SKNode()
Now when you want to add a child to the scene, instead add it to the layer that you want it to be a part of:
Add child nodes to the main layers
gameLayer.addChild(gameSceneNode)
pauseLayer.addChild(resumeButton)
Don't forget to add the layers to the scene too
Add the layers to the scene
addChild(gameLayer)
addChild(pauseLayer)
To pause a layer write this:
gameLayer.isPaused = true
Note that in this example, all the nodes on the gameLayer will be paused, however everything on the pauseLayer will not.
Your complete example might look something like this:
func pauseGame() {
gameLayer.isPaused = true
pauseLayer.isHidden = false
gameLayer.physicsWorld.speed = 0
gameLayer.speed = 0.0
}
func unpauseGame() {
gameLayer.isPaused = false
pauseLayer.isHidden = true
// Whatever else you need to undo
}
For my next game I'm sure I'll give Nik's suggestion a try. As for my issue above, it worked by using the code shown below. The additional code is to dismiss the pause button as well. Now that this works I can maybe add a fadeIn and fadeOut actions to both buttons.
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let nodeUserTapped = atPoint(pointOfTouch)
if nodeUserTapped.name == "PauseButton" {
if (self.isPaused == false) {
pauseGame()
}
}
if nodeUserTapped.name == "ResumeButton" {
if (self.isPaused == true) {
resumeGame()
}
}
}
}
// MARK: - pauseGame
func pauseGame() {
self.isPaused = true
currentGameState = gameState.pauseGame
self.physicsWorld.speed = 0
self.speed = 0.0
if (backgroundMusicIsOn == true) {
backingAudio.stop()
}
if resumeButton.isHidden == true {
resumeButton.isHidden = false
}
if pauseButton.isHidden == false {
pauseButton.isHidden = true
}
}
// MARK: - resumeGame
func resumeGame() {
self.isPaused = false
currentGameState = gameState.inGame
self.physicsWorld.speed = 1
self.speed = 1.0
if (backgroundMusicIsOn == true) {
backingAudio.play()
}
if resumeButton.isHidden == false {
resumeButton.isHidden = true
}
if pauseButton.isHidden == true {
pauseButton.isHidden = false
}
}
How do I make it so when a sprite is originally hidden is unhidden when a button is pressed and when something touches that sprite then it adds +1 to the score?
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
location = touches.first!.location(in: self)
if Top3.contains(location) && Top3.isHidden == true {
Top3.isHidden = false;
}
}
In the same line where Top3.isHidden = false you would have to have a variable to your score and just do score = score + 1.
if Top3.contains(location) && Top3.isHidden == true {
Top3.isHidden = false;
myGlobalScoreVariable = myGlobalScoreVariable + 1
}
Of course it would not be called "myGoalScoreVariable" but whatever you've set your score variable to be.
Unless I'm misunderstanding your question and you actually mean, how to do it once its been unHidden in which case you would just need another if statement when isHidden == false.
if Top3.contains(location) && Top3.isHidden == false {
myGlobalScoreVariable = myGlobalScoreVariable + 1
}