Infinite Background problem with division SpriteKit - swift

I've been trying to implement an infinite background animation, which should change between 4 images of equal height and then repeat the sequence. However, it does not seem to work properly.
Note anchorPoint = CGPoint(x: 0.5, y: 0)
func updateBackground(currentTime: TimeInterval){
var delta: CGFloat = 0.0
if lastUpdate != nil {
delta = CGFloat(currentTime - lastUpdate)
}
//First increment position
activeBackground1.position.y += delta*backgroundVelocity
activeBackground2.position.y += delta*backgroundVelocity
//Detect bounds surpass
if activeBackground1.position.y > activeBackground1.size.height + screen.height/2 {
lastSky = (lastSky + 1)%4
sky1 = SKTexture(imageNamed: "sky" + String(lastSky))
activeBackground1.texture = sky1
//Reposition: background1 new position is equal to minus the entire height of
//background2 + its y size.
activeBackground1.position.y = -abs(activeBackground2.size.height-activeBackground2.position.y)
}
if activeBackground2.position.y > activeBackground2.size.height + screen.height/2 {
lastSky = (lastSky + 1)%4
sky1 = SKTexture(imageNamed: "sky" + String(lastSky))
activeBackground2.texture = sky1
activeBackground2.position.y = -abs(activeBackground1.size.height-activeBackground1.position.y)
}
}
The update algorithm works fine, but when it is needed to reposition one of the two background, it seems there is an offset of about 10.0 CGFloat from one background to another. What am I doing wrong?
EDIT: It turned out that the error was located in my image, which presented some blank rows and therefore generated visualisation glitches. So my code works perfectly.

I do the test and most likely you should use something like:
activeBackground2.position.y = activeBackground1.size.height + activeBackground1.position.y
instead of
activeBackground2.position.y = -abs(activeBackground1.size.height-activeBackground1.position.y)
I did this example and it works correctly: https://github.com/Maetschl/SpriteKitExamples/tree/master/InfiniteBackground/InfiniteBackground
Feel free to see and use.

Your problem is floating point math causing rounding errors. I am on a phone right now so I can’t wrote code, but what you want to do is have 1 parent SKNode to handle your entire background.
Add your background slivers to the parent node.
You then place the moving action on the parent only.
As each sliver leaves the screen, you take the sliver, and move it to the end of the other slivers.
This jumping should always be done with integer math, leaving no holes.
Basically:
The floating point moving math is done on the parent node.
The integer based tile jumping is done on each of the slivers.

Related

Swift UIKit dynamics - Vary UIPushBehavior force magnitude by distance from center of source

Can someone suggest how I can vary the UIPushBehavior magnitude force by distance from source. To mirror the affect of the force of wind from a fan on another object. So the closer the object is to the fan the stronger the force.
if String(describing: identifier) == "fan1" {
self.push = UIPushBehavior(items: [self.object], mode: .instantaneous)
self.push.setAngle(.pi/4, magnitude: 1)
self.animator.addBehavior(self.push)
self.push.addItem(self.object)
}
Use a UIFieldBehaviour to create a linear gravitational field in the direction of the fan. Then you can specify a falloff:
var forceField: UIFieldBehaviour!
// ...
forceField = UIFieldBehavior.linearGravityField(direction: CGVector(dx: 1, dy: -5))
// example values for a "fan" on the bottom left blowing mostly upwards:
forceField.position = CGPoint(x: view.bounds.minX, y: view.bounds.maxY)
forceField.region = UIRegion(radius: 3000)
forceField.minimumRadius = 100
forceField.falloff = 5
forceField.strength = 10
forceField.addItem(view1)
forceField.addItem(view2)
animator.addBehavior(forceField)
Have fun playing around with these values!
Adding collision, another gravity behaviour, and a dynamic item behaviour to two views, we get the following effect:
That feels like a fan on the bottom left to me!
You can also choose a radial gravitational field positioned at where the fan is, if your fan is in a corner and blows "radially", but note that you should use a negative value for strength in that case to say that the field repels rather than attracts.

Fast alternatives for UIBezierPath

