Using SKScene to make an universal app - swift

I already read tons of stuff but im still having a lot of problems with it.
First i went for the correct Scalemode with in my understading is AspectFit. Here the post i read:
Difference between UIViewContentModeScaleAspectFit and UIViewContentModeScaleToFill?
But even so another post tells to use ResizeFill:
Dealing with different iOS device resolutions in SpriteKit
The problem with that is that the SKScene on Ipad only get 1/10 of the screen, it looks like i need to make 2 separate SKScene, one for Ipads and another for Iphone.
But still for Ipad i get 2 black lines on top and bottom of the screen. Then i saw this post:
viewWillLayoutSubviews in Swift
What im using to change views is this code:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let scene = Level(fileNamed: "Level")
scene!.scaleMode = .AspectFit
let transition = SKTransition.fadeWithDuration(0.5)
self.view?.presentScene(scene!, transition: transition)
}
What are the correct practices when using SKScene for an universal app? Im already using Atlas to get #x1, #x2 and #3 images. My SKScene size is 480x320, from what i read is the best size to build an universal app.

I have recently tried converting my iPad app to a universal, and I can shed some light on this.
There are several sizes you need to take into account. First, the view size. This is the SKView instance you order to present the SKScene. Usually, this view has a size equal to the size of [UIScreen mainScreen].size:
iPad | 1024*768
iPad Pro | 1366*1024
iPhone 4" | 320*576
iPhone 4.7" | 375*667
iPhone 5.5" | 414*736
Your SKScene also has a size property which indicates the size of its viewport. This is the size (or the resolution, if you will) in points at which SpriteKit renders the scene.
The last thing to discuss is scaleMode which is how the SKView frames the rendered scene inside itself. You can think of this as a UIImageView framing a UIImage inside itself, only we have fewer options for SKScene.
So if you have a view of 576*320 and a SKScene with size 1024*768 the result is a horizontally stretched image.
Note that when adding an SKScene to an SKView using -presentScene:, the passed scene's size is resized to the view's size.
TL;DR
Depending on your game, you can have 2 .sks files (one for iPhone, one for iPad). Set the size of the root object in each file (the SKScene node) to the size of the device that you're targeting. That's what I'll probably end up doing, anyway.

Related

How do I eliminate SKScene.scaleMode = .aspectFit cutting off on all 4 sides with rotation?

How do I eliminate SKScene.scaleMode = .aspectFit cutting off on all four sides with rotation?
I read elsewhere:
.aspectFit Scales the content to fill the size of the view.
Some portion of the content may be clipped to fill the view’s bounds.
I call setupScene() from within override func viewDidLoad() {..}
In my func setupScene() {..} I call:
if let ourScene = SKScene(fileNamed: "aName") {
ourScene.scaleMode = .aspectFit
}
And, indeed, a portion of the content is clipped to fill the view’s bounds. Resizing between Landscape and Portrait does happen as it should .. but the clipping on top and bottom happens.
I do note that there is zero clipping horizontally, but just vertically:
FWIW, I have used .aspectFill which renders a significant improvement, but clipping still occurs, albeit much less than with .aspectFit.
Finally, it is significant that my SKScenes are much taller than wide - and this may be the source of my problem.
Which begs the question, should I redesign my SKScenes to be square and not rectangular? I just do not know.
Any guidance as to how I can avoid cutoff and instead have the whole SKScene resized to the entire SKView, whether it's in Landscape or Portrait mode with zero cut off?
OR do I just elect AppleTV as the only destination because of the rectangular shape? I think this is an easy way out that just avoids having to find the real solution! .. so, no thanks!
BINGO!!
A Game which height is much greater than its width.
If I allow .aspectFill to literally do its thing, cutoff will occur as already stated above. This makes sense because literally squeezing a tall height into Landscape mode would make all the enclosed Game Pieces super super tiny. Ugly to say the least.
I literally thought of this "by accident" .. namely, when the user rotates their Device 90 degrees to Landscape, do rotate the Game Scene, but do not scrunch the size of all the enclosed Game Pieces and do not change their positions.
Keep these Game Pieces scaled and positioned as if they were still in Portrait mode even though the Device is physically in Landscape mode.
Analogy = when you cut down a tree, it goes from Portrait to Landscape, but the tree does not become shorter when on the ground.
So, go to Info and Custom iOS Target Properties and look for this:
ASAMOF, you can now eliminate any call to .aspectFill or .aspectFit
Remember this is a Game targeted for use with a Gamepad. Do you really want to be scrolling while your nifty Game Piece is trying to avoid hitting a barrier?
BINGO!

How to prevent SpriteKit overlay in SceneKit to stretch?

