Spawning an Enemy - swift

In my game I want an enemy to spawn every 10 seconds. I attempt to accomplish this by, in the GameViewController, writing
var secondEnemyTimer = NSTimer.scheduledTimerWithTimeInterval(10.0, target: self, selector: "secondEnemyFunction", userInfo: nil, repeats: false)
in the viewWillLayoutSubviews method. Then in the secondEnemyFunction I write:
let skView = self.view as! SKView
let gameScene = GameScene(size: skView.bounds.size)
gameScene.enemy2Function()
Then in the enemy2Function in the GameScene class I write:
println("Called!")
enemy2.name = enemyCategoryName
enemy2.size.width = 57
enemy2.size.height = 57
let randomX = randomInRange(Int(CGRectGetMinX(self.frame)), hi: Int(CGRectGetMaxX(self.frame)))
let randomY = randomInRange(Int(CGRectGetMinY(self.frame)), hi: Int(CGRectGetMaxY(self.frame)))
let randomPoint = CGPoint(x: randomX, y: randomY)
enemy2.position = randomPoint
self.addChild(enemy2)
enemy2.physicsBody = SKPhysicsBody(circleOfRadius: enemy1.size.width / 2)
enemy2.physicsBody?.friction = 0
enemy2.physicsBody?.restitution = 1
enemy2.physicsBody?.linearDamping = 0
enemy2.physicsBody?.allowsRotation = false
enemy2.physicsBody?.applyImpulse(CGVectorMake(50, -50))
enemy2.physicsBody?.categoryBitMask = enemyCategory
In the log "Called!" appear yet the enemy is not spawned. Just so you know I did create the enemy at the top of the class by doing:
let enemy2 = SKSpriteNode(imageNamed: "enemy")
Does anyone know how I can spawn my second enemy? Thank you in advance!
-Vinny

You should keep things simple and just do everything inside GameScene. Another thing is to drop NSTimer and use SKAction to spawn enemies. NSTimer don't respect scene's paused state, so you can get into some trouble eventually. This is how you can spawn enemies using SKAction:
GameScene.swift:
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
generateEnemies()
}
func stopGeneratingCoins(){
if(self.actionForKey("spawning") != nil){removeActionForKey("spawning")}
}
func generateEnemies(){
if(self.actionForKey("spawning") != nil){return}
let timer = SKAction.waitForDuration(10)
//let timer = SKAction.waitForDuration(10, withRange: 3)//you can use withRange to randomize duration
let spawnNode = SKAction.runBlock {
var enemy = SKSpriteNode(color: SKColor.greenColor(), size:CGSize(width: 40, height:40))
enemy.name = "enemy" // name it, so you can access all enemies at once.
//spawn enemies inside view's bounds
let spawnLocation = CGPoint(x:Int(arc4random() % UInt32(self.frame.size.width - enemy.size.width/2) ),
y:Int(arc4random() % UInt32(self.frame.size.height - enemy.size.width/2)))
enemy.position = spawnLocation
self.addChild(enemy)
println(spawnLocation)
}
let sequence = SKAction.sequence([timer, spawnNode])
self.runAction(SKAction.repeatActionForever(sequence) , withKey: "spawning") // run action with key so you can remove it later
}
}
When it comes to positioning, I assumed that your scene already has the correct size. If scene is not correctly initialized and has different size (or more precisely, different aspect ratio) than a view, it could happen that enemy get off-screen position when spawned. Read more here on how to initialize the scene size properly.

Related

Part of my animation is not working in SpriteKit