I'm making app, that can build graphs.
I'm working with UIBezierPath, and everything was fine until I decided to make it possible to move and scale the graph using gestures.
here's how i'm drawing:
var isFirstPoint = true
color.set()
funcPath.lineWidth = width
for i in 0...Int(bounds.size.width * contentScaleFactor) {
let cgX = CGFloat(i) / contentScaleFactor
let funcX = Double ((cgX - bounds.width / 2) / scale)
let funcY = function(funcX) * Double(scale)
guard !funcY.isNaN else { continue }
let cgY = -( CGFloat(funcY) - bounds.height / 2)
if isFirstPoint {
funcPath.move(to: CGPoint(x: cgX, y: cgY))
isFirstPoint = false
} else {
if cgY > max(bounds.size.width, bounds.size.height) * 2 {
isFirstPoint = true
} else {
funcPath.addLine(to: CGPoint(x: cgX, y: cgY) )
}
}
}
funcPath.stroke()
is there is a faster way to do it?
A couple of thoughts:
Rebuilding the path for every pixel can be an expensive process. One way to reduce this cost is to reduce the number of data points you render. For example, if you’re adjusting scale and rebuilding the whole path, you might want to use some step factor when updating the path mid-gesture, and replace your for loop with:
let step = 4
for i in stride(from: 0, through: Int(bounds.size.width * contentScaleFactor), by: step) {
...
}
Then, when the gesture ends, you might update the path one more time, with a step of 1.
The other way to do this is to not update the scale and call setNeedsDisplay each time, but rather to just update the transform of the view. (This can result in super fast behavior when zooming in, but is obviously problematic when zooming out, as portions excluded from the path won’t be rendered.)
If you’re testing this, make sure to test release (i.e. optimized) build on physical device. Debug builds, with all of their safety checks, can really slow down the process, which will stand out with something as computationally intensive as this.
By the way, it looks like the building of the path is buried in draw(rect:). I’d decouple the building of the UIBezierPath from the stroking of the path because while, yes, every time you update the function, you may want to update and draw the path, you definitely don’t want re-build the path every time draw(rect:) is called.
Once you do that, the draw(rect:) might not even be needed any. You might just add a CAShapeLayer as sublayer of the view’s layer, set the strokeColor and strokeWidth of that shape layer, and then update its path.
Generally moving and scaling is done by applying transforms to layers. Look at CAShapeLayer to hold your path. As the user gestures, apply transforms; then recreate the path when the gesture completes. In more advanced usage, you may recalculate the actual path more often than just when the user stops (such as if they pause without letting go), but these are refinements.
If you're able to describe the translation (pan) and scale as a CGAffineTransform then you can apply it to your UIBezierPath in one go using apply(_:).

Positioning Nodes At Below the Bottom of the Screen (Spritekit)

