Positioning SKView / SKScene that has SKSceneScaleModeAspectFit? - ios

I'm using a basic setup with a SKView to display a SKScene. The SKScene has a pre-defined size and uses a scale mode SKSceneScaleModeAspectFit:
- (void)viewDidLoad {
[super viewDidLoad];
SKView *skView = (SKView *)self.view;
SKScene *scene = [MyScene sceneWithSize:CGSizeMake(320, 480)];
scene.scaleMode = SKSceneScaleModeAspectFit;
[skView presentScene:scene];
}
As expected, on iPhone 5 this causes letterboxing, i.e. black sections above and under my scene. On iPad, letterboxing causes black stripes to the left and right of the scene.
However, I'd like to position the scene e.g. on iPhone 5 so that the scene is aligned to the bottom of the screen, while all of the black space is positioned above the scene. Is this possible?
I've tried quite a few things with .frame .center .origin, etc. properties of the SKView/SKScene, but can't seem to be able to move the 'display area'. For example, adding
[scene runAction:[SKAction moveTo:CGPointMake(0,-44) duration:0]];
moves the scene's contents inside the display area but not the area itself.
Any ideas?

Related

Spritekit wont show presented scene

Im having a problem. When i present a new scene in spritekit (with objective c) It only shows a gray color with node and fps count on the screen.
I have animated the scene in the .sks file so it should not look like this.
The transition when im presenting the new scene also works, but it just wont show the images and background that i animated in the .sks file.
Here is the code on how i do it.
//In gameviewcontroller.m
// Creating and configuring the scene.
GameScene *Level1 = [GameScene nodeWithFileNamed:#"GameSceneLevel1"];
Level1.scaleMode = SKSceneScaleModeAspectFill;
// GameScene.m inside touchesbegan
SKTransition *reveal = [SKTransition doorwayWithDuration:4];
GameScene *Level1 = [GameScene sceneWithSize:self.view.bounds.size];
Level1.scaleMode = SKSceneScaleModeAspectFill;
[self.view presentScene:Level1 transition:reveal];
You have originally loaded scene from .sks file and as you said you have some animations there. Later on, (inside touchesBegan) you are creating a scene by providing a size (using sceneWithSize static method). So the scene is not loaded from from the archive.
To fix the problem, load your scene like you did initially, in the GameViewController.
EDIT:
To transition between two scenes you have to create two .sks files. You can name them like this : MenuScene and GameScene (you already have this one by default). Note that when you create these files you don't have to write an extension (.sks) but rather just file name. Then you have to create corresponding .m and .h files. So create MenuScene.m and MenuScene.h. GameScene .m and .h files are there by default.
MenuScene should be a subclass (not necessarily direct subclass) of an SKScene.
Then inside of your view controller:
- (void)viewDidLoad
{
[super viewDidLoad];
SKView * skView = (SKView *)self.view;
GameScene *scene = [GameScene nodeWithFileNamed:#"GameScene"];
scene.scaleMode = SKSceneScaleModeAspectFill;
scene.size = skView.bounds.size;
[skView presentScene:scene];
}
Later on in your GameScene's touchesBegan method if you want to transition to the MenuScene, you should do something like this :
MenuScene *nextScene = [MenuScene nodeWithFileNamed:#"MenuScene"];
SKTransition *transition = [SKTransition fadeWithDuration:3];
[self.view presentScene:nextScene transition:transition];
Similarly, to transition back to the GameScene, in your MenuScene's touchesBegan, do this:
GameScene *nextScene = [GameScene nodeWithFileNamed:#"GameScene"];
SKTransition *transition = [SKTransition fadeWithDuration:3];
[self.view presentScene:nextScene transition:transition];
Of course right before each transition you can set scene's scale mode to match the scale mode of the old scene.
From your code, it looks like you are trying to make a transition to the same scene: GameScene -> GameScene. That is certainly possible, but are you sure you wanted that ?
If so, just use the code I've provided for MenuScene's touchesBegan method.

How to make my sprite kit scene appear above a UIView?

I've done some searching but haven't found anything directly addressing my issue: I have an SKScene with several SKNodes each with SKSpriteNode objects for my game, and I am using a background UIImageView (there are some things I need to do with background that cannot be reasonable done with sprite kit - to my knowledge at least); the problem I'm having is that the UIImageView appears above everything and I can't see my game objects.
In my view controller (.m file) I have the following code to call my scene
#implementation GameViewController
- (void)viewDidLoad
{
[super viewDidLoad];
SKView * skView = (SKView *)self.view;
SKScene * scene = [GameScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
[skView presentScene:scene];
}
In the GameScene above, I have several SKNodes where all my game objects are children of (for example nodeHDU, nodePlay, nodePauseMenu...), and I am using a UIImageView as my background (I am switching between background scenes over time and am using some nice transitions such as UIViewAnimationOptionTransitionCrossDissolve; couldn't accomplish this with a SKSpriteNode as background without using multiple SKSpriteNodes and an intricate array of SKActions so I"m using UIImageView)
UIView *backgrounds = [[UIView alloc] init];
[self.view insertSubview:backgrounds belowSubview:self.view];
UIImageView *imageBackground = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,self.view.frame.size.width,self.view.frame.size.height)];
[backgrounds addSubview:imageBackground];
[backgrounds sendSubviewToBack:imageBackground];
[imageBackground setImage:[UIImage imageNamed:#"1-A.png"]];
imageBackground.alpha=1.0;
However, the ImageBackground above is the only thing I see when running the app in Xcode. The other objects from my game (children of other nodes) exist, and various NSLog statements are getting called, but they seem to appear behind the ImageBackground I have. The code above "belowSubview" and "sendSubviewToBack" don't help.
So How can I send the imageBackground to actually be the background?
Your problem is here that you are directly setting SKView as a controller view. To overcome from this issue don't make self.view a type of SKView. Do the following step in your storyboard or Xib :
Add an UIImageView on the controller's view first. Set the image as well.
Then add a SKView over the UIImageViewand set UIClearColor to the background colour.
So the complete hierarchy is like UIViewController->UIView->UIImageView->SKView

How to stop Sprite Kit from reinitializing the scene with iAd banner

I made a game using sprite kit in landscape mode. The only allowed orientations are landscape left and landscape right. When I select the banner ad in my game, rather than freezing the scene and returning to the original point in the game, the entire scene reinitializes itself (music player restarts, content shows as start screen, etc).
The same thing happens when I flip the phone around so that the orientation switches. How can I prevent this?
As for when you flip your phone you need this...
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
SKView * skView = (SKView *)self.view;
if ( !skView.scene ) {...
SKScene * scene = [MenuScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
[skView presentScene:scene];
}
}
I'm still looking into the iAd issue, I'll update if I figure anything out.
Thanks for the response. That was the problem. I had been initializing the scene in the viewWillLayoutSubviews method in my root view controller. I moved the scene initialization to the viewDidLoad method and it fixed the issue.

SpriteKit support multiple device orientations

I have a SpriteKit game which I want to support all orientations. Right now when I change the orientation, the node doesn't keep its position. I use the SKSceneScaleModeResizeFill for scaling, because it will keep the right sprite size.
When I start the game, the game player is positioned in the mid screen like this:
Then when I rotate the device, the position becomes like this:
Here is my view controller code:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
// Configure the view.
SKView * skView = (SKView *)self.view;
if (!skView.scene) {
// Create and configure the scene.
SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeResizeFill;
// Present the scene.
[skView presentScene:scene];
}
}
And my scene code:
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
//Add spaceship in the center of the view
SKSpriteNode *spaceship = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship.png"];
spaceship.position = CGPointMake(size.width/2, size.height/2);
[spaceship setScale:.3];
[self addChild:spaceship];
}
return self;
}
Your sprite does keep its position after the scene resizes — you can see from your screenshots that it keeps the same horizontal and vertical distance from the lower left corner of the scene. The catch is that after the scene has resized, that absolute offset represents a different relative position in your scene.
Sprite Kit can resize a scene automatically, but the relative positioning of nodes after a scene resize isn't something it can do for you. There's no "right answer" to how a scene's content should be rearranged at a different size, because the arrangement of scene content is something your app defines.
Implement didChangeSize: in your SKScene subclass, and put whatever logic you want there for moving your nodes.
For example, you could make it so nodes keep their positions as a relative proportion of the scene size using something like this:
- (void)didChangeSize:(CGSize)oldSize {
for (SKNode *node in self.children) {
CGPoint newPosition;
newPosition.x = node.position.x / oldSize.width * self.frame.size.width;
newPosition.y = node.position.y / oldSize.height * self.frame.size.height;
node.position = newPosition;
}
}
Depending on what's in your scene and you you've arranged it, you probably don't want that, though. For example, if you have HUD elements in each corner of your game scene, you might want them at a fixed offset from the corners, not a proportion of the scene size.
I add I similar issue and found this question. I solved differently, using the viewWillTransitionToSize:withTransitionCoordinator:, as stated by #GOR here.
I added the following code in my view controller (that manage the SKView and its SKScene)
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
//skView is my SKView object, with scaleMode SKSceneScaleModeResizeFill
skView.frame = CGRectMake(0, 0, size.width, size.height);
//currentScene is my SKScene object
currentScene.size = skView.frame.size;
//Then, as all the objects in my scene are children of a unique SKNode* background, I only relocate it
currentScene.background.position = CGPointMake(CGRectGetMidX(currentScene.frame), CGRectGetMidY(currentScene.frame));
}
and it works like a charm!
For Swift 3,
override func didChangeSize(_ oldSize: CGSize) {
for node in self.children{
let newPosition = CGPoint(x:node.position.x / oldSize.width * self.frame.size.width,y:node.position.y / oldSize.height * self.frame.size.height)
node.position = newPosition
}
}
Thus we are able to use a constant and initialise it in one line. Then in node.position = newPosition we can set the new position.
Also we are able to make use of the enhanced for loop leading to a much more elegant solution.

Flipping iPhone orientation reloads viewcontroller

I have a viewcontroller with an SKview (I'm working with spritekit) inside of it and every time I change the orientation of the phone (from landscape one way to landscape another way or vise versa) it reloads the viewcontroller as if I had just opened the app (the viewcontroller is the initial one).
Is there a work around for it?
I can't seem to find anyone having the same problem.
Code:
http://pastie.org/8669630
You are setting up your SKView in viewWillLayoutSubview which gets called every time device is rotated (view frame changes).
Which is good, because view will have correct dimensions, but you should place some var to know should you set up the view again. For example BOOL sceneSetUp
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
if(!self.sceneSetUp){
// Configure the view.
SKView * skView = (SKView *)self.view;
//skView.showsFPS = YES;
// Create and configure the scene.
SKScene * startScene = [StartViewController sceneWithSize:skView.bounds.size];
startScene.scaleMode = SKSceneScaleModeAspectFill;
SKScene * scene = [gameViewController sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:startScene];
//[skView presentScene:scene];
self.sceneSetUp = YES;
}
}

Resources