init () {
super.init(texture: nil, color: .clear, size: initialSize)
// Create physicsBody based on a frame of the sprite
// basically giving it gravity and physics components
let bodyTexture = textureAtlas.textureNamed("MainCharacterFlying1")
self.physicsBody = SKPhysicsBody(texture: bodyTexture, size: self.size)
// Lose momentum quickly with high linear dampening
self.physicsBody?.linearDamping = 0.9
// weighs around 30 kg
self.physicsBody?.mass = 30
// no rotation
self.physicsBody?.allowsRotation = false
createAnimations()
self.run(soarAnimation, withKey: "soarAnimation")
}
// Animations to make the main character seem like it is flying
func createAnimations() {
let rotateUpAction = SKAction.rotate(byAngle: 0, duration: 0.475)
rotateUpAction.timingMode = .easeOut
let rotateDownAction = SKAction.rotate(byAngle: -1, duration: 0.8)
rotateDownAction.timingMode = .easeIn
let flyFrames: [SKTexture] = [textureAtlas.textureNamed("MainCharacterFlying1"), textureAtlas.textureNamed("MainCharacterFlying2"),textureAtlas.textureNamed("MainCharacterFlying3"), textureAtlas.textureNamed("MainCharacterFlying4"),textureAtlas.textureNamed("MainCharacterFlying5"),textureAtlas.textureNamed("MainCharacterFlying4"),textureAtlas.textureNamed("MainCharacterFlying3"),textureAtlas.textureNamed("MainCharacterFlying2")]
var flyAction = SKAction.animate(with: flyFrames, timePerFrame: 0.1)
flyAction = SKAction.repeatForever(flyAction)
flyAnimation = SKAction.group([flyAction,rotateUpAction])
let soarFrames: [SKTexture] = [textureAtlas.textureNamed("MainCharacterFlying5")]
var soarAction = SKAction.animate(with: soarFrames, timePerFrame: 1)
soarAction = SKAction.repeatForever(soarAction)
let soarAnimation = SKAction.group([soarAction,rotateDownAction])
}
When I run this code on the IOS Simulator, I have to click on the screen once in order for my main sprite to show up on the screen, or else it will not. And when I click on the sprite, the sprite will start flapping its wings and go up (I have other code for that) however, the rotateUpAction and rotateDownAction are not showing up at all on the Simulator. So I was wondering if there were any solutions and anyone willing to answer.
Thank you for your time. Also, this code from the class of the main character, the name of the class is "Player"
you declare let soarAnimation inside your createAnimations function, meaning it's not in scope when you call self.run(soarAnimation). Solution: declare soarAnimation as a class property. Alternate solution: have createAnimations() return the SKAction and grab it in init that way

Spawn SKSpriteNode objects at random times

