So far, I almost finished my game. I have a ball that falls due to gravity and the person taps it to make it bounce. Every time the sprite is tapped, +1 is added to the score. There are 3 balls in total that show up when the view loads.I want each ball to show up at a certain point mark(one ball shows up when the person reaches 10 points, another when the player reaches 20).
I tried putting this in update(currentTime: CFTimeInterval), but I end up with this:
(This is the secondball coming in at 10 points.)
Seems like there are an infinite number of balls until the person gets to 21 points which stops the never-ending cascade. If you tap the cascade though, it does pick a ball and makes it jump out which is sorta what I wanted.
This is GameScene.swift (excluding the update(CFTimeInterval) function)
import SpriteKit
class GameScene: SKScene {
var ball: Ball!
var secondball: Ball!
override func didMoveToView(view: SKView) {
//=======Ball 1=======//
let ball = Ball()
ball.position = CGPoint(x:self.frame.midX, y:440)
addChild(ball)
ball.physicsBody = SKPhysicsBody(circleOfRadius: 90)
ball.physicsBody?.dynamic = true
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.friction = 0
ball.physicsBody?.angularDamping = 0
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.usesPreciseCollisionDetection = true
ball.physicsBody!.categoryBitMask = 0
//======Ball 2======//
let secondball = Ball()
secondball.position = CGPoint(x:self.frame.midX * 1.65, y: 440)
addChild(secondball)
secondball.physicsBody = SKPhysicsBody(circleOfRadius: 90)
secondball.physicsBody?.dynamic = false
secondball.physicsBody?.allowsRotation = false
secondball.physicsBody?.friction = 0
secondball.physicsBody?.angularDamping = 0
secondball.physicsBody?.linearDamping = 0
secondball.physicsBody?.usesPreciseCollisionDetection = true
secondball.physicsBody!.categoryBitMask = 0
}
class Ball: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: "Ball")
super.init(texture: texture, color: .clearColor(), size: texture.size())
userInteractionEnabled = true
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let scene = self.scene as! GameScene
scene.score += 1
physicsBody?.velocity = CGVectorMake(0, 100)
physicsBody?.applyImpulse(CGVectorMake(0, 900))
}
}
override func update(currentTime: CFTimeInterval) {
if (score >= 10){
self.backgroundNode.texture = SKTexture(imageNamed: "orangebackground")
}
if (score >= 20){
self.backgroundNode.texture = SKTexture(imageNamed: "yellowbackground")
}
if (score >= 30) {
self.backgroundNode.texture = SKTexture(imageNamed: "greenbackground")
}
if (score >= 40){
self.backgroundNode.texture = SKTexture(imageNamed: "bluebackground")
}
if (score >= 50){
self.backgroundNode.texture = SKTexture(imageNamed: "darkbluebackground")
}
if (score >= 60){
self.backgroundNode.texture = SKTexture(imageNamed: "purplebackground")
}
if (score >= 70){
self.backgroundNode.texture = SKTexture(imageNamed: "brownbackground")
}
if (score >= 80) {
self.backgroundNode.texture = SKTexture(imageNamed: "maroonbackground")
}
if (score >= 90){
self.backgroundNode.texture = SKTexture(imageNamed: "tanbackground")
}
if (score >= 100){
self.backgroundNode.texture = SKTexture(imageNamed: "pinkbackground")
}
if (score >= 125) {
self.backgroundNode.texture = SKTexture(imageNamed: "bronzebackground")
}
if (score >= 150) {
self.backgroundNode.texture = SKTexture(imageNamed: "silverbackground")
}
if (score >= 175) {
self.backgroundNode.texture = SKTexture(imageNamed: "goldbackground")
}
if (score >= 200) {
self.backgroundNode.texture = SKTexture(imageNamed: "elitebackground")
}
}
}
The update method is called faster then the increased score. Therefore in every call of the update method a new ball is added.
Try something like this:
Move this line of code outside of the update method to make a global variable which has one ball instance:
let secondball = Ball()
Inside of update:
if (score == 10) && secondball.parent == nil {
You have choosed to control the touches part to the Ball class, this is possible but not convenient.
First of all give a name to your balls during the creation:
ball.name = "ball1"
self.addChild(ball)
...
secondball.name = "ball2"
secondball.alpha = 0.0 // hide your secondBall at start
self.addChild(secondball)
Then , move the touchesBegan from your Ball class, to your GameScene:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let positionInScene = touch!.locationInNode(self)
let touchedNode = self.nodeAtPoint(positionInScene)
if let name = touchedNode.name {
switch name {
case "ball1": // if you're touching ball1
self.score += 1
ball.physicsBody?.velocity = CGVectorMake(0, 100)
ball.physicsBody?.applyImpulse(CGVectorMake(0, 900))
if score == 10 {
ball.removeFromParent()
// or make some animation like:
//let fadeOut=SKAction.fadeOutWithDuration(1.0)
//ball.runAction(fadeOut,completion: {
// self.ball.removeFromParent()
//})
secondball.alpha = 1
// or make some animation like:
//let fadeIn=SKAction.fadeInWithDuration(0.5)
//secondball.runAction(fadeIn,completion: {
// self.secondball.physicsBody?.dynamic = true
//})
secondball.physicsBody?.dynamic = true
}
case "ball2": // if you're touching ball2
self.score += 1
secondball.physicsBody?.velocity = CGVectorMake(0, 100) // here you can decrease parameters to more difficult
secondball.physicsBody?.applyImpulse(CGVectorMake(0, 900))
if score == 20 {
// do the same thing maked for ball to show other ball
}
case "ball3": // if you're touching ball3
self.score += 1
secondball.physicsBody?.velocity = CGVectorMake(0, 100) // here you can decrease parameters to more difficult
secondball.physicsBody?.applyImpulse(CGVectorMake(0, 900))
if score == 21 {
// do other stuff here
}
default:
break
}
}
}
Related
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let location = touch.location(in: self) // locates touches on the screen
let objects = nodes(at: location) // shows all objects were screen was touched
if objects.contains(editLabel) { // checking if appropriate array of objects contains the editLabel
editingMode.toggle()
} else {
if editingMode {
let size = CGSize(width: Int.random(in: 16...128), height: 16)
let box = SKSpriteNode(color: UIColor(red: CGFloat.random(in: 0...1), green: CGFloat.random(in: 0...1), blue: CGFloat.random(in: 0...1), alpha: 1), size: size)
box.zRotation = CGFloat.random(in: 0...3)
box.position = location // add the box ob ject were the user taps
box.name = "box"
box.physicsBody = SKPhysicsBody(rectangleOf: box.size)
box.physicsBody!.contactTestBitMask = box.physicsBody!.collisionBitMask
box.physicsBody?.isDynamic = false // box object will not move when impact happens
addChild(box) // box object will be added to the screen
} else if usedBalls != 0 {
let ball = SKSpriteNode(imageNamed: allBalls.randomElement() ?? "ballRed.png") // pulls out the red ball from app bundle
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width / 2.0) // animates the ball object
ball.physicsBody!.contactTestBitMask = ball.physicsBody!.collisionBitMask
ball.physicsBody?.restitution = 0.4 // determines the ball object bounciness
ball.position = location // ball will appear where the user tapped on the screen
ball.name = "ball"
// ball.position = CGPoint (x: 550, y: 700)
addChild (ball) // adds the ball object to the screen
usedBalls -= 1
}
}
}
func makeBouncer(at position: CGPoint) {
let bouncer = SKSpriteNode(imageNamed: "bouncer.png")
bouncer.position = position
bouncer.physicsBody = SKPhysicsBody(circleOfRadius: bouncer.size.width / 2.0)
bouncer.physicsBody?.isDynamic = false // bouncer object is fixed to the bottom of the screen
addChild(bouncer)
}
func makeSlot(at position: CGPoint, isGood: Bool) {
var slotBase: SKSpriteNode
var slotGlow: SKSpriteNode
if isGood {
slotBase = SKSpriteNode(imageNamed: "slotBaseGood.png")
slotGlow = SKSpriteNode(imageNamed: "slotGlowGood.png")
slotBase.name = "good"
} else {
slotBase = SKSpriteNode(imageNamed: "slotBaseBad.png")
slotGlow = SKSpriteNode(imageNamed: "slotGlowBad.png")
slotBase.name = "bad"
}
slotBase.position = position
slotGlow.position = position
slotBase.physicsBody = SKPhysicsBody(rectangleOf: slotBase.size)
slotBase.physicsBody?.isDynamic = false
addChild(slotBase)
addChild(slotGlow)
let spin = SKAction.rotate(byAngle: .pi, duration: 10)
let spinForever = SKAction.repeatForever(spin)
slotGlow.run(spinForever)
}
func collisionBetween(ball: SKNode, object: SKNode) {
if object.name == "good" { // green slot
destroy(ball: ball)
score += 1
usedBalls += 1
} else if object.name == "bad" { // red slot
destroy(ball: ball) // the ball will be removed once drops into a green or red slot
score -= 1
}
}
func boxandballCollision(box: SKNode, ball: SKNode) {
if ball.name == "ball" {
destroyObject(box: box)
}
}
func destroyObject(box: SKNode) {
if let fireParticles = SKEmitterNode(fileNamed: "FireParticles") {
fireParticles.position = box.position
addChild(fireParticles)
}
box.removeFromParent() // box should be removed when a ball will hit it
}
func destroy(ball: SKNode) {
if let fireParticles = SKEmitterNode(fileNamed: "FireParticles") {
fireParticles.position = ball.position
addChild(fireParticles)
}
ball.removeFromParent() // ball object is removed from scene when hits a slot
}
func didBegin(_ contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node else { return }
guard let nodeB = contact.bodyB.node else { return }
if nodeA.name == "ball" {
collisionBetween(ball: nodeA, object: nodeB)
} else if nodeB.name == "ball" {
collisionBetween(ball: nodeB, object: nodeA)
}
}
func begin(_ contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node else { return }
guard let nodeB = contact.bodyB.node else { return }
if nodeA.name == "box" {
boxandballCollision(box: nodeA, ball: nodeB)
} else if nodeB.name == "box" {
boxandballCollision(box: nodeB, ball: nodeA)
}
}
I want to remove any box object when a ball will hit it, is just a simple game for studying purposes, but I'm struggling with it. I have used exactly same methods for the box object and this to get notifications about every collision " box.physicsBody!.contactTestBitMask = box.physicsBody!.collisionBitMask".
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 created a game where you tap balls to make them jump up. You get a point for every tap. Why do my sprites start flashing when I reach 10 points. I'm thinking it has something to do with the update(timeInterval) method. It may be a bug or bad coding.
import SpriteKit
class GameScene: SKScene {
let backgroundNode = SKSpriteNode(imageNamed: "redbackground")
override func didMoveToView(view: SKView) {
backgroundNode.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(backgroundNode)
//=======Ball 1=======//
let ball = Ball()
ball.position = CGPoint(x:self.frame.midX, y:440)
addChild(ball)
ball.physicsBody = SKPhysicsBody(circleOfRadius: 90)
ball.physicsBody?.dynamic = true
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.friction = 0
ball.physicsBody?.angularDamping = 0
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.usesPreciseCollisionDetection = true
ball.physicsBody!.categoryBitMask = 0
}
class Ball: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: "Ball")
super.init(texture: texture, color: .clearColor(), size: texture.size())
userInteractionEnabled = true
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let scene = self.scene as! GameScene
scene.score += 1
physicsBody?.velocity = CGVectorMake(0, 100)
physicsBody?.applyImpulse(CGVectorMake(0, 900))
}
override func update(currentTime: CFTimeInterval) {
if (score >= 10){
self.backgroundNode.texture = SKTexture(imageNamed: "orangebackground")
}
if (score >= 20){
self.backgroundNode.texture = SKTexture(imageNamed: "yellowbackground")
}
if (score >= 30) {
self.backgroundNode.texture = SKTexture(imageNamed: "greenbackground")
}
}
See this to see what I mean.(Click or tap link)
This shows the sprites flashing at 10 points
thanks in advance, Jordan
Instead of changing the background image in update, you can add a property observer to score that changes the background only when the score reaches specific values. For example:
let backgroundNames = ["orangebackground","yellowbackground","greenbackground"]
// The didSet observer is called when the score is updated
var score:Int = 0 {
didSet {
// Check if score is divisible by 10
if score % 10 == 0 {
// Determine the array index
let index = score / 10
// Wrap to avoid index out of range
let name = backgroundNames[index % backgroundNames.count]
print (name)
}
}
}
That said, the flashing could be caused by the order in which the nodes are rendered. From Apple's documentation,
...with [ignoresSiblingOrder = true], you cannot predict the
rendering order for nodes that share the same height. The rendering
order may change each time a new frame is rendered.
You can resolve this by setting the zPosition of the background to a negative value, for example
self.backgroundNode.zPosition = -100
so the background is always drawn before the game nodes.
It is flashing because of this
override func update(currentTime: CFTimeInterval) {
if (score >= 10){
self.backgroundNode.texture = SKTexture(imageNamed: "orangebackground")
}
if (score >= 20){
self.backgroundNode.texture = SKTexture(imageNamed: "yellowbackground")
}
if (score >= 30) {
self.backgroundNode.texture = SKTexture(imageNamed: "greenbackground")
}
}
Infact inside the update method, when the score become >= 10, you are updating the texture EVERY FRAME.
This is absolutely wrong.
If you want to update the texture just do it when you need to change it (e.g. when the score goes from 9 to 10, or from 19 to 20 or from 29 to 30.).
But there's no point in updating the texture when it stays at 10 of when it goes from 10 to 11 since you will update it with another texture which is the same.
How can you do it?
Just create a shouldUpdateTexture: Bool = false property, then each time you update the score, if the score goes from 9 to 10 or 19 to 20 or 29 to 30 then turn shouldUpdateTexture to true.
Finally inside the update method check if shouldUpdateTexture is true, in this case set it back to false and update the texture.
Full code
class GameScene: SKScene {
private var score = 0 {
didSet {
shouldUpdateTexture = oldValue < 10 && score >= 10 || oldValue < 20 && score >= 20 || oldValue < 30 && score >= 30
}
}
private var shouldUpdateTexture = false
private var backgroundNode = SKSpriteNode(imageNamed: "defaultbackground")
override func update(currentTime: CFTimeInterval) {
if shouldUpdateTexture {
shouldUpdateTexture = false
switch score {
case 10...19: backgroundNode.texture = SKTexture(imageNamed: "orangebackground")
case 20...29: backgroundNode.texture = SKTexture(imageNamed: "yellowbackground")
case 30...Int.max: backgroundNode.texture = SKTexture(imageNamed: "yellowbackground")
default: break
}
}
}
}
That's it.
Update
As #Knight0fDragon pointed out, it would probably be faster moving the code I put into the update method into the didSet.
Pros: this way you don't need to perform a boolean equality check every frame
Cons: if you change the score value multiple times within the same frame you will perform multiple loads of the texture.
Just an alternative that allows for multiple changes in score to happen, without flooding the update function with unnecessary code
class GameScene: SKScene {
private var score = 0 {
didSet {
///if a change in the 10s happen, then update texture
if(score/10 > oldValue/10)
{
switch score{
case 10...19: backgroundNode.texture = SKTexture(imageNamed: "orangebackground")
case 20...29: backgroundNode.texture = SKTexture(imageNamed: "yellowbackground")
case 30...Int.max: backgroundNode.texture = SKTexture(imageNamed: "greenbackground")
default: break
}
}
}
}
private var shouldUpdateTexture = false
private var backgroundNode = SKSpriteNode(imageNamed: "defaultbackground")
}
Then for optimization: (Not really needed, but may help with how you think about code)
class GameScene: SKScene {
let bgTextures = [SKTexture(imageNamed: "orangebackground"),SKTexture(imageNamed: "yellowbackground"),SKTexture(imageNamed: "greenbackground")]
private var score = 0 {
didSet {
///if a change in the 10s happen, then update texture
let bgIndex = score / 10
if((bgIndex > oldValue/10) && score < bgTextures.count * 10)
{
backgroundNode.texture = bgTextures[bgIndex - 1]
}
else
{
backgroundNode.texture = bgTextures[bgTextures.count - 1]
}
}
}
private var shouldUpdateTexture = false
private var backgroundNode = SKSpriteNode(imageNamed: "defaultbackground")
}
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)
}
I am making a plat form game, but I am having an issue where only one of the movement buttons work. Can someone please take some time to review my code and see what is wrong with it?
I do not get my errors, my guess is that the there is a problem down where I change the sod variable. When I run the app and click and hold the "left button, it moves a little but then slows to a stop. It is as if the script where I changed the variable was only run one time, even though I hold it down. Interestingly enough, before when I ran it it was just fine. Unfortunately, I do not remember what I changed.
Thanks, James
class GameScene: SKScene {
var alien = SKSpriteNode()
var left = SKSpriteNode()
var right = SKSpriteNode()
var jump = SKSpriteNode()
var level = SKSpriteNode()
var background = SKSpriteNode()
var myLabel = SKLabelNode(fontNamed:"Chalkduster")
var playerLight = SKLightNode()
var xspd :Double = 0
var touching :Bool = false
var alienFrame :String = "stand"
var textureFrames :Double = 0
var buttonTouching :Bool = true
var buttonCheck :Int = 0
var ninety :Double = 0
override func didMoveToView(view: SKView) {
self.physicsWorld.gravity = CGVectorMake(0, -9.8)
alien = SKSpriteNode(imageNamed: "alien_stand")
alien.physicsBody = SKPhysicsBody(circleOfRadius: alien.frame.height / 2)
alien.position = CGPoint(x: self.frame.width / 6, y: self.frame.height / 2)
alien.xScale = 0.7
alien.yScale = 0.7
alien.physicsBody?.affectedByGravity = true
alien.physicsBody?.dynamic = true
alien.physicsBody?.allowsRotation = false
alien.zPosition = 1
alien.zRotation = 0
self.addChild(alien)
left = SKSpriteNode(imageNamed: "left")
left.position = CGPoint(x: self.frame.width / 8, y: self.frame.height / 3.5)
left.physicsBody?.pinned = true
left.xScale = 2
left.yScale = 2
left.zPosition = 3
left.alpha = 0.4
self.addChild(left)
right = SKSpriteNode(imageNamed: "right")
right.position = CGPoint(x: self.frame.width / 3, y: self.frame.height / 3.5)
right.physicsBody?.pinned = true
right.xScale = 2
right.yScale = 2
right.zPosition = 4
right.alpha = 0.4
self.addChild(right)
jump = SKSpriteNode(imageNamed: "up")
jump.position = CGPoint(x: (self.frame.width / 8) * 7, y: self.frame.height / 3.5)
jump.physicsBody?.pinned = true
jump.xScale = 2
jump.yScale = 2
jump.zPosition = 5
jump.alpha = 0.4
self.addChild(jump)
myLabel.text = "Hello, World!";
myLabel.fontSize = 45;
myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
self.addChild(myLabel)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
touching = true
if let touch = touches.first {
if xspd > -40 && xspd < 40 {
buttonCheck = 0
if jump.containsPoint(touch.locationInNode(self)) {
alien.physicsBody?.applyImpulse(CGVectorMake(0, 250))
jump.alpha = 0.1
alien.texture = SKTexture(imageNamed: "alien_jump")
buttonCheck += 1
}
if left.containsPoint(touch.locationInNode(self)) {
xspd -= 6
left.alpha = 0.1
alien.xScale = -0.7
buttonCheck += 1
}
if right.containsPoint(touch.locationInNode(self)) {
xspd += 6
right.alpha = 0.1
alien.xScale = 0.7
buttonCheck += 1
}
if buttonCheck > 0 {
buttonTouching = true
}
else {
buttonTouching = false
}
}
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
touching = false
}
override func update(currentTime: CFTimeInterval) {
updatePositions()
playerLight.position = CGPoint(x: alien.position.x, y: alien.position.y)
textureFrames += 1
}
func updatePositions() {
myLabel.text = String(round(xspd))
alien.physicsBody?.applyImpulse(CGVector(dx: CGFloat(xspd), dy: 0))
if touching == true && buttonTouching == true && xspd > 0 {
if xspd > 0 {
ninety = 900
}
else {
ninety = -900
}
if alienFrame == "stand" {
alien.texture = SKTexture(imageNamed: "alien_walk1")
alienFrame = "walk1"
}
else {
if alienFrame == "walk1" {
if textureFrames > 9.9 / xspd {
alien.texture = SKTexture(imageNamed: "alien_walk2")
alienFrame = "walk2"
textureFrames = 0
}
}
else {
if alienFrame == "walk2" {
if textureFrames > 9.9 / xspd {
alien.texture = SKTexture(imageNamed: "alien_walk1")
alienFrame = "walk1"
textureFrames = 0
}
}
}
}
}
else {
if xspd < 0.6 && xspd > -0.6 {
alien.texture = SKTexture(imageNamed: "alien_stand")
}
else {
if xspd != 0 {
xspd = xspd * 0.85
}
}
right.alpha = 0.4
left.alpha = 0.4
jump.alpha = 0.4
}
}
}
When you start the game, the xspd variable starts off with a value of zero. When tapping the left node, you subtract 6 from the xspd giving you the result of -6 on the xspd. Further you have the updatePositions() function, which is called every frame I suppose and within that function you apply an impulse using a vector based on the xspd value. The if-condition after that is never fulfilled when tapping the left node first since you get a negative xspd value that breaks the condition (&& xspd > 0), hence you never get any animations with an initial velocity with a negative x.
To fix the animation, you could encapsulate the xspd in an abs() which will always return a positive number.
&& abs(xspd) > 0
The next issue is that your player stops moving because if you tap and hold on the left node, you won´t be able to maintain the movement speed without having to tap the left node repeatedly.
A suggestion from me, you could try the following:
if xspd < 0.6 && xspd > -0.6 {
alien.texture = SKTexture(imageNamed: "alien_stand")
} else if touching == false && xspd != 0 { // Never attempt to decrease movement speed if the player is in fact touching a movement button
xspd = xspd * 0.85
}
I hope I've understood your problem correctly and that this is helpful