SpriteNode not rendered at first time when using atlases - works with xcassets - swift

I have a very strange behaviour when rendering SKSpriteNodes using an atlas via imageNamed.
sprite = SKSpriteNode(imageNamed: triangle.triangleType1.spriteName)
sprite.size.height = TileHeight
sprite.size.width = TileWidth
sprite.zRotation = DegreesToRadians(triangle.rotation)
sprite.position = pointForColumn(triangle.column, row:triangle.row, columnOffset: TileWidth, rowOffset: TileHeight, tw: TileWidth,th: TileHeight)
triangleLayer.addChild(sprite)
triangle.sprite1 = sprite
addedTriangles++
print(sprite)
Some of those Sprites are not rendered adding them to the layer (another SKSpriteNode) for first time - printing the "sprite" shows that they're size 0,0 and the image shows "TrViolet" instead of "TrViolet#2x.png".
Other ones are rendered as they supposed to be.
Removing all the nodes and executing the same code for a second time DOES work though.
When I put the image in xcassets it works like a charm - even for the first time.
Is there any way to debug this kind of behavior or you have any idea on what might be happening here?

Related

Is there a way to add a fading trail or stroke to a moving SKSpriteNode?

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.

SKPhysicBodies appear to be slightly off-place

Edit: I have been able to solve this problem by using PhysicsEditor to make a polygonal physicsbody instead of using SKPhysicsBody(... alphaThreshold: ... )
--
For some reason I'm having trouble with what I'm assuming is SKPhysicBodies being slightly off-place. While using showPhysics my stationary obstacle nodes appear to have their physicbodies in the correct position, however I am able to trigger collisions without actually touching the obstacle. If you look at the image below it shows where I have found the physicsbodies to be off centre, despite showPhysics telling me otherwise. (Note, the player node travels in the middle of these obstacle nodes).
I also thought it would be worth noting that while the player is travelling, its physicbody appears to travel slightly ahead but I figured this is probably normal.
I also use SKPhysicsBody(... alphaThreshold: ... ) to create the physicbodies from .png images.
Cheers.
Edit: Here's how I create the obstacle physicbodies. Once they're added into the worldNode they are left alone until they need to be removed. Apart from that I don't change them in any way.
let obstacleNode = SKSpriteNode(imageNamed: ... )
obstacleNode.position = CGPoint(x: ..., y: ...)
obstacleNode.name = "obstacle"
obstacleNode.physicsBody = SKPhysicsBody(texture: obstacleNode.texture!, alphaThreshold: 0.1, size: CGSize(width: obstacleNode.texture!.size().width, height: obstacleNode.texture!.size().height))
obstacleNode.physicsBody?.affectedByGravity = false
obstacleNode.physicsBody?.isDynamic = false
obstacleNode.physicsBody!.categoryBitMask = CC.wall.rawValue
obstacleNode.physicsBody!.collisionBitMask = CC.player.rawValue
obstacleNode.physicsBody!.contactTestBitMask = CC.player.rawValue
worldNode.addChild(obstacleNode)
The player node is treated the same way, here is how the player moves.
playerNode.physicsBody?.velocity = CGVector(dx: dx, dy: dy)
I'm assuming you aren't showing the exact images that you used to create your SKSpriteNode and SKPhysicsBody instances. Since you are using a texture to define the shape of your SKPhysicsBody you are likely running up against this:
SKPhysicsBody documentation
If you do not want to create your own shapes, you can use SpriteKit to create a shape for you based on the sprite’s texture.
This is easy and convenient but it can sometimes give unexpected results depending on the textures you are using for your sprite. Perhaps try making an explicit mask or using a simple shape to represent your physics body. There are very good examples and guidelines in that documentation.
I would also follow this pattern when you set the properties on your objects:
// safely unwrap and handle failure if it fails
guard let texture = obstacleNode.texture else { return }
// create the physics body
let physicsBody = SKPhysicsBody(texture: texture,
alphaThreshold: 0.1,
size: CGSize(width: texture.size().width,
height: texture.size().height))
// safely set its properties without the need to unwrap an Optional
physicsBody.affectedByGravity = false
// set the rest of the properties
// set the physics body property on the node
obstacleNode.physicsBody = physicsBody
By setting the properties on a concrete instance of SKPhysicsBody and fully unwrapping and testing Optionals you minimize the chances for a run-time crash that may be difficult to debug.

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.

Prevent SKPhysicsBody From Pushing SKSpriteNode Upwards

I currently have two SKSpriteNodes on top of each other like so (The white part is one and the brown round circle part is the other one):
The white part is positioned 1/3 the way down on top of the round brown sprite node. In the picture, the brown round part has a SKPhysicsBody applied to it already as seen by the light blue outline around it. When I add a SKPhysicsBody around the top ovalish white part it pushes it up and not in the position I wanted it.
How can I have a SKPhysics body coving both bodies of sprites but not have the physics bodies push on one another which makes the white part move upwards? I would like the white part to stay in the position it was in the first image.
Thanks for anyone help!
Here's the code I used for the SKPhysicsBody's:
// create, position, scale & add the round body
roundBody = SKSpriteNode( imageNamed: "roundBody" )
roundBody.position = CGPoint( x: 207, y: 70 )
roundBody.zPosition = 1
roundBody.xScale = 0.3
roundBody.yScale = 0.3
// add sprite node to view
self.addChild( roundBody )
// create, position, scale & add the head
theHead!.position = CGPoint( x: 207, y: roundBody.frame.maxY / 1.15 )
theHead!.zPosition = 2
theHead!.xScale = 0.3
theHead!.yScale = 0.3
// setting up a SKPhysicsBody for the round body
roundBody.physicsBody = SKPhysicsBody( circleOfRadius: roundBody.size.width / 4 )
roundBody.physicsBody!.dynamic = true
roundBody.physicsBody!.affectedByGravity = true
roundBody.physicsBody!.allowsRotation = false
roundBody.physicsBody!.pinned = false
// setting up a SKPhysicsBody for the head
theHead!.physicsBody = SKPhysicsBody(circleOfRadius: theHead!.size.width / 2 )
theHead!.physicsBody!.dynamic = true
theHead!.physicsBody!.affectedByGravity = false
theHead!.physicsBody!.allowsRotation = false
theHead!.physicsBody!.pinned = false
I was able to figure out that if you use SKPhysicsJointPin it does the exact thing I needed! (Which was to basically pin a sprite head on it's body and share a physics body)
let joinTogether = SKPhysicsJointPin.jointWithBodyA(
roundBody.physicsBody!,
bodyB:theHead!.physicsBody!,
anchor: GPointMake(CGRectGetMidX(roundBody.frame),
CGRectGetMinY(theHead!.frame)))
scene!.physicsWorld.addJoint(joint)
Hope this helps someone in the future!
If you never want it to move, set it's .dynamic property to false. Then other objects may or may not bounce/collide with it (depending upon their collisionBitMask) but it won't move in response to those collisions.
Your own answer is correct and a better solution but just to explain further.
The reason of the bodies colliding is that by default a physics body's collision bit mask is set to all categories which means it will collide with everything. In your code you are not calling
roundBody.physicsBody?.collisionBitMask = ...
which is why its using the default values.
To change that you could give your body and head a different collisionBitMask.
Im sure you will deal with this sooner or later when you handle collisions
Also as a tip it's a better idea to not force unwrap the physics bodies unless you have too, even though you know they exist. So you should replace your ! with ? whenever possible.

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?