Show Medals after died in Sprite Kit - swift

In my Game I would like to have Medals like in Flappy Bird. So after the player die it should show the right medal. I made this code:
if self.score > 4{
let platinummedalTexture = SKTexture(imageNamed: "medal_platinum.png")
self.platinummedal = SKSpriteNode(texture: platinummedalTexture)
self.platinummedal.setScale(1)
self.platinummedal.zPosition = 120
self.platinummedal.position = CGPointMake(CGRectGetMidX(self.frame)-65, CGRectGetMidY(self.frame)-0 - 10)
self.addChild(self.platinummedal)
}
if self.score > 3{
let goldmedalTexture = SKTexture(imageNamed: "medal_gold.png")
self.goldmedal = SKSpriteNode(texture: goldmedalTexture)
self.goldmedal.setScale(1)
self.goldmedal.zPosition = 120
self.goldmedal.position = CGPointMake(CGRectGetMidX(self.frame)-65, CGRectGetMidY(self.frame)-0 - 10)
self.addChild(self.goldmedal)
}
if self.score > 2{
let silvermedalTexture = SKTexture(imageNamed: "medal_silver.png")
self.silvermedal = SKSpriteNode(texture: silvermedalTexture)
self.silvermedal.setScale(1)
self.silvermedal.zPosition = 120
self.silvermedal.position = CGPointMake(CGRectGetMidX(self.frame)-65, CGRectGetMidY(self.frame)-0 - 10)
self.addChild(self.silvermedal)
}
if self.score > 1{
let bronzemedalTexture = SKTexture(imageNamed: "medal_bronze.png")
self.bronzemedal = SKSpriteNode(texture: bronzemedalTexture)
self.bronzemedal.setScale(1)
self.bronzemedal.zPosition = 120
self.bronzemedal.position = CGPointMake(CGRectGetMidX(self.frame)-65, CGRectGetMidY(self.frame)-0 - 10)
self.addChild(self.bronzemedal)
}
In the game the score is higher to reach the medals but I put it on this Numbers for testing. So my problem is, if I get a score of 5 or higher it should display the Platinum Medal but it only displays the Gold one after 4 and higher.
The others works well. (2 = Bronze, 3 = silver, 4 = gold, 5 = gold -> wrong)
I dont know how to fix that problem and maybe someone of you can help me.

