SKSpriteNode Stuck with init(edgeLoopFrom:) - SKPhysicsBody - swift

I have a separate class called "Floor" with below.
class Floor: SKNode {
override init() {
super.init()
//let edgeFrame = CGRect(origin: CGPoint(x: 1,y: 1), size: CGSize(width: 1078, height: 1950))
//self.physicsBody = SKPhysicsBody(edgeLoopFrom: edgeFrame)
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
// Apply a physics body to the node
// self.physicsBody?.isDynamic = false
// Set the bit mask properties
self.physicsBody?.categoryBitMask = floorCategory
self.physicsBody?.contactTestBitMask = nailDropCategory
self.physicsBody?.collisionBitMask = balloonCategory
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemted")
}
}
I basically have falling SKSpriteNode's that start at the top of the screen and goes to the bottom where it touches the "Floor" and removes itself then restarts from top again. Issue I'm having is all my SKSpriteNode keeps getting stuck on top and not falling through the border around the frame of the screen. How can I tell my application to ignore those specific nodes and let them in? Appreciate any help!
Here is the object that is moving left and right on the screen but it just falls off the side of the screen without the edgeLoop
if let accelerometerData = motionManager.accelerometerData {
if UIDevice.current.orientation == UIDeviceOrientation.landscapeLeft {
balloon.physicsBody?.velocity = CGVector(dx: accelerometerData.acceleration.x * -500.0, dy: 0)
} else {
balloon.physicsBody?.velocity = CGVector(dx: accelerometerData.acceleration.x * 500.0, dy: 0)
}
}
}

What is happening is that your objects are hitting the top boundary of the edge loop and not being able to make it into the scene.
there is several ways you can do you this, if you absolutely needed the edge loop on the sides I would suggest elongating the loop to be higher than the scene and creating the objects inside the loop but above the visible area. However since you haven't given any indication that you actually need the loop on the sides all I would do is get rid of the edge loop detection.
create a box that is the width of the scene and say a 100px high then put a physics body on it of type floorCategory. then put this box 50 px below the bottom of the screen. Assuming that your floor sprite box has an anchorPoint of 0.5, 0.5 this will hide the box below the screen and the top of the box will sit flush with the bottom of the screen.
Now you will be able to detect when your objects hit the bottom of the screen and you will no longer have to worry about them passing through the edge loop at the top.
OR
an example of elongating the loop would be...
You create a rectangle taller than the screen (green border in the image) to apply the edge loop to, not to the screen size itself

Related

Swift - Sprite Kit floating bubbles stuck to corners

