swift iOS - Can't reset gameScene when character is off screen - ios

I've recently put the finishing touches to my first app however I've spotted a bug that is driving me crazy!
The object of the game is the character (a cat), jumps over obstacles but also has to avoid birds that fly through the air, the score increases every time a bird fly past without contact.
The game is over if contact is made when the cat makes contact with a bird or if the cat fails to jump over an obstacle and is pushed back to the left edge of the screen where a dummy contact node is placed.
Now the bug... everything works as expected however if the cat is in mid air when a bird makes contact the collision propels the cat off the visible screen, at this stage the player is then unable to reset the scene by tapping the screen, only if the cat is visible on the screen would you be able to reset the scene by tapping once and play again.
Here is all the code I could think of that may be useful, if anyone knows how this bug can be crushed please let me know.
override func didMoveToView(view: SKView) {
//details of the cat
cat = SKSpriteNode(texture: catTexture1)
cat.position = CGPoint(x: self.frame.size.width / 2.2, y: self.frame.size.height / 5.1 )
cat.runAction(run, withKey: "runningAction")
cat.physicsBody = SKPhysicsBody(circleOfRadius: cat.size.height / 2.0)
cat.physicsBody!.dynamic = true
cat.physicsBody!.allowsRotation = false
cat.physicsBody?.categoryBitMask = catCategory
cat.physicsBody?.collisionBitMask = crowCategory | worldCategory
cat.physicsBody?.contactTestBitMask = crowCategory | contact2Category
cat.physicsBody!.restitution = -10
moving.addChild(cat)
//contact node increases score when bird makes contact
var contact = SKSpriteNode(color: UIColor.clearColor(), size: CGSizeMake(1, 450))
contact.position = CGPoint(x: self.frame.size.width / 3.3, y: self.frame.size.height / 2.0 )
contact.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(1, 450))
contact.physicsBody!.dynamic = false
contact.physicsBody!.allowsRotation = false
contact.physicsBody?.categoryBitMask = scoreCategory
contact.physicsBody?.contactTestBitMask = crowCategory
self.addChild(contact)
//2nd contact node runs game over code
var contact2 = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(1, 450))
contact2.position = CGPoint(x: self.frame.size.width / 4.0, y: self.frame.size.height / 2.0 )
contact2.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(1, 450))
contact2.physicsBody!.dynamic = false
contact2.physicsBody!.allowsRotation = false
contact2.physicsBody?.categoryBitMask = contact2Category
contact2.physicsBody?.contactTestBitMask = catCategory
self.addChild(contact2)
//touch that initiates reset
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if ableToJump == true {
if (moving.speed > 0){
cat.physicsBody!.velocity = CGVectorMake(0, 0)
cat.physicsBody!.applyImpulse(CGVectorMake(0, 30))
} else if (canRestart) {
resetLabelNode.hidden = true
self.resetScene()
}
}
}

So I found the answer to this on a similar post, what they recommended was to set a frame round the visible screen. I just added the below code to didMoveToView
let frame = CGRectMake(255, 0, 515, self.frame.size.height)
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: frame)
I hope this helps someone in the future!

Related

SKSpriteNode fall off the screen

Can some body help me, I created a SpriteKit ​node called(player) and once I set its physics it fall off the screen​:
let player = SKSpriteNode(imageNamed: "car")
player.position.x = -300
player.zPosition = 1
player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size)
player.physicsBody.categoryBitMask = 1
addChild(player)
I already tried adding
affectedByGravity
but the same result for the player and the enemy.
If I got it, you want your car not to be effected by gravity.
If it is so you might want to setup the physics of your "world", like:
func setWorld() {
self.backgroundColor = UIColor.white
//This should do the trick!
physicsWorld.gravity = CGVector(dx:0, dy:0)
//This is useful if you want to create wall along the borders
let frame = CGRect(x: self.frame.minX, y: self.frame.minY, width: self.frame.width, height: self.frame.height)
//here you set the borders as walls
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
physicsBody?.friction = 0
physicsBody?.restitution = 1
physicsBody?.linearDamping = 0
}
and call this function in didMove

