For the past couple of weeks I have been dabbling with SpriteKit, Xcode, and Swift and despite doing a lot of research I have yet to find out the best way to manage sprite size between devices.
In my game I have found a somewhat round-about way of handling sprite size and position to make my sprites somewhat consistent between devices by manually adjusting characteristics like size, font size, and position with if statements checking for the height of the device. Ideally I would like my entire game views, including the sprites that they house, to be sized proportionally to the device size. I have tried messing with the different SKSceneScaleModes but nothing is really doing the trick.
My question: What is the most efficient way of handling sprite size and positioning between devices in SpriteKit (more specifically in Swift)? I don't care too much about supporting the iPad, mainly just the iPhone 4s - 6 plus. Thanks.
Imo, simply scaling relative to the GameScene's size is good enough. In GameViewController, I initialize the scene to be the view.bounds.size. When you create your sprite, just make a setScale call to make it proportional to view.bounds.size. Since view.bounds.size is based on how big the screen is, you'll get proportionally bigger images for bigger screened devices.
Related
I'm relatively new to swift programming. I have made an app where I didn't use SpriteKit and I would size most views by scaling down the images to a multiple of the screens width and height. Not sure if this is the best tactic, but it worked and I was able to build an app that looked good on all devices.
With spritekit and the scene.sks files, I don't have the option of auto-layouts to size my sprites like I did before. There isn't a lot of content about universal apps online, so I'm struggling to find a solution.
My question is how do I go about sizing objects and making sure my scenes look the same across different devices?
If you have a good example that could explain this that would be very helpful. Thank you!
What we usually do in SpriteKt is to give the SKScene a fixed size and let SpriteKit do the scaling for you on different devices.
So basically we have 2 ways to do it correctly
1) Set scene size to iPad (e.g 1024x768 -landscape, 768x1024 - portrait). This was the default setting in Xcode 7.
You than usually just have/show some extra background at the top/bottom (landscape) or left/right (portrait) on iPads which gets cropped on iPhones.
Examples of games that show more on iPads / crop on iPhones:
Altos Adventure, Leos Fortune, Limbo, The Line Zen, Modern Combat 5.
2) Apple changed the default scene size in xCode 8 to iPhone 6/7 (750*1334-Portait, 1337*750-Landscape). This setting will crop your game on iPads.
Examples of games that show less on iPads:
Lumino City, Robot Unicorn Attack
Choosing between those 2 options is up to you and depends what game you are making. I usually prefer to use option 1 and show more background on iPads.
Regardless of scene size scale mode is usually best left at the default setting of .aspectFill.
You would use the Universal asset slot and/or device specific images. This way you will have a consistent experience on all devices
Spritekit scale full game to iPad
How to make SKScene have fixed width?
Hope this helps
I want to make a landscape app to be universal, so that the sprite nodes scale proportionally to whatever view size is running the app. I'd like an entirely programmatic solution because I don't like the IB.
My game is pretty simple, and I don't need scrolling or zooming of any kind, so the whole game will always be present and take up the entire view.
Is it possible that what I'm looking for is to change the size of the scene to always fit the view? If so, can you explain this thoroughly because I've tried changing this section of my view controller
if let scene = GameScene(fileNamed:"GameScene")
to be the constructor method that takes size as a parameter but Xcode doesn't like that.
Things I've tried
Using fractions of self.view.bounds.width/height. This usually makes all iPhones look good, but on iPads stretches and skews nodes and the boundary box around thew view.
Changing the scaleMode among all four types. I'd like to keep good practice and feel like .AspectFill (default) is the one I should make my app work with, but open to suggestions. Note; I don't want black edges on any device, just the entire view displayed/scaled proportionally.
Applying programmatic constraints. Now I'm fairly new to this and don't understand constraints completely, but no tutorials I've seen even from RayWenderlich talk about constraints on nodes so I didn't delve to deep in this.
Using a method like this to convert points among views. This actually worked pretty well for point positioning of nodes, and if possible I would like this method to work out, but then I still have the problem of sizes of nodes. Also when I build for iPad with this method the view seems to start off as portrait and the nodes look fine but then I have to manually switch it to landscape and the sprites and view boundaries once again get messed up. Here's the method:
func convert(point: CGPoint)->CGPoint {
return self.view!.convertPoint(CGPoint(x: point.x, y:self.view!.frame.height-point.y), toScene:self)
}
Countless vid tutorials on RW and everywhere else on internet.
Thanks in advance! I appreciate the help. I know this topic is weird because a lot of people ask questions about it but everyone's situation seems to be different enough that one solution doesn't fit all.
I initially tried to do the scaling myself with 2 games and it was just madness (scene size = view size or scene scale mode = .ResizeFill). You have to adjust all values e.g font size, sprite sizes, impulses etc for all devices and it will never be consistent.
So you have 2 options basically
1) Set scene size to 1024X768 (landscape) or 768x1024 (portrait). This was the default setting in Xcode 7.
You than usually just have/show some extra background at the top/bottom (landscape) or left/right (portrait) on iPads which gets cropped on iPhones.
Examples of games that show more on iPads / crop on iPhones:
Altos Adventure, Leos Fortune, Limbo, The Line Zen, Modern Combat 5.
2) Apple changed the default scene size in xCode 8 to iPhone 6/7 (7501334-Portait, 1337750-Landscape). This setting will crop your game on iPads.
Examples of games that show less on iPads:
Lumino City, Robot Unicorn Attack
Chosing between the 2 options is up to you and depends what game you are making. I usually prefer to use option 1 and show more background on iPads.
Regardless of scene size scale mode is usually best left at the default setting of .aspectFill.
To adjust specific things such as labels etc you can do it this way
if UIDevice.current.userInterfaceIdiom == .pad {
...
}
You can try the scene scaling yourself, create a new SpriteKit sample game project. Run on all iPhones and you will notice the HelloWorld label looks perfect on all devices.
Now change the default settings to scene size = frame or use .ResizeFill, the HelloWorld label is not scaled properly anymore on all devices.
As a side note, the line
if let scene = GameScene(fileNamed: "GameScene")
references the GameScene.sks file. You said you do everything programatically, therefore you can probably delete the GameScene.sks file and change the line to
let skView = view as! SKView!
let scene = GameScene(size: CGSize(width: 1024, height: 768)) // 768 x 1024 if portrait
Update:
I am now using a slightly different variant as I had problems adapting my game to iPhoneX. I set scene size to 1334x750 and use aspect fit as scale mode. I than run some code to remove the black bars if needed e.g. on iPads or iPhone X. It’s based on this great article (link no longer works).
http://endlesswavesoftware.com/blog/spritekit-skscene-scalemode/
I have a question regarding universal game assets and absolute positioning of a SKNodes in Sprite Kit (iOS 8+).
I will try to present my problem through an example as follows:
Imagine a 2D top down game with a SKSpriteNode which represents a house. A house has multiple child SKSpriteNodes which represent chairs, desk, sofa, etc.
I have 3 versions of house asset:
1x - 200 x 200px (Non-retina iPads),
2x - 400 x 400px (Retina iPhones and iPads),
3x - 600 x 600px (iPhone 6 Plus).
Important:
Child nodes (chairs, desk, etc.) positions are defined in a .plist file. Something like this (JSON representation):
children: [
{
position = {20,20};
},
...
]
Since the position is defined in points and not in pixels, everything gets positioned like expected according to device screen scale. For 1x devices the position stays {20,20}, for 2x position is {40,40} and for 3x the position is {60,60}.
Problem:
The problem is that 200x200px and 400x400px assets are way to small for iPad devices in order to achieve similar look and feel on all devices.
Question:
How to successfully present/import assets in a way that would enable
me to achieve similar (if not the same) look and feel on all
devices/screen sizes without breaking child nodes positioning?
My takes:
Take 1:
I could simply use the existing 400x400px assets on Non-retina iPad devices and 600x600px assets on Retina iPad devices for the house node but the positioning of a child nodes would become broken. This is because the child position value wouldn't change and would still be {20,20} and {40,40} for iPad devices respectively, while the assets would be bigger. This would yield inaccurate child positions relative to the house node.
Take 2:
I could also scale the SKScene size (zoom effect) while using the normal 200x200px and 400x400px sized assets for iPad devices respectively. This works and it keeps the child nodes positioning working but the rendered quality of the scene/assets is not good as it should be. Also, this feels like a hack and we don't want that.
Take 3:
I could also use twice as big assets for iPad devices and double the child nodes position at the runtime. In this case I would use a 400x400px asset for non-retina iPad devices and a new 800x800px asset for retina iPad devices. While this looks great and keeps the child nodes positioning working, it seems like a really big hack fixing child node position during runtime with this:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
position.x *= 2.0f;
position.y *= 2.0f;
}
Thank you for taking the time to read the question!
I could simply use the existing 400x400px assets on Non-retina iPad
devices and 600x600px assets on Retina iPad devices for the house node
but the positioning of a child nodes would become broken. This is
because the child position value wouldn't change and would still be
{20,20} and {40,40} for iPad devices respectively, while the assets
would be bigger. This would yield inaccurate child positions relative
to the house node.
You can simply scale your house node (not the scene) to a larger size. All you need to do is set the scale on your house to a value that looks good on larger devices. And in fact, instead of checking for iPad we can come up with a formula that sets the scale depending on the size of the screen. Something like the code below should work. Note that it assumes your house is positioned perfectly on a iPhone 4 and it will consistently scale to all larger screens. Note that you really could pick any arbitrary size as your base case, but choosing the smallest screen and scaling up is easiest. Just be sure to provide larger textures so that the textures don't become blurry when scaled.
[house setScale:self.scene.size.width/320.0];
OR
You could use two nodes. A root node for holding the "actual" position, and then an image node child for displaying the image. This will allow you to separate your positional data from what's being displayed. You could resize and position your child image node however you want without messing with the actual position of the root node. You could even include this extra image node data in your JSON.
I could also scale the SKScene size (zoom effect) while using the
normal 200x200px and 400x400px sized assets for iPad devices
respectively. This works and it keeps the child nodes positioning
working but the rendered quality of the scene/assets is not good as it
should be. Also, this feels like a hack and we don't want that.
This option can definitely work if your App can handle the different aspect ratios in someway. For example you could allow scrolling the scene if the scene is scaled larger than the device screen. The loss in quality occurs because you are scaling the textures larger than their expected size. You need to provide larger textures to keep the quality high when zooming. In this case you could probably just use your 600x600 images (or maybe even larger) and let it scale with zoom. For example, in my RTS Sprite-Kit game for OS X I scale the entire scene so I get the same look across all devices. And I don't lose any quality because I make sure to provide very large textures so there is no loss in quality while scaling.
I could also use twice as big assets for iPad devices and double the
child nodes position at the runtime. In this case I would use a
400x400px asset for non-retina iPad devices and a new 800x800px asset
for retina iPad devices. While this looks great and keeps the child
nodes positioning working, it seems like a really big hack fixing
child node position during runtime with this:
This could also work, especially if your iPad requires custom layout. However, if possible avoid checking specifically for iPad and instead use the screen size to create layout rules so your nodes dynamically adjust on all screen sizes consistently (See the line of code above). Sometimes this is not possible if your iPad layout is very different from the iPhone, in which case you will have no choice but to check for iPad.
All three of these solution are good. I wouldn't consider any one of them "hacky." They all work for different purposes. You need to find the solution that works best for your game.
I would also recommend you see my two answers below. Not sure but they may help you with understanding universal positioning and scaling in Sprite Kit.
https://stackoverflow.com/a/25256339/2158465
https://stackoverflow.com/a/29171224/2158465
Good luck with your game, let me know if you have any questions.
There's no easy way to do what you want. One approach would be to use a fixed screen size on your devices. The iPhone 5 all the way up to iPhone 6+ all use a 16:9 aspect ratio for their screens. Whereas the iPad and iPhones 4s and earlier, all use a 4:3 screen aspect ratio.
Before presenting your GameScene, you can determine the screen's aspect ratio and then set a fixed view size like this for 16:9:
GameScene *startGame = [[GameScene alloc] initWithSize:CGSizeMake(736, 414)];
startGame.scaleMode = SKSceneScaleModeAspectFit;
or this for 4:3
GameScene *startGame = [[GameScene alloc] initWithSize:CGSizeMake(1024, 768)];
startGame.scaleMode = SKSceneScaleModeAspectFit;
The exact values really do not matter, only the ratios.
Knowing the exact screen size will allow you to place objects in more precise manner regardless of iPhone 5 screen or 6+ screen.
Using image assets, you can also specify iPad versions of an image.
However there's not really a way around adding some extra logic to your app, branching depending on whether or not your running on the iPad, and adjusting the position manually.
We could discuss how to best incorporate that though: I'm not a fan of this "if I'm not this device" checks all throughout the code. Create an abstract superclass and two subclasses, each handling layout (or whatever you may want to call it) for one interface idiom. You will the only need to check once (when instantiating these) and polymorphism will take care of the rest.
You can use a software as PaintCode to dynamically generate texture perfectly sized to your need.
All you have to do is to define the frame' dimensions for each of your devices.
I've been working on an iPad app that has gone through fairly extensive testing on the iPad 2. I recently obtained a "New iPad" (iPad 3), and the app feels significantly slower. Animations / scolling behaviors that were silky smooth in the iPad 2 now feel extremely stuttery on the new iPad.
I do a lot of the standard UI performance tips: using shadowPaths, drawing UITableViewCells using CoreGraphics, rasterizing views that don't change often / don't need to animate.
Are there any pitfalls I should watch be watching out for in transitioning my app to the iPad 3?
Update
I swore that I tried this before but removing rounded corners from my UIViews views ended up speeding the app significantly. clipToBounds also seems to be a significant performance hit (although strangely... setting a view's layer's maskToBounds seems to be okay, which makes no sense to me). Some combination of this and other tweaks seems to have resolved the issue.
Once I do a second visual pass over the app, I'll figure out a way to reimplement rounded corners in a more performant way.
4x the pixels, only 2x the fill rate. The maths says it all.
I think that the biggest thing to watch out for is copies of graphics from system memory to the video card since they are 4X larger than previous iPad graphics with about the same CPU power as the iPad 2.
Maybe it's due to upscaling by iOS? After all it has to calculate a higher res version of everything before showing to screen. Since some things on the screen are hi-res already (font and default ui elements) it has to scale every other element individually.
Have you tried just scaling the images yourself and adding them as retina images to your project?
Good luck.
I'm currently working on an OpenGL game, and recently began refactoring it to support iPhone in addition to iPad. In an attempt to give myself as little work as possible, I simply resized the containing EAGLView to 480 x 360 (to preserve the iPad aspect ratio) and shifted the view up so it's y origin lay at -20 (in order to centre the content, and as the edges could be cropped).
I found this resulted in jerky performance on the device (despite the CADisplay link reporting a frame duration that equated to 59-61 fps) and was at least 20% slower when compared side by side with the iPad version.
I then tried resizing the view to the screen size 480 x 320 and the performance returned to normal (though the rendered content no longer has the correct aspect ratio).
Why is it that the 'off screen' rendering causes a performance hit and why does the displaylink still think it is running at 60fps?
Any ideas?
Thanks
This is mostly a wild guess, but what about clipping regions? There was this hardware functionality that made it possible to mix windows and hardware accelerated graphics. Maybe it is not implemented very well on mobile devices.
You could, however, avoid making the UIView offscreen and try to use glViewport() to shift the parts of the view offscreen. While it seems to be the same, it is handled by a different part of the pipeline (the rasterizer), and should be much faster.