SKAction & SKSpriteNode trouble - swift

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()
}

Related

Node prematurely removed from scene (Swift game)

I have the following code in Swift for a simple platform game:
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var me : SKSpriteNode?
var ceiling : SKSpriteNode?
var studentTimer : Timer?
var cloudTimer : Timer?
let meCategory : UInt32 = 0x1 << 1
let studentCategory : UInt32 = 0x1 << 2
let cloudCategory : UInt32 = 0x1 << 3
let groundAndCeilingCategory : UInt32 = 0x1 << 4
var numberofStudents = 0
var education = ["edu1", "edu2", "edu3", "edu4"]
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
me = childNode(withName: "me") as? SKSpriteNode
me?.physicsBody?.categoryBitMask = meCategory
me?.physicsBody?.contactTestBitMask = studentCategory
me?.physicsBody?.collisionBitMask = groundAndCeilingCategory
// make me run
var meRun : [SKTexture] = []
for number in 1...6 {
meRun.append(SKTexture(imageNamed: "Armature_Run_\(number)"))
}
me?.run(SKAction.repeatForever(SKAction.animate(with: meRun, timePerFrame: 0.1)))
ceiling = childNode(withName: "ceiling") as? SKSpriteNode
ceiling?.physicsBody?.categoryBitMask = groundAndCeilingCategory
ceiling?.physicsBody?.collisionBitMask = meCategory
startTimers()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
me?.physicsBody?.applyForce(CGVector(dx: 0, dy: 20000))
var meJump : [SKTexture] = []
for number in 0...9 {
meJump.append(SKTexture(imageNamed: "Armature_Jump_0\(number)"))
}
me?.run(SKAction.animate(with: meJump, timePerFrame: 0.1))
}
func createStudent() {
let student = SKSpriteNode(imageNamed: "student")
student.physicsBody = SKPhysicsBody(rectangleOf: student.size)
student.physicsBody?.affectedByGravity = false
student.physicsBody?.categoryBitMask = studentCategory
student.physicsBody?.contactTestBitMask = meCategory
student.physicsBody?.collisionBitMask = 0
addChild(student)
student.position = CGPoint(x: size.width / 2 + student.size.width, y: (-size.height / 2) + ((student.size.height)*2))
let moveLeft = SKAction.moveBy(x: -size.width - student.size.width, y: 0, duration: 4)
student.run(SKAction.sequence([moveLeft, SKAction.removeFromParent()]))
}
func startTimers() {
studentTimer = Timer.scheduledTimer(withTimeInterval: 4, repeats: true, block: { (timer) in
if self.numberofStudents < 4 {
self.createStudent()
}
})
cloudTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true, block: { (timer) in
self.createCloud()
})
}
func createCloud() {
let cloud = SKSpriteNode(imageNamed: "cloud")
cloud.zPosition = -1
cloud.physicsBody = SKPhysicsBody(rectangleOf: cloud.size)
cloud.physicsBody?.affectedByGravity = false
cloud.physicsBody?.collisionBitMask = 0
cloud.physicsBody?.categoryBitMask = cloudCategory
addChild(cloud)
let maxY = size.height / 2 - cloud.size.height / 2
let minY = CGFloat(0)
let range = maxY - minY
let cloudY = maxY - CGFloat(arc4random_uniform(UInt32(range)))
cloud.position = CGPoint(x: size.width / 2 + cloud.size.width / 2, y: cloudY)
let moveLeft = SKAction.moveBy(x: -size.width - cloud.size.width, y: 0, duration: 4)
cloud.run(SKAction.sequence([moveLeft, SKAction.removeFromParent()]))
}
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == studentCategory {
contact.bodyA.node?.removeFromParent()
numberofStudents += 1
print(numberofStudents)
print(education[(numberofStudents)-1])
}
if contact.bodyB.categoryBitMask == studentCategory {
contact.bodyB.node?.removeFromParent()
numberofStudents += 1
print(numberofStudents)
print(education[(numberofStudents)-1])
}
}
}
I'm working on what happens when the main character ("me") collides with the "student".
Prior to adding 'physicsWorld.contactDelegate = self' the "student" showed up perfectly, moving across the screen. However, when I add this line of code and run the app, the student is invisible. I believe it is still there somewhere as the print common is still running when the character collides, but it is not visibly colliding with anything.
I removed the contact.bodyB.node?.removeFromParent() and ran it, and indeed the "student" does show again, but obviously does not disappear on contact.
I've looked over this for ages and am sure I'm missing something obvious but cannot work out what it is.
you are not supplying us with all of the information.
As it is your code works as expected.
Here is a vid of your code running (I had to use my own images)
Here is my analysis of what might be wrong.
I removed all the animation sequence items because they have no bearing on this.
I notice you are not putting zPositions...tsk tsk tsk. All items should have a zPosition.
you are not showing us how you define the physicsBody on your "me" sprite. Assumably in the editor? You should include that info next time.
I had to create the "me" character and position it in code since you don't show us where that comes from either. I also created the physicsBody in code to get it to work.
Here is the code I used
me = SKSpriteNode(imageNamed: "orange")
me.position = CGPoint(x: -400, y: (-size.height / 2) + ((me.size.height) * 2))
me.zPosition = 10
addChild(me)
me.physicsBody = SKPhysicsBody(rectangleOf: me.size)
me.physicsBody?.categoryBitMask = meCategory
me.physicsBody?.contactTestBitMask = studentCategory
me.physicsBody?.collisionBitMask = groundAndCeilingCategory
me.physicsBody?.affectedByGravity = false
func createStudent() {
let student = SKSpriteNode(imageNamed: "apple")
student.setScale(0.5)
student.position = CGPoint(x: size.width / 2 + student.size.width, y: (-size.height / 2) + ((student.size.height) * 2))
student.zPosition = 10
addChild(student)
student.physicsBody = SKPhysicsBody(rectangleOf: student.size)
student.physicsBody?.affectedByGravity = false
student.physicsBody?.categoryBitMask = studentCategory
student.physicsBody?.contactTestBitMask = meCategory
student.physicsBody?.collisionBitMask = 0
let moveLeft = SKAction.moveBy(x: -size.width - student.size.width, y: 0, duration: 4)
student.run(SKAction.sequence([moveLeft, SKAction.removeFromParent()]))
}
On a side note, it is not necessary to use timers. SpriteKit has a built in feature for this the "update" func.

