SpriteKit spawn objects inside view frame across devices - swift

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
}

Related

Detect touches on SKSpriteNode with oddly shaped image(s) running animations

Using SpriteKit for an iOS 9.0 app/game (and no physics body).
I have a few SpriteNodes on a scene. SpriteNodes are with oddly shaped images and runs through multiple frames for animation.
What is the best way to detect touches on a SpriteNode's image content, but not on transparent area of the entire image rectangle.
I see a lot of posts on using SKCropNode / MaskImage. In my scenario, I have multiple images/frames for each SpriteNode for the animation.
Please advice on an approach or point me in the right direction. Thanks.
Based on additional info from comments above:
With only 16 shapes, from the 16 frames, one of the more efficient ways would be to draw primitive shapes that roughly approximate the outlines of your character at each frame, and convert these to CGPaths. These can then be swapped in and out for each frame or (better) plucked out appropriately when there's a touch for testing based on the frame currently being shown at time of touch.
CGPaths are very lightweight structs, so there shouldn't be any performance problems with either approach.
CGPathContainsPoint is the old name of this test, which is now a modernised API for Swift 3 and onwards:
Troubles using CGPathContainsPoint SWIFT
There is an app called PaintCode, that's about $100 USD, which translates vectors into CGPaths, amongst other things, so you could use this to import your shapes. You can copy/paste vectors into it, which I suggest, because you might want to draw in a friendly drawing program. PaintCode doesn't have the world's best drawing experience.
Additionally, here's a free, online tool for doing the creation of a polygon path from textures. Probably more painful than using a drawing app and PaintCode, but it's FREE!
Alternative: Physics Bodies from Texture, and Contact with Touch
You can automagically create physics body shapes from a texture, like so, from the docs on this page:
let texturedSpaceShip = SKSpriteNode(texture: spaceShipTexture)
texturedSpaceShip.physicsBody = SKPhysicsBody(texture: spaceShipTexture,
size: CGSize(width: circularSpaceShip.size.width,
height: circularSpaceShip.size.height))
There have been reports of problems with determining if a point is within a given physics body, so it might be better to create a small physics object, place it where the touch is, and then determine if its in contact with the physics body shape appropriate for the current frame of the current game entity.
Caveat:
There's no telling (nor way to control, that I know of) how complex the resultant CGPaths are for these automagically created physics shapes.
I personally would just check the pixel of the current texture to see if is transparent or not. You can do this to get the pixel data:
(Note this code is hypothetical to get you started, I will try to work out a real example later on for you if you can't figure it out.)
(Also note, if you scale sprites, you need to handle that with converting touch location to texture)
let image = sprite.texture.cgImage()
if let dataProvider = image.dataProvider, let data = dataProvider.data
{
let screenScale = UIScreen.main.scale //let's handle retina graphics (may need work)
//touchedLocation is the location on the sprite, if the anchor was bottom left (it may be top left, will have to verify)
//So if I touch the bottom left corner of the sprite, I should get (0,0)
let x = touchedLocation.x * screenScale
let y = touchedLocation.y * screenScale
let width = sprite.texture.size().width * screenScale
let bpp = 4 // 4 bytes per pixel
let alphaChannel = 3 //Alpha channel is usually the highest byte
let index = bpp * (y * width + x) + alphaChannel
let byte = data.withUnsafeBytes({[UInt8](UnsafeBufferPointer(start:$0 + index,count:1))})
print(Alpha: \(byte))
}
else
{
//we have no data
}

Setup Screen Boundary on iPad Pro 12.9 with SKPhysics

I'm trying to create a boundary of physics for the iPad Pro 12.9
This is how I'm doing it:
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
let sceneBody = SKPhysicsBody(edgeLoopFrom: self.frame)
sceneBody.friction = 0
self.physicsBody = sceneBody
....
}
But the Y is way off in Landscape (much lower and higher than the actual screen), and a little ways off in Portrait. But the X is right in both.
I don't know what I'm doing wrong.
Update
I've added a print to the above, and its showing the maxX and maxY of self.frame to be 375 and 667 respectively. In landscape mode. Neither of those numbers are what they should be, as far as I can tell, yet the X value works correctly whilst Y is way off the top and bottom of the screen.
This iPad model's screen resolution is 2732x2048 (half that in points) so I don't see a correlation between these numbers and the reported frame size.
This has something to do with the way you're scaling the scene. When presenting a scene, you may be setting the scaleMode property of the scene, which is of type SKSceneScaleMode. There are four different modes:
fill: Each axis is scaled independently in order to fit the whole screen
aspectFill: The scene is scaled to fill the screen, but keeping the aspect ratio fixed. This is the one your scene is probably set to.
aspectFit: The scene is scaled to fit inside the screen, but keeps the aspect ratio. If the scene has a different aspect ratio from the device screen, there will be letter boxing.
resizeFill: The scene is resized to fit the view.

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.

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