Objects positioning in SpriteKitScene - ios

I am making this basic space shooting game but I can't get the x-coordinates of enemies right. Sometimes, they go out of the screen - or remain half inside at the edges. How can I fix this permanently regardless of which iPhone the app runs on?
here is the code for my positioning: (note that egg is name of my enemy. it is function I made for calling it every single time)
func egg() {
var egg = SKSpriteNode(imageNamed: list[Int(arc4random_uniform(6))])
var min = self.size.width / 8
var max = self.size.width
var point = UInt32(max - min)
egg.position = CGPoint(x: CGFloat(arc4random_uniform(point)), y: self.size.height)
let action = SKAction.moveToY(-100, duration: 2)
let actionDone = SKAction.removeFromParent()
egg.runAction(SKAction.sequence([action, actionDone]))
}

There are several places where things can be going sideways.
• First, it looks like you're assuming the location of your scene's anchorPoint is at (0,0). If it's not (the default .sks file now has an anchorPoint at (0.5,0.5), for instance), that could explain why your enemies never show up. The first thing I would do is double-check the anchorPoint of my scene.
• If you're adding these eggs to some other node besides the scene itself, then those eggs will "inherit" whatever translation, rotation, and scale that their parent node has. In other words, if you add an egg at (20,20) to a node at (30,30), it will appear at (50,50). Make sure the "context" of the eggs is what you expect.
• Your "min" and "max" values seem a little odd. The "min" looks like it is intended to be "indented" by 1/8th of the screen size on the left, but you aren't indenting it on the right. Maybe that's what you intend, but if you intend them to have symmetrical behavior, then you'll want to back "max" off by an eighth, too. Also, you're not adding that eighth back into the x value when you're determining the position, so this could stick an egg at x=0. This would explain why your sprites are sometimes "half inside at the edge".
• The size of your scene is not necessarily the screen dimensions. This depends heavily on the "scaleMode" of your SKScene. Check the documentation for more information on this, but briefly, the scaleMode tells SpriteKit how to render a scene that doesn't match its view in size or aspect ratio. Does it stretch? Does it crop? Does it letterbox? If you run a square 400x400 scene on a 1024x768 screen device, it has to have some way of knowing what you mean. Does it draw that 400x400 in the middle of the screen and let stuff be seen "outside" that rect? Or does it scale it up to fill the screen, cropping off the top and bottom? Or does it scale it up to FIT the screen, allowing space above and below that is technically outside the scene's size? Or does it scale it up and squash the scene to fit exactly? If your scene isn't matching up exactly with your device's screen, this could explain why things are not playing nice and staying within visible bounds.

Related

How do I convert coordinates in SpriteKit?

