enumareteChildNodes with collision detection swift - 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.

Related

Node spacing issues on a vertical scrolling game with variable speeds in Spritekit

So I'm creating a game in Swift with Spritekit and have ran into an issue with getting my game to work. I'm still a beginner with programming so I've likely missed out on a solution to this myself.
Anyway, so the game concept is a simple arcade vertical scroller that involves a player trying to dodge platforms as it descends downward. The mechanics (so far) are a stationary player on the y axis that can move left and right along the x axis while the platforms scroll upward along with the background moving with the platforms to give a visual effect of descent. I've gotten a build working to be fully playable, but there's an issue with spawning the platforms perfectly spaced out. Here's a sketch:
Concept Image
The picture on the left what I'm trying to achieve, while the one on the right is my current and flawed method. The main issue with the one on the right, is that it uses a collision to trigger spawning which means the spawn trigger node (red line) has to be 1 pixel tall to allow for perfect spacing. If the spawn trigger node is more than 1 pixel tall, then the collision may not trigger on that the first pixel of contact and trigger the node a few pixels deep which throws off the entire spacing. Also if the spawn trigger is only 1 pixel tall, it often won't trigger unless the everything is scrolling at slow speeds.
I've tried to think of other methods to approach this but I'm at a loss. I cannot use a simple timer to spawn nodes at intervals because the speed at which the game scrolls is variable and is constantly changing by player controls. The only two other options I can think of (which I don't know how to do either) is either spawn node sets at fixed y-positions and set that on a loop, or change it so the player is actually descending downward while everything is generating around it (seems tougher and maybe unnecessary). I'm considering just rewriting my createPlatforms() method if I need to, but here's the code for that and the background anyway:
var platformGroup = Set<SKSpriteNode>()
var platformSpeed: CGFloat = 0.6 { didSet { for platforms in platformGroup { platforms.speed = platformSpeed } } }
var platformTexture: SKTexture!
var platformPhysics: SKPhysicsBody!
var platformCount = 0
var backgroundPieces: [SKSpriteNode] = [SKSpriteNode(), SKSpriteNode()]
var backgroundSpeed: CGFloat = 1.0 { didSet { for background in backgroundPieces { background.speed = backgroundSpeed } } }
var backgroundTexture: SKTexture! { didSet { for background in backgroundPieces { background.texture = backgroundTexture } } }
func createPlatforms() {
let min = CGFloat(frame.width / 12)
let max = CGFloat(frame.width / 3)
var xPosition = CGFloat.random(in: -min ... max)
if platformCount >= 20 && platformCount < 30 {
stage = 0
setTextures()
xPosition = frame.size.width * 0.125
} else if platformCount == 30 {
stage = 2
setTextures()
} else if platformCount >= 50 && platformCount < 60 {
stage = 0
setTextures()
xPosition = 184
} else if platformCount == 60 {
stage = 3
setTextures()
}
platformPhysics = SKPhysicsBody(rectangleOf: CGSize(width: platformTexture.size().width, height: platformTexture.size().height))
let platformLeft = SKSpriteNode(texture: platformTexture)
platformLeft.physicsBody = platformPhysics.copy() as? SKPhysicsBody
platformLeft.physicsBody?.isDynamic = false
platformLeft.physicsBody?.affectedByGravity = false
platformLeft.physicsBody?.collisionBitMask = 0
platformLeft.scale(to: CGSize(width: platformLeft.size.width * 3, height: platformLeft.size.height * 3))
platformLeft.zPosition = 20
platformLeft.name = "platform"
platformLeft.speed = platformSpeed
let platformRight = SKSpriteNode(texture: platformTexture)
platformRight.physicsBody = platformPhysics.copy() as? SKPhysicsBody
platformRight.physicsBody?.isDynamic = true
platformRight.physicsBody?.collisionBitMask = 0
platformRight.scale(to: CGSize(width: platformRight.size.width * 3, height: platformRight.size.height * 3))
platformRight.zPosition = 20
platformRight.name = "platform"
platformRight.speed = platformSpeed
let scoreNode = SKSpriteNode(color: UIColor.clear, size: CGSize(width: frame.width, height: 32))
scoreNode.physicsBody = SKPhysicsBody(rectangleOf: scoreNode.size)
scoreNode.physicsBody?.isDynamic = false
scoreNode.zPosition = 100
scoreNode.name = "scoreDetect"
scoreNode.speed = platformSpeed
let platformTrigger = SKSpriteNode(color: UIColor.orange, size: CGSize(width: frame.width, height: 4))
platformTrigger.physicsBody = SKPhysicsBody(rectangleOf: platformTrigger.size)
platformTrigger.physicsBody?.isDynamic = true
platformTrigger.physicsBody?.affectedByGravity = false
platformTrigger.physicsBody?.categoryBitMask = Collisions.detect
platformTrigger.physicsBody?.contactTestBitMask = Collisions.spawn
platformTrigger.physicsBody?.collisionBitMask = 0
platformTrigger.physicsBody?.usesPreciseCollisionDetection = true
platformTrigger.zPosition = 100
platformTrigger.name = "platformTrigger"
platformTrigger.speed = platformSpeed
let newNodes: Set<SKSpriteNode> = [platformLeft, platformRight, scoreNode, platformTrigger]
for node in newNodes {
platformGroup.insert(node)
}
let yPosition = spawnNode.position.y - transitionPlatform.size().height
let gapSize: CGFloat = -frame.size.width / 6
print(gapSize)
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: platformLeft.position.y - platformLeft.size.height / 2)
platformTrigger.position = CGPoint(x: frame.midX, y: platformLeft.position.y)
print(platformLeft.position.y)
print(platformLeft.frame.midY)
let endPosition = frame.maxY + frame.midY
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)
nodeArray.append(node)
node.run(moveSequence)
}
platformCount += 1
}
func startPlatforms() {
let create = SKAction.run { [unowned self] in
self.createPlatforms()
}
run(create)
}
func createBackground() {
for i in 0 ... 1 {
let background = backgroundPieces[i]
background.texture = backgroundTexture
background.anchorPoint = CGPoint(x: 0, y: 0)
background.zPosition = -5
background.size = CGSize(width: frame.size.width, height: frame.size.width * 2.5)
background.position = CGPoint(x: 0, y: background.size.height + (-background.size.height) + (-background.size.height * CGFloat(i)))
self.addChild(background)
nodeArray.append(background)
let scrollUp = SKAction.moveBy(x: 0, y: background.size.height, duration: 5)
let scrollReset = SKAction.moveBy(x: 0, y: -background.size.height, duration: 0)
let scrollLoop = SKAction.sequence([scrollUp, scrollReset])
let scrollForever = SKAction.repeatForever(scrollLoop)
background.run(scrollForever)
}
}
Does anybody have any suggestions on how I approach this or change it so it would work perfectly everytime?