Image or sprites sometimes doesn't show while compile xCode project

I got a problem with my simple game. Every picture as Player, background, floor, coins, Score Text etc are set as a "add child" in function didMoveToView. It looks like this:
override func didMoveToView(view: SKView) {
balon.physicsBody = SKPhysicsBody(circleOfRadius: balon.size.width/2)
balon.physicsBody?.dynamic = true
balon.physicsBody?.categoryBitMask = physics.Balon
balon.physicsBody?.contactTestBitMask = physics.Moneta
balon.physicsBody?.collisionBitMask = physics.None
balon.physicsBody?.usesPreciseCollisionDetection = true
physicsWorld.gravity = CGVector(dx: 0,dy: 0)
physicsWorld.contactDelegate = self
/////////////////// MOVE COINS ///////////////////////////////////////
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addCoin),
SKAction.waitForDuration(1.0)
])
))
/////////////////// MOVE BOMB ///////////////////////////////////////
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addBomb),
SKAction.waitForDuration(1.0)
])
))
self.backround.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
self.runningBar.anchorPoint = CGPointMake(0, 0.5)
self.runningBar.position = CGPointMake(CGRectGetMinX(self.frame), CGRectGetMinY(self.frame))
self.orginalBarPosition = self.runningBar.position.x
self.MaxBarPos = self.runningBar.size.width - self.frame.size.width
self.MaxBarPos *= -1
self.balonBaseLine = self.runningBar.position.y + (self.runningBar.size.height/2) + (self.balon.size.height/2)
self.balon.position = CGPointMake(CGRectGetMidX(self.frame) - self.balon.size.width * 2, CGRectGetMidY(self.frame))
self.backround.anchorPoint = CGPointMake(0.5, 0.5)
self.originalBackgroundPosition = self.backround.position.x
self.maxBackgroundPosition = self.backround.size.width - self.frame.size.width * 1.11
self.maxBackgroundPosition *= -1
scoreLabel.text = "Score: 0"
scoreLabel.fontSize = 16
scoreLabel.fontColor = SKColor.blackColor();
scoreLabel.position = CGPoint(x: self.frame.width/2, y: self.frame.height/2)
addChild(scoreLabel)
addChild(backround)
addChild(runningBar)
addChild(balon) }
There is a problem.. Sometimes everything works fine but usually when I'm running my program something will not shows up. For example background and coins shows up but the player (Balon) doesn't, or sometimes Coins and Bombs shows up but the floor(movingBar) doesn't.
RunningBar is moving, it's declared in override func update(currentTime: NSTimeInterval) just like moving background (it's a sky). I don't understand why sometimes everything is OK and mostly is not OK...
I have no errors while compiling my project.
I would really like to understand what's wrong with this.
Thanks for any help!

Sprite Kit efficient random node spawn

