How to solve SKSpriteNode bug on screen? - sprite-kit

I am working on a game in which there are fish nodes which descend from the top of your screen. These fish are initialized with a custom class and are moved with a SKAction.moveTo() please see the code below.
The issue I am having is that my fish position randomly increases in Y value for a single frame and then return to their intended position. It may be hard to see in the photos but it is easily visible while playing. Even though my moveAction moves the fish down the screen the second picture shows the fish higher than it was in the first picture.
SEE FRAME 1
SEE FRAME 2
I call my setupFish() function in the update method where I run a timer that shortens as you collect fish nodes and increase score.
I am not sure this code has anything to do with the issue I'm having but it gives you a better idea of how I put the fish in the game.
override func update(_ currentTime: TimeInterval) {
if nextEnemyAddTime <= currentTime {
priorEnemyAddTime = currentTime
if gamePlaying == true {
setupFish()
}
}
}
private var priorEnemyAddTime: CFTimeInterval = 0
private var nextEnemyAddTime: CFTimeInterval {
let waitTime = score < 50 ? Double.random(in: 1.3...2.3)
: score < 100 ? Double.random(in: 1.2...2.1)
: score < 250 ? Double.random(in: 1.1...1.9)
: score < 500 ? Double.random(in: 1.0...1.7)
: score < 1000 ? Double.random(in: 0.9...1.5)
: score < 2500 ? Double.random(in: 0.8...1.3)
: score < 5000 ? Double.random(in: 0.7...1.1)
: score < 10000 ? Double.random(in: 0.6...0.9)
: score < 50000 ? Double.random(in: 0.4...0.7)
: 0.3
return priorEnemyAddTime + waitTime
}
Here is my custom class of Fish
import SpriteKit
enum FishType: String {
case redFish, purpleFish, orangeFish
}
class Fish: SKSpriteNode {
let type: FishType
init(type: FishType) {
self.type = type
let texture = SKTexture(imageNamed: "fish")
super.init(texture: texture, color: UIColor.clear, size: texture.size())
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here is the function I use to add the fish nodes.
func getNewFish(type: FishType) -> Fish {
let fish = Fish(type: type)
fish.physicsBody?.categoryBitMask = PhysicsCategory.fish
fish.physicsBody?.contactTestBitMask = PhysicsCategory.hero
fish.physicsBody?.collisionBitMask = PhysicsCategory.none
return fish
}
func setupFish() {
let fish = self.getNewFish(type: self.getFishType())
let randomPosition = CGPoint(x: CGFloat.random(in: frame.maxX*0.25...frame.maxX*0.75), y: frame.maxY + fish.size.height/2)
fish.zPosition = 1
fish.name = "fish"
fish.size = CGSize(width: ((fish.texture?.size().width)!)/14, height: (fish.texture?.size().height)!/14)
fish.position = randomPosition
fish.texture = SKTexture(imageNamed: "fish")
fish.zPosition = 1
addChild(fish)
let randomDouble = Double.random(in: 0.5...1.2)
let moveAction = SKAction.move(to: CGPoint(x: randomPosition.x, y: frame.minY), duration: gameSpeed*randomDouble)
fish.run(moveAction) {
fish.removeFromParent()
fish.removeAllActions()
self.streak = 0
}
}
Thanks in advance!

Related

SKAction & SKSpriteNode trouble

I'm in the process of creating a card game in Swift using SKSprite Nodes to show the card faces.
In my 'deal' function, it deals 3 cards to each player, one at a time in a 'round-robin' fashion. This is working fine but I'm trying to add a bit of animation - I'm trying to make it show each card being dealt to the local player by animating it moving from the deck to the players hand position.
I can get the Sprite Nodes to show without the animation but when I try with SKAction, it gives me the following error after the action is completed:
reason: 'Attemped to add a SKNode which already has a parent: name:'local player node' texture:[ 'CARD39' (203 x 350)] position:{281.25, 100.05000305175781}
class GameScene: SKScene {
let tempCard = SKSpriteNode(texture: SKTexture(imageNamed: "back"))
func deal() {
players = createPlayers(with: numberOfPlayers)
tempCard.setScale((screenSize.width/100) * 0.2)
tempCard.zPosition = 10
tempCard.name = "tempcard"
addChild(tempCard)
let localPlayer = 0
var i = 0
repeat {
print("Card number: \(i)")
var x = 0
let xPos = screenSize.width * (0.25 * CGFloat(i+1))
players.forEach { player in
let newCard = self.deck.dealOneCard()
player.hand.addCard(card: newCard)
localPlayerNode = players[localPlayer].hand[i].cardImage()
localPlayerNode.position = CGPoint(x: xPos, y: screenSize.height * 0.15)
localPlayerNode.setScale((screenSize.width/100) * 0.2)
localPlayerNode.name = "local player node"
if player.id == localPlayer {
let moveCard = SKAction.move(to: CGPoint(x: xPos, y: screenSize.height * 0.15),duration: 1.5)
//addChild(localPlayerNode) --using this instead of SKAction works
tempCard.run(moveCard, completion: {() -> Void in
self.tempCard.removeFromParent()
self.addChild(self.localPlayerNode)
})
}
x+=1
}
i+=1
} while i<3
I believe the problem is with adding the player nodes within your loop, and then not removing them afterwards. I'm sure you don't want to remove the players every time you call the " deal() " function so you should add the player nodes in the didMove() method.
I've put together a playground that demonstrates this (github Link).
I've tried to use a delay to make the cards deal 1 by one, but this is a different problem you will need to look elsewhere to solve. (Something with the .run( action) makes it so that it doesn't actually animate until the loop is complete.
example gif with bad pixel art
class GameScene: SKScene {
let scale : CGFloat = 50
var deck : [String] = []
let composition = deckComp()
let numberOfPlayers = 3
var players : [Player] = []
override func didMove(to view: SKView) {
// creates deck
for c in deckComp().colors {
for s in deckComp().suits {
deck.append( s + " of " + c )
}
}
players = createPlayers(numberOfPlayers: numberOfPlayers, center : CGPoint(x : 25, y : 25))
// setup the scales, and players
for plyr in players {
plyr.setScale(scale: (frame.width/10000) * scale)
addChild( plyr.node)
}
}
func createPlayers(numberOfPlayers : Int, center : CGPoint) -> [Player] {
let radius = Float(5*scale)
let two_pi = 2 * 3.14159
let angular_positions = two_pi / Double(numberOfPlayers)
var players_out : [Player] = []
for i in 0...numberOfPlayers - 1 {
let sprite = SKSpriteNode(texture: SKTexture(imageNamed: "card_player.png"))
sprite.zPosition = 1
sprite.position = CGPoint( x : center.x + CGFloat(radius * sin( Float(angular_positions) * Float(i) )), y : center.y + CGFloat(radius * cos( Float(angular_positions) * Float(i) )) )
sprite.texture?.filteringMode = .nearest // .linear for blurry, .nearest for pixely
let player_instance = Player(node : sprite, name : "Player " + String(i + 1), id : Int8(i + 1) )
players_out.append(player_instance)
}
return players_out
}
func deal() {
// I moved the setscale stuff for player sprites to didMove()
// first check if there is enough in deck
if deck.count > players.count {
var i = 0
repeat {
// add the temp card
let tempCard = SKSpriteNode(texture: SKTexture(imageNamed: "back"))
tempCard.size = CGSize( width: tempCard.size.width * (frame.width/10000) * scale, height : tempCard.size.height * (frame.width/10000) * scale )
tempCard.zPosition = 10
tempCard.texture?.filteringMode = .nearest
self.addChild(tempCard)
// done adding temporary card
let xPos = frame.width * (0.25 * CGFloat(i+1))
tempCard.position = CGPoint(x : xPos, y : 0.75 * frame.height)
let newCard = self.deck.popLast() // replaced dealOneCard() since I haven't defined it
players[i].addCard(card: newCard!) // removed hand.addCard(), since I don't have the array extensions
// player.name = "local player node"
let moveCard = SKAction.move(to: players[i].node.position ,duration: 1.5)
//addChild(localPlayerNode) --using this instead of SKAction works
tempCard.run(moveCard, completion: { () -> Void in tempCard.removeFromParent(); })
i += 1
} while i < players.count
} else { print("not enough cards to deal to everyone")} // when deck is empty
}
override func mouseUp(with event: NSEvent) {
deal()
}

Velocity meters per second or pixels per second?

apple documentation gives a physics bodies velocity as in meters per second.
If this is true is there a relationship between pixels and meters? and if there is, what is it?
or is this an error in the documentation and its really pixels per second?
I wrote a small program to try to work this out and got an answer of 135 points to the metre.
https://stackoverflow.com/a/37523818/1430420
Edit: Here is the program for anyone to check to see if my maths is off
//
// GameScene.swift
// gravityTest
//
// Created by Steve Ives on 30/05/2016.
// Copyright (c) 2016 Steve Ives. All rights reserved.
//
import SpriteKit
class GameScene: SKScene {
// var elapsedTime: CFTimeInterval = 0.0
var timeOfLastUpdate: CFTimeInterval = 0.0
var updateCount = 0
let sprite = SKSpriteNode(imageNamed:"Spaceship")
override func didMove(to view: SKView) {
/* Setup your scene here */
// physicsWorld.gravity = CGVectorMake(-9.8, 0)
let location = CGPoint(x: (scene?.frame.midX)!, y: (scene?.frame.midY)!)
sprite.xScale = 0.5
sprite.yScale = 0.5
sprite.physicsBody = SKPhysicsBody(rectangleOf: sprite.size)
sprite.physicsBody?.isDynamic = true
sprite.position = location
self.addChild(sprite)
}
override func update(_ currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
updateCount += 1
if (currentTime - timeOfLastUpdate) < 1 {return}
// if (timeOfLastUpdate - currentTime > 1.0) {
print("\(updateCount) - Time : \(currentTime) \(currentTime-timeOfLastUpdate) Location is : \(sprite.position) Velocity is : \(sprite.physicsBody!.velocity)")
// }
timeOfLastUpdate = currentTime
}
}
I used playground to calculate my distance. I am getting around 150
We know distance is d = (gt^2)/2 where g is gravity and t is time.
d = (9.8m/t^2)(t^2)/2
d = (9.8m)/2
d = 4.9m
In 1 second, we would travel 4.9m
In our case, we travelled 765.9298706054688 points.
765.9298706054688/4.9 gets us around 156.312218491.
Ideally, we would have travelled 735 points, not sure what in my code could cause a 30 point difference, I suspect it has to do with SKAction
//: A SpriteKit based Playground
import PlaygroundSupport
import SpriteKit
class GameScene: SKScene {
private var testNode = SKSpriteNode(color: .red, size: CGSize(width: 20, height: 20))
private var flag = false
private var time : CGFloat = 0
private var beginTest = false
override func didMove(to view: SKView) {
}
#objc static override var supportsSecureCoding: Bool {
// SKNode conforms to NSSecureCoding, so any subclass going
// through the decoding process must support secure coding
get {
return true
}
}
func touchDown(atPoint pos : CGPoint) {
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
testNode = SKSpriteNode(color: .red, size: CGSize(width: 20, height: 20))
let physicsBody = SKPhysicsBody(rectangleOf: testNode.size)
physicsBody.angularDamping = 0
physicsBody.linearDamping = 0
physicsBody.friction = 0
testNode.physicsBody = physicsBody
let action = SKAction.customAction(withDuration: 2)
{
node,elapsedTime in
if elapsedTime >= 1 && !self.flag && self.time == 0{
self.flag = true
self.time = elapsedTime
}
}
testNode.run(action)
beginTest = true
}
override func update(_ currentTime: TimeInterval) {
if beginTest{
beginTest = false
addChild(testNode)
}
}
override func didFinishUpdate() {
}
override func didApplyConstraints() {
if flag{
let position = CGPoint(x:testNode.position.x,y:-testNode.position.y)
print("Node traveled \(position.y) points over \(time) seconds with a velocity of \(testNode.physicsBody!.velocity)")
let pointsPerMeter = -testNode.physicsBody!.velocity.dy/9.8
print("Node traveled \(pointsPerMeter) points per meter over \(time) seconds with a velocity of \(testNode.physicsBody!.velocity)")
flag = false
testNode.removeFromParent()
time = 0
}
}
}
// Load the SKScene from 'GameScene.sks'
let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 640, height: 480))
if let scene = GameScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
sceneView.presentScene(scene)
}
PlaygroundSupport.PlaygroundPage.current.liveView = sceneView
Node traveled 765.9298706054688 points over 1.0 seconds with a velocity of (0.0, -1494.4998779296875)
Node traveled 152.49998754384566 points per meter over 1.0 seconds with a velocity of (0.0, -1494.4998779296875)
An answer to anyone and myself
There is nothing to really define a pixel within apples SKSpriteKit, this is a misnomer in my question. A point on the other hand is well defined.
Everybody probably already knows this but if you attach a SKPhysicsBody to a Node and assign the following properties:
seg.physicsBody?.isDynamic = true
seg.physicsBody?.affectedByGravity = false
seg.physicsBody?.allowsRotation = false
seg.physicsBody?.usesPreciseCollisionDetection = true
seg.physicsBody?.mass = 0
seg.physicsBody?.restitution = 1
seg.physicsBody?.linearDamping = 0
Then applying a velocity dy component of 200 results in the object moving at 200 points per second. This says to me that with all external forces remove Sprite kits physics world translates to 1m/s = 1point/s.

Another SKSpriteNode is removed when current one is touched

I am in developing an application using Xcode and currently I am facing a issue regarding SKSpriteNodes. When there are more than a single SKSpriteNode and when the node is touched, the touched node is not removed but the other not touched is removed. I have also noticed that when there are multiple nodes on the screen only the latest node coming from the top of the screen is removed whilst others are still moving down, although they are being touched. Can someone help identify why this has occurred and the methods of preventing such mistakes please?
For reference, I am have included the class in which the bug is in.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var gameCounter: Int = 0
private var currentLevel: Int = 0
private var debug: SKLabelNode?
private var increasedTouchArea:SKSpriteNode?
private var generatedNode:SKSpriteNode?
private var moveAndRemove:SKAction?
private var moveNode:SKAction?
// Here we set initial values of counter and level. Debug label is created here as well.
override func didMove(to view: SKView) {
print(action(forKey: "counting") == nil)
gameCounter = 0
currentLevel = 1
//backgroundColor = SKColor.gray
debug = SKLabelNode(fontNamed: "ArialMT")
debug?.fontColor = SKColor.red
debug?.fontSize = 30.0
debug?.position = CGPoint(x: frame.midX, y: frame.midY)
debug?.text = "Counter : [ \(gameCounter) ], Level [ \(currentLevel) ]"
if let aDebug = debug {
addChild(aDebug)
}
}
//Method to start a timer. SKAction is used here to track a time passed and to maintain current level
func startTimer() {
print("Timer Started...")
weak var weakSelf: GameScene? = self
//make a weak reference to scene to avoid retain cycle
let block = SKAction.run({
weakSelf?.gameCounter = (weakSelf?.gameCounter ?? 0) + 1
//Maintaining level
if (weakSelf?.gameCounter ?? 0) < 5 {
//level 1
weakSelf?.currentLevel = 1
} else if (weakSelf?.gameCounter ?? 0) >= 5 && (weakSelf?.gameCounter ?? 0) < 10 {
//level 2
weakSelf?.currentLevel = 2
} else {
//level 3
weakSelf?.currentLevel = 3
}
weakSelf?.debug?.text = "Counter : [ \(Int(weakSelf?.gameCounter ?? 0)) ], Level [ \(Int(weakSelf?.currentLevel ?? 0)) ]"
})
run(SKAction.repeatForever(SKAction.sequence([SKAction.wait(forDuration: 1), block])), withKey: "counting")
}
//Method for stopping timer and reseting everything to default state.
func stopTimer() {
print("Timer Stopped...")
if action(forKey: "counting") != nil {
removeAction(forKey: "counting")
}
gameCounter = Int(0.0)
currentLevel = 1
debug?.text = "Counter : [ \(gameCounter) ], Level [ \(currentLevel) ]"
}
//Get current speed based on time passed (based on counter variable)
func getCurrentSpeed() -> CGFloat {
if gameCounter < 30 {
//level 1
return 1.0
} else if gameCounter >= 31 && gameCounter < 49 {
//level 2
return 2.0
} else {
//level 3
return 3.0
}
}
//Method which stop generating stones, called in touchesBegan
func stopGeneratingStones() {
print("STOPPED GENERATING STONES...")
if action(forKey: "spawning") != nil {
removeAction(forKey: "spawning")
}
}
func randomFloatBetween(_ smallNumber: CGFloat, and bigNumber: CGFloat) -> CGFloat {
let diff: CGFloat = bigNumber - smallNumber
return CGFloat(arc4random() % (UInt32(RAND_MAX) + 1)) / CGFloat(RAND_MAX) * diff + smallNumber
}
//Method for generating stones, you run this method when you want to start spawning nodes (eg. didMoveToView or when some button is clicked)
func generateStones() {
print("Generating Stones...")
let delay = SKAction.wait(forDuration: 1, withRange: 0.5) //Change forDuration: delay decreases as game progresses.
//randomizing delay time
weak var weakSelf: GameScene? = self
//make a weak reference to scene to avoid retain cycle
let block = SKAction.run({
let stone: SKSpriteNode? = weakSelf?.spawnNodes(withSpeed: weakSelf?.getCurrentSpeed() ?? 0.0)
stone?.zPosition = 20
if let aStone = stone {
weakSelf?.addChild(aStone)
}
})
run(SKAction.repeatForever(SKAction.sequence([delay, block])), withKey: "spawning")
}
func spawnNodes(withSpeed stoneSpeed: CGFloat) -> SKSpriteNode? {
let nodeSize = CGSize(width: 60, height: 60) //size of shape.
let initalNodePosition = CGPoint(x: randomFloatBetween(0.0, and: (self.view?.bounds.size.width)!) - 110, y: frame.maxY)
generatedNode = SKSpriteNode(color: SKColor.green, size: nodeSize)
generatedNode?.position = initalNodePosition
moveNode = SKAction.moveBy(x: 0, y: self.view!.scene!.frame.minY + self.view!.scene!.frame.minY , duration: 5.25)
generatedNode?.isUserInteractionEnabled = false //Allows users to touch shape.
increasedTouchArea = SKSpriteNode(color: UIColor.clear, size: CGSize(width: nodeSize.width * 1.35, height: nodeSize.height * 1.35))
increasedTouchArea?.name = "generatedNode"
increasedTouchArea?.isUserInteractionEnabled = false
generatedNode?.addChild(increasedTouchArea!)
moveNode?.speed = stoneSpeed
moveAndRemove = SKAction.sequence([moveNode!, SKAction.removeFromParent()])
generatedNode?.run(moveAndRemove!, withKey: "moving")
generatedNode?.name = "generatedNode"
return generatedNode
}
func deleteNode(){
moveAndRemove = SKAction.sequence([SKAction.removeFromParent()])
generatedNode?.run(moveAndRemove!, withKey: "moving")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent? {
for touch in touches {
let locationTocuhed = touch.location(in: self)
let touchedNode : SKNode = self.atPoint(locationTocuhed)
if touchedNode.name == generatedNode?.name {
print("Node touched")
deleteNode()
}
}
if action(forKey: "counting") == nil {
print("Game started...")
startTimer()
generateStones()
} else {
print("Game paused...")
}
}
}
Your method deleteNode() runs the deletion animation on the node pointed to by generatedNode, not the node last touched:
func deleteNode(){
moveAndRemove = SKAction.sequence([SKAction.removeFromParent()])
generatedNode?.run(moveAndRemove!, withKey: "moving")
}
If you want to delete the node last touched, pass a reference to this node to the method deleteNode and then run your deletion animation in that, not generatedNode.

Tower defense: turret tracking enemy and shooting issues

Here is my code:
func bombTowerTurnShoot() {
var prevDistance:CGFloat = 1000000
var closesetZombie = zombieArray[0]
self.enumerateChildNodes(withName: "bomb tower") {
node, stop in
if self.zombieArray.count > 0 {
for zombie in self.zombieArray {
if let bombTower = node as? SKSpriteNode {
let angle = atan2(closesetZombie.position.x - bombTower.position.x , closesetZombie.position.y - bombTower.position.y)
let actionTurn = SKAction.rotate(toAngle: -(angle - CGFloat(Double.pi/2)), duration: 0.2)
bombTower.run(actionTurn)
let turretBullet = SKSpriteNode(imageNamed: "Level 1 Turret Bullet")
turretBullet.position = bombTower.position
turretBullet.zPosition = 20
turretBullet.size = CGSize(width: 20, height: 20)
//turretBullet.setScale (frame.size.height / 5000)
turretBullet.physicsBody = SKPhysicsBody(circleOfRadius: max(turretBullet.size.width / 2, turretBullet.size.height / 2))
turretBullet.physicsBody?.affectedByGravity = false
turretBullet.physicsBody!.categoryBitMask = PhysicsCategories.Bullet //new contact
turretBullet.physicsBody!.collisionBitMask = PhysicsCategories.None
turretBullet.physicsBody!.contactTestBitMask = PhysicsCategories.Zombie
self.addChild(turretBullet)
var dx = CGFloat(closesetZombie.position.x - bombTower.position.x)
var dy = CGFloat(closesetZombie.position.y - bombTower.position.y)
let magnitude = sqrt(dx * dx + dy * dy)
dx /= magnitude
dy /= magnitude
let vector = CGVector(dx: 4.0 * dx, dy: 4.0 * dy)
func fire () {
turretBullet.physicsBody?.applyImpulse(vector)
}
func deleteBullet() {
turretBullet.removeFromParent()
}
turretBullet.run(SKAction.sequence([SKAction.wait(forDuration: 0), SKAction.run(fire), SKAction.wait(forDuration: 2.0), SKAction.run(deleteBullet) ]))
let distance = hypot(zombie.position.x - bombTower.position.x, zombie.position.y - bombTower.position.y)
if distance < prevDistance {
prevDistance = distance
closesetZombie = zombie
}
}
}
}
}
}
What this code does is turns a turret towards the closest zombie and shoot at it. As far as I can tell the turret is turn towards the closest zombie (if you can tell whether this code actually accomplishes that or not I would like to know). The bigger problem I am having is that the turrets sometimes shoot more than one bullet. I think it is because it is trying to fire at all zombies in the array not the specified one (the closest to the tower). How can I make it so that the turret only shoots the zombie that is closest?
class GameScene: SKScene, SKPhysicsContactDelegate {//new contact
var zombieArray:[SKSpriteNode] = []
...
...
}
And I append all the zombie to the array once they are added and remove them from the array once they die.
Basically, I don't know what you were doing wrong exactly. You had a ton of stuff going on, and trying to figure out the bug would probably have taken longer than rewriting it (for me at least). So that is what I did.
Here is a link to the project on github:
https://github.com/fluidityt/ShootClosestZombie/tree/master
For me, this was all about separating actions into somewhat distinct methods, and separating actions in general from logic.
You had so much going on, it was hard to test / see which parts were working correctly or not. This is where having somewhat smaller methods come in, as well as separating action from logic.. Your action may work fine, but perhaps it's not getting called due to a logic error.
So, how I implemented this was to just make your bomb turret it's own class.. that way we can have the bomb turret be in charge of most of its actions, and then let gameScene handle most of the implementation / and or logic.
The demo I've uploaded shows two turrets that auto-orient themselves to the closest zombie every frame, then shoot at them every second. Click the screen to add more zombies.
The turrets independently track the closest zombie to them so if you spawn a zombie on the left and the right, then the left turret will shoot at left zombie, and right turret will shoot at right zombie (and only once!).
class BombTower: SKSpriteNode {
static let bombName = "bomb tower"
var closestZombie: SKSpriteNode!
func updateClosestZombie() {
let gameScene = (self.scene! as! GameScene)
let zombieArray = gameScene.zombieArray
var prevDistance:CGFloat = 1000000
var closestZombie = zombieArray[0]
for zombie in zombieArray {
let distance = hypot(zombie.position.x - self.position.x, zombie.position.y - self.position.y)
if distance < prevDistance {
prevDistance = distance
closestZombie = zombie
}
}
self.closestZombie = closestZombie
}
func turnTowardsClosestZombie() {
let angle = atan2(closestZombie.position.x - self.position.x , closestZombie.position.y - self.position.y)
let actionTurn = SKAction.rotate(toAngle: -(angle - CGFloat(Double.pi/2)), duration: 0.2)
self.run(actionTurn)
}
private func makeTurretBullet() -> SKSpriteNode {
let turretBullet = SKSpriteNode(imageNamed: "Level 1 Turret Bullet")
turretBullet.position = self.position
turretBullet.zPosition = 20
turretBullet.size = CGSize(width: 20, height: 20)
//turretBullet.setScale (frame.size.height / 5000)
turretBullet.physicsBody = SKPhysicsBody(circleOfRadius: max(turretBullet.size.width / 2, turretBullet.size.height / 2))
turretBullet.physicsBody?.affectedByGravity = false
// turretBullet.physicsBody!.categoryBitMask = PhysicsCategories.Bullet //new contact
// turretBullet.physicsBody!.collisionBitMask = PhysicsCategories.None
// turretBullet.physicsBody!.contactTestBitMask = PhysicsCategories.Zombie
return turretBullet
}
private func fire(turretBullet: SKSpriteNode) {
var dx = CGFloat(closestZombie.position.x - self.position.x)
var dy = CGFloat(closestZombie.position.y - self.position.y)
let magnitude = sqrt(dx * dx + dy * dy)
dx /= magnitude
dy /= magnitude
let vector = CGVector(dx: 4.0 * dx, dy: 4.0 * dy)
turretBullet.physicsBody?.applyImpulse(vector)
}
func addBulletThenShootAtClosestZOmbie() {
let bullet = makeTurretBullet()
scene!.addChild(bullet)
fire(turretBullet: bullet)
}
}
// TODO: delete bullets, hit detection, and add SKConstraint for tracking instead of update.
// Also, I think that we are iterating too much looking for nodes. Should be able to reduce that.
// Also also, there are sure to be bugs if zombieArray is empty.
class GameScene: SKScene {
var zombieArray: [SKSpriteNode] = []
private func makeBombArray() -> [BombTower]? {
guard self.zombieArray.count > 0 else { return nil }
var towerArray: [BombTower] = []
self.enumerateChildNodes(withName: BombTower.bombName) { node, _ in towerArray.append(node as! BombTower) }
guard towerArray.count > 0 else { return nil }
return towerArray
}
private func towersShootEverySecond(towerArray: [BombTower]) {
let action = SKAction.run {
for bombTower in towerArray {
guard bombTower.closestZombie != nil else { continue } // I haven't tested this guard statement yet.
bombTower.addBulletThenShootAtClosestZOmbie()
}
}
self.run(.repeatForever(.sequence([.wait(forDuration: 1), action])))
}
override func didMove(to view: SKView) {
// Demo setup:
removeAllChildren()
makeTestZombie: do {
spawnZombie(at: CGPoint.zero)
}
makeTower1: do {
let tower = BombTower(color: .yellow, size: CGSize(width: 55, height: 55))
let turretGun = SKSpriteNode(color: .gray, size: CGSize(width: 25, height: 15))
turretGun.position.x = tower.frame.maxX + turretGun.size.height/2
tower.name = BombTower.bombName
tower.addChild(turretGun)
addChild(tower)
}
makeTower2: do {
let tower = BombTower(color: .yellow, size: CGSize(width: 55, height: 55))
let turretGun = SKSpriteNode(color: .gray, size: CGSize(width: 25, height: 15))
turretGun.position.x = tower.frame.maxX + turretGun.size.height/2
tower.addChild(turretGun)
tower.position.x += 200
tower.name = BombTower.bombName
addChild(tower)
}
guard let towerArray = makeBombArray() else { fatalError("couldn't make array!") }
towersShootEverySecond(towerArray: towerArray)
}
private func spawnZombie(at location: CGPoint) {
let zombie = SKSpriteNode(color: .blue, size: CGSize(width: 35, height: 50))
zombieArray.append(zombie)
zombie.position = location
zombie.run(.move(by: CGVector(dx: 3000, dy: -3000), duration: 50))
addChild(zombie)
}
// Just change this to touchesBegan for it to work on iOS:
override func mouseDown(with event: NSEvent) {
let location = event.location(in: self)
spawnZombie(at: location)
}
// I think this could be a constrain or action, but I couldn't get either to work right now.
private func keepTowersTrackingNearestZombie() {
guard let towerArray = makeBombArray() else { return }
for tower in towerArray {
tower.updateClosestZombie()
tower.turnTowardsClosestZombie()
}
}
override func update(_ currentTime: TimeInterval) {
keepTowersTrackingNearestZombie()
}
}

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.