I am making a game where I would like to spawn obstacles at random times. The obstacles are going to make contact with the player.
Here the code that I have:
var obstacle : SKSpriteNode?
func createObstacles() {
let obstaclesArray = ["obstacle_1", "obstacle_2", "obstacle_3", "obstacle_4", "obstacle_5"]
let randomObs = obstaclesArray.randomElement()
let selectedTexture = SKTexture(imageNamed: randomObs!)
obstacle = SKSpriteNode(imageNamed: randomObs!)
obstacle?.physicsBody = SKPhysicsBody(texture: selectedTexture, size: selectedTexture.size())
obstacle?.position = CGPoint(x: scene!.size.width/2 + selectedTexture.size().width, y: -120)
obstacle?.zPosition = -15
if let obs = obstacle?.physicsBody {
obs.affectedByGravity = true
obs.allowsRotation = false
obs.isDynamic = true
obs.restitution = 0
obs.categoryBitMask = obstacleCategory
obs.collisionBitMask = floorCategory
obs.contactTestBitMask = heroCategory
}
addChild(obstacle!)
}
func spawnObstacles() {
let wait = SKAction.wait(forDuration: 1, withRange: 0.4)
let spawn = SKAction.run {
createObstacles()
}
let moveLeft = SKAction.moveBy(x: -scene!.size.width - obstacle.size.width - 10, y: 0, duration: 2)
let sequence = SKAction.sequence([wait, spawn, moveLeft, SKAction.removeFromParent()])
self.run(SKAction.repeatForever(sequence))
}
I read some a similar questions but the response is my same code but it is not working.
Spawning a Spritekit node at a random time
I also tried other way:
var randDelay = Double.random(in: 0.7 ..< 1.4)
DispatchQueue.main.asyncAfter(deadline: randDelay, execute: {
if self.canCreateObstacle == true {
self.spawnObstacle()
}})
But it is not working and everytime I restart the game it seems like the function is being called two times, if I restart the game a third time it is called 3 times and so on.
Anyone with a good and clean solution to spawn objects at random times?
Do not use DispatchQueue Set up a repeating sequential action using wait(forDuration:withRange:) like you previously had.
https://developer.apple.com/documentation/spritekit/skaction/1417760-wait
First, create a generic node used to spawn obstacles, then attach this generic node to the scene.
Finally assign the repeating sequential action to this node.
Boom, you are done.
The reason why you want to assign it to a random node is because you want to be able to give your game the opportunity to stop generating obstacles, plus you alter the speed property to make the node generate nodes faster or slower.
You also want to detach the spawning/waiting from the moving/destroying, because as of right now, your code is confused. You are saying move the scene left for 2 seconds, then wait a random amount of time to spawn the next enemy, but I think you are trying to just spawn enemies on a time interval and move the enemy to the left.
Your scene code should look something like this
class GameScene : SKScene{
let obstacleGenerator = SKNode()
func didMove(to: view){
let wait = SKAction.wait(forDuration: 1, withRange: 0.4)
let spawn = SKAction.run({createObstacles()})
let sequence = SKAction.sequence([wait, spawn])
obstacleGenerator.run(SKAction.repeatForever(sequence))
addChild(obstacleGenerator)
}
func createObstacles() {
let obstaclesArray = ["obstacle_1", "obstacle_2", "obstacle_3", "obstacle_4", "obstacle_5"]
let randomObs = obstaclesArray.randomElement()
let selectedTexture = SKTexture(imageNamed: randomObs!)
obstacle = SKSpriteNode(imageNamed: randomObs!)
obstacle.position = CGPoint(x: scene!.size.width/2 + selectedTexture.size().width, y: -120)
obstacle.zPosition = -15
let body = SKPhysicsBody(texture: selectedTexture, size: selectedTexture.size())
body.affectedByGravity = true
body.allowsRotation = false
body.isDynamic = true
body.restitution = 0
body.categoryBitMask = obstacleCategory
body.collisionBitMask = floorCategory
body.contactTestBitMask = heroCategory
obstacle.physicsBody = body
addChild(obstacle!)
let moveLeft = SKAction.moveBy(x: -scene!.size.width - obstacle.size.width - 10, y: 0, duration: 2)
let seq = SKAction.sequence([moveLeft,SKAction.removeFromParent()])
obstacle.run(seq)
}
}
Now as for your spawning increasing with each reset, you never post how you are resetting. I am going to assume you never remove the previous action upon reset, and this is why your rate increases. It is always better to just create a new GameScene instance when doing a reset.

Inconsistent contact detection in Swift 3 using SpriteKit

