Nodes position within scene size - swift

In my SKScene, I am making a simple space shooter game. How can I make sure that my enemies always appear within the screen size regardless of which iphone the game is played at?
In other words words, how do I calculate the max and min X-coordinates of the scene size and more importantly how do i know what's the current scene size depending on which iphone the game is run at?

Don't resize your scene depend by iPhone model, leave Sprite-kit do this kind of job:
scene.scaleMode = SKSceneScaleMode.ResizeFill
The scene is not scaled to match the view. Instead, the scene is
automatically resized so that its dimensions always matches those of
the view.
About the size, when a scene is first initialized, its size property is configured by the designated initializer. The size of the scene specifies the size of the visible portion of the scene in points. This is only used to specify the visible portion of the scene.
Update to help you in positioning:
If you want to set your positions instead to use scaleMode you can
set your scene.scaleMode to .AspectFill for this to work on all scenes and the scene size has to be 2048x1536 or 1536x2048. This will make it scaleable for iPad too.
class StartScene: SKScene {
let playableArea: CGRect!
}
override init(size: CGSize) {
//1. Get the aspect ratio of the device
let deviceWidth = UIScreen.mainScreen().bounds.width
let deviceHeight = UIScreen.mainScreen().bounds.height
let maxAspectRatio: CGFloat = deviceWidth / deviceHeight
//2. For landscape orientation, use this
let playableHeight = size.width / maxAspectRatio
let playableMargin = (size.height - playableHeight) / 2.0
playableArea = CGRect(x: 0, y: playableMargin, width: size.width, height: playableHeight)
//3. For portrait orientation, use this
let playableWidth = size.height / maxAspectRatio
let playableMargin = (size.width - playableWidth) / 2.0
playableArea = CGRect(x: playableMargin, y: 0, width: playableWidth, height: size.height)
super.init(size: size)
}
So you can positioning your object with:
ball.position = CGPoint(x: CGRectGetMidX(playableArea), y: CGRectGetMaxY(playableArea) - (ball.size.height * 0.90))
This code works in the iPhone 4S, 5, 5S, 6, 6 Plus, 6S, 6S Plus, and iPads.
If you want to see your borders (for debug or not):
func drawWorkArea() {
let shape = SKShapeNode()
let path = CGPathCreateMutable()
CGPathAddRect(path, nil, workArea)
shape.path = path
shape.strokeColor = SKColor.redColor()
shape.lineWidth = 8
addChild(shape)
}

Related

SKCameraNode and Aspect Ratio

I'm making a game and would like to create a universal scene size. Its height will be 2048, for example. The width of the level will be different depending on the level.
I want the height of the level (the play area) to fit completely on the screen of the device, regardless of the device whatever it is (iPhone, iPad).
GameViewController:
scene.scaleMode = .aspectFill
SKScene:
let cameraNode = SKCameraNode ()
let background = SKSpriteNode(imageNamed: "Background")
override func didMove(to view: SKView) {
background.size = CGSize(width: 2048, height: 1536)
background.position = CGPoint(x: 1024, y: 768)
addChild(background)
cameraNode.position = CGPoint(x: background.size.width / 2,
y: background.size.height / 2)
addChild(cameraNode)
camera = cameraNode
}
override func didChangeSize(_ oldSize: CGSize) {
let screenHeight = UIScreen.main.bounds.size.height
let backgroundHeight = background.size.height
let scaleFactor = backgroundHeight / screenHeight
cameraNode.setScale(scaleFactor)
}
different iOS devices will have dramatically different sizes. hence you need to scale your camera based on the size of the screen and the size of your background image.
GameViewController
scene.scaleMode = .resizeFill
SKScene
class GameScene: SKScene {
let cameraNode = SKCameraNode()
let background = SKSpriteNode(imageNamed: "background")
override func didMove(to view: SKView) {
//set up camera note
self.addChild(cameraNode)
self.camera = cameraNode
//add background image
self.addChild(background)
}
override func didChangeSize(_ oldSize: CGSize) {
//calculate scale factor based on screen size and image size
let screen_height = UIScreen.main.bounds.size.height
let background_height = background.size.height
let scale_factor = background_height/screen_height
cameraNode.setScale(scale_factor) //scale the camera accordingly
}
}

How do I make an object change color when it hits another object?

I'm making a screen saver in swift, and the screen saver has a paddle, and a ball, like pong. I am trying to make it so that when the ball hits the paddle, both the paddle and the ball change to a random color. How would I accomplish this?
It is curently set to fill in white.
private func drawBall() {
let ballRect = NSRect(x: ballPosition.x - ballRadius,
y: ballPosition.y - ballRadius,
width: ballRadius * 2,
height: ballRadius * 2)
let ball = NSBezierPath(roundedRect: ballRect,
xRadius: ballRadius,
yRadius: ballRadius)
NSColor.white.setFill()
ball.fill()
}
private func drawPaddle() {
let paddleRect = NSRect(x: paddlePosition - paddleSize.width / 2,
y: paddleBottomOffset - paddleSize.height / 2,
width: paddleSize.width,
height: paddleSize.height)
let paddle = NSBezierPath(rect: paddleRect)
NSColor.white.setFill()
paddle.fill()
}
it seems to me that you want a Collision detector.
I suggest use iOS UIDynamic instead of implementing your own physics engine.
specifically this class UICollisionBehavior.

Resizing nodes in SpriteKit view according with diifferent display's size

I want to create an universal application with spriteKit view.
But the iPad display is bigger tha iPhone display!
1) How can I positioning the nodes that they resizing according to the display dimensions? I mean when I add a nodes for iPhone screen I don't want that on iPad the dimension of this node is equal to the dimension of on iPhone, I want have the same nodes with differents sizes for each different screen.
2) And also How can I positionig this nodes for differents display?
Thank a lot
first set a automatic margin for your app:
var gameArea: CGRect
override init(size: CGSize) {
let maxAspectRatio: CGFloat = 16.0/9.0
let playableWidth = size.height / maxAspectRatio
let margin = (size.width - playableWidth) / 2
gameArea = CGRect(x: margin, y: 0, width: playableWidth, height: size.height)
super.init(size: size)
}
Then you can implement your SKNode
player.physicsBody = SKPhysicsBody(polygonFrom: path)
player.physicsBody!.affectedByGravity = false
player.physicsBody!.categoryBitMask = PhysicsCategories.Player
player.physicsBody!.collisionBitMask = PhysicsCategories.None
player.physicsBody!.contactTestBitMask = PhysicsCategories.Enemy
player.constraints = [SKConstraint.positionX(SKRange(lowerLimit: gameArea.minX + player.size.width/2, upperLimit: gameArea.maxX - player.size.width/2))]`
I want have the same nodes with differents sizes for each different
screen.
If I understand what you want, here my answer:
You have two options:
1 - https://www.raywenderlich.com/49695/sprite-kit-tutorial-making-a-universal-app-part-1
2 - programmatically resizing:
// code in objc
// defines global
#define IS_IPAD ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad )
#define IS_IPHONE ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone )
#define SCALE_FACTOR ( IS_IPAD ? 2 : 1 ) // what ever you want
// usage in the code
sprite.size = CGSizeMake(width * SCALE_FACTOR, height * SCALE_FACTOR);
// etc...
EDIT:
The position is always relative to the parent. The anchor points are also important here. Here an example how to add a label direct to the scene. The label position is top middle:
func addBestScoreLabel(bestScore: Int) {
let bestScoreLabel = SKLabelNode()
bestScoreLabel.text = "Best Score: " + String(bestScore)
bestScoreLabel.fontSize = 20 // what ever
bestScoreLabel.fontColor = .red // what ever
bestScoreLabel.zPosition = 10 // what ever
bestScoreLabel.position = CGPoint(x: self.frame.width/2, y: self.frame.height - bestScoreLabel.frame.height)
// add the label to the scene
self.addChild(bestScoreLabel)
}
// call the function
addBestScoreLabel(bestScore: 1234)

SpriteKit: unwanted stretching of sprite when resizing/scaling

I've seen similar questions before but seems that no clear solution is provided. Currently I have a SKScene with child, contained by a SKView. Note: I'm not using a Game project; instead, I'm using the SKView in a normal UIView, and just add SKView as the subview of the UIView. The sprite itself is a small image, and it only occupies a certain portion in the middle-bottom part of the whole frame. The code looks something like this:
let imageView = SKView()
imageView.frame = CGRect(x: ..., y: ..., width: ..., height: ...) //all are predefined constants
let sprite = SKSpriteNode(texture: SKTexture(imageNamed: "image"))
sprite.position = CGPoint(x: imageView.frame.width * 0.5, y: imageView.frame.height * 0.5) // so it's positioned in the center
let scene = SKScene(size: imageView.frame.size) // also tried with sprite.size, but not working
scene.addChild(sprite)
scene.scaleMode = .aspectFit
imageView.presentScene(scene)
self.frame.addSubview(imageView)
Now the problem is the sprite is not in its original shape; it's stretched along the vertical axis. I tried changing the scaleMode with all 5 choices: .fill / .aspectFill / .aspectFit / .resizeFill / none, but none of them give the sprite its original shape/ratio. I've also tried changing the constants for the imageView's frame, but that only cuts the sprite (if imageView.frame.height is decreased). So may I know how to address this issue?
Your code works fine as written, so the problem is likely elsewhere. I modified it a little bit so you can test it out in a Playground. Just make a new Playground and copy and paste. You'll also have to drag in an image for your sprite.
import SpriteKit
import PlaygroundSupport
// create the outer UIView and show it on the playground
let outerView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
PlaygroundPage.current.liveView = outerView
// create the SKView and add it to the outer view
let imageView = SKView(frame: CGRect(x: 25, y: 25, width: 250, height: 250))
outerView.addSubview(imageView)
// create the scene and present it
var scene = SKScene(size: imageView.frame.size)
imageView.presentScene(scene)
// create the sprite, position it, add it to the scene
let sprite = SKSpriteNode(texture: SKTexture(imageNamed: "worf.jpg"))
sprite.position = CGPoint(x: imageView.frame.width * 0.5, y: imageView.frame.height * 0.5)
scene.scaleMode = .aspectFit
scene.addChild(sprite)
Here's the result:
No stretching! So your issue is probably related to the UIView you're placing it in. For example if you add:
outerView.transform = CGAffineTransform(scaleX: 1, y: 2)
Now the image is stretched as you describe. Take a look at your containing view and everything above it.

Is there a way to make a responsive layer node?

I'm new to SpriteKit, I've searched for the answer to this but can't seem to find one.
Is there a way to make a node that will expand to fit whatever device you're on?
Right now I'm creating layers with either a concrete dimension or a ratio. What I'd like to do is create an expandable rectangle, attach a physics body to it and have that be the boundary for my game.
what I'm currently doing:
gamescene.swift
override func didMoveToView(view: SKView) {
let maxAspectRatio: CGFloat = 16.0 / 9.0
let maxAspectRatioheight = size.width / maxAspectRatio
let playableMargin: CGFloat = (size.height - maxAspectRatioheight) / 2
let playableRect = CGRect(x: 0, y: playableMargin, width: size.width, height: size.height - playableMargin * 2)
physicsBody = SKPhysicsBody(edgeLoopFromRect: playableRect)
}
but I've experimented with using a background as well:
// declared with the other fields outside method
var background = SKSpriteNode(imageNamed: "background")
//inside didMoveToView
let bounds = SKNode()
bounds.physicsBody = SKPhysicsBody(edgeLoopFromRect:
CGRect(x: 0, y: 0,
width: background.size.width,
height: background.size.height))
bounds.physicsBody!.categoryBitMask = PhysicsCategory.Boundary
bounds.physicsBody!.friction = 0
worldNode.addChild(bounds)
Any tips would be greatly appreciated!