I'm trying to create a floating bubble view on my Watch App. The bubbles can collide & bounce off each other & the sides of the screen. But for some reason the bubbles are appearing out of the view bounds & getting stuck on the sides of the frame instead of bouncing off. This code works as expect on my iOS application but when using the same code in my Watch app, it doesn't.
It doesn't make much sense to me that this exact code works perfectly on my iOS app but not on the Watch App.
I'm passing the below code into a SpriteView in my SwiftUI View
let ballCategory: UInt32 = 0xb0001
let edgeCategory: UInt32 = 0xb0010
var nodeCount = 0
override func sceneDidLoad() {
//set physicsWorld properties
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
//set edges as PhysicsBody
let edge = SKPhysicsBody(edgeLoopFrom: self.frame)
edge.friction = 0
edge.categoryBitMask = edgeCategory
self.physicsBody = edge
makebubble()
makebubble()
makebubble()
makebubble()
}
func makebubble() {
let bubbleTexture = SKTexture(imageNamed: "bubble")
let bubble = SKSpriteNode(texture: bubbleTexture)
let bphysicsBody = SKPhysicsBody(circleOfRadius: bubbleTexture.size().height/2)
bphysicsBody.isDynamic = true
bphysicsBody.usesPreciseCollisionDetection = true
bphysicsBody.restitution = 0.5
bphysicsBody.friction = 0
bphysicsBody.angularDamping = 0
bphysicsBody.linearDamping = 0
bphysicsBody.categoryBitMask = ballCategory
bphysicsBody.collisionBitMask = ballCategory | edgeCategory
bphysicsBody.contactTestBitMask = ballCategory | edgeCategory
bubble.physicsBody = bphysicsBody
bubble.name = "bubble"
// Get a random possition within the width of the scene
let x = CGFloat(randomize(number: Int(size.width - 40)))
let y = CGFloat(randomize(number: Int(size.height - 40)))
// position the bubble
bubble.position.x = x
bubble.position.y = y
// Add the bubble
addMyChild(node: bubble)
}
func addMyChild(node:SKSpriteNode){
self.addChild(node)
node.physicsBody!.applyImpulse(CGVector(dx: 10.0, dy: -2.0))
nodeCount += 1
}
// function that returns a random int from 0 to n-1
func randomize(number: Int) -> Int{
return Int(arc4random()) % number
}
This has nothing to to with watchOS and everything to do with the small screen size of Apple Watches. Try running your code on iOS with a frame modifier of width 150 and height 150 and you'll see what I mean; the bubbles will likely stick to the side.
Your bubbles look like they stick to the edges because they slow down over time (due to restitution being 0.5 instead of 1) and it's statistically more probable for a bubble to have its final movement close to the screen edge (since they will eventually move to the edge, bounce off from it, thus slowing down and eventually halting).
Here are 3 things you can do about this:
as mentioned, increase restitution to 1 (this is optional, as it won't solve the "sticking to the edge" problem on its own, but it helps making the slowing down issue better)
detect when the bubbles stop (you can do this by checking the x and y velocity in the update(_:) function of your SKScene) and make a force that moves them slightly in a random direction. If you are in a fancy mood, you can even make a timer to make random, barely noticable forces that act like small air movements (chances are, it will even make the animation a bit more realistic)
create an outside bounding box with slightly non-linear/circular borders and corners to make the bubbles bounce off the walls in a different way

SpriteKit - Nodes not adding to SKCameraNode - Swift

Im trying to pin the game pad controller to the bottom left on my camera node but when i add the node as a child of my camera it doesnt show up?
let gameCamera = SKCameraNode()
var joypadBackground : SKSpriteNode = SKSpriteNode(imageNamed: "a")
override func didMove(to view: SKView) {
//Set game camera
self.camera = gameCamera
joypadBackground.position = convert(CGPoint(x: 0, y: 0), to: gameCamera)
joypadBackground.size = CGSize(width: 50, height: 50)
joypadBackground.zPosition = 1000
gameCamera.addChild(joypadBackground)
}
I had a hard time with this same problem the first time I was working with SKCameraNode and creating a heads up display.
Basically you have to remember that there are two parts to the camera. Running its functionality and rendering its children. By setting the scene's camera to gameCamera you've setup the functionality, but your camera isn't in the node tree for rendering. So, if you ever have a camera that needs to render its children don't forget to add it to the scene as a child, then the camera's children will be displayed.
self.camera = gameCamera
self.addChild(gameCamera)
Hope that helps someone avoid a very common error with a very simple solution.
You don't need
convert(CGPoint(x: 0, y: 0), to: gameCamera)
You can just set the CGPoint position to (0,0) and it should be at that point relative to the camera's space.
Not sure if this helps, at all, but what I do is (generally) position a child node AFTER I've added it to its parent. This is mainly a mental reminder, to me, that the child's position is within the coordinate space of the parent. So I'd do something like this:
gameCamera.addChild(joypadBackground)
joypadBackground.position = CGPoint(x: 0, y: 0)
If you're using a mid screen origin in your SKScene, this should be in the middle of the screen.
Bottom left will be a negative x and negative y value, size of which is relative to your frame size.

SpriteKit convertPointToView / convertPoint

Question How do I get the purple square to stay anchored at the bottom left of the screen regardless of the camera position?
Details I have an SKScene named colorScene that has a camera node and two shape nodes - one blue and one purple square. The camera is constrained to the blue square which is positioned at 0,0:
Now, say I want to position the purple square at the bottom left of the screen. And I want it to stay there no matter where the camera goes. First, I would make the purple square a child of the camera node. But then what? If I position the purple square at 0,0, strangely, it's in the middle of the screen. (Shouldn't the camera node be the entire screen area?) And if I try convertPoint to go from the camera node to the scene coordinates:
purpleNode?.position = convertPoint(CGPoint(x: 0, y: 0), toNode: cameraNode!)
...things get even more mysterious:
From the Apple docs:
The scene is rendered so that the camera node’s origin is placed in the middle of the scene.
This should explain what you experienced
If I position the purple square at 0,0, strangely, it's in the middle of the screen. (Shouldn't the camera node be the entire screen area?)
Solution
Since we want to place purple on the bottom-left of the screen, we should start with the coordinate related to the view
let bottomLeftOfView = CGPoint(x: 0, y: view.frame.height)
Next we need to convert these coordinate from the view to the scene
purple.position = convertPointFromView(bottomLeftOfView)
This is the full code
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
let camera = SKCameraNode()
self.camera = camera
addChild(camera)
let purple = SKSpriteNode(imageNamed: "square")
purple.color = SKColor.purpleColor()
purple.colorBlendFactor = 1
let bottomLeftOfView = CGPoint(x: 0, y: view.frame.height)
purple.position = convertPointFromView(bottomLeftOfView)
camera.addChild(purple)
}
}
Purple and anchorpoint
Only a quarter of the purple sprite is inside the screen. This because its anchorpoint is (0.5, 0.5): the center. So we just told SpriteKit to place the center of purple to the corner of the screen.
In order to have purple entirely into the screen we just need to tell SpriteKit to use use the bottom left corner of purple as anchorpoint
purple.anchorPoint = CGPointZero
This is the final full code
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
let camera = SKCameraNode()
self.camera = camera
addChild(camera)
let purple = SKSpriteNode(imageNamed: "square")
purple.color = SKColor.purpleColor()
purple.colorBlendFactor = 1
purple.anchorPoint = CGPointZero
let bottomLeftOfView = CGPoint(x: 0, y: view.frame.height)
purple.position = convertPointFromView(bottomLeftOfView)
camera.addChild(purple)
}
}