Adding physics bodies to all tiles on an SKTileMapNode

I'm trying to make a simple game in the latest version of Xcode. I use a TileMap that is pretty simple. I'm trying to figure out how to add a physics body to each and every tile. I'm stuck at getting the position of each tile and adding an SKNode there. Any way at getting the position of each tile would be extremely helpful. All the code below is in didMove(to: view)
let tileSize = grassTileMap.tileSize
let halfWidth = CGFloat(grassTileMap.numberOfColumns) / 2.0 * tileSize.width
let halfHeight = CGFloat(grassTileMap.numberOfRows) / 2.0 * tileSize.height
for node in self.children {
if node.name == "grassTileMap" {
grassTileMap = node as! SKTileMapNode
}
for col in 0..<grassTileMap.numberOfColumns {
for row in 0..<grassTileMap.numberOfRows {
let tileDef = grassTileMap.tileDefinition(atColumn: col, row: row)
if tileDef == nil {
print("no tile here")
} else {
let hitboxTileNode = SKSpriteNode(color: UIColor.clear, size: CGSize(width: 35.5, height: 35.5))
// I need to set the position of each tile to hitBoxTileNode here
hitboxTileNode.anchorPoint = CGPoint(x: 0, y: 0)
hitboxTileNode.physicsBody = SKPhysicsBody(edgeLoopFrom: hitboxTileNode.frame)
hitboxTileNode.physicsBody?.affectedByGravity = false
hitboxTileNode.physicsBody?.isDynamic = false
hitboxTileNode.physicsBody?.pinned = false
hitboxTileNode.physicsBody?.restitution = 0
hitboxTileNode.physicsBody?.friction = 0
addChild(hitboxTileNode)
}
}
}
}

Y Position appear to be different although y is set to a constant value (Swift Sprite)