How to fix: Calling a node by name (generated by func) won't work

I'm currently working on my first test game in which the player have to jump over some randomly generated bars.
I wrote a func which should generate the bars outside of my scene.
My problem at the moment is that when I'm trying to call the created bars with "self.childNode(withName:)" xCode is telling me "Fatal error: Unexpectedly found nil while unwrapping an Optional value".
I've already read the Apple Documentation for "childNode(withName:)" and added "//" before the name of the node. In addition I used the stackoverflow search but I can't find anything that solved my problem.
//
// GameScene.swift
// PlaxerJump
//
//
import SpriteKit
import AVFoundation
class GameScene: SKScene {
let bottom1 = SKSpriteNode(imageNamed: "Bottom")
let bottom2 = SKSpriteNode(imageNamed: "Bottom")
let player = SKSpriteNode(imageNamed: "Player")
var velocity = CGFloat(0)
var onGround = true
var value = CGFloat(5)
override func didMove(to view: SKView) {
// Hintergrund
self.backgroundColor = SKColor.lightGray
bottom1.anchorPoint = CGPoint.zero
bottom1.position = CGPoint.zero
bottom1.zPosition = 1
self.addChild(bottom1)
bottom2.anchorPoint = CGPoint.zero
bottom2.position = CGPoint(x: bottom1.size.width - 1, y: 0)
bottom2.zPosition = 1
self.addChild(bottom2)
// Spieler
player.position = CGPoint(x: player.size.width / 2 + 20, y: bottom1.size.height + player.size.height / 2)
player.zPosition = 2
self.addChild(player)
//Balken
addBalken(xScale: 1.5, yScale: 1, name: "ba1", xPoint: 0)
}
func addBalken(xScale: CGFloat, yScale: CGFloat, name: String, xPoint: CGFloat) {
let balken = SKSpriteNode(imageNamed: "Balken")
balken.anchorPoint = CGPoint.zero
balken.position = CGPoint(x: self.size.width + (2 * balken.size.width) + xPoint, y: bottom1.size.height - 16)
balken.zPosition = 1
balken.xScale = xScale
balken.yScale = yScale
balken.name = name
addChild(balken)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if onGround == true {
velocity = -16
onGround = false
self.run(SKAction.playSoundFileNamed("Jump.wav", waitForCompletion: false))
}
}
override func update(_ currentTime: TimeInterval) {
//Player
player.zRotation -= CGFloat(Double.pi * 5) / 180
velocity += 0.6
player.position.y -= velocity
if player.position.y <= bottom1.size.height {
player.position.y = bottom1.size.height
velocity = 0
onGround = true
}
//Bottom
bottom1.position.x -= 4
bottom2.position.x -= 4
if bottom1.position.x < -bottom1.size.width {
bottom1.position.x = bottom2.position.x + bottom2.size.width
} else if bottom2.position.x < -bottom2.size.width {
bottom2.position.x = bottom1.position.x + bottom1.size.width
}
//Balken - ** THIS IS THE PART WHERE THE ERROR OCCURS **
let balke1 = self.childNode(withName: "//ba1") as! SKSpriteNode
balke1.position.x -= value
if balke1.position.x < self.size.width {
balke1.position.x = self.size.width + (2 * balke1.size.width)
value = CGFloat(arc4random_uniform(UInt32(value)))
}
}
}
I just want to call the node so I can use it to implement the bars in the game.
Change this line of code:
addBalken(xScale: 1.5, yScale: 1, name: "//ba1", xPoint: 0)
to:
addBalken(xScale: 1.5, yScale: 1, name: "ba1", xPoint: 0)
The // only applies when searching for the node, so keep these characters in this line:
let balke1 = self.childNode(withName: "//ba1") as! SKSpriteNode
EDIT:
I think the root cause of your problem is that you forgot to call addChild in your addBalken function. Simply creating a node isn't enough. The node must also be added to the scene as well. So this is the final code:
func addBalken(xScale: CGFloat, yScale: CGFloat, name: String, xPoint: CGFloat) {
let balken = SKSpriteNode(imageNamed: "Balken")
balken.anchorPoint = CGPoint.zero
balken.position = CGPoint(x: self.size.width + (2 * balken.size.width) + xPoint, y: bottom1.size.height - 16)
balken.zPosition = 1
balken.xScale = xScale
balken.yScale = yScale
balken.name = name
//add the node to the scene
addChild(balken)
}