Lag when trying to implement parallax effect in swift (spritekit)

I am trying to create game in SpriteKit with parallax effect, but when I implement my method, my hero lags (quickly shakes) from time to time (no pattern observed)...
I wrote this simple code that illustrates the method and also contains the same problem.
The hero should stay in the center of the screen and only his position relative to his parent (foreground) should change. But it lags (quickly shakes for a really short moment) every now and then. I also noticed that when I run the game, the hero sinks down very, very slowly a few pixels (maybe 5-10) before it starts holding its position after ~15 sec.
I can't figure out what is causing this behavior. Any help would be appreciated. I am testing on IPhone 6.
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
let hero = SKSpriteNode(imageNamed: "hero")
let foreground = SKNode()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(size: CGSize) {
super.init(size: size)
physicsWorld.gravity = CGVector(dx: 0.0, dy: -0.5)
physicsWorld.contactDelegate = self
// anchor point of scene is at (x: 0.5, y: 0.5)
hero.position = CGPoint(x: 0, y: 0)
hero.physicsBody = SKPhysicsBody(rectangleOfSize: hero.size)
hero.physicsBody?.dynamic = true
hero.physicsBody?.allowsRotation = false
addChild(foreground)
foreground.addChild(hero)
}
override func update(currentTime: NSTimeInterval) {
foreground.position = CGPoint(x: 0, y: -(hero.position.y))
}
}
// I need the foreground to move in opposite direction so I can add stars,
// monsters, etc while keeping the player in the view... Update method does
// this: hero is pulled down by gravity so his y position would be -1 pixel
// relative to foreground but I assign foreground the opposite y-position of
// hero (+1) and since hero's position relative to foreground is -1, he should
// be positioned at y-position = 0 which, in this code example, should create
// an illusion that he is positioned at the center and doesn't move