Hello I'm trying to spawn bullets at the bottom of my screen to travel upwards but the current code that I have spawns the bullets at the top of the screen. I've tried making the height negative and nothing happened. Here's the code I'm working with, thanks.
let randomBulletPosition = GKRandomDistribution(lowestValue: -300, highestValue: 300)
let position = CGFloat(randomBulletPosition.nextInt())
bullet.position = CGPoint(x: position, y: self.frame.size.height + bullet.size.height)
Some nice conversions will help you.
Now, do not do this all the time, this should be a one and done type deal, like in a lazy property.
First, we want to get the bottom of our view
let viewBottom = CGPoint(x:scene!.view!.midX,y:scene!.view!.frame.maxY) //In a UIView, 0,0 is the top left corner, so we look to bottom middle
Second, we want to convert the position to the scene
let sceneBottom = scene!.view!.convert(viewBottom, to:scene!)
Finally we want to convert to whatever node you need it to be a part of. (This is optional if you want to place it on the scene)
let nodeBottom = scene!.convert(sceneBottom,to:node)
Code should look like this:
let viewBottom = CGPoint(x:scene!.view!.midX,y:scene!.view!.frame.maxY)
let sceneBottom = scene!.view!.convert(viewBottom!, to:scene!)
let nodeBottom = scene!.convert(sceneBottom,to:node)
Of course, this is a little ugly.
Thankfully we have convertPoint and convert(_from:) to clean things up a little bit
let sceneBottom = scene.convertPoint(from:viewBottom)
Which means we can clean up the code to look like this:
let sceneBottom = scene.convertPoint(from:CGPoint(x:scene!.view!.midX,y:scene!.view!.frame.maxY))
let nodeBottom = node.convert(sceneBottom,from:scene!)
Then we can make it 1 line as:
let nodeBottom = node.convert(scene.convertPoint(from:CGPoint(x:scene!.view!.midX,y:scene!.view!.frame.maxY),from:scene!)
As long as the node is available to the class, we can make it lazy:
lazy var nodeBottom = self.node.convert(self.scene!.convertPoint(CGPoint(x:self.scene!.view!.midX,y:self.scene!.view!.frame.maxY),from:self.scene!)
This means the first time you call nodeBottom, it will do these calculations for you and store it into memory. Everytime after that, the number is preserved.
Now that you know where the bottom of the screen is in the coordinate system you want to use, you can assign the x value to whatever your random is producing, and you can subtract the (node.height * (1 - node.anchorPoint.y)) to fully hide your node from the scene.
Now keep in mind, if your node moves between various parents, this lazy will not update.
Also note, I unwrapped all optionals with !, you may want to be using ? and checking if it exists first.

Adding multiple borders to detect nodes on and off screen

I am using Sprite Kit to add some circle icons to a scene. I have added some code to create a border around the outside of the scene size this is used to detect contact and remove the node from parent.
// Outside border collision detection
var largeBorder = CGRectMake(0, 0, size.width, size.height)
largeBorder.origin.x -= (mainIconRef.size.width + mainIconRef.size.width/3)
largeBorder.origin.y -= (mainIconRef.size.height + mainIconRef.size.height/3)
largeBorder.size.width += ((mainIconRef.size.width + mainIconRef.size.width/3) * 2)
largeBorder.size.height += ((mainIconRef.size.height + mainIconRef.size.height/3) * 2)
let pathMainView = CGPathCreateWithRect(largeBorder, nil)
self.physicsBody = SKPhysicsBody (edgeLoopFromPath: pathMainView)
self.physicsBody?.dynamic = false
self.physicsBody?.categoryBitMask = ColliderCategory.Wall.rawValue
self.physicsBody?.contactTestBitMask = ColliderCategory.Tap1.rawValue | ColliderCategory.Tap2.rawValue | ColliderCategory.Tap3.rawValue | ColliderCategory.TapFire.rawValue
self.physicsBody?.usesPreciseCollisionDetection = true
This is all working as expected. What I would like to do now is add another path/border/box in the middle of the screen and detect when the icons contact this. This is so I can tell they are at least a certain part of the size on the screen/scene itself.
What I am not sure about is that we set the self.physicsBody above. I do not want to override to it, I just want to add an additional border which is invisible (not shown) that I can track contact with (not collision). Is this possible without adding as a node?
Why not use an invisible node? Just turn off the physics for the node and pick the right shape for the physicsbody.
Set the right collisionbitmask, categorybitmask, contactbitmask etc and the icons will pass through the node and register "contact".
It'll do all of what you want.
Why don't you want to use a node?

Flipping a child sprite's xscale in Spritekit using Swift

Alright I'm offically stumped. I've been able to figure out my problems myself in the past but I'm lost on this one.
I'm trying to flip a child sprite's xscale. I want the flip to occur when the child's x position is negative. With the code below, the sprite does flip, every time the sprite reaches negative position, it loops back and forth flipping until reaching positive again.
I've tried numerous variations, but this is the code in it's simplest form. Any help or alternatives would be appreciative.
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
let pin = childNodeWithName(Pin1) as SKSpriteNode!
let guyPoint = guy.convertPoint(guy.position, fromNode: pin)
if guyPoint.x <= 0 {
guy.xScale = -1
}
else {
guy.xScale = 1
}
}
So if it is constantly flipping it's probably an issue of coordinate space. So check to make sure your convertPoint method is accurately returning the point you want. I would use println() to see what's going on there.
Probably what's happening is you want to flip the xScale when the child node is negative in the scene space or something (i.e. when the child is off the screen) but instead guyPoint might be the position of the child in its parent's coordinate space (so relative to the parent).
Also try with and without the else{} part to see if that changes anything.