Why does the position not change for the sprite nodes? - sprite-kit

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.

Related

SKSpriteNode Stuck with init(edgeLoopFrom:) - SKPhysicsBody

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

SpriteKit: using SKView in UIView instead of initializing a Game project

Completely new to SpriteKit. Currently I have a UIView, and I want to add a sprite node to it (like a small UIImageView, but I want animation for it so using SpriteKit). Therefore I didn't initialize my project to be a game project, as found in almost all of tutorials for SpriteKit. I've found a note here: link and what I have now is sth like:
func initializeImage() {
let imageView = SKView()
imageView.frame = CGRect(x: self.frame.width / 2 - Constants.imageWidth / 2, y: self.frame.height - Constants.imageHeight, width: Constants.imageWidth, height: Constants.imageHeight)
// so place it somewhere in the bottom middle of the whole frame
let sheet = SpriteSheet(texture: ...)
let sprite = SKSpriteNode(texture: sheet.itemFor(column: 0, row: 0))
sprite.position = imageView.center //basically the same position as the imageView.frame's x and y value
let scene = SKScene(size: imageView.frame.size)
scene.backgroundColor = SKColor.clear
scene.addChild(sprite)
imageView.presentScene(scene)
self.frame.addSubview(imageView)
}
The SpriteSheet is similar to this: sprite sheet; it's essentially cutting an image atlas and divide it into smaller images. I tracked the process and this step is indeed giving the smaller image (the var 'sprite'). But if running I only have a black square now (should be the size as defined by Constants). If I set scene.backgroundColor to be white then it's white. May I know how I should proceed from here, as how should I make the sprite showing up?
All of your code looks good except for this:
sprite.position = imageView.center // basically the same position as the imageView.frame's x and y value
That is basically not the position you think it is. The coordinate system in SpriteKit is a) relative to the (SK)scene, not to whatever view the SKView is contained in, and b) flipped vertically relative to the UIKit coordinate system. If you want a sprite centered in the scene, you probably want to set its position based on the scene's size:
sprite.position = CGPoint(x: scene.size.width / 2, y: scene.size.height / 2)
By the way, the external SpriteSheet code might not be needed (and you're more likely to benefit from Apple's optimizations) if you slice up your sprite sheet and put it in an Xcode asset catalog.

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.

How to generate a new node when another one hits a certain y value

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.

SKShapeNode ellipseInRect, sprite does not appear in scene

I'm trying to create an ellipse in the center of my scene:
let center = (CGRectGetMidX(view.scene.frame), CGRectGetMidY(view.scene.frame))
let size = (view.scene.frame.size.width * 0.3, view.scene.frame.size.height * 0.3)
let ellipse = SKShapeNode (ellipseInRect: CGRectMake(center.0, center.1, size.0, size.1))
ellipse.strokeColor = UIColor.blackColor()
ellipse.position = CGPointMake(center)
self.addChild(ellipse)
This was added to didMoveToView, and the node count on the view shows 1, but I do not see the path. How do I add an ellipse to my scene using the SKShapeNode ellipseInRect API?
The problem lies in ellipse.position = CGPointMake(center). For some reason, this changes the position of the ellipse relative to itself rather than relative to the view - so if you did ellipse.position = CGPoint(x: 100, y: 100) then it would set the position to 100 up and 100 to the right of the ellipse itself as opposed to 100,100 on the scene. If you comment out this line, then you should be able to see you ellipse on the screen - I certainly could when it tried it. Hope that helps you position it to where you want.