I'm having an issue with contact detection in Swift 3 using SpriteKit. The contact detection is working...sometimes. It seems purely random as to when it fires and when it doesn't.
I have a yellow "bullet" that moves up on the screen to hit a red sprite named targetSprite. The desired behavior is to have the bullet removed when it hits the target, but sometimes it just passes through underneath.
I've found many questions about contact detection not working at all, but I haven't found any dealing with inconsistent detection.
What can I do to fix this?
Here's the code:
import SpriteKit
import GameplayKit
enum PhysicsCategory:UInt32 {
case bullet = 1
case sprite1 = 2
case targetSprite = 4
// each new value should double the previous
}
class GameScene: SKScene, SKPhysicsContactDelegate {
// Create sprites
let sprite1 = SKSpriteNode(color: SKColor.blue, size: CGSize(width:100,height:100))
let targetSprite = SKSpriteNode(color: SKColor.red, size: CGSize(width:100,height:100))
let bullet = SKSpriteNode(color: SKColor.yellow, size: CGSize(width: 20, height: 20))
// show the bullet?
var isShowingBullet = true
// Timers
//var timer:Timer? = nil
var fireBulletTimer:Timer? = nil
// set up bullet removal:
var bulletShouldBeRemoved = false
let bulletMask = PhysicsCategory.bullet.rawValue
override func didMove(to view: SKView) {
// Physics
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.centerRect.size)
targetSprite.physicsBody?.affectedByGravity = false
bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.centerRect.size)
bullet.physicsBody?.affectedByGravity = false
// Contact Detection:
targetSprite.physicsBody?.categoryBitMask = PhysicsCategory.targetSprite.rawValue
targetSprite.physicsBody?.contactTestBitMask =
//PhysicsCategory.sprite1.rawValue |
PhysicsCategory.bullet.rawValue
targetSprite.physicsBody?.collisionBitMask = 0 // no collision detection
// bullet physics
bullet.physicsBody?.categoryBitMask = PhysicsCategory.bullet.rawValue
bullet.physicsBody?.contactTestBitMask =
PhysicsCategory.targetSprite.rawValue
bullet.physicsBody?.collisionBitMask = 0 // no collision detection
// execute once:
fireBulletTimer = Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(self.fireBullet),
userInfo: nil,
repeats: false)
// Add sprites to the scene:
self.addChild(sprite1)
self.addChild(bullet)
self.addChild(targetSprite)
// Positioning
targetSprite.position = CGPoint(x:0, y:300)
// Note: bullet and sprite1 are at 0,0 by default
// Delegate
self.physicsWorld.contactDelegate = self
}
func didBegin(_ contact: SKPhysicsContact) {
print("didBegin(contact:))")
//let firstBody:SKPhysicsBody
// let otherBody:SKPhysicsBody
// Use 'bitwise and' to see if both bits are 1:
if contact.bodyA.categoryBitMask & bulletMask > 0 {
//firstBody = contact.bodyA
//otherBody = contact.bodyB
print("if contact.bodyA....")
bulletShouldBeRemoved = true
}
else {
//firstBody = contact.bodyB
//otherBody = contact.bodyA
print("else - if not contacted?")
}
/*
// Find the type of contact:
switch otherBody.categoryBitMask {
case PhysicsCategory.targetSprite.rawValue: print(" targetSprite hit")
case PhysicsCategory.sprite1.rawValue: print(" sprite1 hit")
case PhysicsCategory.bullet.rawValue: print(" bullet hit")
default: print(" Contact with no game logic")
}
*/
} // end didBegin()
func didEnd(_ contact: SKPhysicsContact) {
print("didEnd()")
}
func fireBullet() {
let fireBulletAction = SKAction.move(to: CGPoint(x:0,y:500), duration: 1)
bullet.run(fireBulletAction)
}
func showBullet() {
// Toggle to display or not, every 1 second:
if isShowingBullet == true {
// remove (hide) it:
bullet.removeFromParent()
// set up the toggle for the next call:
isShowingBullet = false
// debug:
print("if")
}
else {
// show it again:
self.addChild(bullet)
// set up the toggle for the next call:
isShowingBullet = true
// debug:
print("else")
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if bulletShouldBeRemoved {
bullet.removeFromParent()
}
}
}
Sorry for the inconsistent indentation, I can't seem to find an easy way to do this...
EDIT:
I have found that using 'frame' instead of 'centerRect' makes the collision area the size of the sprite. For example:
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.centerRect.size)
should be:
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.frame.size)
First advice - Do not use NSTimer (aka Timer) in SpriteKit. It is not paired with a game loop and can cause different issues in a different situations. Read more here ( answer posted by LearnCocos2D)
So, do this:
let wait = SKAction.wait(forDuration: 1)
run(wait, completion: {
[unowned self] in
self.fireBullet()
})
What I have noticed is that if I run your code in Simulator, I get the behaviour you have described. didBegin(contact:) is being fired randomly. Still, this is not happening on a device for me, and device testing is what matters.
Now, when I have removed Timer and did the same thing with SKAction(s) everything worked, means contact were detected every time.
Have you tried adding
.physicsBody?.isDynamic = true
.physicsBody?.usesPreciseCollisionDetrction =true
SpriteKit physics engine will calculate collision correctly if you do following:
1) set "usesPreciseCollisionDetection" property to true for bullet's physics body. This will change collision detection algorithm for this body. You can found more information about this property here, chapter "Working with Collisions and Contacts".
2) move your bullet using applyImpulse or applyForce methods. Collision detection will not woking correctly if you move body by changing it's position manually. You can find more information here, chapter "Making Physics Bodies Move".