There are likely better ways to do this, although since you want to know what is wrong this answer will be focused on your structure. Basically you have a conflict with the gold and platinum because there's nothing stopping the if statement from continuing down to the 4 even though the score is above that. What I would recommend is to use an else if statement after your first if:
var score : Int = 5
if score >= 5 {
print("platinum")
}
else if score >= 4 {
print("gold")
}
else if score >= 3 {
print("silver")
}
else if score <= 2 {
print("bronze")
}
Also you should use less than/greater along with equals to make sure that you are setting the right medal for the appropriate score. I personally like a bottom up approach (it's just the reverse of what you see above), but it's really personal preference in that regard. Here's an example:
var score : Int = 5
if score <= 2 {
print("bronze")
}
else if score <= 3 {
print("silver")
}
else if score <= 4 {
print("gold")
}
else if score >= 5 {
print("platinum")
}

Related

Is a Separate Function Preferred for 2 Lines of Repeated Code in Swift?

I am designing a small game, and have decided on an initial way to distribute points based on the number of seconds elapsed between the time a question was asked and an enter button was pressed.
I understand that best practice is to create a function to call when lines are going to be repeated, but it seems to me that it may be a little excessive to call it when there are only two lines repeated four times. Using the code below, would it be "proper" or preferred for me to create a function for the last two lines in each section?
//Assigns points to score based on elapsed time and updates score
func updateScore() {
timer.invalidate()
if timeToAnswer < 3 {
questionScore = 10 //10 points for answering so quickly
score = score + questionScore //Updates score
timeToAnswer = 0 //Resets var timeToAnswer
}
else if (timeToAnswer >= 3) && (timeToAnswer < 6) {
questionScore = 7
score = score + questionScore
timeToAnswer = 0
}
else if (timeToAnswer >= 6) && (timeToAnswer < 11) {
questionScore = 10 - timeToAnswer
score = score + questionScore
timeToAnswer = 0
}
else {
questionScore = 1
score = score + questionScore
timeToAnswer = 0
}
}
Thank you very much to anybody that offers assistance or advice.
You can do it in a better way.
You can use switch statement, too. Anyway:
func updateScore() {
timer.invalidate()
if timeToAnswer < 3 {
questionScore = 10
}
else if (timeToAnswer >= 3) && (timeToAnswer < 6) {
questionScore = 7
}
else if (timeToAnswer >= 6) && (timeToAnswer < 11) {
questionScore = 10 - timeToAnswer
}
else {
questionScore = 1
}
score = score + questionScore //Updates score
timeToAnswer = 0 //Resets var timeToAnswer
}

If an if statement gets called and all the conditions are true, do all the else if statements get called also?

If an if statement gets called and all the conditions are true, do all the else if statements get called also?
like:
if coins > 19 && speedLvl == 1 {
speedLvl = 2
coins = coins - 20
}
else if coins > 49 && speedLvl == 2 {
speedLvl = 3
coins = coins - 50
}
else if coins > 99 && speedLvl == 3 {
speedLvl = 4
coins = coins - 100
}
If the player has 1000 coins do then speedLvl the go to 4 ?
No, and you can visualize it like this:
if coins > 19 && speedLvl == 1 {
speedLvl = 2
coins = coins - 20
}
else {
if coins > 49 && speedLvl == 2 {
speedLvl = 3
coins = coins - 50
}
else {
if coins > 99 && speedLvl == 3 {
speedLvl = 4
coins = coins - 100
}
}
}
Although this code would be more easily written in Swift 4 as:
switch (speedLvl, coins) {
case (1, 20..<50):
speedLvl += 1
coins -= 20
case (2, 50..<100):
speedLvl += 1
coins -= 50
case (3, 100...):
speedLvl += 1
coins -= 100
default: break;
}
or better yet, perhaps:
let levelUpCosts = [0, 20, 50, 100]
let levelUpCost = levelUpCosts[speedLvl]
if levelUpCost < coins {
coins -= levelUpCost
speedLvl += 1
}
If you want to multiple level ups to be possible, all in one shot, then you can do something like this:
let levelUpCosts = [0, 20, 50, 100]
var affordedLevelUpsCost = 0
let affordedLevelUps = levelUpCosts.lazy.prefix(while: { cost in
let newCost = affordedLevelUpsCost + cost
let canAffordLevelUp = newCost < coins
if canAffordLevelUp { affordedLevelUpsCost = newCost }
return canAffordLevelUp
})
speedLvl += affordedLevelUps.count
coins -= affordedLevelUpsCost
No, the if-else conditions will move on after a successful condition is reached.
So, if coins > 19 && speedLvl == 1 if this condition is true, the rest of the else-if conditions won't even be checked.
If the first if-statement is not true, then it will go to each if-else statement until it reaches a true condition. Once it reaches a true if-else condition, it will not check any remaining if-else conditions.
If you would like each of these conditions to be checked, remove the else and have each as a stand alone if-statement to check each condition individually without any dependency on the other conditions.

Detect overlaping if enumerating nodes

I would like to know how should I detect overlapping nodes while enumerating them? Or how should I make that every random generated position in Y axis is at least some points higher or lower.
This is what I do:
1 - Generate random number between -400 and 400
2 - Add those into array
3 - Enumerate and add nodes to scene with generated positions like this:
var leftPositions = [CGPoint]()
for _ in 0..<randRange(lower: 1, upper: 5){
leftPositions.append(CGPoint(x: -295, y: Helper().randomBetweenTwoNumbers(firstNumber: leftSparkMinimumY, secondNumber: leftSparkMaximumY)))
}
leftPositions.enumerated().forEach { (index, point) in
let leftSparkNode = SKNode()
leftSparkNode.position = point
leftSparkNode.name = "LeftSparks"
let leftSparkTexture = SKTexture(imageNamed: "LeftSpark")
LeftSpark = SKSpriteNode(texture: leftSparkTexture)
LeftSpark.name = "LeftSparks"
LeftSpark.physicsBody = SKPhysicsBody(texture: leftSparkTexture, size: LeftSpark.size)
LeftSpark.physicsBody?.categoryBitMask = PhysicsCatagory.LeftSpark
LeftSpark.physicsBody?.collisionBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.contactTestBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.isDynamic = false
LeftSpark.physicsBody?.affectedByGravity = false
leftSparkNode.addChild(LeftSpark)
addChild(leftSparkNode)
}
But like this sometimes they overlap each other because the generated CGPoint is too close to the previous one.
I am trying to add some amount of triangles to the wall and those triangles are rotated by 90°
To describe in image what I want to achieve:
And I want to avoid thing like this:
Your approach to this is not the best, i would suggest only storing the Y values in your position array and check against those values to make sure your nodes will not overlap. The following will insure no two sparks are within 100 points of each other. You may want to change that value depending on your node's actual height or use case.
Now, obviously if you end up adding too many sparks within an 800 point range, this just will not work and cause an endless loop.
var leftPositions = [Int]()
var yWouldOverlap = false
for _ in 0..<randRange(lower: 1, upper: 5){
//Moved the random number generator to a function
var newY = Int(randY())
//Start a loop based on the yWouldOverlap Bool
repeat{
yWouldOverlap = false
//Nested loop to range from +- 100 from the randomly generated Y
for p in newY - 100...newY + 100{
//If array already contains one of those values
if leftPosition.contains(p){
//Set the loop Bool to true, get a new random value, and break the nested for.
yWouldOverlap = true
newY = Int(randY())
break
}
}
}while(yWouldOverlap)
//If we're here, the array does not contain the new value +- 100, so add it and move on.
leftPositions.append(newY)
}
func randY() -> CGFloat{
return Helper().randomBetweenTwoNumbers(firstNumber: leftSparkMinimumY, secondNumber: leftSparkMaximumY)
}
And here is a different version of your following code.
for (index,y) in leftPositions.enumerated() {
let leftSparkNode = SKNode()
leftSparkNode.position = CGPoint(x:-295,y:CGFloat(y))
leftSparkNode.name = "LeftSparks\(index)" //All node names should be unique
let leftSparkTexture = SKTexture(imageNamed: "LeftSpark")
LeftSpark = SKSpriteNode(texture: leftSparkTexture)
LeftSpark.name = "LeftSparks"
LeftSpark.physicsBody = SKPhysicsBody(texture: leftSparkTexture, size: LeftSpark.size)
LeftSpark.physicsBody?.categoryBitMask = PhysicsCatagory.LeftSpark
LeftSpark.physicsBody?.collisionBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.contactTestBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.isDynamic = false
LeftSpark.physicsBody?.affectedByGravity = false
leftSparkNode.addChild(LeftSpark)
addChild(leftSparkNode)
}

How to find the speed of SKSpriteNode from 5 frames ago in Swift

So I have this Swift code using SpriteKit:
let ballX = ball.position.x
let ballY = ball.position.y
let ballR = ball.size.width / 2
let sceneW = self.size.width
let sceneH = self.size.height
if ballY >= sceneH - ballR - 3 {
framesOnTop += 1
} else {
framesOnTop = 0
}
if framesOnTop > 2 {
// Some code here
}
I have the ball node (SKSpriteNode) that can hit the ceiling and bounce back (scene has no gravity) but sometimes it can hit the ceiling at a very steep angle and get stuck in an infinite loop, left right left right left right. In the if statement that says framesOnTop > 2 I am successfully detecting when it gets stuck, and I want to make it bounce back down at the right angle with physicsBody.applyImpulse. But to do this I need the velocity from before it gets stuck (~5 frames before). Is this possible or is there a better way to fix it?

Why does SKLabelNode increment too much on Contact? (Swift)

I am trying to have my Label to increment +1 every time a sprite makes contact with a Contact node, but it increments by large numbers like +23. I think what is happening is that its taking into account every millisecond that the sprite is touching the node, but i don't know how to fix it.
Here is my Label node code within DidMoveToView:
score = 0
scoreLabelNode = SKLabelNode(fontNamed:"[z] Arista Light")
scoreLabelNode.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/4)
scoreLabelNode.zPosition = 100
scoreLabelNode.fontSize = 500
scoreLabelNode.alpha = 0.03
scoreLabelNode.text = String(score)
self.addChild(scoreLabelNode)
here is my contact node:
var contactNode = SKNode()
contactNode.position = CGPoint(x: self.frame.size.width + asteroidTexture.size().height / 15 + rocket.size.width, y: 0.0)
contactNode.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake( asteroid.size.width/16, self.frame.size.height*2 ))
contactNode.physicsBody?.dynamic = false
contactNode.physicsBody?.categoryBitMask = scoreCategory
contactNode.physicsBody?.contactTestBitMask = rocketCategory
contactNode.runAction(asteroidsMoveAndRemove)
moving.addChild(contactNode)
and here is my code where when my rocket sprite makes contact with the contactNode it increments:
func didBeginContact(contact: SKPhysicsContact) {
if moving.speed > 0 {
if ( contact.bodyA.categoryBitMask & scoreCategory ) == scoreCategory || ( contact.bodyB.categoryBitMask & scoreCategory ) == scoreCategory {
// Rocket has contact with score entity
score++
scoreLabelNode.text = String(score)
println("HIT")
}
else{
gameOver()
}
}
'moving' is when my asteroid sprites are moving, the contact nodes move with it
It's probably not every millisecond, but instead every frame, and that's why you likely end up with numbers like +23 (The collision is taking place for a 1/3 of a second and if it's running at 60 FPS that is about 20 or so frames).
One thing you could do is subclass SKNode into an actual ContactNode class that has a property hasBeenStruck that is initially set to false or something to that extent.
Then when you check contact, you first check to see if hasBeenStruck is false. If it is, register the hit and update the score and then set that ContactNode's hasBeenStruck property to true. This way, on the next 20 or so checks to see if contact has happened, it won't keep updating the label because the if condition has failed.
However, if your game allows a user to hit the same ContactNode twice and get score both times, this isn't a complete solution. If that is the case, you could use a timer or "invincibility" period for the given ContactNode.