I am searching for the most efficient way to Nodes with random SKSpriteNodes on given positions.
I have 10 Platforms, on each of them I want to have an Enemy (SKNode) that contains for 3 different SKSpriteNodes which will randomly spawn. These SKSpriteNodes all do other behaviors, thats why I can't simply change the Texture of the SKNode.
As with my Code, I wanted to spawn the same Node on different Platforms.
However, the code does not work properly, the nodes do spawn but only at 1 position(the position from the last SpawnEnemycode in didMoveToView), meaning 10 nodes at 1 position.
I also tried it with adding 10 different Enemy nodes, each given a Platform. But this just looks like a copy & paste code thats inefficient.
How can I fix my problem, or is there either another way to spawn them more efficiently?
My Code:
class GameScene: SKScene, SKPhysicsContactDelegate {
var Enemy: SKNode!
override func didMoveToView(view: SKView) {
/* Setup your scene here */
self.physicsWorld.contactDelegate = self
self.physicsBody?.velocity = CGVectorMake(0, 0)
self.anchorPoint = CGPoint(x: 0, y: 0.20)
backgroundColor = SKColor(red: 81.0/255.0, green: 192.0/255.0, blue: 201.0/255.0, alpha: 1.0)
self.Enemy1 = SKNode()
addChild(Enemy1)
SpawnPlayer()
SpawnEnemy(CGPoint(x: self.frame.size.width / 2, y: Platform1.position.y + 15))
SpawnEnemy(CGPoint(x: self.frame.size.width / 2, y: Platform2.position.y + 15))
//Same code for all the other Platforms.
}
func SpawnEnemy(position: CGPoint!){
switch (arc4random_uniform(3)){
case 0:
Picture1.filteringMode = .Nearest
Picture2.filteringMode = .Nearest
Animation1 = SKAction.animateWithTextures([Picture1,Picture2], timePerFrame: 0.5)
AnimationRepeat1 = SKAction.repeatActionForever(Animation1)
Sprite1 = SKSpriteNode (imageNamed: "Sprite1.png")
Sprite1.size = CGSize(width: 64, height: 64)
Sprite1.zPosition = 2
Sprite1.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 45, height: 50))
Sprite1.physicsBody?.dynamic = true
Sprite1.physicsBody?.allowsRotation = false
Sprite1.physicsBody?.restitution = 0.0
Sprite1.physicsBody?.usesPreciseCollisionDetection = true
Sprite1.physicsBody?.categoryBitMask = EnemyCategory
Sprite1.physicsBody?.collisionBitMask = PlayerCategory | Platform1Category
Sprite1.physicsBody?.contactTestBitMask = Wall1Category | Wall2Category | PlayerCategory
Sprite1.runAction(AnimationRepeat1)
Enemy.position = position
Enemy.addChild(Sprite1)
case 1:
Picture3.filteringMode = .Nearest
Picture4.filteringMode = .Nearest
Animation1 = SKAction.animateWithTextures([Picture3,Picture4], timePerFrame: 0.5)
AnimationRepeat1 = SKAction.repeatActionForever(Animation1)
Sprite2 = SKSpriteNode (imageNamed: "Sprite2.png")
Sprite2.size = CGSize(width: 64, height: 64)
Sprite2.zPosition = 2
Sprite2.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 45, height: 50))
Sprite2.physicsBody?.dynamic = true
Sprite2.physicsBody?.allowsRotation = false
Sprite2.physicsBody?.restitution = 0.0
Sprite2.physicsBody?.usesPreciseCollisionDetection = true
Sprite2.physicsBody?.categoryBitMask = EnemyCategory
Sprite2.physicsBody?.collisionBitMask = PlayerCategory | Platform1Category
Sprite2.physicsBody?.contactTestBitMask = Wall1Category | Wall2Category | PlayerCategory
Sprite2.runAction(AnimationRepeat1)
Enemy.position = position
Enemy.addChild(Sprite2)
case 2:
Picture5.filteringMode = .Nearest
Picture6.filteringMode = .Nearest
Animation1 = SKAction.animateWithTextures([Picture5,Picture6], timePerFrame: 0.5)
AnimationRepeat1 = SKAction.repeatActionForever(Animation1)
Sprite3 = SKSpriteNode (imageNamed: "Sprite3.png")
Sprite3.size = CGSize(width: 64, height: 64)
Sprite3.zPosition = 2
Sprite3.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 45, height: 50))
Sprite3.physicsBody?.dynamic = true
Sprite3.physicsBody?.allowsRotation = false
Sprite3.physicsBody?.restitution = 0.0
Sprite3.physicsBody?.usesPreciseCollisionDetection = true
Sprite3.physicsBody?.categoryBitMask = EnemyCategory
Sprite3.physicsBody?.collisionBitMask = PlayerCategory | Platform1Category
Sprite3.physicsBody?.contactTestBitMask = Wall1Category | Wall2Category | PlayerCategory
Sprite3.runAction(AnimationRepeat1)
Enemy.position = position
Enemy.addChild(Sprite3)
default:
return
}
}
The reason you're only seeing one position is that you're only creating one "enemy" and moving it around with SpawnEnemy. Since your sprites are all added to the same "enemy", they will all show up wherever your enemy was moved to last.
Let's chop down what your code is to the relevant bits:
var Enemy: SKNode!
override func didMoveToView(view: SKView) {
self.Enemy1 = SKNode()
addChild(Enemy1)
SpawnEnemy(CGPoint(x: self.frame.size.width / 2, y: Platform1.position.y + 15))
SpawnEnemy(CGPoint(x: self.frame.size.width / 2, y: Platform2.position.y + 15))
}
func SpawnEnemy(position: CGPoint!){
switch (arc4random_uniform(3)){
case 0:
Enemy.position = position
Enemy.addChild(Sprite1)
case 1:
Enemy.position = position
Enemy.addChild(Sprite2)
case 2:
Enemy.position = position
Enemy.addChild(Sprite3)
default:
return
}
}
So, in didMoveToView(), you create one Enemy node. Then, as you call SpawnEnemy, you do a bunch of stuff to add other sprites, but as far as Enemy is concerned, you're just changing its position. Since you're adding the sprites to the same node, when you move that Enemy node, the child sprites move with it, too.
You have two options to fix this. Either add your sprites to some higher-level node (like the scene), or create new "enemy" nodes for each of the sprites you intend to add. Either should work, but the best choice will depend on what you're trying to accomplish with them - the scope of that discussion is beyond what you're talking about here.

