How to scale/position nodes Swift SpriteKit? Custom View? - swift

I'm working on a game but can't figure out the right way to scale/ position everything. I have a universal app and when I switch from device to device the nodes aren't in the right place everytime. I'm using AspectFill because it sizes my nodes the the right way for each device. The problem is the positioning. I don't know if I'm right in making the conclusion that I need to figure out how to make the view the same size as the screen so that it changes for each device and then positions and scales everything properly, but this is what I've been trying to figure out. I've been trying many different things online and looking at things like how to make a custom view. I've tried to make a rectangle that ha constraints to fill the screen and then setting that rectangle to equal the UIView or SKView. I've tried many different things and looked at many things online but they are either confusing and I don't know if I'm trying them right or they don't pertain to my situation. This is causing a lot of confusion for me and I was hoping someone could help with this issue. I think the issue is that I need to make a custom view that relates to the size of the screen. I don't know if this is possible or what I should be going after. It would be great if someone could clear this up for me.

To keep my games universal I make all my nodes sizes and positions dependant on SKScene size
Instead of using hardcoded numbers try using proportions from your SKScene size. For example:
Instead of writing this:
node.position = CGPointMake(100, 100)
Write somthing like this:
node.position = CGPointMake(world.frame.size.width / 32 * 16, world.frame.size.height / 18 * 9)
world - SKNode of exactly same size as your SKScene
Same for sizes
let blackSquare = SKSpriteNode(texture: nil, color: UIColor.blackColor(), size: CGSizeMake(world.frame.size.width / 32, world.frame.size.width / 32))
Also GameViewController should look something like this:
override func viewDidLoad() {
super.viewDidLoad()
// Configure the view
let skView = view as! SKView
skView.multipleTouchEnabled = false
// Create and configure the scene
let scene = GameScene(size: skView.bounds.size)
scene.scaleMode = .AspectFill
scene.size = skView.bounds.size
skView.presentScene(scene);
}
Was this helpfull?

Related

SpriteKit spawn objects inside view frame across devices

