one score at a time, update method - swift

I'm creating a simple game with SpriteKit (mostly for learning), and I got a question about score adding. some background: I'm checking if a sprite (SKShapeNode) contains another one, if true, I'm checking their color, if it is the same color, the player should get 1 score. I wrote this function:
func onMatch(){
for ring in mColorRings {
if(mPlayer.contains(ring.position)){
if mPlayer.fillColor.isEqual(ring.fillColor) {
score += 1
mScoreLbl.text = "\(score)"
}
}
}
}
which works, the problem is, I'm calling this function inside the update method. as the update method runs a lot, it calls my function a lot of time and as long as mPlayer contains ring it is adding 1 score to the player. How can I avoid that ?

This depends on your game mechanics. If the ring is supposed to give you a score one time then disappear you can safely remove it within that if test. If you want the ring to stay put and maybe be reused later you can add a boolean to the ring class called something like "scoreGiven" and redo your if test to something like this:
func onMatch(){
for ring in mColorRings {
if !ring.scoreGiven{
if(mPlayer.contains(ring.position)){
if mPlayer.fillColor.isEqual(ring.fillColor) {
score += 1
mScoreLbl.text = "\(score)"
ring.scoreGiven = true
}
}
}else if(!mPlayer.contains(ring.position)){
ring.scoreGiven = false
}
}
This is just an example, but note the "not"s in the updated if statements

Ok, so this is what i suggest:
var playerPassed = false
func onMatch(){
for ring in mColorRings {
if(mPlayer.contains(ring.position)) {
if ((mPlayer.fillColor.isEqual(ring.fillColor)) && playerPassed == false) {
score += 1
mScoreLbl.text = "\(score)"
playerPassed = true
}
}
}
}
You're creating a bool and you check if that is false(default) and if so, the block executes and when it does, the bool is set to true and the condition will be false and no longer return true.

Related

SKSpriteNode isn't responding when I try to hide it (Spoiler: SKAction timing issue)

OK, so I've been working on doing an RPG-style dialog box for a project, and while most of it is going smoothly, the one thing that's tripping me up right now is the little icon in the corner of the box to let you know there's more.
I tried to figure out how to get draw the shape, but not having any luck getting Core Graphics to draw triangles I decided to just use a PNG image of one instead. The code below shows everything relevant to how it's been set up and managed.
That being figured out, I'm now trying to get it to hide the marker when updating the box and show it again afterward. Here's what I've tried so far:
Method 1: Use .alpha = 0 to hide it from view during updates, restore with .alpha = 1
Method 2: Remove it from the node tree
Method 3: Place it behind the box background (located at .zPosition = -1)
The result has been consistent across all 3 methods: The triangle just stays in place, unresponsive when invoked.
class DialogBox: SKNode {
private var continueMarker = SKSpriteNode(imageNamed: "continueTriangle") // The triangle that shows in the lower-right to show there's more to read
init() {
/// Setup and placement. It appears in the proper position if I draw it and don't try to hide anything
continueMarker.size.width = 50
continueMarker.size.height = 25
continueMarker.position = CGPoint(x: ((width / 2) - (continueMarker.size.width * 0.9)), y: ((continueMarker.size.height * 0.9) - (height - margin)))
addChild(continueMarker)
}
func updateContent(forceAnimation: Bool = false) {
/// Determine what content to put into the box
hideContinueMarker()
/// Perform the content update in the box (which works as it should)
showContinueMarker()
}
func showContinueMarker() {
// continueMarker.alpha = 1 /// Method 1: Use .alpha to hide it from view during updates
// if (continueMarker.parent == nil) { // Method 2: Remove it from the tree
// addChild(continueMarker)
// }
continueMarker.zPosition = -2 /// Method 3: place it behind the box background (zPosition -1)
}
func hideContinueMarker() {
// continueMarker.alpha = 0 /// Method 1
// if (continueMarker.parent != nil) { /// Method 2
// continueMarker.removeFromParent()
// }
continueMarker.zPosition = 2 /// Method 3
}
}
OK, so while typing this one up I had some more ideas and ended up solving my own problem, so I figured I'd share the solution here, rather than pull a DenverCoder9 on everyone.
On the plus side, you get a look at a simple way to animate text in SpriteKit! Hooray!
In a final check to make sure I wasn't losing my mind, I added some print statements to showContinueMarker() and hideContinueMarker() and noticed that they always appeared simultaneously.
What's that mean? SKAction is likely at fault. Here's a look at the code for animating updates to the box:
private func animatedContentUpdate(contentBody: String, speaker: String? = nil) {
if let speaker = speaker {
// Update speaker box, if provided
speakerLabel.text = speaker
}
var updatedText = "" // Text shown so far
var actionSequence: [SKAction] = []
for char in contentBody {
updatedText += "\(char)"
dialogTextLabel.text = updatedText
// Set up a custom action to update the label with the new text
let updateLabelAction = SKAction.customAction(withDuration: animateUpdateSpeed.rawValue, actionBlock: { [weak self, updatedText] (node, elapsed) in
self?.dialogTextLabel.text = updatedText
})
// Queue up the action so we can run the batch afterward
actionSequence.append(updateLabelAction)
}
/// HERE'S THE FIX
// We needed to add another action to the end of the sequence so that showing the marker again didn't occur concurrent with the update sequence.
let showMarker = SKAction.customAction(withDuration: animateUpdateSpeed.rawValue, actionBlock: { [weak self] (node, elapsed) in
self?.showContinueMarker()
})
// Run the sequence
actionSequence.append(showMarker)
removeAction(forKey: "animatedUpdate") // Cancel any animated updates already in progress
run(SKAction.sequence(actionSequence), withKey: "animatedUpdate") // Start the update
}
In case you missed it in the big block there, here's the specific bit in isolation
let showMarker = SKAction.customAction(withDuration: animateUpdateSpeed.rawValue, actionBlock: { [weak self] (node, elapsed) in
self?.showContinueMarker()
})
Basically, we needed to add showing the triangle as an action at the end of the update sequence instead of just assuming it would occur after the update since the function was invoked at a later time.
And since all 3 methods work equally well now that the timing has been fixed, I've gone back to the .alpha = 0 method to keep it simple.

Can you check if a SKSpriteNode is indirectly connected to another?

I'm working on a game where the player moves around a space station, consisting of tiles (called parts, inheriting SKSpriteNode). One of these parts is the 'Source'.
Here is a helpful image.
In the game, asteroids may hit the space station, and cause tiles to be destroyed. When this happens, I need to check if all the tiles are still connected to the source. If they are not, they need to be removed from the station (can't have floating tiles!). Useful point: Each tile has a name, "T|Empty" or for the source, "T|Source". Also, the obtainConnectedTiles function just finds the tiles up, down, left, and right of the part.
What I've tried
func checkIfConnectedToSource(_ part: SpaceStationPart) -> Bool {
var foundSource = false
let splitArray1 = part.name?.components(separatedBy: "|")
if splitArray1?[1] == "Source" {
foundSource = true
}
while !foundSource {
let connections = part.obtainConnectedTiles(scene: self)
if connections.count != 0 {
for i in 0..<connections.count {
let splitArray = connections[i].name?.components(separatedBy: "|")
if splitArray?[1] != "Source" {
if checkIfConnectedToSource(connections[i]) {
foundSource = true
}
} else {
foundSource = true
}
}
} else {
print("Connections was 0!")
}
}
return foundSource
}
I tried this, but it threw "EXC_BAD_ACCESS" on the line when I find the components of the name, although I'm not sure why.
The tiles are also always in a grid, 100 points apart from each other.
So, the question boils down to: How can I check if an SKSpriteNode is connected to another SKSpriteNode, sometimes indirectly?

Rolling a dice in swift using a while function and printing results until the result is 1

I'm really new to Swift and I have a task asking me to create a while loop that simulates rolling a 6-sided dice repeatedly until a 1 is rolled. After each roll, print the value.
In just about every iteration I've tried over the last 2 hours I keep ending in an infinite loop that explodes Xcode.
Any help would be fantastic!
var dieRoll = Int.random(in: 1...6)
while dieRoll <= 6 {
print (dieRoll)
if dieRoll == 1 {
print ("You win!")
}
}
Got it to this point, it no longer runs endlessly but it acts weird and returns values of 1 without printing "You win!"
func dieRoll(x: Int) -> Int {
return Int.random(in:1...6)
}
while dieRoll(x: 0) > 1 {
print(dieRoll(x: 0))
if dieRoll(x: 1) == 1 {
print("You win!")
}
else {
RETURN
}
}
Your dieRoll variable is declared AS A VARIABLE yet you never change it! Try “rerolling” within the While Loop
Also, there’s always the chance that a 6 never gets rolled... idk if you want to mess with “real” probabilities but if you’re finding issues you may want to institute a “max number of rolls”... personally I wouldn’t but hey you never know
TDLR: last line of the while-loop should reroll your dieRoll var
Okay, so I got away from the string text and focused on what the code was saying versus what my typing was saying. Ended up with this (probably a monstrosity to you experienced folks) but it looks something like this.
var rolling = Int.random(in: 1...6)
while rolling > 1 {
print(rolling)
if rolling == 1 {
break
} else {
rolling = Int.random(in: 1...6)
}
}
print(rolling)
And every time I run it it ends on a 1 so it does what it needs to!

Why is runAction() function being called multiple times within my update() method?

Within my func update(currentTime: CFTimeInterval) method in Sprite Kit, I have a gameTicker that increments by one integer every time the game is updated.
When the gameTicker is divisible by 500, I pause the ticker, disable enemies from spawning by removing the original action called in didMoveToView(), and start a nextLevelDelayTicker that functions as a brief delay. Once the nextLevelDelayTicker reaches 100, I start incrementing the original gameTicker again, reset the nextLevelDelayTicker to 0, and run an action to start spawning enemies again.
Basically, once the nextLevelDelayTicker is equal to 100, I only want to run the contents of the conditional one time. I added a print("yolo") in there to see if it was only being called once when the conditional is met, and indeed it is. However, for some reason runAction(spawnAction, withKey: "spawnAction") is being called multiple times. As soon as the condition self.nextLevelDelayTicker.getTimePassed() == 100 is met, an enormous amount of enemies are being spawned within a very short period of time.
How is runAction(spawnAction, withKey: "spawnAction") being called multiple times, but print("yolo") is only being called once?
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if gameTicker.isActive == true {
gameTicker.increment()
}
// If gameTicker is equal to 5 seconds, increase enemy amount
if gameTicker.getTimePassed() % 500 == 0 {
self.enemyAmount += 1
self.removeActionForKey("spawnAction")
gameTicker.isActive = false
}
// If level has been completed and last ghost has been killed, activate next level scene
if gameTicker.isActive == false && enemyArray.count == 0 {
self.nextLevelDelayTicker.increment()
if self.nextLevelDelayTicker.getTimePassed() == 100 {
print("YOLO")
self.gameTicker.isActive = true
self.nextLevelDelayTicker.reset()
let spawnAction = SKAction.repeatActionForever(
SKAction.sequence([
SKAction.waitForDuration(2),
SKAction.runBlock({
[unowned self] in
self.spawnEnemy()
})
])
)
runAction(spawnAction, withKey: "spawnAction")
}
}
}
So it turns out I'm a dodo head. runAction(spawnAction, withKey: "spawnAction") wasn't being called more than once, it was:
if gameTicker.getTimePassed() % 500 == 0 {
self.enemyAmount += 1
self.removeActionForKey("spawnAction")
gameTicker.isActive = false
}
being called multiple times, adding an enormous amount of enemies to the self.enemyAmount global variable that was being used to determine how many enemies spawn when spawnAction was run.
I added a flag to the conditional and it is only being called once every level cycle compared to what was ~300 times per game cycle:
if gameTicker.getTimePassed() % 500 == 0 && gameTicker.isActive == true{
self.enemyAmount += 1
self.removeActionForKey("spawnAction")
gameTicker.isActive = false
print("STOP SPAWNING")
}

Swift - Array of AVplayers?

I am putting together an app that plays drum beats based on hard-coded arrays as such:
var beatArray = [[String]]()
var beat01 = [["kick"], ["snare"], ["kick"], ["snare"]]
var beat02 = [["kick"], ["kick", "snare"], ["kick"], ["kick","snare"]]
Based on a set of parameters, beat01 or beat02 is chosen and when a button is pressed, an NSTimer is started with this following Selector:
// THIS IS WHAT HAPPENS EVERY BEAT
func increaseCounter() {
beatCounter++
if beatCounter >= beatArray.count {
beatCounter = 0
}
print(beatCounter)
if beatArray[beatCounter].contains("kick") {
kickPlayer.currentTime = 0
kickPlayer.play()
}
if beatArray[beatCounter].contains("snare") {
snarePlayer.currentTime = 0
snarePlayer.play()
}
}
kickPlayer and snarePlayer are AVAudioPlayers.
Since there are only two instruments, separately declaring each IF statement is fine, but as I add more and more instruments, this will soon be chaotic.
Is there a way to streamline this process? I'm looking for something along the lines of:
if beatArray[beatCounter].contains("INSTRUMENT") {
INSTRUMENTPlayer.currentTime = 0
INSTRUMENTPlayer.play()
}
put your players in a map
let players = ["kick": kickPlayer, "snare": snarePlayer, ...]
then
for beat in beatArray[beatCounter] {
if let player = players[beat] {
player.currentTime = 0
player.play()
}
}