i have a Problem and need your help.
I have modified a code from this old thread
here
That i get a vertical moving background. The code works nice. But i have between every moving background image a tiny black line.
looks like something overlaps. But why?
It would be nice if anyone can help me to fix this error.
Thanks alot
Here is my code:
var bg = SKSpriteNode()
var bg2 = SKSpriteNode()
var bg3 = SKSpriteNode()
var parallax = SKAction()
override func didMove(to view: SKView) {
bg = SKSpriteNode(imageNamed: "back1")
bg.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
bg.zPosition = 1
bg.size = self.size
bg2 = SKSpriteNode(imageNamed: "back2")
bg2.position = CGPoint(x: self.size.width/2, y: self.size.height/2+self.size.height)
bg2.zPosition = 1
bg2.size = self.size
bg3 = SKSpriteNode(imageNamed: "back3")
bg3.position = CGPoint(x: self.size.width/2, y:self.size.height/2+self.size.height+self.size.height)
bg3.zPosition = 1
bg3.size = self.size
self.addChild(bg)
self.addChild(bg2)
self.addChild(bg3)
parallax = SKAction.repeatForever(SKAction.move(by: CGVector(dx: 0, dy: -self.frame.size.height), duration: 4))
bg.run(parallax)
bg2.run(parallax)
bg3.run(parallax)}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if bg.position.y <= -self.frame.size.height {
bg.position.y = self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
if bg2.position.y <= -self.frame.size.height {
bg2.position.y = self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
if bg3.position.y <= -self.frame.size.height {
bg3.position.y = self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
}
You are having floating point rounding issues. The concept of 1/2 a pixel does not exist, so when you get to a position like 12.5, the system needs to either make it 12, or 13.
Now, since decimal does not convert well to fractal binary you are going to end up with numbers like 12.499929932092434234234324 and 12.50000000342423423424, but as far as you know, it is still 12.5.
To fix this, you need to force your position to always round in the same direction, either up or down.
You are probably going to want to round down since most grids work in a 0 based indexing system.
To fix your code, we need to do:
override func update(_ currentTime: TimeInterval) {
bg.position.x.round(.down)
bg.position.y.round(.down)
bg2.position.x.round(.down)
bg2.position.y.round(.down)
bg3.position.x.round(.down)
bg3.position.y.round(.down)
// Called before each frame is rendered
if bg.position.y <= -self.frame.size.height {
bg.position.y += self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
if bg2.position.y <= -self.frame.size.height {
bg2.position.y += self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
if bg3.position.y <= -self.frame.size.height {
bg3.position.y += self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
}
Now of course, as you get better with development, you are going to want to move these types of things outside of your update function. Eventually, you will want to perform these checks when position changes, this way if nothing is moving, you are not needlessly executing these lines of code to fix a position that does not need fixing.
Edit:
Noticed another problem, you need to do += the height, not = height, because if you are at -1, that will also cause a gap, since you are not accounting for the line of height - 1 (Basically, your first image ends at height - 2, and your new image starts at height, making a gap at height - 1)
Related
I'm currently working on an application where I'm trying to set up a moving background. I have a transparent image full of clouds that I'm currently using. My problem is, how can I make it move more smoother? I've tried to play with the speeds but it still looks laggy. Any help would be a blessing.
Here's a video of what I got going. http://sendvid.com/78ggkzcj
And here's a picture of the cloud image. Cloud Image
Here's my code. What you think I should change or do differently?
class GameScene: SKScene {
// Background
let background = SKSpriteNode(texture:SKTexture(imageNamed: "background"))
// Clouds
var mainCloud = SKSpriteNode()
var cloud1Next = SKSpriteNode()
// Time of last frame
var lastFrameTime : TimeInterval = 0
// Time since last frame
var deltaTime : TimeInterval = 0
override func didMove(to view: SKView) {
background.position = CGPoint(x: 0, y: 0)
// Prepare the clouds sprites
mainCloud = SKSpriteNode(texture:
SKTexture(imageNamed: "cloudbg1"))
mainCloud.position = CGPoint(x: 0, y: 0)
cloud1Next = mainCloud.copy() as! SKSpriteNode
cloud1Next.position =
CGPoint(x: mainCloud.position.x + mainCloud.size.width,
y: mainCloud.position.y)
// Add the sprites to the scene
self.addChild(background)
self.addChild(mainCloud)
self.addChild(cloud1Next)
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
// First, update the delta time values:
// If we don't have a last frame time value, this is the first frame,
// so delta time will be zero.
if lastFrameTime <= 0 {
lastFrameTime = currentTime
}
// Update delta time
deltaTime = currentTime - lastFrameTime
// Set last frame time to current time
lastFrameTime = currentTime
// Next, move each of the four pairs of sprites.
// Objects that should appear move slower than foreground objects.
self.moveSprite(sprite: mainCloud, nextSprite:cloud1Next, speed:100)
}
// Move a pair of sprites leftward based on a speed value;
// when either of the sprites goes off-screen, move it to the
// right so that it appears to be seamless movement
func moveSprite(sprite : SKSpriteNode,
nextSprite : SKSpriteNode, speed : Float) -> Void {
var newPosition = CGPoint.zero
// For both the sprite and its duplicate:
for spriteToMove in [sprite, nextSprite] {
// Shift the sprite leftward based on the speed
newPosition = spriteToMove.position
newPosition.x -= CGFloat(speed * Float(deltaTime))
spriteToMove.position = newPosition
// If this sprite is now offscreen (i.e., its rightmost edge is
// farther left than the scene's leftmost edge):
if spriteToMove.frame.maxX < self.frame.minX {
// Shift it over so that it's now to the immediate right
// of the other sprite.
// This means that the two sprites are effectively
// leap-frogging each other as they both move.
spriteToMove.position =
CGPoint(x: spriteToMove.position.x +
spriteToMove.size.width * 2,
y: spriteToMove.position.y)
}
}
}
}
Your code looks fine. You are getting low fps because you are running your game in the simulator. If you run on a real device, it should be smooth.
I'm not sure what tools I should use for what I'm trying to do since I'm only really familiar with SKSpriteNodes and a little bit with SKShapeNodes.
My mission is as follows:
Add a line to the scene, SKShapeNode?
Rotate the line along it's bottom point (beginning point?) by some angle. Imagine a clock hand for this, rotating around the bottom point
Find the new point (x,y coord) of the top point (end point?) after the line has been translated
Does anyone know how I can accomplish this? I'm currently using an SKShapeNode for my line and rotating it with .zRotation but I can't seem to accomplish my goal. There doesn't seem to be an achorPoint property for SKShapeNodes, so I can't change the point of rotation. Also I'm clueless on how to find the position of the end point of my line AFTER it has been rotated, I created it as follows:
let linePath = CGMutablePath()
linePath.move(to: begin)
linePath.addLine(to: end)
let line = SKShapeNode()
line.path = linePath
line.strokeColor = UIColor.black
line.lineWidth = 5
SceneCoordinator.shared.gameScene.addChild(line)
I'm rotating using:
public func rotate(angle: Double) {
var transform = CGAffineTransform(rotationAngle: CGFloat(angle))
line.path = linePath.mutableCopy(using: &transform)
}
SKShapeNode can be pretty expensive if you notice your FPS dropping. Also, you can easily turn a shape into a sprite to get .anchorPoint, but be warned that anchorpoint's behavior is not always as expected and you may have bugs later on (especially with physics):
func shapeToSprite(_ shape: SKShapeNode) -> SKSpriteNode {
let sprite = SKSpriteNode(texture: SKView().texture(from: shape))
sprite.physicsBody = shape.physicsBody // Or create a new PB from alpha mask (may be slower, IDK)
shape.physicsBody = nil
return sprite
}
override func didMove(to view: SKView) {
let shape = SKShapeNode(circleOfRadius: 60)
let sprite = shapeToSprite(shape)
sprite.anchorPoint = CGPoint()
addChild(sprite)
}
Otherwise, you are going to have to either 1), redraw the line with the correct rotation, 2), rotate the shape then reposition it at a new location...
Both are going to be MATH :[ so hopefully the anchorppoint works for you
I'm creating a simple game with Swift and SpriteKit.
I want to add an endless background (vertically), I only found answers for background with images but I need to do it without image, only background color.
I thought about checking if the player is in the frame.maxY, if so, to move it back to the starting point, but I was wondering if there is a better idea.
//Does not matter which ring we chose as all ring's 'y' position is the same.
func moveBackgroundUp(){
if ((mPlayer.position.y >= self.frame.maxY) || (mRingOne.position.y >= self.frame.maxY)) {
mPlayer.position.y = 150 //mPlayers original starting point.
for ring in mRings {
ring.position.y = 350
}
}
}
Thanks in advance!
Don't just move a background up the screen, that' really isn't the way to go about it. What you should do is detect the position of the camera (assuming it moves with the player), and when it's position is about to occupy space outside of the occupied space of your current background sprite, add a new background sprite to the scene where the last one left off. Here is an example of how to do that with just a red sprite:
First add a property to the scene to track level position:
// To track the y-position of the level
var levelPositionY: CGFloat = 0.0
Now create a method to update your background:
func updateBackground() {
let cameraPos = camera!.position
if cameraPos.y > levelPositionY - (size.height * 0.55) {
createBackground()
}
}
func createBackground() {
// Create a new sprite the size of your scene
let backgroundSprite = SKSpriteNode(color: .red, size: size)
backgroundSprite.anchorPoint = CGPoint(x: 0.5, y: 0)
backgroundSprite.position = CGPoint(x: 0, y: levelPositionY)
// Replace backgroundNode with the name of your backgroundNode to add the sprite to
backgroundNode.addChild(backgroundSprite)
levelPositionY += backgroundSprite.size.height
}
Now you want to call updateBackground inside your overridden update(_:) method:
override func update(_ currentTime: TimeInterval) {
// All your other update code
updateBackground()
}
Also, make sure to create an initial background when you first create the scene:
override func didMove(to view: SKView) {
createBackground()
}
NOTE! - It's important to set the custom anchor point for the background sprite for this code to work properly. An anchor of (0.5, 0) allows the background sprite to be anchored in the middle of the scene on the x-axis, but at the bottom of the scene on the y-axis. This allows you to easily stack one on top of the other.
EDIT - I forgot to mention that it's also a good idea to conserve resources and remove any background nodes that are outside the viewable area and won't be coming back in (i.e. a continuous scrolling game where you can't go backwards). You could do that by updating your updateBackground method above:
func updateBackground() {
let cameraPos = camera!.position
if cameraPos.y > levelPositionY - (size.height * 0.55) {
createBackground()
}
// Make sure to change 'backgroundNode' to whatever the name of your backgroundNode is.
for bgChild in backgroundNode.children {
// This will convert the node's coordinates to scene's coordinates. See below for this function
let nodePos = fgNode.convert(fgChild.position, to: self)
if !isNodeVisible(bgChild, positionY: nodePos.y) {
// Remove from it's parent node
bgChild.removeFromParent()
}
}
}
func isNodeVisible(_ node: SKNode, positionY: CGFloat) -> Bool {
if !camera!.contains(node) {
if positionY < camera!.position.y - size.height * 2.0 {
return false
}
}
return true
}
So above you just loop through all the children inside your background node and detect if they are out of view, and if so remove them from the parent. Make sure to change my generic backgroundNode to whatever the name of your background node is.
I have a circle moving up a line, and when that circle reaches a certain y point, how can I make it so that another node would generate from below?
Here is the code I currently have for populating the circles, but I am not able to use it with a physics body, as it generates too many nodes and slows down my app:
func createCirclesOnLine(line: CGFloat) {
var currentY : CGFloat = -110
let maxY = self.size.width * 15
let spacing : CGFloat = 120
while currentY < maxY {
let circle = SKSpriteNode(imageNamed: "first#2x")
circle.physicsBody?.dynamic = false
circle.position = CGPointMake(line, currentY)
//circle.physicsBody?.restitution = -900
circle.size = CGSizeMake(75, 75)
// circle.physicsBody = SKPhysicsBody(rectangleOfSize: circle.size)
let up = SKAction.moveByX(0, y: 9000, duration: 90)
circle.runAction(up)
foregroundNode.addChild(circle)
currentY += CGFloat((random() % 400) + 70)
}
Will post more code if necessary.
There are two ways you can go about this. One is to simply check every circle's y position to see if it's above the screen. You'll need a reference to the circles so...
class GameScene: SKScene {
var circles = Array<SKSpriteNode>()
...
In your createCirlcesOnLine function, add each circle to the array as you create it.
...
self.addChild(circle)
circles.append(circle)
Then, in your update method, enumerate through the circles to see if any of them are above the top of the screen.
override func update(currentTime: NSTimeInterval) {
for circle in circles {
if circle.position.y > self.size.height + circle.size.height/2 {
//Send circle back to the bottom using the circle's position property
}
}
}
This solution will work but causes a lot of unnecessary checks on every update cycle.
A second more efficient (and slightly more complicated) recommendation is to add an invisible node above the top of the screen that stretches the screen width. When the circle collides with it, just move it to the bottom of the screen. Look into implementing the SKPhysicsContactDelegate protocol and what needs to happen for that to work. If you run into problems with this solution, post a separate question with those issues.
I am trying to get my background to move endless from right to left.
My let:
let background = SKSpriteNode(imageNamed: "background")
let background2 = SKSpriteNode(imageNamed: "background")
my didMoveToView
background.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
background.zPosition = 2
background2.position = CGPointMake(CGRectGetMidX(self.frame)+(background.position.x/2),CGRectGetMidY(self.frame))
background2.zPosition = 2
background.setScale(2)
background2.setScale(2)
self.addChild(background)
self.addChild(background2)
var backgroundMovement = SKAction.moveByX(background.size.width, y: 0, duration: 1)
background.runAction(backgroundMovement, completion: {() -> Void in
self.background.position = CGPointZero
})
var backgroundMovement2 = SKAction.moveByX(background2.size.width, y: 0, duration: 1)
background2.runAction(backgroundMovement2, completion: {() -> Void in
self.background2.position = CGPointZero
})}
My update func is empty.
I have uploaded a picture of how it looks like when running on device: http://s18.postimg.org/kbn83tvmx/i_OS_Simulator_Screen_Shot_09_Aug_2015_23_33_39.png
The image is still on half of the screen, and the image does not move now either.
The reason your background only takes up half of the screen is because you are using the regular size of the image in pixels. You need to edit the xScale and yScale properties in order to make it make it bigger. What you need is:
background.setScale(2)
Next, to move the background sprites, use SKAction instead since you have more control over timing. The motion within update won't be consistant because, unless you have an algorithm to control exactly when update: is called, you will see that the movement speeds up or slows down. To move the background, use code that looks something like this after your didMoveToView method:
var backgroundMovement = SKAction.moveByX(background.size.x, y: 0, duration: someDuration)
background.runAction(background, completion: {() -> Void in
//place background image at start again
background.position = ...
})
Loop this code if you want the background to move continuously and edit it in any way you'd like to make it look better (like having both backgrounds move with SKAction). Hope this helps
I'm going to copy and paste my code from another game I've made. This code moves a background from right-left forever. I'll do best to explain this code.
func createBackground () {
var backgroundTexture = SKTexture(imageNamed: "bg")
var moveBackgroundByX = SKAction.moveByX(-backgroundTexture.size().width, y: 0, duration: backgroundSpeed)
var replaceBackground = SKAction.moveByX(backgroundTexture.size().width, y: 0, duration: 0)
var moveBackgroundForever = SKAction.repeatActionForever(SKAction.sequence([moveBackgroundByX, replaceBackground]))
for var i:CGFloat = 0; i < 3; i++ {
background = SKSpriteNode(texture: backgroundTexture)
background.position = CGPoint(x: backgroundTexture.size().width/2 + (backgroundTexture.size().width * i), y: CGRectGetMidY(self.frame))
background.size.height = self.frame.height
background.runAction(moveBackgroundForever)
movingObjects.addChild(background)
}
}
What you need to do is:
Move background (moveBackgroundByX). This moves the background in the X direction. You need to set a duration.
Replace background1 (replaceBackground).
Create an ACTION forever that moves background1, then replaces it automatically with NO delay.
Run a LOOP that decides how many backgrounds you will need. Maybe you might need more than 3.
Inside that loop, adjust the background.position.
Call runAction on your SKSpriteNode instance.
Add the new SKSpriteNode into your another SKView.
If you have any questions please let me know.