SpriteKit having trouble removing nodes from scene

I have a method that creates a object that moves across the screen, and i run this method a lot of times to produce a lot of objects, but what i can't do now is remove them when i need to. I've tried
childNodeWithName("monster")?.removeFromParent()
but that doesn't work, they still complete their action. This is the method
func spawn() {
let ran = Int(arc4random_uniform(1400));
var monster = SKSpriteNode(imageNamed: "spike")
monster = SKSpriteNode(texture: text)
monster.position = CGPoint(x: ran, y: 800);
monster.zPosition = 1;
monster.physicsBody = SKPhysicsBody(texture: text, size: text.size())
monster.physicsBody?.categoryBitMask = PhysicsCategory.Monster
monster.physicsBody?.contactTestBitMask = PhysicsCategory.Player
monster.physicsBody?.collisionBitMask = 0
monster.physicsBody?.dynamic = false
monster.name = "monster"
self.addChild(monster);
let move = SKAction.moveTo(CGPointMake(monster.position.x, -100), duration: 1.5);
let remove = SKAction.runBlock { () -> Void in
monster.removeFromParent()
self.score += 1
}
monster.runAction(SKAction.sequence([move,remove]))
}
How can i remove every "monster" node at once when i need to?
To remove every monster node at once you can use SKNode's enumerateChildNodesWithName:usingBlock: method, like this:
self.enumerateChildNodesWithName("monster") {
node, stop in
node.removeAllActions()
node.removeFromParent()
}
Here, self is a scene because you've added monsters to the scene. If you for example added monsters to some container node, then you should run this method on that node, eg. containerNode.enumerateChildNodesWithName("monster"){...}

Detect if a SKSpriteNode left the screen at the bottom and call a function

I have tried to find a way to detect if a SKSpriteNode left the screen (I would like to call a Game Over function).
I have declared the node within a function (It is not global if I get that right?) and made and SKAction that moves the Node out of the screen and removes it afterwards.
This is what I came up with:
var node = SKSpriteNode()
let nodeTexture = SKTexture(imageNamed: "node")
nodeTexture.filteringMode = .Nearest
node = SKSpriteNode(texture: nodeTexture)
let nodeFalling = SKAction.moveToY(-70, duration: 1.6)
let nodeRemoving = SKAction.removeFromParent()
node.runAction(SKAction.sequence([nodeFalling, nodeRemoving]))
self.addChild(node)
Now what I need is to call GameOver() if the node left the screen.
I am very thankful for every answer!
In your scene you have to remember the reference for node you want to check and then in update method you just do the following:
if node.position.y < -node.size.height/2.0 {
node.removeFromParent()
gameOver()
}
Edit:
class MyScene: SKScene {
// ....
//here is the list of nodes which you want to check
var nodesToCheck = [SKSpriteNode]()
//here is your spawn function
func spawnNewNode() {
var node = SKSpriteNode()
let nodeTexture = SKTexture(imageNamed: "node")
nodeTexture.filteringMode = .Nearest
node = SKSpriteNode(texture: nodeTexture)
let nodeFalling = SKAction.moveToY(-70, duration: 1.6)
let nodeRemoving = SKAction.removeFromParent()
node.runAction(SKAction.sequence([nodeFalling, nodeRemoving]))
self.addChild(node)
nodesToCheck.append(node)
}
//and here is the update method
override func update(currentTime: NSTimeInterval) {
super.update(currentTime)
// ... every other update logic
for node in nodesToCheck {
if node.position.y < -node.size.height/2.0 {
node.removeFromParent()
gameOver()
}
}
}
func gameOver() {
println("Damn!")
}
}
Dont forget to remove your node from nodeToCheck array when they are no longer scene members.