I am creating my first game in spriteKit. I followed a few tutorial, so I am able to make the game work at a single size.
The game has objects spawn from the top of the screen and fall towards the player, which is at the bottom.
The issue I am having is that both the player and objects' coordinates are relative to the scene, which is by default of the iPad pro 9.7 size.
Now when I run the game on an iPhone 8, objects are spawn outside the view as well, and the player can also move a bit past the left/right (I am using aspectFill as scaling mode, so the sides get cut off).
What is the proper way to position both the player and objects as children of the current view, so that they are properly scaled and their coordinates are relative to it?
I was simply using this to give the player a starting position (that's a placeholder of course):
player = Player(color: .red, size: CGSize(width: 40, height: 80))
player.position = CGPoint(x: frame.midX, y: frame.size.height/5)
player.zPosition = ZPosition.player
addChild(player)
Edit: to clarify, the problem is that midX is 384, but the midX of the view is 160 on an iPhone8, thus the coordinate mismatch
Here is an image to clarify https://imgur.com/a/0I8i5CX
The player is only supposed to be spawned in the center, that is not a concern, the problem is that at any given time a touch coordinate system is different from the player's system, making it impossible to be clicked.
Also, my center in gamescene.sks is 0,0
You need to factor in the aspect ratio when you are developing in sprite kit. If you are truly using a "single size," then no matter what device you are on, your center should be the same. (0,0)
From what you are telling me with
the problem is that midX is 384, but the midX of the view is 160 on an iPhone8
is that you are reading from your view, not your scene.
This is bad, because your view and your scene are going to be two different sizes.
The first thing you need to do is define your playing area. If you want your paddle to hit the sides of all devices, then you need to develop in 18:39 aspect ratio (or 9:16 if you plan on using the safe areas on iphone x)
This means that on Ipads, the object will be cut off from the top of the screen because the clipping will happen at the top of the screen instead of the sides.
Now if you want to have the paddles to hit the sides of the screen and the object to spawn at the top of the screen, then you will need to use .resizeScale mode and normalize all of your units (usually between 0 and 1).
This means you are going to end up creating a different game experience across devices with different aspect ratios, as opposed to a different viewing experience from just clipping.
please share an image so that I can understand the problem properly now it looks like you are facing problems in constraints and you want your player position always on the middle of every screen which is quite simple you have to make the x value 0
player.position = CGPoint(x: 0, y: frame.size.height/5)
I solved by adding margins and using them as variables
guard let viewWidth = view?.frame.width else {return}
if viewWidth < frame.width { // scaled device
leftMargin = (frame.width - viewWidth) / 2
rightMargin = leftMargin - viewWidth
}

how to paint/erase SKSpriteNode

A little bit stuck on how to paint/draw an effect like an alpha Chanel onto an SKSpriteNode node i've started off with setting up the two images I need (ps if there is another way to do this is sprite-kit id love to know how to paint the masks
1)The hidden picture - SKSpriteNode *hiddenimageNode
2)The overlay that gets scratched away SKSpriteNode *myOverlay
3)And finally a mask node comprising of
UIImage *image;
SKTexture *maskTexture= [SKTexture textureWithImage:image];
maskNode = [SKSpriteNode spriteNodeWithTexture:maskTexture];
all of these are placed inside of a node "cropNode" [SKCropNode node];
this works more like a static image (that of a circle moving at touch location and not quite what I'm after, I'm hoping to be able to scratch away the entire image)
this works fine but its Not quite the look I'm after
Pictures: Dragging finger from pos1 to pos02, while "erasing purple layer to reveal a smileyface"
is there a way to make it look like I'm erasing the sprite?
nubie coder
//Updating project...
So since then I have tried using this code
https://github.com/oyiptong/CGScratch
and have added it to my SkScene by creating a subview then placing the UIView (Scratchview into it)the erasing is working however the erasing is not happening where the touches are occurring, any ideas why this might be happening?
If you are doing this in iOS 8, then your best bet is to just use SKSpriteNodes as your masking nodes, there is a weird bug with other kinds of nodes that causes distortion.
If you are doing this with iOS9 +, then SKShapeNodes are fixed.
I am going to explain the concept for iOS 9. To get this to work in iOS 8 is a real pain, since subtraction does not subtract alpha in iOS 8.
Now for your mask nodes, you only have 2 options for drawing, On and Off based on the alpha level of the pixels in your mask image. So what you want to do is incorporate subtraction alpha blending to create your desired effect.
let bigcircle = SKShapeNode(circleOfRadius: 80)
bigcircle = .whiteColor()
let littlecircle = SKShapeNode(circleOfRadius: 40)
littlecircle.position = CGPoint(x: 10, y: 10)
littlecircle.fillColor = .whiteColor()
littlecircle.blendMode = .Subtract
bigcircle.addChild(littlecircle)
maskNode = bigcircle
What this code is doing is making a big white circle with a radius of 80 points, and drawing a white circle inside of it at 40 points. Since we are using subtraction blending, it is going to take the new color and subtract it from the old (in our case white(1,1,1,1) - white(1,1,1,10 = transparent(0,0,0,0)) and get us a nice hole in our mask that will end up being cropped out of the layer over your smiley face.

My app is on 200% zoom

When I run my game it's like the zoom was on 200%. I dont know what to do to make it normal.
class GameViewController: UIViewController {
var scene: GameScene!
override func viewDidLoad() {
super.viewDidLoad()
// CONFIGURE THE VIEW
let skView = view as! SKView
skView.multipleTouchEnabled = false
// create and configure the scene
scene = GameScene(size: skView.bounds.size)
scene.scaleMode = .AspectFill
// Present the scene
skView.presentScene(scene)
}
p.s: I already checked and it's not in the simulator's settings
The reason your scene looks zoomed is explained by the scaling factor (AspectFill). You'll likely want to use either Fill or ResizeFill.
With SKSceneScaleMode you have four options:
Fill
Each axis of the scene is scaled independently so that each axis in
the scene exactly maps to the length of that axis in the view.
AspectFill
The scaling factor of each dimension is calculated and the larger of
the two is chosen. Each axis of the scene is scaled by the same
scaling factor. This guarantees that the entire area of the view is
filled but may cause parts of the scene to be cropped.
AspectFit
The scaling factor of each dimension is calculated and the smaller of
the two is chosen. Each axis of the scene is scaled by the same
scaling factor. This guarantees that the entire scene is visible but
may require letterboxing in the view.
ResizeFill
The scene is not scaled to match the view. Instead, the scene is
automatically resized so that its dimensions always match those of the
view.
Available in iOS 7.0 and later.
↳ https://developer.apple.com/library/prerelease/ios/documentation/SpriteKit/Reference/SKScene_Ref/#//apple_ref/c/tdef/SKSceneScaleMode

SpriteKit- How to zoom-in and zoom-out of an SKScene?

If I'm making a game in SpriteKit that has a large "world", and I need the user to have the option of zooming in and out of the SKScene, how would I go about this? Or, to make things simpler, in the didMoveToView function, how can I present more of the world to the user's device's screen (without using world.runAction(SKAction.scaleTo(0.5)) or something)?
There's a SKCameraNode that's built specifically for this. The SKCameraNode defines the viewport into your scene. You create a camera node and assign it to the camera property of your scene.
let cameraNode = SKCameraNode()
cameraNode.position = CGPoint(x: scene.size.width / 2, scene.size.height / 2)
scene.addChild(cameraNode)
scene.camera = cameraNode
You can then create actions and run those actions on the camera. So to zoom in on the scene, you'd do this.
let zoomInAction = SKAction.scale(to: 0.5, duration: 1)
cameraNode.run(zoomInAction)
The cameraNode basically is a square node in the scene, that I think takes the proportions of the view by default? Cuz there's no size initializer. So when you make it smaller, the scene looks like it gets zoomed. To zoom out you'd make an action that increases the scale. Basically imagine a rectangle on your entire scene, and whatever is in the cameraNode's rectangle directly shows on your iPhone screen. You can also add moveTo actions and sequence actions and set timingModes on the actions same as if it were your normal spriteNode.
Here's the WWDC where the apple guy shows what I've just said. CameraNode bit is around 3 mins before the end.
https://developer.apple.com/videos/play/wwdc2015-604/
So, the best solution I could could find goes something like this. In the didMoveToView function, create an SKSpriteNode called World and make it whatever size you want your world to be. Then, write world.setScale(0.5) if you want a 50% zoom-out. However, if you have a player node that needs to always be centered in the screen, you'll need to add the following to your update function.
override func update(currentTime: CFTimeInterval) {
world.position.x = -player.position.x * (0.5)
world.position.y = -player.position.y * (0.5)
}

SKScene scale + anchorPoint = strange behavior

I have an empty SKScene which needs to be horizontally centered, so I set it's anchorPoint:
self.anchorPoint = CGPoint(0.5f, 0);
I also need to scale it, per SpriteKit - Set Scale and Physics, so I run an action on it like this:
[self runAction:[SKAction scaleTo:0.5f duration:0.0f]];
My problem is that when I detect touches, I get locations that seem strangely off.
I detect touches using the usual (locationInNode:self) method, and I'm currently adding a little blue square where the touches are, but when I touch the 4 corners of my device, I see a frame that is a quarter of my screen (correctly) but is moved to the left by a seemingly arbitrary amount
Here are some things I've already checked:
scene is initialized in viewWillLayoutSubviews, I know it has the correct initial dimensions
scene's scaleMode is set to SKSceneScaleModeAspectFill, but I've tried all of them to no avail
I was struggling with the same issue for awhile, but I think I finally got it figured out. You're not supposed to scale the scene (like it hints if you try setScale). You're supposed to resize it.
Try this:
myScene.scaleMode = SKSceneScaleModeAspectFill;
And then while zooming:
myScene.size = CGSizeMake(newX, newY);
Set Anchor Point as
self.anchorPoint = CGPoint(0.5f, 0);
And set Scene Scale Mode as ASPECT FIT, not aspect fill.
SKSceneScaleModeAspectFit