Setting a repeated background in an SKScene - swift

I am building an iOS game using the SpriteKit library. I have a background of stars. However, I don't want to upload a static image, because it might scale incorrectly depending on the device size. So I got a png that I repeat over to fill up the screen. I tried doing this:
self.backgroundColor = UIColor(patternImage: UIImage(named: "background.png")!);
However, it only sets the background to a black color. Does anyone have any suggestions on how to do this?

I think this will help you, Firstly set the background using the below code:
for index in 0..<2 {
let bg = SKSpriteNode(imageNamed: "Your image name")
bg.position = CGPoint(x: index * Int(bg.size.width), y: 0)
bg.anchorPoint = CGPoint(x: 0, y: 0)
bg.name = "background"
self.addChild(bg)
}
and then use this code to move the background:
self.enumerateChildNodes(withName: "background", using: {(node, stop) -> Void in
if let bg = node as? SKSpriteNode {
bg.position = CGPoint(x: bg.position.x - 3.0, y: bg.position.y)
if bg.position.x <= -bg.size.width {
bg.position = CGPoint(x: bg.position.x + bg.size.width * 2, y: bg.position.y)
}
}
})

Related

I'm trying to create an infinite vertical scrolling background with SpirteKit, however I can't seem to get the code to work

The loop isn't timed correctly and resets too quickly. The looped image is also cut off showing only a fraction of it rather than the full looped background image stitched to the previous one.
I've attempted playing around with the .position, .size, and the SKAction values, but I just can't seem to tweak it to where it works properly.
func scrollingBackground() {
let BGTexture = SKTexture(imageNamed: "Background")
for i in 0 ... 1 {
let background = SKSpriteNode(texture: BGTexture)
background.zPosition = -30
background.anchorPoint = CGPoint(x: 0, y: 0)
background.size = CGSize(width: (frame.size.width), height: (frame.size.height))
background.position = CGPoint(x: 0, y: -BGTexture.size().height + (BGTexture.size().height + (BGTexture.size().height * CGFloat(i))))
addChild(background)
let scrollUp = SKAction.moveBy(x: 0, y: BGTexture.size().height, duration: 20)
let scrollReset = SKAction.moveBy(x: 0, y: -BGTexture.size().height, duration: 0)
let scrollLoop = SKAction.sequence([scrollUp, scrollReset])
let scrollForever = SKAction.repeatForever(scrollLoop)
background.run(scrollForever)
}
I hoping to get a seamless infinite background loop that runs indefinitely rather than this jumpy mess I currently have. I might add that my background image isn't originally sized perfectly to the devices, but I'm wanting to make sure my background fits to all iPhone devices.
Your first BG is at y = 0, Your second BG is at y = height. I am assuming that you are on anchor point 0.5 0.5, which would make height / 2 off screen. Your action moves BG 1 exactly to where it is off screen, then bumps back to 0. Your BG2 starts off screen, continues to move off screen, then when it hits 2 * height, resets to 1 * height, thus never appears on the screen. This is going to give the appearance of a black bar at the bottom, because BG2 is never drawn. Multiply your i by negative height instead, and that should cause the BG2 to appear on the bottom instead.
func scrollingBackground() {
let BGTexture = SKTexture(imageNamed: "Background")
for i in 0 ... 1 {
let background = SKSpriteNode(texture: BGTexture)
background.zPosition = -30
background.anchorPoint = CGPoint(x: 0, y: 0)
background.size = CGSize(width: (frame.size.width), height: (frame.size.height))
background.position = CGPoint(x: 0, y: -BGTexture.size().height + (BGTexture.size().height + (-BGTexture.size().height * CGFloat(i))))
addChild(background)
let scrollUp = SKAction.moveBy(x: 0, y: BGTexture.size().height, duration: 20)
let scrollReset = SKAction.moveBy(x: 0, y: -BGTexture.size().height, duration: 0)
let scrollLoop = SKAction.sequence([scrollUp, scrollReset])
let scrollForever = SKAction.repeatForever(scrollLoop)
background.run(scrollForever)
}

How to identify specific nodes in SKCamera's view?

I am trying to recycle same SKSpriteNode to make continuous background. I created SKSpriteNode as bg0, bg1, bg2 and positioned them properly when presenting the scene. Somehow, below code ONLY re-positions bg0 once.
cam is SKCameraNode, therefore, I am checking whether camera contains the background nodes. Due to their size, always one of them should not be visible in the camera viewport. However, like I said this only works once and when camera shows recycled bg0 does not recognize it.
Thank you in advance.
PS: I already tried cam.intersects(bg0) as well, got the same result.
func updateBgPos() {
if (player?.position.y)! > self.frame.height {
if !cam.contains(bg0!) {
print("bg0 is not in the scene")
newPosBgY = (bg2?.position.y)! + self.frame.height
bg0?.physicsBody = nil
bg0?.position = CGPoint(x: self.frame.width / 2, y: newPosBgY)
bg0?.physicsBody = bg1?.physicsBody
} else if !cam.contains(bg1!) {
print("bg1 is not in the scene")
newPosBgY = (bg0?.position.y)! + self.frame.height
bg1?.physicsBody = nil
bg1?.position = CGPoint(x: self.frame.width / 2, y: newPosBgY)
bg1?.physicsBody = bg0?.physicsBody
} else if !cam.contains(bg2!) {
print("bg2 is not in the scene")
newPosBgY = (bg1?.position.y)! + self.frame.height
bg2?.physicsBody = nil
bg2?.position = CGPoint(x: self.frame.width / 2, y: newPosBgY)
bg2?.physicsBody = bg1?.physicsBody
}
}
}
Well, finally I've figured it out how to achieve this. I hope this helps someone else.
I initially created and placed two SKSpriteNode as background as bg0 and bg1 like below:
bg0 = SKSpriteNode(imageNamed: "bg0")
bg1 = SKSpriteNode(imageNamed: "bg1")
bg0!.name = "bg0"
bg1!.name = "bg1"
bg0?.scale(to: CGSize(width: sceneWidth, height: sceneHeight))
bg1?.scale(to: CGSize(width: sceneWidth, height: sceneHeight))
bg0?.zPosition = zPosBg
bg1?.zPosition = zPosBg
backgroundArr.append(bg0!)
backgroundArr.append(bg1!)
bg0?.position = CGPoint(x: sceneWidth / 2, y: 0)
bg1?.position = CGPoint(x: sceneWidth / 2, y: sceneHeight)
self.addChild(bg0!)
self.addChild(bg1!)
After, on the update method I called below function:
func updateBgPos() {
guard let playerPosY = player?.position.y else { return }
guard let bg0PosY = bg0?.position.y else { return }
guard let bg1PosY = bg1?.position.y else { return }
if playerPosY - bg0PosY > sceneHeight / 2 + camFollowGap {
print("bg0 is not in the scene")
newPosBgY = bg1PosY + sceneHeight
bg0?.position = CGPoint(x: sceneWidth / 2, y: newPosBgY)
} else if playerPosY - bg1PosY > sceneHeight / 2 + camFollowGap {
print("bg1 is not in the scene")
newPosBgY = bg0PosY + sceneHeight
bg1?.position = CGPoint(x: sceneWidth / 2, y: newPosBgY)
}
}
PS: camFollowGap is CGFloat value that I subtracted when I position my camera to the player. When dealing with continuous background I had to add that value into calculation to avoid positioning delay and appearances of temporary gaps between backgrounds.

Color a sprite a different color from its texture

I was trying to change the color of a SKSPriteNode from green to white. It is originally green in its texture. However, in some cases, I want it to be completely white. I tried doing this:
func loadBackButton() {
backButton = SKSpriteNode(imageNamed: "BackButton")
backButton.color = .white
backButton.size = CGSize(width: self.size.width * CGFloat(0.125), height: self.size.width * CGFloat(0.125))
backButton.position = CGPoint(x: self.size.width * CGFloat(-0.426), y: self.size.height * CGFloat(0.449))
self.addChild(backButton)
}
That did not change anything. So I did a little research and tried doing this instead:
func loadBackButton() {
backButton = SKSpriteNode(imageNamed: "BackButton")
backButton.colorBlendFactor = 1
backButton.color = .white
backButton.size = CGSize(width: self.size.width * CGFloat(0.125), height: self.size.width * CGFloat(0.125))
backButton.position = CGPoint(x: self.size.width * CGFloat(-0.426), y: self.size.height * CGFloat(0.449))
self.addChild(backButton)
}
However, that also didn't change anything. Does anyone know why is it not changing anything?
You can always add an overlay using an SKCropNode and a white sprite node if you need it to go white.
Basically:
let croppedNode = SKCropNode()
croppedNode.maskNode = sprite.copy() as? SKNode
sprite.addChild(croppedNode)
let whiteNode = SKSpriteNode(color:.white,size:sprite.size)
croppedNode.addChild(whiteNode)
croppedNode.zPosition = 1

How to change color of SKShapeNode randomly each time spawned?

Currently, when running this code, a colored ball runs from the top of the screen to the bottom. When it reaches the bottom it respawns at the top and drops again. I need the ball to change to a random color each time it spawns again. I have a colorChange action but it still doesn't change the color of the ball when it runs.
import SpriteKit
class GameScene: SKScene {
let ball = SKShapeNode(circleOfRadius: 20)
let label = SKLabelNode(fontNamed: "Futura")
let colorChange = SKAction.colorizeWithColor(.blueColor(), colorBlendFactor: 1, duration: 1)
let randomRed = Int(arc4random_uniform(255))
let randomGreen = Int(arc4random_uniform(255))
let randomBlue = Int(arc4random_uniform(255))
override func didMoveToView(view: SKView) {
let sceneBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
self.physicsBody = sceneBody
//Ball Transition
let ballTransition = SKAction.sequence([SKAction.fadeInWithDuration(1)])
ball.runAction(ballTransition)
ball.fillColor = UIColor(red: 100, green: 100, blue: 0, alpha: 1)
ball.physicsBody = SKPhysicsBody(circleOfRadius: 25)
ball.physicsBody?.affectedByGravity = false
ball.position = CGPoint(x: self.frame.size.width/2, y: CGFloat(self.frame.size.height*1))
ballMovement()
self.addChild(ball)
self.addChild(label)
}
func ballMovement() {
let moveBall = SKAction.moveToY(0, duration: 3)
let goBackUp = SKAction.moveToY(self.frame.size.height, duration:0)
let keepFalling = SKAction.sequence([moveBall, goBackUp, colorChange])
ball.runAction(SKAction.repeatActionForever(keepFalling))
//Label Sprite
label.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
label.fontColor = SKColor.redColor()
label.fontSize = 30
}
override func update(currentTime: CFTimeInterval) {
label.text = "\(ball.position.y)"
if ball.position.y < 26 {
ball.position = CGPoint(x: self.frame.size.width/2, y: CGFloat(self.frame.size.height*1))
}
}
}
Instead of using an SKAction to change the color, just do
ball.color = UIColor...
To generate a random color check this out: How to make a random background color with Swift
and use that to generate a random color
ball.color = .randomColor()
I suggest you don't use an SKShape for an important piece that moves because I find that they can slow down the app and cause problems. I would use an SKSpritenode instead for the ball. If you then make the ball default as all white, you can change the colour by doing this:
ball.colorBlendFactor = 1
ball.color = SKColor(hue: randomNumberFunctionBetweenZeroAndOne(), saturation: 0.75, contrast: 0.5)
The hue should be a random value between 0 and 1.
If you put this inside a function, you could call the function to change the colour of the ball inside and SKAction or anywhere else.

Making SKLabelNode as a crop node of SKShapeNode

Can't find in web, how to make SKLabelNode cropping SKShapeNode. When i colorize the background, my goal is colorize the label too with the same method, so both of them have to colorize simultaneously. But can't imagine how to crop SKShapeNode with this label. Help me please!
But can't imagine how to crop SKShapeNode with this label.
If I understand you correctly, you can set SKLabelNode as a mask of a SKCropNode, like this:
override func didMoveToView(view: SKView) {
backgroundColor = .blackColor()
let cropNode = SKCropNode()
cropNode.position = CGPoint(x: frame.midX, y: frame.midY)
cropNode.zPosition = 1
let mask = SKLabelNode(fontNamed: "ArialMT")
mask.text = "MASK"
mask.fontColor = .greenColor()
mask.fontSize = 28
cropNode.maskNode = mask
let nodeToMask = SKSpriteNode(color: .purpleColor(), size: CGSize(width: 200, height: 200))
nodeToMask.position = CGPoint(x: 0, y: 0)
nodeToMask.name = "character"
cropNode.addChild(nodeToMask)
//Now colorize the sprite which acts like background
let colorize = SKAction.sequence([
SKAction.colorizeWithColor(.orangeColor(), colorBlendFactor: 0, duration: 1),
SKAction.colorizeWithColor(.purpleColor(), colorBlendFactor: 0, duration: 1)
])
nodeToMask.runAction(SKAction.repeatActionForever(colorize), withKey: "colorizing")
addChild(cropNode)
}
The result: