How to resize an SKEmitterNode? - ios

I just wanted to do something that to me seems really simple, which is make an emitter into the entire background of a view... then I would want the view to be able to aspectFill and scale and so forth, allowing the emitter to look proper whatever I did...
I'm not looking to just scale the emitter. I want the objects to remain the right size, but i want the area of the emitter to change... think of it like the "canvas size" (without resizing) option in photoshop..
I would use this effect, for example, to add snow or rain to an entire scene, or make a snow globe sprite...
Thing is, maybe I'm just not looking in the right place, but it seems that any size properties on an SKEmitterNode are read only... what am I doing wrong?
here's some code, where the resulting emitter is just a small rectangle in the middle of the view.
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
if let emitter = SKEmitterNode(fileNamed: "Bokeh") {
emitter.position = view.center
scene.addChild(emitter)
}
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
I need to clarify a little more. For snow, I would want the particles to instantiate before they enter the scene, and then fall through the scene, and only die after they have left the scene, rather than randomly appear throughout the scene, and then continue to fall down...I would want the snow to take up the entire bounds of the scene.

What you are looking for is called particlePositionRange :
The range of allowed random values for a particle’s position.
You can change it like this:
emitterNode.particlePositionRange = CGVector(dx: dx, dy: dy)
dx should be the width of your emitter, and dy the height. So that might be the size of a scene (note that by default size of the scene is 1024x768 if not specified differently).
Same thing you can do using the Particle Editor by changing the values in Position Range section:

Related

Swift: Positioning Children of the SKCameraNode

Context:
there is a cursor (like your mouse) SKSpriteNode
cam is a SKCameraNode and is a child to the cursor (i.e. wherever your cursor goes, so follows the camera).
cam is purposely not centered on the cursor; rather, it is offset so the cursor appears at the top of the view, and there remains empty space below
A simple schematic is given below
Goal:
The goal is two add to sprites to the lower left and lower right corners of the camera's view. The sprites will be children of the camera, so that they always stay in view.
Question
How can I position a sprite in the corner of a camera, especially given that the SKSpriteNode does not have an anchorPoint attribute (as an SKSpriteNode typically has, which let me offset the camera as a child to the cursor)?
Note: One can position the SKSpriteNodes on the GameScene and then call .move(toParent: SKNode), which gets you closers but also messes with the position and scale of the SKSpriteNodes
var cam: SKCameraNode!
let cursor = SKSpriteNode(imageNamed: "cursor")
override func didMove(to view: SKView) {
// Set up the cursor
cursor.setScale(spriteScale)
cursor.position = CGPoint(x: self.frame.midX, y: raisedPositioning)
cursor.anchorPoint = CGPoint(x:0.5, y:0.5)
cursor.zPosition = CGFloat(10)
addChild(cursor)
// Set up the camera
cam = SKCameraNode()
self.camera = cam
cam.setScale(15.0)
// Camera is child of Cursor so that the camera follows the cursor
cam.position = CGPoint(x: cursor.size.width/2, y: -(cursor.size.height * 4))
cursor.addChild(cam)
// Add another sprite here and make it child to cursor
...
the cameraNode has no size, but you can get the current screen size with the frame property
frame.size
then you can position your node accordingly, for example if you want to position the center of yournode in the left corner you set the position as this:
yournode.position.x = 0
yournode.position.y = frame.size.height
This is best solved with a "dummy node" that acts as the camera's screen space coordinates system.
Place this dummy node at the exact centre of the view of the camera, at a zPosition you're happy with, as a child of the camera.
...from SKCameraNode docs page:
The scene is rendered so that the camera node’s origin is placed in
the middle of the scene.
Attach all the HUD elements and other pieces of graphics and objects you want to stay in place, relative to the camera, to this dummy object, in a coordinate system that makes sense relative to the camera's "angle of view", which is its frame of view.
...from a little further down the SKCameraNode docs page:
The camera’s viewport is the same size as the scene’s viewport
(determined by the scene’s size property) and the scene is still
scaled by its scaleMode property when it is rendered into the view.
Whenever the camera moves, it moves the dummy object, and all the children of the dummy object move with the dummy object.
The biggest advantage of this approach is that you can shake or otherwise move the dummy object to create visual effects indicative of motion and explosions. But also a neat system for removal from view, too.

Swift sprite kit vertical background infinite image

I have 3 images:
topBg.png
midBg.png
botBg.png
I want to set topBg.png at top scene and height = 200
middleBg.png should be infinite scale or repeat vertically
botBg.png - should be in bottom and height = 200
i have next code:
override func didMove(to view: SKView) {
self.bgTopSpriteNode = self.childNode(withName: "//bgTopNode") as? SKSpriteNode
self.bgMiddleSpriteNode = self.childNode(withName: "//bgMiddleNode") as? SKSpriteNode
self.bgBottomSpriteNode = self.childNode(withName: "//bgBottomNode") as? SKSpriteNode
if let bgTopSpriteNode = self.bgTopSpriteNode,
let bgMiddleSpriteNode = self.bgMiddleSpriteNode,
let bgBottomSpriteNode = self.bgBottomSpriteNode {
bgTopSpriteNode.size.width = self.frame.width
bgTopSpriteNode.size.height = 200
bgTopSpriteNode.position.x = 0
bgMiddleSpriteNode.size.width = self.frame.width
bgMiddleSpriteNode.size.height = self.frame.height-400
bgMiddleSpriteNode.position.x = 0
bgBottomSpriteNode.size.width = self.frame.width
bgBottomSpriteNode.size.height = 200
bgBottomSpriteNode.position.x = 0
}
}
But how to set Y position of images. Because coordinates begin from center of screen, not from left top and i don't know how to convert them.
There are a couple of different ways to achieve what you're looking to do.
First, you can compute the y position of the top and the bottom of the screen using simply size.height / 2 if you have the anchorPoint of your scene at (0.5,0.5). (Don't use frame - use size. That way, you take into account the scaleMode of the scene.)
It sounds like you are frustrated that the origin of the scene is in the center. If you'd like to move it to the corner, you can easily do so by setting the scene's anchorPoint property, say, to (0.0, 0.0) for the lower left corner. Then, your y-values are 0 and size.height. If you are using the .sks editor, this is exposed in the interface - you can just set it there. Otherwise, you can set it programmatically.
Finally, you can set the scaleMode of your scene to something like .aspectFill, set the size of the scene directly (say, to 1024x768 for an iPad), and just place the images wherever they need to go. This approach works particularly well with .sks files, if you are using them; when you load up a scene, you can set the size of the scene based on the aspect ratio of the view it's in to accommodate different aspect ratios. For instance, you could adopt a 320x480 "reference size" for your iPhone scenes. Whenever you load up the scene, you could set the size of the scene to be 320 points wide and however many points tall to match the aspect ratio of the device. Then, all your graphics would be produced at 320pt wide, and you could slide them up or down proportionally across the scene's size for layout. This is a little more complicated, but it's a lot easier than trying to deal with separate layout considerations for multiple devices.
I should also point out a couple of things.
You can use the anchorPoint property of a sprite to dictate where the sprite's coordinates are measured from. This is handy for cases where you want images to be flush up against something. For instance, if you want an image flush against the left side of the screen, set its position to be exactly the left side of the screen, and then set its anchorPoint.x to 0.0; this will put the left edge of the sprite against the left edge of the screen. This also works for scenes, as you encountered - moving the anchorPoint of the scene moves everything in the scene relative to its size.
You don't need three images for what you're describing. You can use a single sprite and just set its centerRect property to tell it to use the top and bottom of an image and stretch the center part vertically. You have to do a little math to set the right xScale and yScale (not width and height, IIRC), but then you can draw all of that with one sprite instead of three. This would be really handy in your case, because you could just leave the sprite at (0,0), set its scale to match the size of the entire scene, and set the centerRect property - you wouldn't have to do any positioning math at all.

How to create CGPath from a SKSpriteNode in SWIFT

I have a SKSpriteNode create with the level generator.
I need to create exactly the same shape using CGPath.
self.firstSquare = childNodeWithName("square") as! SKSpriteNode
var transform = CGAffineTransformMakeRotation(self.firstSquare.zRotation)
let rect = CGRect(origin: self.firstSquare.position, size: self.firstSquare.size)
let firstSquareCGPath:CGPath=CGPathCreateWithRect(rect, &transform)
print(self.firstSquare.position)
=> firstSquare position (52.8359451293945, -52.9375076293945)
To check if my CGPath has been created as I want, I created a SKShapeNode with my CGPath:
let shape:SKShapeNode=SKShapeNode(path:path)
shape.fillColor = self.getRandomColor()
addChild(shape)
print(shape.position)
=> shape position (52.8359451293945, -52.9375076293945)
The result is not what I expected.
So I don't know if my CGPath is wrong, or if it's when I convert it in SKShapeNode that I lose the initial sprite properties.
To understand why I need to do that, please read this stack
EDIT 1,2
I added:
shape.position = self.firstSquare.position
And I obtained:
EDIT 3 :
I updated my explanations above, the anchor point of my firstSquare is now (0.5, 0.5)
Usually I don't use .SKS files, I preefer to write only code, but here seems you simply have two squares , one with the original .sks position, the other added in second time without any position.
If you want to overlay your first square simply:
shape.position = self.firstSquare.position
EDIT:
I also seen your anchorPoint was setted to (0.0,0.0) but the default anchor point is (0.5,0.5). Try to correct also this parameter to match with your sks.
You .sks size layout could be not the same as scene, so:
scene.size = skView.bounds.size
Please take a look also to this parameter to change before you present your scene:
scene.scaleMode = .ResizeFill
In fact, the scale mode affects also to the positioning.
What is scaleMode?
The scaleMode of a scene determines how the scene will be updated to fill the view when the iOS device is rotated. There are four different scaleModes defined:
SKSceneScaleModeFill – the x and y axis are each scaled up or down so that the scene fills the view. The content of the scene will not maintain its current aspect ratio. This is the default if you do not specify a scaleMode for your scene.
SKSceneScaleModeAspectFill – both the x and y scale factors are calculated. The larger scale factor will be used. This ensures that the view will be filled, but will usually result in parts of the scene being cropped. Images in the scene will usually be scaled up but will maintain the correct aspect ratio.
SKSceneScaleModeAspectFit – both the x and y scale factors are calculated. The smaller scale factor will be used. This ensures that the entire scene will be visible, but will usual result in the scene being letterboxed. Images in the scene will usually be scaled down but will maintain the correct aspect ratio.
SKSceneScaleModeResizeFill – The scene is not scaled. It is simply resized so that its fits the view. Because the scene is not scaled, the images will all remain at their original size and aspect ratio. The content will all remain relative to the scene origin (lower left).
I found a solution, I need to use SKShapeNode(path:, centered:), then apply the transformation and set the position.
self.firstSquare = childNodeWithName("square") as! SKSpriteNode
let firstSquareCGPath:CGPath=CGPath(rect: CGRect(origin: self.firstSquare.position, size: self.firstSquare.size), transform: nil)
let shape:SKShapeNode=SKShapeNode(path: firstSquareCGPath, centered: true) //important
shape.zRotation = self.firstSquare.zRotation
shape.position = self.firstSquare.position
shape.fillColor = SKColor.blue()
addChild(shape)
It works perfectly!
Just a pending question, as I set the transformation and the position in last (because of the SKShapeNode properties), I don't know if my CGPath above is set correctly (like my SKSpriteNode).

SpriteKit: What's up with the coordinate system?

I'm teaching myself how to do SpriteKit programming by coding up a simple game that requires that I lay out a square "game field" on the left side of a landscape-oriented scene. I'm just using the stock 1024x768 view you get when creating a new SpriteKit "Game" project in XCode - nothing fancy. When I set up the game field in didMoveToView(), however, I'm finding the coordinate system to be a little weird. First of all, I expected I would have to place the board at (0, 0) for it to appear in the lower-left. Not so -- it turns out the game board has to be bumped up about 96 pixels in the y direction to work. So I end up with this weird code:
let gameFieldOrigin = CGPoint(x:0, y:96) // ???
let gameFieldSize = CGSize(width:560, height: 560)
let gameField = CGRect(origin: gameFieldOrigin, size: gameFieldSize)
gameBorder = SKShapeNode(rect: gameField)
gameBorder.strokeColor = UIColor.redColor()
gameBorder.lineWidth = 0.1
self.addChild(gameBorder) // "self" is the SKScene subclass GameScene
Furthermore, when I add a child to it (a ball that bounces inside the field), I assumed I would just use relative coordinates to place it in the center. However, I ended up having to use "absolute" coordinate and I had to offset the y-coordinate by 96 again.
Another thing I noticed is when I called touch.locationInNode(gameBorder), the coordinates were again not relative to the border, and start at (0, 96) at the bottom of the border instead of (0, 0) as I would have guessed.
So what am I missing here? Am I misunderstanding something fundamental about how coordinates work?
[PS: I wanted to add the tag "SpriteKit" to this question, but I don't have enough rep. :/]
You want to reference the whole screen as a coordinate system, but you're actually setting all the things on a scene loading from GameScene.sks. The right way to do is modify one line in your GameViewController.swift in order to set your scene size same as the screen size. Initialize scene size like this instead of unarchiving from .sks file:
let scene = GameScene(size: view.bounds.size)
Don't forget to remove the if-statement as well because we don't need it any more. In this way, the (0, 0) is at the lower-left corner.
To put something, e.g. aNode, in the center of the scene, you can set its position like:
aNode.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));

SKShapeNode Using Wrong Width and Height

Recently I have been creating a game where I put circles on the screen. However, I noticed that the circulars were not circles, but in fact ovals. The y axis had been stretched of the x axis had been shrunk. In order to figure out the issue, I am now trying to debug with a rectangle. I made the rectangle have the same height and width (square) however, when turned into an skshapenode it is no longer the correct shape. The code:
var testSquare = CGRect(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame), width: 1, height: 1)
var squareNode = SKShapeNode(rect: testSquare)
println(testSquare.width)
println(testSquare.height)
println(squareNode.frame.width)
println(squareNode.frame.height)
self.addChild(squareNode)
Here is what gets printed from this:
1.0
1.0
513.5
385.5
If I change the testSquare's height and width both to 100 this is what gets printed:
100.0
100.0
612.5
484.5
For some reason the frame of the squarenode in the x adds 512.5 and in the y adds 384.5. The rectangle that is created is longer in the Y for some reason on the screen though. Maybe the frame is not the number I should be printing, but regardless the shape on the screen is NOT a square. Does anyone know why that is? Any help is greatly appreciated!
are you using the sks file to make your SKView? Or are you instantiating it in your viewcontroller? Important to get the dimensions for your scene correct. I know this isn't directly answering your question, but if you're getting weird stretching and stuff, maybe you should start with this and debug from there..
this gives me the correct scene dimensions.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// Configure the view.
let skView = self.view as SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
let scene = GameScene(size: skView.bounds.size)
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}

Resources