I'm creating a game that involves a timer that spawns objects every second that fall down the screen. To gain points you must catch the objects.
I want the rate of spawning to increase once the player reaches a certain amount of points.
I have tried to do this by assigning the interval of the timer an integer (SpeedNumber) value of 1 second to start with and have created and if statement that is supposed to change that integer to 0.5 once the player reaches a certain amount of points. This makes sense to me but it is not working.
Why is this not working and what should I change?
import SpriteKit
struct physicsCatagory {
static let person : UInt32 = 0x1 << 1
static let Ice : UInt32 = 0x1 << 2
static let IceTwo : UInt32 = 0x1 << 3
static let IceThree : UInt32 = 0x1 << 4
static let Score : UInt32 = 0x1 << 5
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var scorenumber = Int()
var lifenumber = Int()
var SpeedNumber : Double = 0.5
var person = SKSpriteNode(imageNamed: "Person")
let Score = SKSpriteNode()
var ScoreLable = SKLabelNode()
override func didMoveToView(view: SKView) {
self.scene?.backgroundColor = UIColor.purpleColor()
physicsWorld.contactDelegate = self
self.scene?.size = CGSize(width: 640, height: 1136)
lifenumber = 0
SpeedNumber = 1
Score.size = CGSize(width: 648, height: 1)
Score.position = CGPoint(x: 320, y: -90)
Score.physicsBody = SKPhysicsBody(rectangleOfSize: Score.size)
Score.physicsBody?.affectedByGravity = false
Score.physicsBody?.dynamic = false
Score.physicsBody?.categoryBitMask = physicsCatagory.Score
Score.physicsBody?.collisionBitMask = 0
Score.physicsBody?.contactTestBitMask = physicsCatagory.IceThree
Score.color = SKColor.blueColor()
self.addChild(Score)
person.position = CGPointMake(self.size.width/2, self.size.height/12)
person.setScale(0.4)
person.physicsBody = SKPhysicsBody(rectangleOfSize: person.size)
person.physicsBody?.affectedByGravity = false
person.physicsBody?.categoryBitMask = physicsCatagory.person
person.physicsBody?.contactTestBitMask = physicsCatagory.Ice
person.physicsBody?.collisionBitMask = physicsCatagory.Ice
person.physicsBody?.dynamic = false
ScoreLable.position = CGPoint(x: self.frame.width / 2, y: 1000)
ScoreLable.text = "\(scorenumber)"
ScoreLable.fontColor = UIColor.yellowColor()
ScoreLable.fontSize = 100
ScoreLable.fontName = "Zapfino "
self.addChild(ScoreLable)
var IceThreeTimer = NSTimer.scheduledTimerWithTimeInterval(SpeedNumber, target: self, selector: ("spawnThirdIce"), userInfo: nil, repeats: true)
self.addChild(person)
}
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA
let secondBody = contact.bodyB
if firstBody.categoryBitMask == physicsCatagory.person && secondBody.categoryBitMask == physicsCatagory.IceThree || firstBody.categoryBitMask == physicsCatagory.IceThree && secondBody.categoryBitMask == physicsCatagory.person{
scorenumber++
if scorenumber == 5 {
SpeedNumber = 0.5
}
ScoreLable.text = "\(scorenumber)"
CollisionWithPerson(firstBody.node as! SKSpriteNode, Person: secondBody.node as! SKSpriteNode)
}
if firstBody.categoryBitMask == physicsCatagory.Score && secondBody.categoryBitMask == physicsCatagory.IceThree ||
firstBody.categoryBitMask == physicsCatagory.IceThree && secondBody.categoryBitMask == physicsCatagory.Score{
lifenumber++
if lifenumber == 3{
self.view?.presentScene(EndScene())
}
//self.view?.presentScene(EndScene())
}
}
func CollisionWithPerson (Ice: SKSpriteNode, Person: SKSpriteNode){
Person.removeFromParent()
}
func spawnThirdIce(){
var Ice = SKSpriteNode(imageNamed: "Ice")
Ice.setScale(0.9)
Ice.physicsBody = SKPhysicsBody(rectangleOfSize: Ice.size)
Ice.physicsBody?.categoryBitMask = physicsCatagory.IceThree
Ice.physicsBody?.contactTestBitMask = physicsCatagory.person | physicsCatagory.Score
Ice.physicsBody?.affectedByGravity = false
Ice.physicsBody?.dynamic = true
let MinValue = self.size.width / 8
let MaxValue = self.size.width - 20
let SpawnPoint = UInt32(MaxValue - MinValue)
Ice.position = CGPoint(x: CGFloat(arc4random_uniform(SpawnPoint)), y: self.size.height)
self.addChild(Ice)
let action = SKAction.moveToY(-85, duration: 2.5)
let actionDone = SKAction.removeFromParent()
Ice.runAction(SKAction.sequence([action,actionDone]))
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
person.position.x = location.x
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
person.position.x = location.x
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
You could do what you want as follows:
First declare 2 properties (in your class but outside all the function definitions)
var timeOfLastSpawn: CFTimeInterval = 0.0
var timePerSpawn: CFTimeInterval = 1.0
Then, in Update, check to see if the timePerSpawn has been exceeded. If so, call your spawn process which spawns new objects and then reset the time since the last spawn:
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if (currentTime - timeOfLastSpawn > timePerSpawn) {
spawnObject()
self.timeOfLastSpawn = currentTime
}
}
func spawnObject() {
// Your spawn code here
}
The advantage of making the spawn process a separate function is that you can call it from didMoveToView or any other place to spawn objects outside of the normal time-controlled cycle.
You can change the value of timePerSpawn as necessary to control the rate at which objects are spawned.
You could also look into creating an SKAction that runs spawnObject at specified time intervals, but I think to change the rate at which objects are spawned, you'll have to delete and re-create the SKAction, but your could do this in a setter for timePerSpawn.
You shouldn't really use NSTimer is SpriteKit, as the SpriteKit engine will be unaware of what the timer is doing and can't control it (one example is that the timer keeps running if the set the scene to paused).
You should move your declaration of var IceThreeTimer to the class level (outside of the method, right where you declare SpeedNumber. This will make sure that your handle to the pointer will be available in both the didMoveToView and didBeginContact methods.
In didMoveToView you change your declaration to:
// I removed the "var"
IceThreeTimer = NSTimer.scheduledTimerWithTimeInterval(SpeedNumber, target: self, selector: ("spawnThirdIce"), userInfo: nil, repeats: true)
Then in didBeginContact you modify:
if scorenumber == 5 {
SpeedNumber = 0.5
// Stop the already running timer
IceThreeTimer.invalidate()
// Schedule a new timer
IceThreeTimer = NSTimer.scheduledTimerWithTimeInterval(SpeedNumber, target: self, selector: ("spawnThirdIce"), userInfo: nil, repeats: true)
}
Related
Create enemy
touchesBegan and didBegin contact function
My enemy node is not being removed from the scene every time my sword node touches it. I'm just wondering if anyone could explain to me what I'm doing wrong?
(UPDATE BELOW)
import SpriteKit
import GameplayKit
import AVFoundation
class LevelTwo: SKScene, SKPhysicsContactDelegate{
var levelBg = SKSpriteNode(imageNamed: "level2")
var hero = SKSpriteNode()
var enemy = SKSpriteNode()
var sword = SKSpriteNode()
var health1 = SKSpriteNode(imageNamed: "playerhplv2")
var health2 = SKSpriteNode(imageNamed: "playerhplv2")
var health3 = SKSpriteNode(imageNamed: "playerhplv2")
var musicPath = URL(fileURLWithPath: Bundle.main.path(forResource: "gameMusic", ofType: "mp3")!)
var musicGamePlayer = AVAudioPlayer()
var runMonster = SKAction()
var waitMonster = SKAction()
var sequenceMonster = SKAction()
var repeatMonster = SKAction()
enum CollisionNum: UInt32{
case swordNum = 1
case enemyNum = 2
case playerNum = 4
}
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
///music
do{
musicGamePlayer = try AVAudioPlayer(contentsOf: musicPath)
musicGamePlayer.prepareToPlay()
musicGamePlayer.numberOfLoops = -1
musicGamePlayer.play()
}
catch{
print(error)
}
//bg
levelBg.position = CGPoint(x: 0, y: 0)
levelBg.zPosition = 1
levelBg.size = levelBg.texture!.size()
levelBg.setScale(1.25)
self.addChild(levelBg)
//hero
let playerTexture = SKTexture(imageNamed: "main")
hero = SKSpriteNode(texture: playerTexture)
hero.position = CGPoint(x: 0, y: 0)
hero.zPosition = 2
hero.setScale(0.6)
hero.physicsBody = SKPhysicsBody(texture: playerTexture, size: CGSize(width: hero.size.width, height: hero.size.height))
hero.physicsBody!.categoryBitMask = CollisionNum.playerNum.rawValue
hero.physicsBody!.collisionBitMask = CollisionNum.enemyNum.rawValue //player is allowed to bump into rocks and skulls
hero.physicsBody!.contactTestBitMask = CollisionNum.enemyNum.rawValue // same as collisions
hero.physicsBody!.isDynamic = false
self.addChild(hero)
//health1
health1.position = CGPoint(x: 130, y: 150)
health1.zPosition = 3
health1.setScale(0.75)
self.addChild(health1)
//health2
health2.position = CGPoint(x: 230, y: 150)
health2.zPosition = 3
health2.setScale(0.75)
self.addChild(health2)
//health3
health3.position = CGPoint(x: 320, y: 150)
health3.zPosition = 3
health3.setScale(0.75)
self.addChild(health3)
runMonster = SKAction.run(addMonster)
waitMonster = SKAction.wait(forDuration: 0.3)
sequenceMonster = SKAction.sequence([runMonster,waitMonster])
repeatMonster = SKAction.repeatForever(sequenceMonster)
run(repeatMonster)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let locale = touch.location(in: self)
hero.position.x = locale.x
hero.position.y = locale.y
}
}
func addMonster(){
//random position based off the bg size
let monsterHigherX = Int(levelBg.size.width)
let monsterHigherY = Int(levelBg.size.height)
let monsterLowerX = monsterHigherX * -1
let monsterLowerY = monsterHigherY * -1
let randomLocaleX = Int(arc4random_uniform(UInt32(monsterHigherX - monsterLowerX))) + monsterLowerX
let randomLocaleY = Int(arc4random_uniform(UInt32(monsterHigherY - monsterLowerY))) + monsterLowerY
let movementEnemy = SKAction.moveBy(x: -5, y: -5, duration: 0.2)
let movementForever = SKAction.repeatForever(movementEnemy)
let enemyTexture = SKTexture(imageNamed: "boss0")
enemy = SKSpriteNode(texture: enemyTexture)
enemy.zPosition = 2
enemy.setScale(0.5)
enemy.position = CGPoint(x: randomLocaleX, y: randomLocaleY)
enemy.physicsBody = SKPhysicsBody(texture: enemyTexture, size: CGSize(width: enemy.size.width, height: enemy.size.height))
enemy.physicsBody!.isDynamic = true
enemy.physicsBody!.affectedByGravity = false
enemy.physicsBody!.categoryBitMask = CollisionNum.enemyNum.rawValue
enemy.physicsBody!.collisionBitMask = CollisionNum.swordNum.rawValue
enemy.physicsBody!.contactTestBitMask = CollisionNum.swordNum.rawValue
enemy.run(movementForever)
self.addChild(enemy)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let swordTexture = SKTexture(imageNamed: "blade-0")
sword = SKSpriteNode(texture: swordTexture)
sword.setScale(0.50)
sword.zPosition = 2
sword.position = hero.position
sword.physicsBody = SKPhysicsBody(texture: swordTexture, size: CGSize(width: sword.size.width, height: sword.size.height))
sword.physicsBody!.velocity = CGVector(dx: 1200, dy:0)
sword.physicsBody!.isDynamic = true
sword.physicsBody!.affectedByGravity = true
sword.physicsBody!.usesPreciseCollisionDetection = true
sword.physicsBody!.categoryBitMask = CollisionNum.swordNum.rawValue
sword.physicsBody!.collisionBitMask = CollisionNum.enemyNum.rawValue
sword.physicsBody!.contactTestBitMask = CollisionNum.enemyNum.rawValue
self.addChild(sword)
}
func didBegin(_ contact: SKPhysicsContact) {
let collision: UInt32 = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collision == CollisionNum.swordNum.rawValue | CollisionNum.enemyNum.rawValue {
enemy.removeFromParent()
}
}
override func update(_ currentTime: TimeInterval) {
}
}
(Ive attached my whole level2 class) Thank you so much for the suggestion; however, when I tried implementing this I still run into the same problem (im running this on the iphone simulator) Im wondering whether the error is with my enum or my implementation of my physics with my nodes
You do not want to remove "enemy" because "enemy" is always the last monster you added. You need to check which contactBody is the enemy so you can remove it. You can do that by guaranteeing which node you want to associate as A, and which you want to associate as B by looking at the categoryBitMask value:
func didBegin(_ contact: SKPhysicsContact) {
//This guarantees the lower categoryBitMask (Providing you are only using one) is in A
let bodyA = contact.bodyA.categoryBitMask <= contact.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
let bodyB = contact.bodyA.categoryBitMask <= contact.bodyB.categoryBitMask ? contact.bodyB : contact.bodyA
if bodyA.categoryBitMask == CollisionNum.swordNum.rawValue && bodyB.categoryBitMask == CollisionNum.enemyNum.rawValue {
bodyB.node.removeFromParent()
}
}
Of course this will lead to problems with multiple collisions, so instead you may want to do:
var removeNodes = SKNode()
func didBegin(_ contact: SKPhysicsContact) {
//This guarantees the lower categoryBitMask (Providing you are only using one) is in A
let bodyA = contact.bodyA.categoryBitMask <= contact.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
let bodyB = contact.bodyA.categoryBitMask > contact.bodyB.categoryBitMask ? contact.bodyB : contact.bodyA
if bodyA.categoryBitMask == CollisionNum.swordNum.rawValue && bodyB.categoryBitMask == CollisionNum.enemyNum.rawValue {
bodyB.node.moveToParent(removeNodes)
}
}
func didFinishUpdate(){
removeNodes.removeAllChildren()
}
Try this:
func didBegin(_ contact: SKPhysicsContact) {
let collision: UInt32 = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collision == CollisionNum.swordNum.rawValue | CollisionNum.enemyNum.rawValue {
enemy.removeFromParent()
}
}
You were only testing if bodyA is equal to the enemy, however, bodyA may be equal to the sword instead.
I am trying to find a way to shoot more than one bullets as power up increases. Also it's only going up straight on the first 4 power ups, but I would like it to have a little angle as it reaches 5 and up.
Can someone help me implement that with the following codes I currently have?
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var player: SKSpriteNode!
var scoreLabel: SKLabelNode!
var score: Int = 0 {
didSet {
scoreLabel.text = "Score: \(score)"
}
}
var gameTimer: Timer!
var possibleAliens = ["alien", "alien2", "alien3"]
//bitmask for alien and torpedo's physics body
let alienCategory:UInt32 = 0x1 << 1
let photonTorpedoCategory:UInt32 = 0x1 << 0
//lives
var livesArray:[SKSpriteNode]!
//powerUp
var powerUp: Int = 1
//didMove
override func didMove(to view: SKView) {
addLives()
starField = SKEmitterNode(fileNamed: "Starfield")
starField.position = CGPoint(x: 0, y: 1472)
starField.advanceSimulationTime(10)
self.addChild(starField)
starField.zPosition = -1
player = SKSpriteNode(imageNamed: "shuttle")
player.position = CGPoint(x: self.frame.size.width / 3.6, y: player.size.height / 2 + 20)
self.addChild(player)
//physicsWorld
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.physicsWorld.contactDelegate = self
//score and scoreLabel
scoreLabel = SKLabelNode(text: "Score: 0")
scoreLabel.position = CGPoint(x: 80, y: self.frame.size.height - 60)
scoreLabel.fontName = "AmericanTypewriter-Bold"
scoreLabel.fontSize = 28
scoreLabel.fontColor = UIColor.white
score = 0
self.addChild(scoreLabel)
//create a timeInterval that can be changed depending on the difficulty
var timeInterval = 0.6
if UserDefaults.standard.bool(forKey: "hard"){
timeInterval = 0.2
}
//gameTimer
gameTimer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(addAlien), userInfo: nil, repeats: true)
//motion Manager initialization in didMove
motionManger.accelerometerUpdateInterval = 0.2
//creatingan acceleration data in didMove
motionManger.startAccelerometerUpdates(to: OperationQueue.current!) { (data: CMAccelerometerData?, error: Error?) in
if let accelerometerData = data{
let acceleration = accelerometerData.acceleration
self.xAcceleration = CGFloat(acceleration.x) * 0.75 + self.xAcceleration * 0.25
}
}
}
//func addLives
func addLives() {
//initialize livesArray from GameScene
livesArray = [SKSpriteNode]()
for live in 1 ... 3 {
let liveNode = SKSpriteNode(imageNamed: "shuttle")
liveNode.name = "live\(live)"
liveNode.position = CGPoint(x: self.frame.size.width - CGFloat((4-live)) * liveNode.size.width, y: self.frame.size.height - 60)
self.addChild(liveNode)
livesArray.append(liveNode)
}
}
//func addAlien
func addAlien() {
//using GK, pick possibleAliens[arrays] randomly, and shuffle
possibleAliens = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: possibleAliens) as! [String]
//bring the aliens to random position
let alien = SKSpriteNode(imageNamed: possibleAliens[0])
let randomAlienPosition = GKRandomDistribution(lowestValue: 0, highestValue: 414)
//make the position constant, use randomAlien and get next integer and use CGFloat then set alien position
let position = CGFloat(randomAlienPosition.nextInt())
alien.position = CGPoint(x: position, y: self.frame.size.height + alien.size.height)
//physicsBody of addAlien
alien.physicsBody = SKPhysicsBody(rectangleOf: alien.size)
alien.physicsBody?.isDynamic = true
alien.physicsBody?.categoryBitMask = alienCategory
alien.physicsBody?.contactTestBitMask = photonTorpedoCategory
alien.physicsBody?.collisionBitMask = 0
self.addChild(alien)
//make aliens move
let animationDuration: TimeInterval = 6
//SKAction to alien will make alien move from top to bottom of the screen, then remove alien from screen and from parent so it doesnt consume too much memory
var actionArray = [SKAction]()
actionArray.append(SKAction.move(to: CGPoint(x: position, y: -alien.size.height), duration: animationDuration))
//THIS ACTION WILL SEE IF IT REACHES THE FINAL DESTINATION BEFORE IT GETS ERASED AND TAKES A LIFE
//A RUN ACTION THAT WILL PLAY SOUND WHEN PLAYER LOSES SOUND.
actionArray.append(SKAction.run{
self.run(SKAction.playSoundFileNamed("loose.mp3", waitForCompletion: false))
if self.livesArray.count > 0 {
let liveNode = self.livesArray.first
liveNode!.removeFromParent()
self.livesArray.removeFirst()
if self.livesArray.count == 0{
let transition = SKTransition.flipHorizontal(withDuration: 0.5)
let gameOver = SKScene(fileNamed: "GameOverScene") as! GameOverScene
gameOver.score = self.score
self.view?.presentScene(gameOver, transition: transition)
}
}
})
actionArray.append(SKAction.removeFromParent())
//make a run function on the alien to pass allong actionArray
alien.run(SKAction.sequence(actionArray))
}
//fire fireTorpedo when tapped
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
fireTorpedo()
}
//func FireTorpedo or bullet
func fireTorpedo(){
//adds sound, image and position of the torpedo
self.run(SKAction.playSoundFileNamed("torpedo.mp3", waitForCompletion: false))
let torpedoNode = SKSpriteNode(imageNamed: "torpedo")
//powerUp switch
switch (powerUp)
{
case 1:
torpedoNode.position.x = player.position.x
torpedoNode.position.y = player.position.y
case 2:
torpedoNode.position.y = player.position.y
torpedoNode.position.x = player.position.x - 10
torpedoNode.position.x = player.position.x + 10
default:
print("out of torpedo ammo")
break
}
torpedoNode.position.y += 5
//add physicsBody for torpedo just like the aliens
torpedoNode.physicsBody = SKPhysicsBody(circleOfRadius: torpedoNode.size.width / 2)
torpedoNode.physicsBody?.isDynamic = true
torpedoNode.physicsBody?.categoryBitMask = photonTorpedoCategory
torpedoNode.physicsBody?.contactTestBitMask = alienCategory
torpedoNode.physicsBody?.collisionBitMask = 0
torpedoNode.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(torpedoNode)
//add animation like in alien
let animationDuration: TimeInterval = 0.3
//make torpedo move up and disappear
var actionArray = [SKAction]()
actionArray.append(SKAction.move(to: CGPoint(x: player.position.x, y: self.frame.size.height + 10), duration: animationDuration))
actionArray.append(SKAction.removeFromParent())
//run the torpedo
torpedoNode.run(SKAction.sequence(actionArray))
}
func didBegin(_ contact: SKPhysicsContact){
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
//check if two bodies touch
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask{
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
//to findout which body is the torpedo and which is alien
if (firstBody.categoryBitMask & photonTorpedoCategory) != 0 && (secondBody.categoryBitMask & alienCategory) != 0 {
torpedoDidCollideWithAlien(torpedoNode: firstBody.node as? SKSpriteNode, alienNode: secondBody.node as? SKSpriteNode)
}
}
func torpedoDidCollideWithAlien (torpedoNode: SKSpriteNode?, alienNode: SKSpriteNode?){
if let explosion = SKEmitterNode(fileNamed: "Explosion"){
if let alien = alienNode{
explosion.position = alien.position
}
self.addChild(explosion)
self.run(SKAction.playSoundFileNamed("explosion.mp3", waitForCompletion: false))
if let torpedo = torpedoNode{
torpedo.removeFromParent()
}
if let alien = alienNode{
alien.removeFromParent()
}
//see the explosion effect longer and not disappear immediately with run function with action and completion handler
self.run(SKAction.wait(forDuration: 2)){
explosion.removeFromParent()
}
//add and update score
score += 5
}
}
override func didSimulatePhysics() {
player.position.x += xAcceleration * 50
if player.position.x < -20 {
player.position = CGPoint(x: self.size.width + 20, y: player.position.y)
} else if player.position.x > self.size.width + 20 {
player.position = CGPoint(x: -20, y: player.position.y)
}
}
}
So I've got a class named FlashyPaddleEffect. I also mention it in another class, GameScene, but it doesn't apply any effect that it should. The paddle should be flashing blue and white colors, but it simply stays white. The paddle also should lose its physics body when it is blue.
If you need any other information, don't hesitate to ask.
NOTE: There might be problems with the code I gave (because of indents, it's quite tricky to make code by indenting 4 spaces every line, sorry).
import SpriteKit
import GameplayKit
class FlashyPaddleEffect {
var node = SKSpriteNode()
var ballNode = SKSpriteNode()
var updateTimer: Timer? = nil
var timer: Timer? = nil
#objc func changeNodeColor() {
switch node.color {
case SKColor.blue: node.color = SKColor.white
case SKColor.white: node.color = SKColor.blue
default: _ = 1 + 2
}
}
#objc func update() //I used the objc prefix to silence the warning the selectors of the timers produced. {
let previousPhysicsBody = node.physicsBody
if node.color == SKColor.blue {
node.physicsBody = nil
}
node.physicsBody = previousPhysicsBody
}
func make(applyEffectTo: SKSpriteNode, ball: SKSpriteNode) {
node = applyEffectTo
ballNode = ball
timer = Timer.scheduledTimer(timeInterval: 0.6, target: self, selector: #selector(changeNodeColor), userInfo: nil, repeats: true)
updateTimer = Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(update), userInfo: nil, repeats: true)
_ = node
_ = timer
}
}
class GameScene: SKScene {
var ball = SKSpriteNode()
var player = SKSpriteNode()
var enemy = SKSpriteNode()
var scores = [0, 0]
var initScores = [0, 0]
var scoreLabels: [SKLabelNode]? = nil
let playLabel = SKLabelNode()
let timeLabel = SKLabelNode()
let timeUpLabel = SKLabelNode()
var secondsLeft: Int = 180
var initialTime: Int? = nil
var timer = Timer()
var amountOfPauseMenuCloses = 0
var resultsLabel = SKLabelNode()
let flashEffect = FlashyPaddleEffect() //I define a variable to mention the class easier later on.
override func didMove(to view: SKView) {
//Initialize nodes of the scene editor.
ball = self.childNode(withName: "Ball") as! SKSpriteNode
player = self.childNode(withName: "PPaddle") as! SKSpriteNode
enemy = self.childNode(withName: "EPaddle") as! SKSpriteNode
//Set styles for the Play Notification Label (referred to as PNL later)
playLabel.position = CGPoint(x: 0, y: 0)
playLabel.text = "Tap anywhere to play."
playLabel.name = "playLabel"
//Set styles for the Timer Label (referred to as Timer later)
timeLabel.position = CGPoint(x: 90, y: 0)
timeLabel.text = String(secondsLeft)
//Doing manipulations connected with scores here.
scores = [0, 0]
initScores = scores
scoreLabels = [self.childNode(withName: "PScoreLabel") as! SKLabelNode, self.childNode(withName: "EScoreLabel") as! SKLabelNode]
//Create a border for our ball to bounce off.
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
border.restitution = 1
border.linearDamping = 0
border.angularDamping = 0
self.physicsBody = border
//To avoid the ball's damping.
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.angularDamping = 0
showPause() //Show the (pause) menu at the beginning
//Set a variable to refer to as a time standard later.
initialTime = secondsLeft
//The game timer.
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(reduceSecondFromTimer), userInfo: nil, repeats: true)
flashEffect.make(applyEffectTo: player, ball: ball) //This is where I reference the class in another class.
}
//A function to show the (pause) menu.
func showPause(hideNodes: Bool = true) {
self.addChild(playLabel)
if hideNodes == true {
ball.removeFromParent()
player.removeFromParent()
enemy.removeFromParent()
scoreLabels?[0].removeFromParent()
scoreLabels?[1].removeFromParent()
}
}
override func update(_ currentTime: TimeInterval) {
var alreadyChangedTextLabel = false
for i in scoreLabels! {
if i.name == "PScoreLabel" {i.text = String(scores[0])}
if i.name == "EScoreLabel" {i.text = String(scores[1])}
}
if ball.position.y <= player.position.y {scores[1] += 1}
if ball.position.y >= enemy.position.y {scores[0] += 1}
if secondsLeft == 0 {
showPause(hideNodes: false)
if alreadyChangedTextLabel == false {
timeUpLabel.text = "TIME UP! \(whoIsWinning(scores: scores)) won!"
alreadyChangedTextLabel = true
}
timeUpLabel.name = "timeUpLabel"
timeUpLabel.position = CGPoint(x: 0, y: 180)
if !(self.children.contains(timeUpLabel)) {
self.addChild(timeUpLabel)
}
timeLabel.removeFromParent()
}
if self.children.contains(playLabel) {
secondsLeft = initialTime!
}
timeLabel.text = String(secondsLeft)
let alignWithBall = SKAction.move(to: CGPoint(x: ball.position.x, y: enemy.position.y), duration: 0.8)
enemy.run(alignWithBall)
}
func initGame() {
//Initializing process for every game.
scores = initScores
ball.position = CGPoint(x: 0, y: 0)
if amountOfPauseMenuCloses == 1 {ball.physicsBody?.applyImpulse(CGVector(dx: 15, dy: 15))}
secondsLeft = initialTime!
timeLabel.text = String(secondsLeft)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for childNode in self.children {
if childNode.name == "playLabel" {
if self.children.contains(playLabel) {
playLabel.removeFromParent()
}
if !(self.children.contains(player)) {
self.addChild(player)
}
if !(self.children.contains(enemy)) {
self.addChild(enemy)
}
if !(self.children.contains(ball)) {
self.addChild(ball)
}
if !(self.children.contains((scoreLabels?[0])!)) {
self.addChild((scoreLabels?[0])!)
}
if !(self.children.contains((scoreLabels?[1])!)) {
self.addChild((scoreLabels?[1])!)
}
if !(self.children.contains(timeLabel)) {
self.addChild(timeLabel)
}
if self.children.contains(timeUpLabel) {
timeUpLabel.removeFromParent()
}
amountOfPauseMenuCloses += 1
initGame()
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
if self.nodes(at: t.location(in: self)).contains(player) {player.position.x = t.location(in: self).x}
}
}
func reduceSecondFromTimer() {
secondsLeft -= 1
}
func whoIsWinning(scores: Array<Int>) -> String {
var r: String? = nil
if scores[0] >= scores[1] {
r = "You"
}
if scores[1] >= scores[0] {
r = "Enemy"
}
return r!
}
}
Thanks a lot for answers.
P.S It's my first question ever so don't judge me strictly.
1) Do not use NSTimer use SKAction. I can see the way you are doing it you stack timer after timer, this is bad.
2) Do not have your temp variable global (node in this case), it makes code hard to read
3) Do not remove your physics body, simply remove the category.
func make(applyEffectTo: SKSpriteNode, ball: SKSpriteNode) {
ballNode = ball
let blue = SKAction.colorize(with SKColor.blue, colorBlendFactor: 1.0, duration sec: 0)
let white = SKAction.colorize(with SKColor.white, colorBlendFactor: 1.0, duration sec: 0)
let wait = SKAction.wait(for:0.6)
let turnOnPhysics = SKAction.run({applyEffectTo.physicsBody?.categoryBitmask = #######})
let turnOffPhysics = SKAction.run({applyEffectTo.physicsBody?.categoryBitmask = 0})
let seq = [blue, turnOffPhysics,wait,white,turnOnPhysics,wait]
let repeat = SKAction.repeatForever(seq)
applyEffectTo.run(repeat, withKey:"flashing")
}
Note: I have no idea what your categoryBitmask is, you need to fill it in
So I want a game made, this was striped from the Xcode SpriteKit sampler, pretty simple. It will evolve greatly as I get this key issue out of the way. It has a player, Wall's, and a door. Nodes are assigned, player works fine. Wall's attempted for children in self, but crashes with my comments removed. I have a guess as multiple nodes of same name? But the door, when assigned node, for some reason no matter what slowly falls, with no gravity ticked and no gravity coded.
Those are lesser concerns. I come to you today to pick at why my collisions might not be activating my collision argument functions, to enter the house.
Yes I am aware it says contact mapped to the event. It suits my theory I am pretty sure.
//
// GameScene.swift
// Sandbox
//
// Created by M on 7/1/16.
// Copyright © 2016 M. All rights reserved.
//
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var entities = [GKEntity]()
var graphs = [GKGraph]()
private var lastUpdateTime : TimeInterval = 0
private var label : SKLabelNode?
var playerNode : SKSpriteNode?
var wallNode : SKSpriteNode?
var doorNode : SKSpriteNode?
private var spinnyNode : SKShapeNode?
var furnishing : SKSpriteNode?
var playerCategory = 0x1 << 0
var wallCategory = 0x1 << 1
var doorCategory = 0x1 << 2
var pathCategory = 0x1 << 3
func nextRoom() {
let sceneNode = SKScene(fileNamed: "MyScene")
sceneNode?.scaleMode = .aspectFill
// Present the scene
if let view = self.view {
view.presentScene(sceneNode)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
func loadRoom() {
let furnishing = SKSpriteNode(color: #colorLiteral(red: 0.2464724183, green: 0.05352632701, blue: 0.03394328058, alpha: 1), size:CGSize(width:25, height:25))
doorNode?.addChild(furnishing)
}
func enterHouse() {
let newWindow = CGSize(width: 500, height: 500)
doorNode?.scale(to: newWindow)
loadRoom()
}
func exitHouse(){
let oldWindow = CGSize(width: 100, height: 100)
doorNode?.scale(to: oldWindow)
}
override func sceneDidLoad() {
self.lastUpdateTime = 0
physicsWorld.contactDelegate = self
// Get nodes from scene and store for use later
self.playerNode = self.childNode(withName: "//player") as? SKSpriteNode
playerNode?.physicsBody = SKPhysicsBody(rectangleOf: (playerNode?.frame.size)!)
playerNode?.physicsBody?.isDynamic = true
playerNode?.physicsBody?.affectedByGravity = false
playerNode?.physicsBody?.categoryBitMask = UInt32(playerCategory)
playerNode?.physicsBody?.collisionBitMask = UInt32(wallCategory)
playerNode?.physicsBody?.contactTestBitMask = UInt32(doorCategory)
for child in self.children {
/*if child.name == "wall" {
if let child = child as? SKSpriteNode {
wallNode?.physicsBody = SKPhysicsBody(rectangleOf: (wallNode?.frame.size)!)
wallNode?.physicsBody?.isDynamic = false
wallNode?.physicsBody?.categoryBitMask = UInt32(wallCategory)
wallNode?.physicsBody?.collisionBitMask = UInt32(playerCategory)
self.addChild(child)
}
}*/
}
self.doorNode = self.childNode(withName: "door") as? SKSpriteNode
doorNode?.physicsBody?.affectedByGravity = false
doorNode?.physicsBody?.isDynamic = false
doorNode?.physicsBody = SKPhysicsBody(rectangleOf: (doorNode?.frame.size)!)
doorNode?.physicsBody?.categoryBitMask = UInt32(doorCategory)
doorNode?.physicsBody?.contactTestBitMask = UInt32(playerCategory)
}
func touchDown(atPoint pos : CGPoint) {
let fromX = playerNode?.position.x
let fromY = playerNode?.position.y
let toX = pos.x
let toY = pos.y
let resultX = toX - (fromX)!
let resultY = toY - (fromY)!
let newX = (playerNode?.position.x)! + resultX / 10
let newY = (playerNode?.position.y)! + resultY / 10
playerNode?.position.x = newX
playerNode?.position.y = newY
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
func didBeginContact(contact: SKPhysicsContact) {
//this gets called automatically when two objects begin contact with each other
// 1. Create local variables for two physics bodies
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
// 2. Assign the two physics bodies so that the one with the lower category is always stored in firstBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if secondBody.categoryBitMask == UInt32(doorCategory){
enterHouse()
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
// Initialize _lastUpdateTime if it has not already been
if (self.lastUpdateTime == 0) {
self.lastUpdateTime = currentTime
}
// Calculate time since last update
let dt = currentTime - self.lastUpdateTime
// Update entities
for entity in self.entities {
entity.update(withDeltaTime: dt)
}
self.lastUpdateTime = currentTime
}
}
I presume this line is causing the crash
wallNode?.physicsBody = SKPhysicsBody(rectangleOf: (wallNode?.frame.size)!)
as you are force unwrapping (!) the wallNode size however looking at your code you never assign it to anything like so
wallNode = self.childNode(withName: "wallNode") as? SKSpriteNode
or in the for loop.
Try this code in your for in loop that should avoid crashes and assigns your wall node.
for child in self.children where child.name == "wall" {
if let child = child as? SKSpriteNode {
wallNode = child // Try this
if let wallNode = wallNode { // safely unwrap wall node to avoid crashes
wallNode.physicsBody = SKPhysicsBody(rectangleOf: (wallNode.frame.size))
wallNode.physicsBody?.isDynamic = false
wallNode.physicsBody?.categoryBitMask = UInt32(wallCategory)
wallNode.physicsBody?.collisionBitMask = UInt32(playerCategory)
self.addChild(wallNode) // add wall node here instead if you are using your wallNode property
}
}
}
Hope this helps
I am developing a game on sprite kit, and when the player loses they are taken to the EndScene and giving the option to play again. The problem I am having is when the player presses the button to play again they are taken to the GameScene, but instead the GameScene its appearing all blue. Can anyone tell me what I am doing wrong? I posted the EndScene below, if you need me to post the GameScene class just ask.
class EndScene: SKScene {
var RestartBtn : UIButton!
override func didMoveToView(view: SKView) {
scene?.backgroundColor = UIColor.whiteColor()
RestartBtn = UIButton(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 30))
RestartBtn.setTitle("Restart", forState: UIControlState.Normal)
RestartBtn.setTitleColor(UIColor.darkGrayColor(), forState: UIControlState.Normal)
RestartBtn.addTarget(self, action: Selector("Restart"), forControlEvents: UIControlEvents.TouchUpInside)
self.view?.addSubview(RestartBtn)
}
func Restart() {
// close EndScene and start the game again
self.view?.presentScene(GameScene())
RestartBtn.removeFromSuperview()
}
}
GameScene
import SpriteKit
struct PhysicsCatagory {
static let Enemy : UInt32 = 1 //000000000000000000000000000001
static let Bullet : UInt32 = 2 //00000000000000000000000000010
static let Player : UInt32 = 3 //00000000000000000000000000100
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var Score = Int()
var Player = SKSpriteNode(imageNamed: "PlayerGalaga.png")
var ScoreLbl = UILabel()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
physicsWorld.contactDelegate = self
self.scene?.backgroundColor = UIColor.blackColor()
Player.position = CGPointMake(self.size.width / 2, self.size.height / 5)
Player.physicsBody = SKPhysicsBody(rectangleOfSize: Player.size)
Player.physicsBody?.affectedByGravity = false
Player.physicsBody?.categoryBitMask = PhysicsCatagory.Player
Player.physicsBody?.contactTestBitMask = PhysicsCatagory.Enemy
Player.physicsBody?.dynamic = false
var Timer = NSTimer.scheduledTimerWithTimeInterval(0.2, target: self,
selector: Selector("SpawnBullets"), userInfo: nil, repeats: true)
var EmenyTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target:
self, selector: ("SpawnEnemies"), userInfo: nil, repeats: true)
self.addChild(Player)
ScoreLbl.text = "\(Score)"
ScoreLbl = UILabel(frame: CGRect(x: 0,y: 0, width: 100, height: 20))
ScoreLbl.backgroundColor = UIColor(red: 0.1, green: 0.1, blue: 0.1,
alpha:0.3 )
ScoreLbl.textColor = UIColor.whiteColor()
self.view?.addSubview(ScoreLbl)
}
func didBeginContact(contact: SKPhysicsContact) {
var firstBody : SKPhysicsBody = contact.bodyA
var secondBody : SKPhysicsBody = contact.bodyB
if ((firstBody.categoryBitMask == PhysicsCatagory.Enemy) &&
(secondBody.categoryBitMask == PhysicsCatagory.Bullet) ||
(firstBody.categoryBitMask == PhysicsCatagory.Bullet) &&
(secondBody.categoryBitMask == PhysicsCatagory.Enemy)){
CollisionWithBullet(firstBody.node as! SKSpriteNode, Bullet:
secondBody.node as! SKSpriteNode)
}
else if ((firstBody.categoryBitMask == PhysicsCatagory.Enemy) &&
(secondBody.categoryBitMask == PhysicsCatagory.Player) ||
(firstBody.categoryBitMask == PhysicsCatagory.Player) &&
(secondBody.categoryBitMask == PhysicsCatagory.Enemy)){
CollisionWithPerson(firstBody.node as! SKSpriteNode, Person:
secondBody.node as! SKSpriteNode)
}
}
func CollisionWithBullet(Enemy: SKSpriteNode, Bullet:SKSpriteNode){
Enemy.removeFromParent()
Bullet.removeFromParent()
Score++
ScoreLbl.text = "\(Score)"
}
func CollisionWithPerson(Enemy:SKSpriteNode, Person:SKSpriteNode){
Enemy.removeFromParent()
Player.removeFromParent()
self.view?.presentScene(EndScene())
ScoreLbl.removeFromSuperview()
}
func SpawnBullets(){
var Bullet = SKSpriteNode(imageNamed: "BulletGalaga.png")
Bullet.zPosition = -5
Bullet.position = CGPointMake(Player.position.x, Player.position.y)
let action = SKAction.moveToY(self.size.height + 30, duration: 0.5)
let actionDone = SKAction.removeFromParent()
Bullet.runAction(SKAction.sequence([action, actionDone]))
Bullet.physicsBody = SKPhysicsBody(rectangleOfSize: Bullet.size)
Bullet.physicsBody?.categoryBitMask = PhysicsCatagory.Bullet
Bullet.physicsBody?.contactTestBitMask = PhysicsCatagory.Enemy
Bullet.physicsBody?.affectedByGravity = false
Bullet.physicsBody?.dynamic = false
self.addChild(Bullet)
}
func SpawnEnemies(){
var Enemy = SKSpriteNode(imageNamed: "EnemyGalaga.png")
var MinValue = self.size.width / 8
var MaxValue = self.size.width - 20
var SpawnPoint = UInt32(MaxValue - MinValue)
Enemy.position = CGPointMake(CGFloat(arc4random_uniform(SpawnPoint)),
self.size.height)
Enemy.physicsBody = SKPhysicsBody(rectangleOfSize: Enemy.size)
Enemy.physicsBody?.categoryBitMask = PhysicsCatagory.Enemy
Enemy.physicsBody?.contactTestBitMask = PhysicsCatagory.Bullet
Enemy.physicsBody?.affectedByGravity = false
Enemy.physicsBody?.dynamic = true
let action = SKAction.moveToY(-70, duration: 2.0)
let actionDone = SKAction.removeFromParent()
Enemy.runAction(SKAction.sequence([action, actionDone]))
self.addChild(Enemy)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event:
UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
Player.position.x = location.x
}
}
override func touchesMoved(touches: Set<NSObject>, withEvent event:
UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
Player.position.x = location.x
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
From what I can see, when there's a collision with person, you're removing nodes from the view, so they're still going to be gone when you recall GameScene from the restart() function in the EndScene.
What you can do is create a func initialScene() in GameScene where you create everything in that function and call it in the didMoveToView function (instead of doing it all in the didMoveToView function). That way, whenever you present the GameScene from another class, it will call that function and recreate everything you need as if you actually restarted. Hope that's a helpful start, if you need anything else just comment.