I have a SKShapenode with a path and in the function update() I want to add a point so the line moves forward. I am using the following:
var path = wave.path as! CGMutablePath()
path.addLine(to: CGPoint(x: path.currentPoint.x + 2, y: 0 ))
wave.path = path
However, when I have more than about 500 nodes the frame rate drops significantly. I think this is because I grab the path, edit it and then put the path back into the shape. Could this be more done more direct and efficient by editing the path directly?
Related
I have 10 SKSpriteNodes that are connected via SKPhysicsJointPins. Together, they form a chain (each node is a chain-link image and each node has a physicsBody).
There are also some balls that bounce around and interact with the chain.
Desired behavior: The balls should never penetrate the chain.
Actual behavior: The chain-link joints allow too much leeway, and the balls go through the chain. To be clear, I'm not talking about tunneling -- the balls don't go through the nodes, themselves. Instead, the chain-nodes sort of move aside and the balls go through the opening.
The code to create the chain-links (please assume that all variables are defined and that the positioning of each node is correct. Hopefully you can see what's going on here):
let chains = mainData.getLevelChains()
let bodyA = SKSpriteNode(imageNamed: "chainLink.png")
bodyA.size = CGSize(width: chainLinkWidth, height: chainLinkHeight)
bodyA.position = CGPoint(x:chains[i].leftAnchorPosition.x + CGFloat(halfChainLinkWidth) + (chainWidth * (0.1 * CGFloat(j))), y: chains[i].leftAnchorPosition.y)
bodyA.name = "body"
bodyA.physicsBody = SKPhysicsBody(texture: bodyA.texture!, size: CGSize(width: chainLinkWidth, height: chainLinkHeight))
bodyA.physicsBody?.categoryBitMask = CollisionTypes.chain.rawValue
bodyA.physicsBody?.collisionBitMask = CollisionTypes.ball.rawValue
addChild(bodyA)
let joint = SKPhysicsJointPin.joint(withBodyA: previousChainLink.physicsBody!, bodyB: bodyA.physicsBody!, anchor: CGPoint(x: Double(previousChainLink.position.x)+halfChainLinkWidth, y: Double(previousChainLink.position.y)))
scene?.physicsWorld.add(joint)
Question: What can I do to ensure that the chain maintains its structural integrity so the balls never go through the chain?
In the end, I solved the problem by setting the mass of the chain-links to a value slightly greater than that of the balls.
ball.physicsBody?.mass = 5.0
chainLink.physicsBody?.mass = 6.0
Still new, but slowly building my first app/game, and am slowly getting there.
I'd like to be able to add a fading streak or trail as one of my SKSpriteNodes moves. Whether it is moving due to touchesMoved or being sent back to its original spot by code. I just want to add a nice visual effect.
The only thing I can think of is calculating the distance, breaking it down into x movements, then gradually move the Sprite a little, with some fade options, and then repeat in a loop till it gets back to home base, using a lot of nested SKActions and sequences.
I know that has to be wrong because it's just so ugly.
When I look at the Sprite Kit's Particle File, it has so few options. And I'm not really sure that's what I should be looking at. I have also looked at SKAction's options, and if there's an option there I'm missing it.
Surely in Swift's huge animation library there has to be something?
Let's create a basic sprite and an emitter, and make the emitter a child of the sprite so that it follows it:
let sprite = SKSpriteNode(color: .white, size: CGSize(width: 20, height: 10))
let emitter = SKEmitterNode() // better to use the visual designer in Xcode...
emitter.particleLifetime = 1.0
emitter.particleBirthRate = 50.0
emitter.particleSpeed = 100.0
emitter.emissionAngleRange = .pi / 5
emitter.particleTexture = SKTexture(imageNamed: "spark")
emitter.particleScale = 0.1
emitter.particleAlphaSpeed = -1.0
emitter.emissionAngle = -.pi
sprite.addChild(emitter) // attach it to the sprite
emitter.position.x = -15 // but not to the center
scene.addChild(sprite)
sprite.run(SKAction.group([ // let's run some actions to test it:
SKAction.sequence([
SKAction.move(to: CGPoint(x: 200, y: 200), duration: 5),
SKAction.move(to: CGPoint(x: 50, y: 50), duration: 5),
]),
SKAction.rotate(byAngle: .pi * 2.0, duration: 10)
]))
(Click to open animated GIF if it doesn't display correctly:)
To the casual observer, it looks fine… Except that, after some scrutiny, you'll realize what's off: the particles emitted live in the universe of the parent sprite, moving and rotating with it, even long after they were emitted! That's not right!
That's because the targetNode of the emitter is its parent, and it should be the scene!
So let's insert this line somewhere:
emitter.targetNode = scene // otherwise the particles would keep moving with the sprite
(Click to open animated GIF if it doesn't display correctly:)
Okay, this is a no-go: the particles now stay in the "universe" of the scene, but apparently their emission angle fails to follow that of the parent (which looks like a bug to me).
Luckily, we can attach a custom action to the emitter which keeps aligning this angle to the parent sprite:
emitter.run(SKAction.repeatForever(
SKAction.customAction(withDuration: 1) {
($0 as! SKEmitterNode).emissionAngle = sprite.zRotation + .pi
_ = $1
}))
(Click to open animated GIF if it doesn't display correctly:)
Okay, now new particles are launched in the correct direction, and keep moving that way even if the sprite moves or rotates in the meantime. This seems to be the most realistic simulation so far, though there may still be ways to improve it by modifying the behavior of the emitter on the fly.
Sorry for the jaggy GIFs, apparently my Mac is too slow to render and capture at the same time. The animations themselves run just fine.
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(_:).
Okay I am fairly new to programming and am trying to create a simple game. In the background I'm having an object move from one side of the screen to another and then off the screen using SKAction and SKTransition. All I need to do is loop this transition so when the object goes back off the screen it starts again and comes back on. I'm using SpriteKit.
Here is my code.
//Walls
Walls = SKSpriteNode(imageNamed: "Walls")
Walls.position = CGPoint(x: 1080 + Walls.frame.width / 2, y: self.frame.height / 2)
Walls.zPosition = 1
Walls.runAction(SKAction.moveTo(CGPoint(x: -300 + Walls.frame.width / 2, y: self.frame.height / 2),duration: 6.0))
self.addChild(Walls)
Where can I add in the reapeatActionForever code command or something similar?
Thanks for your help in advance. Sam. :)
From what I gather, you're looking for how to use repeatActionForever... Please clarify the question so people don't have to guess
runAction(SKAction.repeatActionForever(/*SKAction or SKSequence etc...*/))
I'd put this code in didMoveToView if you want it to start immediately after the scene loads. Otherwise, put it in a block or function where you want it to start. To stop it, change it to:
runAction(SKAction.repeatActionForever(/*SKAction or SKSequence etc...*/), withKey: "actionKeyName")
Then to stop it:
removeAction(forKey: "actionKeyName")
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?