How to interact with a SKSpriteNode after another one of the same var name has been created?

I appologise for the confusing question title, but I wasn't quite sure how to phrase my question. My ultimate goal is to remove an SKSpriteNode a certain amount of time after it's creation. However, there is a small complication. Using the following code
func remove() {
laser.removeFromParent()
}
runAction(
SKAction.sequence([
SKAction.waitForDuration(5.0),
SKAction.runBlock(remove)
])
)
I'm able to remove the SKSpriteNode I named 'laser'. When I call my function once, fireLasers(), everything runs smoothly and the laser disappears after 5 seconds. The problem is when I call it twice within a period of 5 seconds. When this happens, the first laser will stick around indefinitely and the second disappears earlier than intended. I understand why this happens, but would like to know if there's a way around it. Here's the code for the GameScene which creates the world, background image, player Sprite, and defines the fireLasers function. There's a lot of stuff that goes on in the ViewController that works with the velocities and directions of the sprites, but I hope this is sufficient to find a solution.
import SpriteKit
class GameScene: SKScene {
var player = SKSpriteNode()
var world = SKShapeNode()
var worldTexture = SKSpriteNode()
var laser = SKShapeNode()
override func didMoveToView(view: SKView) {
self.anchorPoint = CGPointMake(0.5, 0.5)
self.size = CGSizeMake(view.bounds.size.width, view.bounds.size.height)
// Add world
world = SKShapeNode(rectOfSize: CGSizeMake(7000, 7000))
world.physicsBody = SKPhysicsBody(edgeLoopFromPath: world.path!)
world.fillColor = SKColor.blackColor()
self.addChild(world)
// Add Background Image
worldTexture = SKSpriteNode(imageNamed: "grid")
worldTexture.size = CGSize(width: 7000, height: 7000)
world.addChild(worldTexture)
// Add player
player = SKSpriteNode(imageNamed: "Spaceship")
player.size = CGSize(width: 50, height: 50)
player.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 50, height: 50))
player.physicsBody?.affectedByGravity = false
player.physicsBody?.dynamic = true
world.addChild(player)
}
override func update(currentTime: CFTimeInterval) {
world.position.x = -player.position.x
world.position.y = -player.position.y
}
func fireLasers() {
laser = SKShapeNode(rectOfSize: CGSizeMake(10, 50))
laser.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 10, height: 50))
laser.position.x = player.position.x
laser.position.y = player.position.y + CGFloat(50)
laser.fillColor = SKColor.redColor()
laser.physicsBody?.dynamic = true
laser.physicsBody?.affectedByGravity = false
world.addChild(laser)
func remove() {
laser.removeFromParent()
}
runAction(
SKAction.sequence([
SKAction.waitForDuration(5.0),
SKAction.runBlock(remove)
])
)
}
}
Instead of keeping a reference to the laser (which will, as you noticed, force you to only have one around at a time), why not run an action on the created laser nodes themselves? This will allow you to use the handy SKAction class method removeFromParent():
let newLaser = makeNewLaser()
let laserAction = SKAction.sequence([SKAction.waitForDuration(5), SKAction.removeFromParent()])
newLaser.runAction(laserAction)

