Why does SKNode() when assigned to a variable spawns an endless horde of enemies? - sprite-kit

So I'm making a game where enemies fly past you and despawn when they die or leave the screen, and I was also making a play/pause button, and in order to do so I made a variable called worldNode and assigned it SKNode(). I made the variable inside no function but inside the GameScene class. Then every time I added something, I would do
worldNode.addChild(thing)
Since that allows me to pick and choose what gets paused and unpaused without freezing the whole game. But when I do it to these lines of code it spawns every enemy in at once, immediately killing the player. This code is from the update function.
if currentWave.enemies.isEmpty {
for (index, position) in positions.shuffled().enumerated() {
let enemy = EnemyNode(type: enemyTypes[enemyType], startPosition: CGPoint(x: enemyStartX, y: position), xOffset: enemyOffsetX * CGFloat(index * 3), moveStraight: true)
worldNode.addChild(enemy)
}
} else {
for enemy in currentWave.enemies {
let node = EnemyNode(type: enemyTypes[enemyType], startPosition: CGPoint(x: enemyStartX, y: positions[enemy.position]), xOffset: enemyOffsetX * enemy.xOffset, moveStraight: enemy.moveStraight)
worldNode.addChild(node)
}
}
only for the addChild(node) does this happen, and I can't leave it out because then they keep flying around the screen. What would I do to fix this?

Related

swift correct syntax if else while etc

I have a SKSpritenode which can be dragged around the screen, within a boundary.
I have a test for being within the boundary.
if backgroundRect.contains(paddleRect){
let paddleX = paddle.position.x + (touchLocation.x - previousLocation.x)
let paddleY = paddle.position.y + (touchLocation.y - previousLocation.y)
paddle.position = CGPoint(x: paddleX, y: paddleY)
} else {
print ("paddleRect not contained by backgroundRect")
}
At the moment I can drag the paddle around within the background. When I drag the paddle and it hits the edge of the background then it isn't contained within the background and stops so I can't move it anymore.
I would like be able to move it just within the background rectangle but not stop responding to drags at the edge. I know "if else" isn't the correct way to do this, but am wondering how I write it so the paddle keeps moving, within the bounds of the background rectangle.
Is it a "while continue" loop? or something else?
The issue is that once you exit the rectangle, you lose the ability to change the position of the paddle. Because of that, every subsequent check of backgroundRect.contains(paddleRect) will return false, forever.
What you should do instead is always compute the potential position, and either use it, or discard it:
let potentialPosition = CGPoint(
x: paddle.position.x + (touchLocation.x - previousLocation.x),
y: paddle.position.y + (touchLocation.y - previousLocation.y)
)
// This line is approximated, I don't know the exact relation between
// `paddleRect` and `paddle`, but you get the idea
if backgroundRect.contains(potentialPosition) {
paddle.position = potentialPosition
} else {
print ("paddleRect not contained by backgroundRect")
}

Detect collision between SKNode and Particles?

I have this function that creates some lava:
func setupLava() {
let emitter = SKEmitterNode(fileNamed: "Lava.sks")!
emitter.particlePositionRange = CGVector(dx: 200, dy: 0.0)
emitter.advanceSimulationTime(3.0)
emitter.zPosition = 4
emitter.position = CGPoint(x: self.frame.width / 2, y: 300)
lava.addChild(emitter)
}
And I want to detect when the player collides with it. How do I do that?
From the documentation:
Particles are not represented by objects in SpriteKit. This means you
cannot perform node-related tasks on particles, nor can you associate
physics bodies with particles to make them interact with other
content. Although there is no visible class representing particles
added by the emitter node, you can think of a particle as having
properties like any other object.
So you cannot use SpriteKit to detect collisions with the emitted Lava, but you can associate a physics body to the Lava object and collide with it not it individual emitted nodes. Use categoryContactMask, contactBitMask fields of the physicsBody of your nodes to detect contacts.

MoveTo SKAction forever

I have the following code to move an "enemy" to my "players" position.
let follow = SKAction.moveTo(player.position, duration: 2)
enemy.runAction(SKAction.repeatActionForever(action), withKey: "moving")
This code works fine. However, when I move the player, I would like the SKAction to "run again" so that the enemy is always moving towards the last location that the player was at. Therefore, if the player does not keep moving, they will eventually get caught.
How come the "repeatActionForever" is not working? The enemy moves to the initial location of the player, but when you move the player to a new location, the enemy does not move to that new location.
Thanks :D
repeatActionForever is working, it is not dynamic, it will only move to the player at the time the action was created, it does not know anything has changed.
You need to do runBlock for this:
let follow =
SKAction.sequence(
[
SKAction.runBlock(
{
//this will allow the enemy to constantly follow the player
enemy.runAction(SKAction.moveTo(player.position, duration: 2), withKey:"move")
}),
SKAction.waitForDuration(0.1)
]
)
enemy.runAction(SKAction.repeatActionForever(follow), withKey: "moving")
Now the problem with this approach is your enemy speed changes based on the distance of the player. Instead you want to do this:
//duration is expected time to reach the player
let follow = SKAction.customActionWithDuration(duration,
actionBlock:
{
node,elapsedTime in
//change the nodes velocity
//this formula is dependent on gameplay
})
enemy.runAction(SKAction.repeatActionForever(follow), withKey: "moving")