I am making a game where blocks (the enemy) are hard coded to a y value of 500 and have a random x value. I have two blocks, one that is a SKSpriteNode and another one which is a copy of the SKSpriteNode, each block has their own function, addEnemy() and addEnemyCopy()
When I run the game I see that the blocks are being spawned at different y values even though they are both hardcoded to a y value of 500. I printed out the positions of both blocks and I saw that sometimes the two blocks were being spawned at a y value of 500 and sometimes they were being spawned at a y value of around 400 or 300. Why is this happening? I have the y value set to 500, so the block should always be spawning at 500 on the y axis and then be spawning at a random value on the x axis. Both blocks are spawning at a random X value so that part works fine, I do realize that sometimes the blocks spawn off screen, but ignore that for now.
Here's the code relevant:
original enemy function:
func addEnemy() {
enemy.position = CGPoint(x: CGFloat(arc4random() % UInt32(size.width - enemy.size.width)), y: 500)
print("Enemy position: ")
print(enemy.position)
enemy.zPosition = 1
enemy.setScale(0.4)
enemy.physicsBody = SKPhysicsBody(texture: enemy.texture!, size: enemy.size)
enemy.physicsBody?.affectedByGravity = false
enemy.physicsBody?.categoryBitMask = 1
enemy.physicsBody?.collisionBitMask = 0
enemy.physicsBody?.contactTestBitMask = 2
self.addChild(enemy)
let moveEnemy = SKAction.moveTo(y: player.position.y - 300, duration: 1)
let deleteEnemy = SKAction.removeFromParent()
let enemySequence = SKAction.sequence([moveEnemy, deleteEnemy])
enemy.run(enemySequence,
completion: {
if self.playerLost == false {
if self.score >= 1 {
self.enemy.removeFromParent()
self.scoreLabel.removeFromParent()
self.score += 1
self.setScore()
self.addEnemy()
} else {
self.score += 1
self.setScore()
self.enemy.removeFromParent()
self.addEnemy()
}
}
}
)
}
copy enemy function:
func addEnemyCopy() {
let enemyCopy = enemy.copy() as! SKSpriteNode
enemy.position = CGPoint(x: CGFloat(arc4random() % UInt32(size.width - (enemy.size.width + 100))), y: 500)
print("Enemy copy: ")
print(enemyCopy.position)
self.addChild(enemyCopy)
let moveEnemy1 = SKAction.moveTo(y: player.position.y - 300, duration: 1)
let deleteEnemy1 = SKAction.removeFromParent()
let enemySequence1 = SKAction.sequence([moveEnemy1, deleteEnemy1])
enemyCopy.run(enemySequence1,
completion: {
if self.playerLost == false {
if self.score >= 1 {
enemyCopy.removeFromParent()
self.scoreLabel.removeFromParent()
self.setScore()
self.addEnemyCopy()
} else {
self.setScore()
self.addEnemyCopy()
}
}
}
)
}
Not sure what other code I should put here, or if I should put all of it. Please feel free to ask in the comments to show more of the code.
Thanks! I know I can always count on Stack Overflow to help me!
Copy and paste failure:
func addEnemyCopy() {
let enemyCopy = enemy.copy() as! SKSpriteNode
****enemyCopy.position = CGPoint(x: CGFloat(arc4random() % UInt32(size.width - (enemy.size.width + 100))), y: 500)***
print("Enemy copy: ")
print(enemyCopy.position)
self.addChild(enemyCopy)
let moveEnemy1 = SKAction.moveTo(y: player.position.y - 300, duration: 1)
let deleteEnemy1 = SKAction.removeFromParent()
let enemySequence1 = SKAction.sequence([moveEnemy1, deleteEnemy1])
enemyCopy.run(enemySequence1,
completion: {
if self.playerLost == false {
if self.score >= 1 {
enemyCopy.removeFromParent()
self.scoreLabel.removeFromParent()
self.setScore()
self.addEnemyCopy()
} else {
self.setScore()
self.addEnemyCopy()
}
}
}
)
}
I recommend using refactor more often. That is, if you rename 1 variable, it will rename that variable anywhere that is attached to that declaration.

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.

Stop repeatActionForever in Sprite Kit swift

I created an SKAction that repeatsForever (it's the planet spinning). But I want to stop it once the lander lands on the landing pad. So far my code is not working. I tried both removeAllActions() and removeActionForKey as you'll see. The contact detection works just fine, and there's a bunch of code not shown which includes the collision delegate, etc.
func createPlanet() {
var planet = SKSpriteNode()
planet.zPosition = 1
planet.name = "mars"
redPlanet = SKSpriteNode(imageNamed: "redPlanet")
redPlanet.name = "red"
redPlanet.zPosition = 2
redPlanet.physicsBody = SKPhysicsBody(texture: redPlanet.texture!, size: size)
redPlanet.physicsBody!.dynamic = false
redPlanet.physicsBody!.categoryBitMask = planetMask
redPlanet.physicsBody!.contactTestBitMask = 0
planet.addChild(redPlanet)
landingPad = SKSpriteNode(imageNamed: "landingPad")
landingPad.name = "pad"
landingPad.zPosition = 3
landingPad.position = CGPoint(x: 0, y: redPlanet.size.height / 2 - 60)
landingPad.physicsBody = SKPhysicsBody(rectangleOfSize: landingPad.size)
landingPad.physicsBody!.dynamic = false
landingPad.physicsBody!.categoryBitMask = landingPadMask
landingPad.physicsBody!.collisionBitMask = landerMask
landingPad.physicsBody!.contactTestBitMask = landerMask
planet.addChild(landingPad)
planet.position = CGPoint(x: frame.size.width / 2, y: -redPlanet.size.height / 6)
let spinner = SKAction.rotateByAngle(CGFloat(M_PI), duration: 3)
planet.runAction(SKAction.repeatActionForever(spinner), withKey: "planetSpin")
addChild(planet)
}
And this...
func didBeginContact(contact: SKPhysicsContact) {
if !hasLanded {
if contact.bodyA.node!.name == "lander" {
hasLanded = true
print("bodyA contact")
physicsWorld.speed = 0
removeAllActions()
removeActionForKey("planetSpin")
} else if contact.bodyB.node!.name == "lander" {
print("bodyB contact")
hasLanded = true
physicsWorld.speed = 0
removeAllActions()
removeActionForKey("planetSpin")
}
}
}
You're not removing actions from the planet node, you're removing them from the scene or parent node or whatever your didBeginContact is a member of.
Make planet a class variable and in didBeginContact, call planet.removeAllActions()