SKSpriteNode ball not colliding with border

My ball (SKSpriteNode) is suppose to bounce of the wall that is around the screen, but it simply past through it, and disappeared from the screen.
GameScene.swift
let borderCategory: UInt32 = 1
let ball = SKShapeNode(circleOfRadius: 30)
let bCategoryName = "Ball"
let bCategory: UInt32 = 2
let paddle = SKShapeNode(rectOfSize: CGSize(width: 90, height: 35))
let pCategoryName = "Paddle"
let pCategory: UInt32 = 3
override func didMoveToView(view: SKView) {
/* Setup your scene here */
self.backgroundColor = SKColor.whiteColor()
self.physicsWorld.gravity = CGVectorMake(0, 0)
let borderBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
NSLog(String(self.frame.width) + ":" + String(self.frame.height), nil)
self.physicsBody = borderBody
self.physicsBody.friction = 0.0
self.physicsBody.categoryBitMask = borderCategory
self.physicsBody.restitution = 1.0
setupPaddle()
setupBall()
}
func setupPaddle() {
paddle.name = pCategoryName
paddle.position = CGPointMake(CGRectGetMidX(self.frame), paddle.frame.size.height * 0.6)
paddle.fillColor = SKColor.blackColor()
self.addChild(paddle)
paddle.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 90, height: 35))
paddle.physicsBody.restitution = 0.1
paddle.physicsBody.friction = 0.4
paddle.physicsBody.dynamic = false
paddle.physicsBody.categoryBitMask = pCategory
paddle.physicsBody.collisionBitMask = bCategory
}
func setupBall() {
ball.name = bCategoryName
ball.position = CGPointMake(self.frame.size.width/2, paddle.position.y+paddle.frame.height+10)
ball.fillColor = SKColor.blackColor()
self.addChild(ball)
ball.physicsBody = SKPhysicsBody(circleOfRadius: 30)
ball.physicsBody.friction = 0.0
ball.physicsBody.restitution = 1.0
ball.physicsBody.linearDamping = 0.0
ball.physicsBody.angularDamping = 0.0
ball.physicsBody.allowsRotation = false
ball.physicsBody.dynamic = true
ball.physicsBody.categoryBitMask = bCategory
ball.physicsBody.collisionBitMask = borderCategory
}
The output for NSLog(String(self.frame.width) + ":" + String(self.frame.height), nil)
is 1024.0:768.0, ran on iPhone 5S, IOS8.
How do i solve this problem? I believe that self.frame is the problem, but i already changed my the controller view from viewDidLoad to viewWillLayoutSubview.
EDIT 1: I noticed that the ball is now bouncing after setting the boundary's restitution to 1.0. BUT, I realised the ball is bouncing in an unknown region. It is bouncing out of the screen, then return back. The weird thing is it can bounce once it hit the top of the screen, but it won't hit side of the screen
EDIT 2: I realised another problem, UIDevice.currentDevice().orientation.isPortrait returns false, but yet I'm running my game in portrait mode
EDIT 3: If I changed orientation to landscape, the ball can escape out of the screen from the top and bottom by a little then bounce back, the ball bounces off the side of the screen correctly. But now the paddle is below the screen.

Resources