In WWDC 2017 SceneKit session they recommend using overlaySKScene to overlay a SpriteKit layer over SceneKit for HUD, etc. It seems to work fine until I rotate the iPhone/iPad. Now the text in SpriteKit all got stretched (if turn from portrait to landscape) or shrunk (if turn from landscape to portrait). Is there anyway to keep the text or graphics in same size regardless of orientation?
sceneView.overlaySKScene = SKScene(size: sceneView.frame.size)
I solved the issue in a very simple way. Just resize the overlaySKScene and reposition everything in
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
let width = sceneView.frame.height
let height = sceneView.frame.width
sceneView.overlaySKScene?.size = CGSize(width:width, height:height)
...
}
Hope this helps others facing similar problem.
You need to look at how SKScene works:
This means set the overlay to size of the iphone screen for the given orientation:
sceneView.overlaySKScene = SKScene(size: sceneView.frame.size)
Now I am assuming nothing else changes, so that means your scaleMode is set to .fill, which means stretch graphics till all sides are filled in.
When sitting in portrait mode, you will not notice anything because it is 1:1 scale.
Now you flip it to landscape.
The system is going to attempt to convert your portrait screen size to a landscape size.
This means you are going to get fat stubby graphics because it is going to stretch out your sides, and shrink your top and bottom to fill all edges.
This is why doing code like sceneView.overlaySKScene = SKScene(size: sceneView.frame.size) is bad. You end up getting yourself confused.
I always recommend either using an SKS file, or setting a constant scene size, so sceneView.overlaySKScene = SKScene(size: CGSize(750,1334))
Then, depending on how I want to display my overlay, I use the proper scale mode.
In games where I want the overlay to take up the exact percentage of screen space, I use .aspectFill
In games where I want the overlay to take up the exact amount of pixel space, I use .resizeFill
By looking at the scaleMode, I now know how my scene is suppose to behave.
With .aspectFill, I am going to need to detect when orientation was flipped, and at that time either present the SKS corresponding to orientation, or move nodes corresponding to orientation.
With .resizeFill, I need to create a resize method that adheres to when the orientation is flipped, and move the nodes in proportion to the new size.
You can detect when orientation flips in the ViewController with
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
then use the view to get to the scene to call your resize function/load a new scene.

SpriteKit Coordinates differ between iPad and iPhone

I have a very simple SpriteKit scene that is not behaving like I would expect. It is a universal app and the code that I am using to draw a single red square in the scene is as follows:
let redSquare = SKSpriteNode(color: UIColor .redColor(), size: CGSizeMake(100.0, 100.0))
redSquare.anchorPoint = CGPointMake(0.0, 0.0)
redSquare.position = CGPointMake(1.0, 1.0)
addChild(redSquare)
This code is in the GameScene didMoveToView method of the default app that xCode creates for you when you select SpriteKit game.
When run on the iPad it behaves as expected drawing the red square 1 point off the bottom and left edge of the screen.
When I run it on the iPHone 6 Plus simulator however it shows up like this:
The spaceship shows up at the correct touchpoint on both devices, however a println() of the touch location shows (1,1) on the iPad and (1,100) on the iPhone.
This indicates that the coordinates from (x,0) - (x,99) are off the bottom of the iPhone screen which makes no sense to me at all.
Why do the two devices not display it the same way? The behavior is the same in both the simulators and on the actual devices.
Thanks!
Scooter
This is caused because the size of the screen on iPhone is incorrect - have a look in the scene's sks file, you should see the size of the scene is set to (1024, 768) by default. To correct this you need to set the scene size and scale mode:
scene.size = skView.bounds.size
scene.scaleMode = .AspectFill
You should do this is didMoveToView or when you instantiate the scene.

How to place sprite kit elements overtop of regular UIKit elements

I'm trying to geive the user the ability to drag a skspritenode over a UIImageView and UIButton, but whenever I create the scene it appears behind the UI elements. How do I fix this?
I am afraid that is just not possible. All the sprites and any other elements are rendered frame by frame on the SKScene node and cannot be "dragged" or placed anywhere else. These are not UIKit elements.
In this case, your view is acting as the SKScene and hence all the sprites are being rendered onto that.
SKView is a UIView/NSView in itself. All the Sprite Kit nodes (the scene is also a node) are constituents of the SKView and contained within it. You can only change the draw order of views but not their contents.
To give an example: what you're trying to do is the equivalent of trying to place a UIImageView on top of a UIButton's background but below the UIButton's label.
While technically you could achieve this for plain UIView elements with some trickery, the same will never work for Sprite Kit nodes. They aren't views to begin with but drawn onto an OpenGL framebuffer represented/managed by the SKView. From the perspective of Cocoa an SKView is a single view with no subviews in it.
Simply put: nodes are not views, and nodes are technically incompatible to views.
The UIImageView and its UIButton and whatever other content you want in there must be in a UIView that's sitting lower than the SKView you have the Sprite Kit contents in.
And you'll need to ensure the SKView is transparent (at least its background) and that it passes touches through to the UIView underneath if you need them down there sometimes.

Rotating camera view on iOS

I've read few questions here on stack and I'm in a dead-end. I want to have device (iPhone/iPad) camera as a background, to one of my ViewControllers. I'm using Storyboard and UINavigationsViewController, so I'm modyfing a child ViewController. As far as I read, this means that I cannot force this "child" to start in specific orientation (first problem).
Secondly, I decided to go with UIImagePickerController, but as I got it right - you cannot rotate this component, right?
So basically my question is where to go from here, to achieve camera view on background no matter what device orientation? It surely can be done, because Vuforia i.e. has it (and they are using UIImagePickerController with EAGLView).
You have two options to do that task:
1) You can use Eaglview to render the frames of camera device in background.
2) You can use caeagllayer to do the same.
To draw on that layer you have to use drawableproperties and CaTransformationMatrix