Why does the position not change for the sprite nodes?

I've commented the code. This is my first game so my knowledge is very shallow. I don't even know if there is a better way of doing this.
Now, the problem I am running into is that although the animation works, the position property I am printing is always 0. Why? My idea was that in the update method, I will check the position property and if it is above a certain height on the screen, I'll move the top sprite to the bottom (like a queue data structure - pull out from one end and add to the other end) and adjust the middle to be the top and the bottom to be the middle. That way, I just need 3 sprites to animate a scroll wheel of numbers.
override func didMoveToView(view: SKView) {
/* Idea is to have a scrolling wheel of numbers animate.
The approach is to have 3 'number' sprite nodes inside a crop node. Only one sprite number node is visible at a given time
because of the crop node's mask.
Initially, number 0, 1 and 2 one under the other and a crop sprite to crop out everything but just one number
that needs to be displayed. Then, we animate number 1 in and push out number 0 and then animate number 2 in and push
out number 1. When the position of the sprite containing 0 moves above a certain position 'y', we move it to the bottom
and have number 4 replace 0 so it can roll in next. So we have a continuous scrolling wheel of numbers */
//A slot to hold the numbers
let numSlot = SKSpriteNode(color: SKColor.clearColor(), size: CGSizeMake(100, 180))
//Create a crop node
let cn = SKCropNode.init()
//Add the crop node mask so only 1 number is visible
cn.maskNode = SKSpriteNode(color: SKColor.redColor(), size: CGSizeMake(100, 180))
//Add the number slot sprite to the crop node sprite at a certain position
numSlot.addChild(cn)
numSlot.position = CGPointMake(size.width/2, size.height/2 - 25)
self.addChild(numSlot)
//The three images that should live inside the numSlot node (with only 1 visible at a given time)
let zeroSprite = SKSpriteNode.init(imageNamed: "0.png", normalMapped: false)
let oneSprite = SKSpriteNode.init(imageNamed: "1.png", normalMapped: false)
let twoSprite = SKSpriteNode.init(imageNamed: "2.png", normalMapped: false)
//Add the three numbers to the numberHolderSprite and then add the numberHolderSprite to the crop Nod
let numberHolderSprite = SKSpriteNode.init()
numberHolderSprite.addChild(zeroSprite)
oneSprite.position = CGPointMake(0, -120)
numberHolderSprite.addChild(oneSprite)
twoSprite.position = CGPointMake(0, -240)
numberHolderSprite.addChild(twoSprite)
//Add the number holder sprite to the crop node
cn.addChild(numberHolderSprite)
top = zeroSprite
middle = oneSprite
bottom = twoSprite
var numSlotSpriteAction1 = SKAction.moveToY(top!.position.y + 500, duration: 0.5)
var numSlotSpriteAction2 = SKAction.moveToY(middle!.position.y + 500, duration: 0.5)
var numSlotSpriteAction3 = SKAction.moveToY(bottom!.position.y + 500, duration: 0.5)
println("Position \(top!.position.y)")
var numSlotGroupAction = SKAction.group([numSlotSpriteAction1, numSlotSpriteAction2, numSlotSpriteAction3])
numberHolderSprite.runAction(numSlotGroupAction)
println("Position \(top!.position.y)")
//Just a simple background image
let bg = SKSpriteNode.init(imageNamed: "bg.png", normalMapped: false)
bg.position = CGPointMake(size.width/2, size.height/2)
self.addChild(bg)
}
println("Position \(top!.position.y)")
is being executed prior to any SKActions being run. top is really zeroSprite, and has the default position of 0, 0.
However, even if your SKActions were run, it would not affect top. This is because the SKActions are run on the numberHolderSprite node. This means numberHolderSprite's position will change, not the sub-nodes. That action will also not be run until the next animation loop is processed.
So I am guessing this code will not do what you expect it to do.