SKAction problems when reloading the game

I'm having some problems with a SKAction when clicking retry after the player dies. It's the shooting SKAction that I'm having difficulty with. It's shooting well when I run the game but when I click "Retry" to play again only one bullet fires. After that, the SKAction stops. All the other SKActions work when i play again, but not that one.
I know what the problem is. I have all the relevant functions in a func called initialize() that I add in the didMove. But I'm having difficulty calling some of the functions there since the functions have a parameter.
These are the shooting functions I have:
func fireMissile() {
let missile = SKSpriteNode(color: .yellow, size: CGSize(width: 20,
height: 5))
missile.name = "Missile"
missile.position = CGPoint(x: player.position.x + 28, y:
player.position.y + 10)
missile.zPosition = 2
missile.physicsBody = SKPhysicsBody(rectangleOf: missile.size)
missile.physicsBody?.isDynamic = false
missile.physicsBody?.categoryBitMask = ColliderType.Bullet
missile.physicsBody?.collisionBitMask = ColliderType.Enemy |
ColliderType.Boat
missile.physicsBody?.contactTestBitMask = ColliderType.Enemy |
ColliderType.Boat
let missileFlightTime = travelTime(to: missileDestination, from:
player.position, atSpeed: missileSpeed)
missile.zRotation = direction(to: missileDestination, from:
missile.position)
self.addChild(missile)
let missileMove = SKAction.move(to: missileDestination,
duration:
TimeInterval(missileFlightTime))
let missileRemove = SKAction.removeFromParent()
missile.run(SKAction.sequence([missileMove, missileRemove]))
}
func travelTime(to target : CGPoint, from : CGPoint, atSpeed speed :
CGFloat) -> TimeInterval {
let distance = sqrt(pow(abs(target.x - from.x),2) +
pow(abs(target.y - from.y),2))
return TimeInterval(distance/speed)
}
func direction(to target : CGPoint, from: CGPoint) -> CGFloat {
let x = target.x - from.x
let y = target.y - from.y
var angle = atan(y / x)
if x < 0 {
angle = angle + CGFloat.pi
}
return angle
}
And then we have the didMove function:
func initialize() {
score = 0
physicsWorld.contactDelegate = self
createPlayer()
createBG()
createWall()
spawnEnemiesLeft()
spawnEnemiesRight()
spawnBoat()
fireMissile()
createlabel()
}
When i try adding the "func travelTime()" and func "direction()", it requires for me to also add the CGPoint for the "to target" and "from", and I don't know what to type in there, I don't know if thats even the way to go, but it should be.

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 can I add a physics body to an SKAtlasTexture or create an animation through Images.xcassets?

