Getting different colour backgrounds to scroll up - swift

I am coding for my A2 coursework project, and making a well-known game 'Fall Down'. However i am trying to get the background to change every cycle while it scrolls - so it changes from blue to red to yellow etc. However when I run it, this only works for the first two colours, then flashes back to the first colour. Here is the code i currently have
var background = SKSpriteNode()
override func didMoveToView(view: SKView) {
let blueTexture = SKTexture(imageNamed: "BlueBackground")
let redTexture = SKTexture(imageNamed: "RedBackground")
let yellowTexture = SKTexture(imageNamed: "YellowBackground")
let greenTexture = SKTexture(imageNamed: "GreenBackground")
let purpleTexture = SKTexture(imageNamed: "PurpleBackground")
let TextureArray = [blueTexture, redTexture, yellowTexture, greenTexture, purpleTexture]
let levelProgress = SKAction.moveByX(0, y:blueTexture.size().height, duration: 5)
let newLevel = SKAction.moveByX(0 , y: -blueTexture.size().height, duration: 0)
let sequenceForever = SKAction.repeatActionForever(SKAction.sequence([ levelProgress, newLevel]))
var a = 0
for var i: CGFloat = 0; i<5; i++ {
let currentBG = TextureArray[a]
a++
background = SKSpriteNode(texture: currentBG)
background.position = CGPoint(x: CGRectGetMidX(self.frame), y: -blueTexture.size().height/2 + blueTexture.size().height * i)
background.size.width = self.frame.width
background.runAction(sequenceForever)
self.addChild(background)
}
if anyone could point me in the right direction or if any more information is needed let me know! This is my first post so any posting advice would be great too.

It might by because of this:
let levelProgress = SKAction.moveByX(0, y:blueTexture.size().height, duration: 5)
let newLevel = SKAction.moveByX(0 , y: -blueTexture.size().height, duration: 0)
let sequenceForever = SKAction.repeatActionForever(SKAction.sequence([ levelProgress, newLevel]))
background.position = CGPoint(x: CGRectGetMidX(self.frame), y: -blueTexture.size().height/2 + blueTexture.size().height * i
you always use the blueTexture.size. try to change it to a constant value.

Related

My SKSpriteNode speed values are not updating properly

What I'm trying to do is update my SKSpriteNodes so I can change their scrolling speeds dynamically, however they aren't really working consistently. I didn't include the code, but I have another method with a switch case that sets the value of platformSpeed whenever the state is changed (in this case, the switch case is changed with UIButtons). In my code I have an SKSpriteNode array and a platformSpeed property that includes didSet so my value is updated properly.
In my method to create the platforms, I grouped my SpriteNodes into platformGroup then looped through them with addChild(). Not sure why it's acting this way but here's a quick video of what it looks like in action:
demonstration clip
So with the buttons I'm changing the switch case, and as you can see, not all of the nodes speeds are updating properly and some get faster than others and eventually pass them. I need them to stay equal distance between each other.
Now here's my code:
class GameScene: SKScene, SKPhysicsContactDelegate {
var platformGroup = [SKSpriteNode]()
var platformSpeed: CGFloat = 1.0 {
didSet {
for platforms in platformGroup {
platforms.speed = platformSpeed
}
}
}
let platformTexture = SKTexture(imageNamed: "platform")
var platformPhysics: SKPhysicsBody!
func createPlatforms() {
let platformLeft = SKSpriteNode(texture: platformTexture)
platformLeft.physicsBody = platformPhysics.copy() as? SKPhysicsBody
platformLeft.physicsBody?.isDynamic = false
platformLeft.scale(to: CGSize(width: platformLeft.size.width * 4, height: platformLeft.size.height * 4))
platformLeft.zPosition = 20
let platformRight = SKSpriteNode(texture: platformTexture)
platformRight.physicsBody = platformPhysics.copy() as? SKPhysicsBody
platformRight.physicsBody?.isDynamic = false
platformRight.scale(to: CGSize(width: platformRight.size.width * 4, height: platformRight.size.height * 4))
platformRight.zPosition = 20
let scoreNode = SKSpriteNode(color: UIColor.clear, size: CGSize(width: frame.width, height: 32))
scoreNode.physicsBody = SKPhysicsBody(rectangleOf: scoreNode.size)
scoreNode.physicsBody?.isDynamic = false
scoreNode.name = "scoreDetect"
scoreNode.zPosition = 40
platformGroup = [platformLeft, platformRight, scoreNode]
let yPosition = frame.width - platformRight.frame.width
let max = CGFloat(frame.width / 4)
let xPosition = CGFloat.random(in: -80...max)
let gapSize: CGFloat = -50
platformLeft.position = CGPoint(x: xPosition + platformLeft.size.width - gapSize, y: -yPosition)
platformRight.position = CGPoint(x: xPosition + gapSize, y: -yPosition)
scoreNode.position = CGPoint(x: frame.midX, y: yPosition - (scoreNode.size.width / 1.5))
let endPosition = frame.maxY + (platformLeft.frame.height * 3)
let moveAction = SKAction.moveBy(x: 0, y: endPosition, duration: 7)
let moveSequence = SKAction.sequence([moveAction, SKAction.removeFromParent()])
for platforms in platformGroup {
addChild(platforms)
platforms.run(moveSequence)
}
platformCount += 1
}
func loopPlatforms() {
let create = SKAction.run { [unowned self] in
self.createPlatforms()
platformCount += 1
}
let wait = SKAction.wait(forDuration: 1.1)
let sequence = SKAction.sequence([create, wait])
let repeatForever = SKAction.repeatForever(sequence)
run(repeatForever)
}
I think I can see what's going wrong. When you change platformSpeed, it changes the speed of all the platforms in platformGroup. And createPlatforms() is being called multiple times. Now, each time it's called you create a pair of platforms and assign these to platformGroup. Since you call the function multiple times, it's overwriting any existing values in the array. That's why changing platformSpeed only updates the speed of the latest platforms you've created---the older platforms stay the same speed because they're not in platformGroup anymore.
To fix this, my advice would be to have platformGroup store all the platforms currently on the screen. You could do this by changing
platformGroup = [platformLeft, platformRight, scoreNode]
to something like
let newNodes = [platformLeft, platformRight, scoreNode]
platformGroup += newNodes
// Alternatively, platformGroup.append(contentsOf: newNodes)
Now you need to make sure you're 1) only adding the new nodes to the scene, and 2) removing the old nodes from platformGroup when they're removed from the parent. You could do this by changing
let moveAction = SKAction.moveBy(x: 0, y: endPosition, duration: 7)
let moveSequence = SKAction.sequence([moveAction, SKAction.removeFromParent()])
for platforms in platformGroup {
addChild(platforms)
platforms.run(moveSequence)
}
to something like
let moveAction = SKAction.moveBy(x: 0, y: endPosition, duration: 7)
for node in newNodes {
let moveSequence = SKAction.sequence([
moveAction,
SKAction.removeFromParent(),
SKAction.run {
self.platformGroup.remove(node)
}
])
addChild(node)
node.run(moveSequence)
}
Now that you're keeping a track of all platforms ever made, your speed changes should be applied consistently to every platform on the screen. Hope this works!

Swift - SpriteKit how do I do infinite background scrolling with two unique backgrounds?

My code works perfectly fine for one common background but I am unable to figure out the required logic have it working perfectly fine if the backgrounds were unique.
override func didMove(to view: SKView)
{
createBackground()
}
func createBackground ()
{
var texture1 = SKTexture(imageNamed: "card1")
let texture2 = SKTexture(imageNamed: "card2")
for i in 0...1
{
let background = SKSpriteNode(texture: texture1)
background.anchorPoint = CGPoint.zero
background.position = CGPoint(x: 0, y: texture1.size().height * CGFloat(i))
addChild(background)
let moveDown = SKAction.moveBy(x: 0, y: -background.size.height, duration: 5)
let reset = SKAction.moveBy(x: 0, y: background.size.height, duration: 0)
let sequence = SKAction.sequence([moveDown, reset])
let forever = SKAction.repeatForever(sequence)
background.run(forever)
texture1 = texture2
}
}
}

How to properly copy SKTileMapNode in swift

When I run loop on node everything works fine, node moving correct. But when i run loop on next action interrupts when next reaches left border of the screen (halfway). I assume something wrong with copy. Thanks!
func moveBackground (name: String, speed: CGFloat = 0.01) {
guard let node = childNode(withName: name) as? SKTileMapNode else {
fatalError("\(name) node not loaded")
}
let width = node.frame.size.width
let startPositionX = width
node.position = CGPoint(x: startPositionX , y: 0)
let next = node.copy() as! SKTileMapNode
next.tileSet = node.tileSet
next.position = CGPoint(x: startPositionX , y: 0)
self.addChild(next)
let distance = width * 2
let duration = TimeInterval(speed * distance)
let moveAction = SKAction.moveBy(x: -distance, y: 0, duration: duration)
let resetAction = SKAction.moveTo(x: startPositionX, duration: 0)
let sequence = SKAction.sequence([moveAction, resetAction])
let loop = SKAction.repeatForever(sequence)
//node.run(loop)
next.run(loop)
}

Swift 3 Expect Argument Error

I am creating some functions in order to make a game similar to Flappy Bird. I am a complete beginner and am trying to understand everything fully as I go before moving on. I have been able to get my obstacle to move but when I attempt to put it into a function to allow me more flexibility later on with multiple obstacles I receive an error.
'Cannot convert type '()' to expected argument type 'SKAction'
class GameScene : SKScene, SKPhysicsContactDelegate {
var Player = SKSpriteNode()
var Ground = SKSpriteNode()
var Roof = SKSpriteNode()
var Background = SKSpriteNode()
let Obstacle1 = SKSpriteNode(imageNamed: "Fire Barrel 1")
override func didMove(to view: SKView) {
// Create Background Color
backgroundColor = bgColor
// Set World Gravity
self.physicsWorld.gravity = CGVector(dx: 0.0, dy: -4.0)
// Create Player
Player = SKSpriteNode(imageNamed: "Player")
Player.setScale(0.5)
Player.position = CGPoint(x: -self.frame.width / 2 + 100, y: -Player.frame.height / 2)
self.addChild(Player)
// Create Ground
Ground = SKSpriteNode(imageNamed: "BGTileBtm")
Ground.anchorPoint = CGPoint(x: 0,y: 0.5)
Ground.position = CGPoint(x: -self.frame.width / 2, y: -self.frame.height / 2)
self.addChild(Ground)
// Create Roof
Roof = SKSpriteNode(imageNamed: "BGTileTop")
Roof.anchorPoint = CGPoint(x: 1,y: 1)
Roof.position = CGPoint(x: -self.frame.width / 2, y: self.frame.height / 2 - Roof.frame.height)
Roof.zRotation = CGFloat(M_PI)
self.addChild(Roof)
// Set Physics Rules
Player.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Player"), size: Player.size)
Player.physicsBody!.affectedByGravity = true
Player.physicsBody!.allowsRotation = false
Ground.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Ground"), size: Ground.size)
Ground.physicsBody!.affectedByGravity = false
Ground.physicsBody!.isDynamic = false
// Obstacle
func addObstacle1(){
Obstacle1.position = CGPoint(x: self.frame.width / 2, y: -self.frame.height / 2 + Obstacle1.frame.height)
Obstacle1.zPosition = 1
addChild(Obstacle1)
}
func moveObstacle1(){
let distance = CGVector(dx: -self.frame.width, dy: 0)
let moveDistance = SKAction.move(by: distance, duration: 5)
run(moveDistance)
}
addObstacle1()
Obstacle1.run(moveObstacle1())
}
Change the declaration of moveObstacle1 to this:
func moveObstacle1() -> SKAction{
let distance = CGVector(dx: -self.frame.width, dy: 0)
let moveDistance = SKAction.move(by: distance, duration: 5)
return moveDistance
}
EDIT:
Regarding your comment,
run is a method. When you call it, it runs the SKAction you passed in. That's it! What you are trying to do is run(moveObstacle1()). What does that mean exactly? How can you pass a method call as a parameter? At runtime, the return value of moveObstacle1() is passed to run. In other words, for run(moveObstacle1()) to compile, moveObstacle1() must return a value using the return statement. And that value must be of type SKAction, since that's the thing you're passing to run.
return is used to return a value from a moveObstacle1(), so that you can call run(moveObstacle1()).
run is just a regular old method.
Change:
Obstacle1.run()
To:
Obstacle1.run(SKAction(moveObstacle1()))
The error message is pretty clear, you need to pass a SKAction to your SKSpriteNode.
Edit
import SpriteKit
import GameplayKit
class GameScene : SKScene, SKPhysicsContactDelegate {
var Player = SKSpriteNode()
var Ground = SKSpriteNode()
var Roof = SKSpriteNode()
var Background = SKSpriteNode()
let Obstacle1 = SKSpriteNode(imageNamed: "Spaceship")
let sprite = SKSpriteNode(imageNamed:"Spaceship")
override func didMove(to view: SKView) {
// Create Background Color
backgroundColor = UIColor.green
// Set World Gravity
self.physicsWorld.gravity = CGVector(dx: 0.0, dy: -4.0)
// Create Player
Player = SKSpriteNode(imageNamed: "Player")
Player.setScale(0.5)
Player.position = CGPoint(x: -self.frame.width / 2 + 100, y: -Player.frame.height / 2)
self.addChild(Player)
// Create Ground
Ground = SKSpriteNode(imageNamed: "BGTileBtm")
Ground.anchorPoint = CGPoint(x: 0,y: 0.5)
Ground.position = CGPoint(x: -self.frame.width / 2, y: -self.frame.height / 2)
self.addChild(Ground)
// Create Roof
Roof = SKSpriteNode(imageNamed: "BGTileTop")
Roof.anchorPoint = CGPoint(x: 1,y: 1)
Roof.position = CGPoint(x: -self.frame.width / 2, y: self.frame.height / 2 - Roof.frame.height)
Roof.zRotation = CGFloat(M_PI)
self.addChild(Roof)
// Set Physics Rules
Player.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Player"), size: Player.size)
Player.physicsBody!.affectedByGravity = true
Player.physicsBody!.allowsRotation = false
Ground.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Ground"), size: Ground.size)
Ground.physicsBody!.affectedByGravity = false
Ground.physicsBody!.isDynamic = false
sprite.size = CGSize(width:50, height: 50)
sprite.position = CGPoint(x:self.frame.midX, y:self.frame.midY);
self.addChild(sprite)
Obstacle1.run(SKAction(moveObstacle1()))
}
func moveObstacle1(){
let action = SKAction.moveTo(x: self.frame.size.width * 2, duration: 20)
sprite.run(action)
}
}

Swift: Pause endless scrolling background (i.e. infinite loop)

In my game I would like certain things to pause when the "endGame" function is called, while other things stay running. I have been able to accomplish this for most actions by using the "paused" boolean, but I can't get it to work for my scrolling background. I know the problem has to do with how the background action is contained in a for loop, but I'm not sure how to get around this. Here is an example of my code:
class GameScene: SKScene {
var sun = SKSpriteNode()
var background = SKSpriteNode()
override func didMoveToView(view: SKView) {
sun = SKSpriteNode(imageNamed: "Sun")
sun.zPosition = -2
sun.setScale(0.8)
sun.position = CGPoint(x: self.frame.size.width * 0.632, y: self.frame.size.height * 0.90)
let rotate = SKAction.rotateByAngle(CGFloat(M_PI_2), duration: NSTimeInterval(10))
let rotateForever = SKAction.repeatActionForever(rotate)
sun.runAction(rotateForever)
addChild(sun)
let backgroundTexture = SKTexture(imageNamed: "Background")
backgroundTexture.filteringMode = .Nearest
let moveBackground = SKAction.moveByX(-backgroundTexture.size().width, y: 0, duration: NSTimeInterval(0.004 * backgroundTexture.size().width * 2.0))
let resetBackground = SKAction.moveByX(backgroundTexture.size().width, y: 0, duration: 0.0)
let moveBackgroundForever = SKAction.repeatActionForever(SKAction.sequence([moveBackground,resetBackground]))
for var i:CGFloat = 0; i < 4.0 + self.frame.size.width / ( backgroundTexture.size().width ); ++i {
background = SKSpriteNode(texture: backgroundTexture)
background.setScale(0.5)
background.zPosition = -1
background.position = CGPointMake(i * background.size.width, backgroundVert)
background.runAction(moveBackgroundForever)
addChild(background)
}
}
func endGame {
sun.paused = true
background.paused = true
}
}
In this example the "paused" boolean works perfectly for the rotating SKSpriteNode called "sun", but it doesn't work for the background. Any thoughts?
What you should do is create 1 SKNode for your background sprites, add all the background nodes to this node, then add the master node to your scene. Then, when you need to pause, you only pause the master node, and the master node will then pause all of its children
for var i:CGFloat = 0; i < 4.0 + self.frame.size.width / (backgroundTexture.size().width ); ++i {
var background = SKSpriteNode(texture: backgroundTexture)
background.setScale(0.5)
background.zPosition = -1
background.position = CGPointMake(i * background.size.width, backgroundVert)
background.runAction(moveBackgroundForever)
self.background.addChild(background)
}
addChild(background)
Looks like you are creating multiple backgrounds in the for loop. But you are pausing only one.