I'm trying to grasp the conversion thing in SpriteKit but despite having read the documentation and several posts on SO I can't seem to get it right. As far as I understand there are two coordinate systems that work independently of one another, one for the scene and one for the view, which is why I simply can't use the things like UIScreen.main.bounds.maxX to determine screen corners that the node can relate to. Am I getting this right?
Anyway, here's my attempt at converting coordinates:
let mySquare = SKShapeNode(rectOf: CGSize(width: 50, height: 50))
mySquare.fillColor = SKColor.blue
mySquare.lineWidth = 1
let myPoint = CGPoint(x: 200, y: 0)
let newPosition = mySquare.convert(myPoint, from: self)
mySquare.position = newPosition
print(newPosition)
self.addChild(mySquare)
The print returns the exact same position as went in so obviously I'm not doing this right, but I have tried a number of different constellations but with pretty much no result; the coordinates remain the same. I have also tried let myPoint = CGPoint(x: UIScreen.main.bounds.maxX, y: UIScreen.main.bounds.maxY) but same there; no conversion.
What am I missing? In my head I read the conversion above as "convert myPoint from the view coordinate system and use it for my node mySquare.
There are lots of coordinate systems floating around, and so lots of potential sources of confusion:
Scene coordinates: that's your game's world, and what you usually think about when imagining coordinates and how to position things overall.
Node: Nodes have their own coordinate system. Once you start building a hierarchy, that matters. Imagine, e.g., an on-screen joystick that has a background showing a graphic of movement directions and a central "knob" that the player can manipulate. You might represent the joystick as a node with two children. One child is a sprite for the background, and the other is a sprite for the knob. The background sprite would naturally be at position (0,0), meaning the center of the overall joystick. The knob would move around, with (0,0) meaning centered and maybe (0,100) meaning up a bit. The overall joystick might sit at (200,300) in the scene. Then the background sprite would show up at (200,300) in the scene and the knob, when up, would be at (200,300)+(0,100) = (200,400) in the scene. The convert(from:) and convert(to:) are for converting within the node hierarchy. You could ask where the knob is in the overall scene's coordinates by knob.convert(.zero, to: scene) or joystick.convert(knob.position, to: scene). You very rarely need to do that sort of conversion.
View coordinates: The view is a window on the scene, i.e., what's actually being shown. If you've got a full screen game, the view is basically determined by the screen size in points. How view coordinates map to scene coordinates determines what part of the scene you actually see. If you need to go between view coordinates and scene coordinates, you use the scene's convertPoint(fromView:) and convertPoint(toView:) methods.
If you don't do anything special and have the scene size the same as the view size, then the scene-view mapping will have (0,0) in the scene at the lower left corner of the view. Another common convention is to have (0,0) in the scene at the center of the screen by setting the scene's anchorPoint to (0.5,0.5). Or perhaps you've designed the scene so that the world is 2000x2000 in size and there will be a nontrivial scaling and possible letter-boxing or cropping involved (depending on the setting of the scene's scaleMode). Or if your game has a camera node and, e.g., the camera is set to follow the player around, then the view-to-scene mapping will be changing as the player moves.
In your code, calling mySquare.convert(from:) doesn't really even make sense since the square hasn't been added to the scene at the time you're doing the "conversion".
Anyway, if you really want to do something like "put a square in the top-left corner of the screen", then you can take the point in the view's frame and convert it to scene coordinates and set the square's position to that.
override func didMove(to view: SKView) {
...
mySquare.position = convertPoint(fromView: CGPoint(x: view.frame.minX, y: view.frame.minY))
addChild(mySquare)
...
}
Edit: I would encourage you though to think mostly in terms of the overall scene, after some initial consideration of how the game should map to devices with screens of different sizes and aspect ratios. Once you're thinking in terms of the scene, then the scene's frame (rather than the view's frame) becomes the most natural reference when you're imagining "at the left edge" or "near the bottom right".

How to tell height of a sprite after zrotation?

I have a sprite like this:
let sprite = SKSpriteNode(imageNamed: "sprite.png")
print("sprite height: \(sprite.size.height)") //results to 150
sprite.zRotation = 90 * degreesToRadians //turns sprite 90 Degrees
Results to this:
However:
print("sprite height: \(sprite.size.height)") //results to 150
Sprite height is still 150 even though it takes far less space height-wise.
Is there a way to get the actual height of a sprite after zrotating it? I know I could easily work around this with in the example above but my real problem is that I have various sprites at various zrotations and I'm trying to make sure that all of them are fully visible on screen.
So basically I have a sprite (red bar), an anchor point at 0,0 (blue dot) and visible screen (black frame).
I zrotate the sprites to random angles using arc4random_uniform but some sprites end up not being completely visible on screen. Basically I would have to know the height of the green arrow or assign it as the anchorPoint after zrotation. Or perhaps there are other ways that I have not thought of. All help appreciated!
0x141E's comment works.
sprite.frame.size.height
results to correct outcome.

Dealing with different iOS device resolutions in SpriteKit

I'm playing around with SpriteKit in Xcode 6, iOS 8 beta 5. Everything is all laid out and working perfectly on the iPhone 4S simulator, however when switching to the 5S, the elements at the bottom of the screen are cut off.
It was to my understanding that the bottom left corner of the iPhone screen should be CGPoint(0, 0) but after checking the location by printing the coordinates to the console that the lowest point of the left corner I could click was around (5, 44). Is there something wrong in my scene setup thats causing this?
No changes have been made to the GameViewController file and even after I strip the GameScene file the problem persists.
Can anyone at least point me in the right direction with this?
Adding the following code will fix your problem (code is in Swift):
scene.scaleMode = SKSceneScaleMode.ResizeFill
Now if you want to know why this fixes your problem, what your problem actually is, and how to handle multiple resolutions – I suggest you continue reading.
There are three things that can impact the position of nodes in your scene.
1) Anchor Point
Make sure your scene's anchor point is set to (0,0) bottom left. By default the scene's anchor point starts at (0,0) so i'm assuming that is not causing the issue.
2) Size Check the size of your scene. I typically make my scene size match the size of the device (i.e. iPad, iPhone 4-inch, iPhone 3.5 inch), then I place another layer in the scene for storing my nodes. This makes me able to do a scrolling effect for devices with smaller resolutions, but it depends on your game of-course. My guess is that your scene size might be set to 320, 480 which could be causing the positioning problems on your iPhone 5s.
3) Scale Mode The scale mode has a huge effect on the positioning of nodes in your scene. Make sure you set the scale mode to something that makes sense for your game. The scale mode kicks in when your scene size does not match the size of the view. So the purpose of the scale mode is to let Sprite Kit know how to deal with this situation. My guess is that you have the scene size set to 320,480 and the scene is being scaled to match the iPhone 5 view which will cause positioning problems identical to what you described. Below are the various scale modes you can set for your scene.
SKSceneScaleMode.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.
SKSceneScaleMode.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.
SKSceneScaleMode.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.
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.
Conclusion
It looks like you want to remove the scaling of your scene, that way your positions in the scene will match the actual positions in the view. You can either set your scene's size to match the view size, in which case no scaling will take place. Or you can set your scene's scale mode to ResizeFill which will always make the scene's size match your view's size and it won't scale anything. In general I would stay away from any scaling and instead adjust the interface and the scene size to best suit each device. You may also want to add zoom and/or scrolling to allow devices with smaller resolutions to achieve the same view field.
But what if I want to scale my scene?
If however you need to scale your scene, but you still want positions to be relative to the view (i.e. You want (0,0) to be the bottom left of screen even when scene is cutoff) then see my answer here
Additional Info
See answer here for sample code showing how I layout nodes dynamically.
See answer here for more details about scaling to support multiple devices.
If you want to preserve the size of your scene (usually desired when you work with a fixed size and coordinates system), you might want to add padding to either side of your scene. This would remove the letter boxing and preserve all the physics and dynamics of your app on any platform.
I created a small Framework to help with this:
https://github.com/Tokuriku/tokuriku-framework-stash
Just:
Download the ZIP file for the Repository
Open the "SceneSizer" sub-folder
Drag the SceneSizer.framework "lego block" in your project
Make sure that the Framework in Embedded and not just Linked
Import the Framework somewhere in your code import SceneSizer
And you're done, you can now call the sizer Class with:
SceneSizer.calculateSceneSize(#initialSize: CGSize, desiredWidth: CGFloat, desiredHeight: CGFloat) -> CGSize
Just in case, try doing CMD+1, worked for me. Some of the elements were cut off because they were simply not displayed in Simulator - I stress this, this is just a simulator feature (and a bug if you ask me, wasted hours of time to solve this). CMD+2, CMD+3 views can sometimes hide parts of the scene.

Can SKPhysicsJoints be used in a scrolling SpriteKit game?

This seems to be a major oversight by Apple, but since SKPhysicsJoints have their anchor points set in Scene coordinates, this makes doing any kind of scrolling game impossible.
To simulate a camera in SpriteKit you create a WorldNode which contains all of the gameplay elements, and then pan that around the scene. Unfortunately, doing this causes the Scene coordinates of every object in the game to change on every frame as you pan the world around. In turn, this breaks the joint anchor points, and things go berserk.
There isn't even a way to change the joint's anchor point, so I don't even have a way of just updating the coordinate every frame. It would seem that using SKPhysicsJoint in a scrolling game is not an option.
Does anyone know of a way around this?
Ok, I think I figured out what was going on, and I was totally incorrect in my original assumption. The reason my anchor points looked incorrect is because the [convertPoint toNode] call was returning me Scene coordinates that were incorrect. After several hours I realized it was off by exactly half the screen dimensions. My Scene has an anchorPoint of (0.5, 0.5), but this screws up the conversion values. So, if I simply offset the point by width/2, height/2 it's correct:
GPoint pt = CGPointMake(anchorWorldX, anchorWorldY);
pt = [gGameScene convertPoint:pt fromNode:gGameWorld]; // convert to scene coords, but it's WRONG
pt.x += scene.size.width * scene.anchorPoint.x; // this properly adjusts the value to be correct
pt.y += scene.size.height * scene.anchorPoint.y;
SKPhysicsJointPin* pin =[SKPhysicsJointPin jointWithBodyA:hinge.physicsBody bodyB:door.physicsBody anchor:pt];

Spritekit - Bottom of screen coordinates

What are the coordinates for the bottom of the screen... or how can I create a "floor" at the bottom of the screen in spritekit?
Sorry, but I don't understand screen coordinates that well in spritekit.
You need to understand the Sprite Kit coordinate system as explained in Apple's Documentation here.
Here's how you create a floor at the bottom of the screen in SpriteKit:
SKNode *floor = [SKNode node];
node.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:CGRectMake(CGRectGetMidX(self.frame),1.0 , CGRectGetWidth(self.frame), 1)];
[self addChild: floor];
You need some universal approach to get coordinates of corners on the screen.
Using code from that answer you can get CGRect with necessary information.
Example:
let screenRect = getVisibleScreen(
sceneRect: self.scene!.frame,
viewRect: self.view!.frame )
And then you can use it:
screenRect.minX
screenRect.maxX
screenRect.minY
screenRect.maxY
screenRect.width
screenRect.height
This is more then enough to calculate coordinates of "floor" or any other relative positions.
The location of the bottom of the screen will depend on what coordinate system you are using for your scene.
Out of the box, the bottom of the screen will be at y coordinate zero, but there are a few things that can happen that will affect that.
For instance, if you are using the scene editor in xCode, and your scene's anchorPoint property is something other than y=0, then the "origin" of your scene will not be at the bottom of the screen. In the recent xCode beta, they changed the default behavior to have the scene's origin at the center of the scene instead of the lower left corner, so that would explain why you might be seeing things in the center of the screen when you expect them to be at the bottom.
Also, the "bottom of the screen" will be relative to whatever parenting structure you have in your scene. For instance, if you place a background sprite in your scene, and want to attach a floor sprite to that which is at the bottom of the screen, you'll have to do some computing to figure out where to place it because you are going to inherit the translation and rotation of the floor's parent node (and any parents that node has).
To keep things simple, you can just place everything directly on the stage and manage their z-order manually. This will let you, basically, use the same coordinate system for everything. This is often fine; as long as you're not trying to do anything complex with your sprites, you don't need a complicated "tree" of nodes.
But even with this approach, the metrics of your scene are going to have to be handled dynamically. The width and height of your scene are going to depend on how you approach displaying your scene on different devices with different sizes. For instance, the top right of an iPhone 4 is going to be in a different place than the top right of an iPad Pro. A full discussion of how to deal with that is beyond the scope of your question, but generally, you'll probably want to use a "reference width" or a "reference height" for your scene, use .AspectFit or .AspectFill for the scaleMode, and set your scene's size accordingly. (I.e., inspect the view's frame to get the actual aspect ratio of your scene and set your scene size to match your reference metric on one axis and scale the other axis to match the device's aspect ratio.) This will let you use the same metrics for all devices (although one of your two axes will be fluid).

Resources