I wanted to create a small animation of a car driving down the road, so I made an atlas of 9 different pictures. The car simply looks like its wheels are rotating and the car is bouncing a bit as it drives along. I already made an SKSpriteNode with an image and added a physics body on it so that it can jump and be affected by gravity.
So I was wondering how to add either a physics body to an SKAtlasTexture or create an animation through my image.xcassets folder. I tried to just change the SKSpriteNode to SKAtlasTexture, but that obviously didn't work as there are no physics bodies in SKAtlasTexture. So that's where I'm at. Any suggestions or solutions would be greatly appreciated.
Here some parts of my code:
class PlayScene: SKScene, SKPhysicsContactDelegate {
let road = SKSpriteNode(imageNamed: "road")
var origRoadPositionX = CGFloat(0)
var maxRoad = CGFloat(0)
var groundSpeed = 3
var carBaseLine = CGFloat(0)
let car = SKSpriteNode(imageNamed: "car")
enum ColliderType:UInt32{
case car = 1
case tower = 2
}
override func didMoveToView(view: SKView) {
self.backgroundColor = UIColor(hex: 0x80E8FF)
self.physicsWorld.contactDelegate = self
//Car
self.car.position = CGPointMake(CGRectGetMinX(self.frame)-20 + self.car.size.width, self.carBaseLine)
self.car.physicsBody = SKPhysicsBody (rectangleOfSize: self.car.size)
self.car.physicsBody?.allowsRotation = false
self.car.physicsBody?.affectedByGravity = false
self.car.physicsBody?.categoryBitMask = ColliderType.car.rawValue
self.car.physicsBody?.contactTestBitMask = ColliderType.tower.rawValue
self.car.physicsBody?.collisionBitMask = ColliderType.tower.rawValue
self.addChild(car)
If more code is needed in order to find a solution, let me know and i can supply more of it.
You can use atlas folder for performing animation with images.
Consider below example:
import SpriteKit
class GameScene: SKScene {
var bombFrames : [SKTexture]!
var bomb : SKSpriteNode!
let NoneCategory : UInt32 = 0x1 << 0
let ProjectileCategory : UInt32 = 0x1 << 2
let bombCategory : UInt32 = 0x1 << 7
override func didMoveToView(view: SKView) {
/* Setup your scene here */
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.runBlock(addBomb), SKAction.waitForDuration(5.0)])))
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
func addBomb() {
let Name = "Bomb"
let AnimatedAtlas = SKTextureAtlas(named: Name)
var Framese = [SKTexture]()
let numImages = AnimatedAtlas.textureNames.count
for var i=1; i<=numImages; i++ {
let TextureName = "\(i)"
Framese.append(AnimatedAtlas.textureNamed(TextureName))
}
bombFrames = Framese
let firstFrame = bombFrames[0]
bomb = SKSpriteNode(texture: firstFrame)
let actualY = random(min: bomb.size.height/2, max: size.height - bomb.size.height/2)
bomb.position = CGPoint(x: size.width + bomb.size.width/2, y: actualY)
bomb.physicsBody = SKPhysicsBody(texture: bomb.texture, size: bomb.texture!.size())
bomb.physicsBody?.dynamic = true
bomb.physicsBody?.categoryBitMask = bombCategory
bomb.physicsBody?.contactTestBitMask = ProjectileCategory
bomb.physicsBody?.collisionBitMask = NoneCategory
bomb.physicsBody?.usesPreciseCollisionDetection = true
addChild(bomb)
let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
let actionMove = SKAction.moveTo(CGPoint(x: -bomb.size.width/2, y: actualY), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
bomb.runAction(SKAction.sequence([actionMove, actionMoveDone]))
playBombAnimation()
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func playBombAnimation() {
bomb.runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(bombFrames, timePerFrame: 0.1, resize: false, restore: true)), withKey:"bombAnimation")
}
}
And don't forget to add atlas folder into your project navigator like this:
As you can see in code you can add physics body to your sprite. and if you want you can try this way.
Hope this will help.