Accessing properties of multiple SKShapeNodes

In my program I have a method called addObstacle, which creates a rectangular SKShapeNode with an SKPhysicsBody, and a leftward velocity.
func addObstacle(bottom: CGFloat, top: CGFloat, width: CGFloat){
let obstacleRect = CGRectMake(self.size.width + 100, bottom, width, (top - bottom))
let obstacle = SKShapeNode(rect: obstacleRect)
obstacle.name = "obstacleNode"
obstacle.fillColor = UIColor.grayColor()
obstacle.physicsBody = SKPhysicsBody(edgeLoopFromPath: obstacle.path!)
obstacle.physicsBody?.dynamic = false
obstacle.physicsBody?.affectedByGravity = false
obstacle.physicsBody?.contactTestBitMask = PhysicsCatagory.Ball
obstacle.physicsBody?.categoryBitMask = PhysicsCatagory.Obstacle
obstacle.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(obstacle)
obstacle.runAction(SKAction.moveBy(obstacleVector, duration: obstacleSpeed))
}
In a separate method, called endGame, I want to fade out all the obstacles currently in existence on the screen. All the obstacle objects are private, which makes accessing their properties difficult. If there is only one on the screen, I can usually access it by its name. However, when I say childNodeWithName("obstacleNode")?.runAction(SKAction.fadeAlphaBy(-1.0, duration: 1.0)), only one of the "obstacles" fades away; the rest remain completely opaque. Is there a good way of doing this? Thanks in advance (:
You could probably go with:
self.enumerateChildNodesWithName("obstacleNode", usingBlock: {
node, stop in
//do your stuff
})
More about this method can be found here.
In this example I assumed that you've added obstacles to the scene. If not, then instead of scene, run this method on obstacle's parent node.
And one side note...SKShapeNode is not performant solution in many cases because it requires at least one draw pass to be rendered by the scene (it can't be drawn in batches like SKSpriteNode). If using a SKShapeNode is not "a must" in your app, and you can switch them with SKSpriteNode, I would warmly suggest you to do that because of performance.
SpriteKit can render hundreds of nodes in a single draw pass if you are using same atlas and same blending mode for all sprites. This is not the case with SKShapeNodes. More about this here. Search SO about this topic, there are some useful posts about all this.

How do run an SKAction on a group of SKSpriteNodes with the same name?

I'm making a game in Xcode 6 using spriteKit and swift. I have a plane on scene, and to make it look like its moving, I'm create clouds off the scene to the left of the screen. I then us an SKAction to move the cloud to the right side of the screen. This works great. You then click to jump off the plane, and the plane moves up off the scene. I then have it start making the clouds on the bottom of the scene, then they move up off the top of the scene, but the problem is, the already existing clouds still have to move to the right side of the screen. My question is, how do I make it so all of the existing clouds stop their action that moves them to the right, then begin to move up exactly where they are? How do I access the group of existing clouds all at the same time once they have been created? I also want the clouds to slow down after you have jumped when you tap the screen to open your parachute, but this should be able to be done by ending the SKAction that moves the clouds, then using another SKAction on them that moves them up slower, but I don't know how to access a group of SKSpriteNodes.
Here is the code that I have to make the clouds:
//This is how the cloud is first declared at the top of the .swift file
var cloud = SKSpriteNode()
//This is the function that runs every certain interval through an NSTimer
func createCloud()
{
cloud = SKSpriteNode(imageNamed: "cloud")
cloud.xScale = 1.25
cloud.yScale = cloud.xScale/2
cloud.zPosition = -1
self.addChild(cloud)
if personDidJump == false
{
let moveRight = SKAction.moveToX(CGRectGetWidth(self.frame) + CGRectGetWidth(cloud.frame), duration: cloudSpeed)
var apple = CGRectGetWidth(self.frame)
var randomNumber:CGFloat = CGFloat(arc4random_uniform(UInt32(apple)))
cloud.position = CGPointMake(-CGRectGetWidth(cloud.frame), randomNumber)
cloud.runAction(moveRight)
}
if personDidJump == true
{
let moveUp = SKAction.moveToY(CGRectGetHeight(self.frame) + CGRectGetWidth(cloud.frame), duration: cloudSpeed)
var apple = CGRectGetWidth(self.frame)
var randomNumber:CGFloat = CGFloat(arc4random_uniform(UInt32(apple)))
cloud.position = CGPointMake(randomNumber, -CGRectGetHeight(cloud.frame))
cloud.runAction(moveUp)
}
}
Also, should I be worried about deleting the clouds when they move off the scene? Or can I just leave them there because you can't see them, and from what I've seen, they don't lag you.
Any help would be greatly appreciated. Thank you.
-Callum-
Put your clouds in an array. When your player jumps (or whenever you need to move them) run through the array and run your action on each cloud.
stackoverflow.com/a/24213529/3402095
^ That is where I found my answere ^
When you make a node give it a name:
myNode.name = "A Node Name"
self.addChild(myNode)
If there are a lot of these nodes, later you can change properties or preform SKAcions on these nodes or do whatever you want to do by using enumerateChildNodesWithName:
self.enumerateChildNodesWithName("A Node Name")
{
myNode, stop in
myNode.runAction(SKAction.moveToY(CGRectGetHeight(self.Frame), duration: 1)
}
I hope this is useful to whoever may need it.