ButtonNode with enable

This is a follow-up to my previous ButtonNode post.  In this update I will be adding additional functionality to the button to allow the button to be enabled and disabled.

For this new functionality I want the ButtonNode to be able to do the following:

  • Maintain a boolean value indicating the current state of the button
  • Prevent the button from being clicked when it is disabled
  • Update the look of the button so that it appears to be disabled
  • Allow the button to toggled between the enabled and disabled state

To start we will simply add a new public property that represents the enable state of the button

@property (assign, nonatomic) BOOL enable;

Defining this property will automatically synthesize the getter and setter methods. But, since we want to change how the button looks and behaves when it is disabled we need to be able to execute code whenever the buttons’s enable property is set. The simplest way to do this is to create our own setter method which will be used instead of the auto-synthised setter.

// Setter for the enable property. Update the button based on the new state
– (void)setEnable:(BOOL)enable {

if (enable) {

self.userInteractionEnabled = YES;
self.alpha = 1.0;

} else {

self.userInteractionEnabled = NO;
self.alpha = 0.5;

}
_enable = enable;

}

In the setter method, when the button is being disabled, we set the userInteractionEnabled to NO and apply an alpha of 0.5 to dim the button. When the button is enable we simply reset the alpha to 1 and re-enable user interaction. Finally we update the underlying _enabled variable to track the state of the button.

The ButtonNode sample app, available on our github (ButtonNode) page, has been updated with this new functionality.

You can find me on twitter @mellowcoder

ButtonNode

When we first started developing with SpritKit we quickly felt the need for some higher level abstractions.  One example of this is a button.  As a result we ended up creating a simple ButtonNode class that we could use throughout our game.  This post will present an example of our button node extracted from our Fillip’s Feast game, available in the Apple App Store.

The following features are included in this basic button node implementation.

  • Buttons would use images from textures or named image assets
  • Provide visual feedback when button is selected

The ButtonNode subclassed SKSpriteNode and provides 2 contractor methods.

+(instancetype)buttonWithImageNamed:(NSString*)image forTarget:(id)target andSelector:(SEL)selector;
+(instancetype)buttonWithTexture:(SKTexture*)texture forTarget:(id)target andSelector:(SEL)selector;

The implementation of each of these only varies based on the source of the image to be used for the button.  Here is what we have for the creating the button with a named image.

// Allocates and initializes a new button node from a named image
+(instancetype)buttonWithImageNamed:(NSString*)image forTarget:(id)target andSelector:(SEL)selector {

ButtonNode *button = [[ButtonNode alloc] initWithImageNamed:image forTarget:(id)target andSelector:selector];
return button;

}

// Init button with a named image
-(instancetype)initWithImageNamed:(NSString*)image forTarget:(id)target andSelector:(SEL)selector {

if (self = [super initWithImageNamed:image]) {

self.target = target;
self.selector = selector;
self.userInteractionEnabled = true;
self.color = [SKColor grayColor];

}
return self;

}

With this constructor we can now create our new button node and add it to our scene.  Since this is a subclass of SKSpriteNode you use the normal SKNode position property to set the location of the button within the scene.

-(void)addMyButton {

ButtonNode *myButton = [ButtonNode buttonWithImageNamed:@”myButtonImage” forTarget:self andSelector:@selector(myButtonAction)];
myButton.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:myButton];

}

Notice in the method above we are setting the target to self (our current scene) and the selector (the method that will be called) as myButtonAction.  In the scene you then need to add the myButtonAction method with the appropriate code to be executed when the button has been pressed and released.

So now we have to address what happens when the button is touched.  There are a few different scenarios we have to deal with. First what happens when the button is touched, then what happens when the user touches the button but then drags out of the button area, and then finally what happens if the user is within the button area and lifts their finger off the button.  The final case is what is commonly referred to a “touch up inside event”.  Lets go through the code for each of these.

First what happens when the user touches the button.  In this case we want to give the user a visual indication that they are touching the button.  For this we will respond to the “touches begin” event.  Here we are responding to this event and if the touch happens to be inside our button we will set the button to the selected state.

// When the button is touched blend the button color with the image to give it a selected appearance
– (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInNode:self.parent];
if (touches.count == 1 && CGRectContainsPoint(self.frame, touchPoint)) {

[self setSelectedState:YES];

}

}

Setting the button to the selected state will simply update the color blend factor so the buttons appearance changes.

// Helper method to set or unset the button selected state
– (void)setSelectedState:(BOOL)selected {

if (selected) {

self.isSelected = YES;
self.colorBlendFactor = 0.7;

} else {

self.isSelected = NO;
self.colorBlendFactor = 0.0;

}

}

So what happens if the user has touched the button but then drags their finger off of the button.  In this case we want to change the state of the button to be not selected.  But, we also need to handle the case of the user then dragging their finger back within the area of the button.  To handle both of these cases we need to handle the touches moved event.

– (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {

UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInNode:self.parent];
if (CGRectContainsPoint(self.frame, touchPoint)) {

[self setSelectedState:YES];

} else {

[self setSelectedState:NO];

}

}

So when in the touches is moved we are checking to see if the touch is inside or outside of our button and setting the Selected state as appropriate.

Finally we need to handle the case where the user has touched the button and then releases it.  For this we need to respond to the touches ended event. If the button selected state is true we will call the activateButton method,

– (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

if (self.isSelected) {

[self setSelectedState:NO];
[self activateButton];

}

}

Then finally we need to respond to the activateButton method.  In this case we are going to simply call the selector on the target that was passed in when the button was created.

// When activated the button will selector on the target to invode the buttons action
– (void)activateButton{

if (self.target != nil && [self.target respondsToSelector:self.selector]){

[self.target performSelectorOnMainThread:(self.selector) withObject:self waitUntilDone:NO];

}

}

In future posts we will expand on the button node class adding additional functionality.

A sample app, ButtonNode , with this ButtonNode class can be found on our github page.

You can find me on twitter @mellowcoder

SpriteKit and iOS9

This is a short post discussing a few issues that we encountered with updating our SpritKit based game, Fillips Feast, to work with iOS 9.

For the most part everything just worked when running Fillip’s Feast on iOS 9.  But, we did encounter a few issues.

One of the first things we noticed was that our intro video was not being shown when running under iOS 9.  The video which played fine under prior OS versions was not visible in iOS 9.  But, it was playing so it was not an issue of the video.  For game loading intro we created a loading scene that is presented when the app first starts.  This includes a splash screen, which is an SKSpritNode that displays the first frame of the video and an SKVideoNode used to play the intro video.  We also add a message to the intro view once all the assets have finished loading to let the user know that they can tap the screen to skip the rest of the video.

After a bit of debugging we determined that the issues was related to the zPosition of the video.  In building the intro scene we had not explicitly set the zPosition for the elements.  Simply adding the video node to the scene after adding the splash screen sprite node had been working fine.  Explicitly setting the zPosition for the video node, now allowed the video to be shown in iOS 9.  But, this had an undesirable side effect in iOS 8 and prior.  Now the video was displaying a black screen that flashed before the video started playing.  The solution to this was to test for the availability of an iOS 9 feature and only set the zPosition on iOS 9.  To do this we simply tested to see that [SKCameraNode class] returned true before setting the zPostion.  We also found a few other places in the app where the zPosition needed to be set.

At this point things were looking good, except on iOS 9 the intro video was not playing smoothly.  It would play about a third of the way though, freeze, then jump forward and play the final third of the video.  This was not an issue on devices that where running an older version of iOS.  This problem was isolated to the SKLabelNode we where adding at the bottom of the page to let the user know that could “tap to continue” and skip the rest of the video.  We had actually had similar performance issue trying to use an SKLableNode to display the score you are awarded when you catch a bug.  The fix for this was to swap out the label node for an SKSpritNode with an image for the text message.  The downside of this is it makes it impractical to internationalize this text.

Fillip’s Feast Intro Video

 

fillip feast health dashboard

Game Health Indicator

This post is going to present a simple solution for building a dynamic health indicator for your game.  This is based on the health indicator we included in our Fillip’s Feast game, available in the Apple App Store.

The following are the features we wanted for our health meter.

  • Follow the overall look of the game
  • Provide level indicator based on size
  • Provide visual color indication of health

Step one was to create a sprite in which to display the health level.  For our game the health indicator shows as a bar within a wooden sign.

Health Status Bar

Next we need to create a sprite that can be scaled to represent the players health level and who’s color can be adjusted to give an additional visual indication of level.  This is done by creating sprite node with a color and size.  The initial color does not matter since we will be setting the color based on the players health level.  The width is set to match the length of the level indicator in our banner.  For our banner this is .875 of the full banner width.


// For the reserve indicator create a white sprite node the same size as the health banner
CGSize reserveSize = healthBanner.size;
reserveSize.width = reserveSize.width * 0.875;
self.reserveIndicator = [SKSpriteNode spriteNodeWithColor:[UIColor whiteColor] size:reserveSize];

// Set the anchor point for the indicator to be the left side for horizontal and center for vertical
self.reserveIndicator.anchorPoint = CGPointMake(0.0,0.5);

// Posistion the indicator so the left side lines up with the indicator area in the banner
self.reserveIndicator.position = CGPointMake(-reserveSize.width/2,0);


The final setup step requires the creation of a SKCropNode and mask so that health indicator fits within the appropriate portion of the banner window.  For this we use another sprite image.

Health Bar Mask


// We will use a crop node to mask out health indicator so it only shows up in the healt bar area of the banner
SKCropNode *reserveCropNode = [[SKCropNode alloc]init];
reserveCropNode.maskNode = [SKSpriteNode spriteNodeWithTexture:[health textureNamed:@”HealthBar_Mask”]];

// Add the health level indicator as a child of the crop node
[reserveCropNode addChild:self.reserveIndicator];


The final piece is to add a method that will be called to update the health meter whenever the players health level changes.  When the player has full heath the meter will be green.  When he is almost out of health it will be red and the length of the indicator will scale accordingly.


– (void)updateReserveDisplay:(NSInteger)reserve {
self.reserveIndicator.color = [SKColor colorWithRed:((100.0-reserve)/100.0) green:(reserve/100.0) blue:0.0 alpha:1.0];
[self.reserveIndicator setXScale:(reserve/100.0)];
}

Here is how the banner looks with about 70% health.

fillip feast health dashboard

A copy of a sample app, health_level_indicator, that puts all of this together can be found on our github page.

You can find me on twitter @mellowcoder

Web Inspector for iOS Development

If you have done any web related development you are probably aware of the Web Inspector that is available in Safari on your desktop system.  If not, here is how to enable this feature.

First you need to enable development mode in Safari.  To do this go to Preferences and select the Advanced Tab.  At the bottom of the Advanced page will be a checkbox to Enable the Develop Menu.

Enable Safari Develop menu

After enabling this option you will be able to bring up the web inspector for any web page shown in Safari.  The inspector provides many tools prototyping, optimizing, and debugging web content.  Take a look at Apples Safari Web Inspector Guide for details on this tool.

Web Inspector

But, did you also realize you can use the web inspector with your mobile device.  To enable this on Mobile open the settings app and go to the Safari Settings and select the Advanced Option. There you will see an option to enable the Web Inspector

Enable Inspector on Mobile

As the note in the preference screen says, connect your iOS device to your computer bring up safari on both the mobile device and desktop.  Then on the desktop, under the Develop menu, in the second section you will see the different devices for which you can launch the web inspector.  This will work for both tethered mobile devices as well as the iOS simulator.

Launch Web Inspector for Mobile

If you are developing an app that uses a web view you can also use the Web Inspector with that app.

Working with Images in SpriteKit – Texture Atlas

By: Mark Johnson @mellowcoder

 What is a Texture Atlas

A Texture Atlas is simply a large image file that is created by combining together multiple individual images.  This can save memory and allows iOS to more efficiently render the textures in your application.  The nice thing about SpriteKit is that Apple takes care of building the texture atlas for you.  All you need to do is add the individual image files and the textures will be generated when your app is built.

The following is an excerpt from the SpriteKit Guide on when to use Texture Atlases

When you create a texture atlas, you want to strike a balance between collecting too many textures or too few. If you use too few images, Sprite Kit may still need many drawing passes to render a frame. If you include too many images, then large amounts of texture data may need to be loaded into memory at once. Because Xcode builds the atlases for you, you can switch between different atlas configurations with relative ease. So experiment with different configurations of your texture atlases and choose the combination that gives you the best performance.

Starting Point

As in my previous posts on working with images in SpriteKit, I am going to start off with a new App generated with the SpriteKit template.  After creating the new App, I deleted the boilerplate touchesBegan method as well as the code in the initWithSize method that adds the label.

For this test app I have a couple of different images (a frog and a spaceship) in three different sizes.  I have also added a label on each of the images so I am sure which image is actually being rendered in each of the different simulators.

finder-screen-shot

Adding a Texture Atlas

All you need to do to create a texture atlas is create a folder in your project with “.atlas” at the end of the name.  You can manually create this folder within the project or you can create the folder in the finder with all your images organized within it and then add the folder to your project.  For this app I setup my folder in the finder.  Within the .atlas folder you can use subfolders to organize your files.  The following screenshot shows the folder I have setup for this example.

atlas-file-structure

Once you have added the atlas to your project you will then add/remove images as needed from the finder.  Changes to the content of the atlas are not handled within the project editor.

Using a Texture Atlas in SpriteKit

To use images from the atlas in your project you first need to instantiate the atlas.  For example the following initializes the character atlas and then loads the individual images into sprite nodes.


SKTextureAtlas *character = [SKTextureAtlas atlasNamed:@”character”];

SKSpriteNode *frog = [SKSpriteNode spriteNodeWithTexture:[character textureNamed:@”Frog”]];
frog.anchorPoint = CGPointMake(0.5,0.0);
frog.position = CGPointMake(CGRectGetMidX(self.frame), self.frame.size.height*0.25);
[self addChild:frog];

SKSpriteNode *ship = [SKSpriteNode spriteNodeWithTexture:[character textureNamed:@”Spaceship”]];
ship.anchorPoint = CGPointMake(0.5,1.0);
ship.position = CGPointMake(CGRectGetMidX(self.frame), self.frame.size.height*0.75);
[self addChild:ship];


 Supporting multiple devices

As you may have noticed in the file list above I have used the standard naming convention to differentiate the images for each device.  When you build your project it will create a texture map for each device based on the file naming convention.  You can see this by locating the built application in finder.  If you select the product in the Navigator in Xcode you can find the location of the actual files.

product-location

If you then navigate to this location in the finder you can select the actual built app, right click and select the “Show Package Contents” option.

source-show-package-content

Then selecting the “character.atlasc” folder will show you the actual texture atlases for each of the devices along with the .plist file that is used to mange the files as well as the individual images.  The “.1” on the files is a sequence number.  If the texture atlas gets too large Xcode will  break the atlas into multiple files.  If this happens you would see a “.2” or more versions of the texture atlas.

built-character-atlas

Bug Related to iPad specific Texture Atlas

There is one caveat to this.  Even though the “character.1~ipad.png” file is created and it is correctly represented in the “character.plist” when running the app on the non-retina iPad, simulator or device, it always uses a auto scaled down version of the retina images.  I have entered a bug report with Apple for this and will update this post when I get an update on this issue.

Working with Images in SpriteKit – Image Sets

By: Mark Johnson @mellowcoder

Asset Catalog – Image Sets

This is the second in a series of posts on working with images in SpriteKit.  In this post we will look at using Images Sets.  Asset Catalogs and Image Sets were introduced in Xcocde 5.  If you start a new app in Xcode 5 the App Icon and Launch Images are automatically setup in the Asset Catalog.  In this post we will look at adding a new image to the Asset Catalog for use in SpriteKit.

To illustrate this I am going to create a new universal app using the SpriteKit template.  After creating the new App I first deleted the boilerplate touchesBegan method as well as the code in the initWithSize method that adds the label.  In the init method I added the following code to load a test image named “Frog” and have it centered on the screen.


SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:@”Frog”];
sprite.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:sprite];


For the image I am using  three different sizes of one of the Frog images from our Fillip Frog game.  To make it clear which image is actually being used I have added a text label on the image for testing purposes.

  • fillip-ipad-retina.png – 654 x 312
  • fillip-ipad.png – 323 x 156 (50%)
  • fillip-iphone-retina.png – 303 x 146 (46.9%)

One of the nice things about using Image Sets is that you do not need to follow a specific naming convention for your image files.  So in our code above we are loading an image named “Frog” but none of the actual images we are going to use have that name.

Create a new Image Set

In your project select the Image Catalog.  It is the folder named Images.xcassets in the explorer. You should already see that there is an item for the AppIcon and the LauchImage.  Select the “+” button at the bottom of this list.  This will present a contextual menu.  Select the “New Image Set” item.

create-image-set

 

The new image set will be created with the default name of “image”.  Select this item and then change the name to Frog.  Additionally, change the devices popup from “Universal” to “Device Specific”.  You will notice that in addition to the iPhone and iPad checkboxes there is also a checkbox for the iPhone Retina 4-inch.  If you had a different image you wanted to use just for the 4-inch iPhone you could check this box as well.

setup-image-set

 

The next step is to add the images to the image set.  To do this simply drag the images from the finder to the appropriate spot in the image set.  Because we are using SpriteKit in this App, there is no need to add the non-retina iPhone images. SpriteKit requires iOS 7 which is not supported on the non-retina iPhone.

adding-images

 

Now running the app on each of the simulators we should see the appropriate image for each of the different devices.

in-simulator

But, suppose that we actually want to use the same image for multiple devices.  For example since the sizes are very similar we may want to use the same image for the the iPhone Retina and the iPad.  To do this you could simply drag the image from the finder into the project a second time.  This will create a second copy of the image in your project. But, there is also a manual process you can use to avoid having to bring in a second copy of the image.  If you look at the “Frog.imageset” in finder you will see it is a folder that contains your images as well as a Contents.json file.

file-structure

If you open the Contents.json and manually edit the iPhone 2X file name to be the same as that for the iPad 1X, you can then delete the iPhone Retina image from your project.  Your app will then use the same image for both the iPad and the iPhone.  This can help to reduce the overall size of your app.

In the next post we will look at setting up and using a Texture Atlas, which is the preferred method for working with images within SpriteKit.

 

Working with Images in SpriteKit – Image Files

By: Mark Johnson @mellowcoder

The following is the first in a series of posts on working with images in SpriteKit. Specifically how to manage image files when developing a Universal App.  This first post will be a general discussion of image sizes and look at the naming conventions that can be used when adding images directly to a project.

Before we get started we need to discuss screen sizes.  SpriteKit requires iOS 7, so we do not need to support images for the non-retina iPhone.  This means there are currently four screen sizes we will be working with.

  • iPhone Retina (3.5  inch)  –  960 x 640
  • iPhone Retina (4 inch)  –  1136 x 640
  • iPad  –  1024 x 768
  • iPad Retina  –  2048 x 1536

The approach we have taken when designing our images is to first create the highest resolution images for use with the iPad Retina and then scale down the images for the other devices.  This means that the images for the iPad are simply scaled down by 50% from the retina size.  Scaling down to the iPhone it is a little more complicated and depends on how the images are being used (characters or backgrounds) and the supported orientation (portrait or landscape) of the app.  For most characters sprites we use the same size images that are used with the iPad.  For other images it may be appropriate to scale them relative to scaling factor associated with the width or the height.  The table below shows the potential scaling factors that could be used.

device scale factors

Once you have worked out the appropriate scaling factors to use, the next question is how to get the correct image for each device.  When adding images directly into your project this can be done using Apples’s image file naming convention.  The following is the naming convention for each of the different devices that support SpriteKit.

  • iPhone Retina (3.5  inch)*  –  image_name@2x~iphone.png
  • iPhone Retina (4 inch)*  –  image_name@2x~iphone.png
  • iPad  –  1024 x 768  –  image_name~ipad.png
  • iPad Retina  –  2048 x 1536  –  image_name@2x~ipad.png

* The ~iphone extension on the name is optional but, I find it helps to avoid confusion.

Then to load the images you simply use the base image name.

    spSKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:@"image_name"];

Unfortunately, there isn’t a specific naming convention that can be used to automatically load a different image on the 4 inch iPhone.  Additionally, when using the naming conventions there isn’t an easy way to share an image between devices.  For example, if you wanted to use the same image for the iPhone and the iPad since there scaling is almost the same.

In the next post we will look how to work around these two issues using Image Sets.

 

SpriteKit Landscape Setup

By: Mark Johnson @mellowcoder

Configuring a Landscape Only Game Using SpriteKit

When we began developing our first game in SpriteKit we started out the same way that I suspect most everyone else does.  Open up Xcode and create a new game using the SpriteKit framework template.  The simple default app that is created worked fine when I ran it in the simulator.  But, I wanted my app to only run in landscape and the default app comes up in portrait view and is configured to support both orientations.

So, I update the target Deployment Info, unchecking the portrait options and ran the demo app again.  Everything looks fine so I think I am good to go.

Deployment Info Landscape Only

 

The Problem

Next, I remove the existing scene setup code and start adding my background sprites to build up my scene.   My first background sprite should be located in the lower left corner of the screen so I position it there and run the simulator.  But, when the simulator comes up the image is on the far left but it is below the bottom of the screen.

Image off the screen

 I add some log statements to look at the size of the view and the size of the screen and in each case it reports the size as if it was in portrait orientation rather then landscape.

One Option is to use viewDidLayoutSubviews

One option to address this problem is to add a “viewDidLayoutSubviews” method to your view controller and then add your code to present your scene inside this method. The viewDidLayoutSubviews method is the recommended place to put any geometry related code. It is called after the system has completed any auto-layout items. This means, in addition to the initial presentation of the view, it will also be called anytime the device orientation changes and the view auto-rotates. If you are supporting both portrait and landscape orientations and need to manually adjust the layout when the orientation changes, you should put that code here. If you do add your code to present the scene, you will need to ensure that it only runs the first time the scene is loaded.  But, for the initial setup and presentation of the scene, I think there is a simpler solution.

A Simpler Solution for Landscape only orientation

Simply, update the SKScene scaleMode that is defined in the view controller.  The SpriteKit template sets up the scaleMode as “SKSceneScaleModeAspectFill”. Instead, you should change this to “SKSceneScaleModeResizeFill”.  Now if you run the simulator you will see that the background image is correctly positioned.  Please see last weeks post, SpriteKit – Understanding SKScene scaleMode, for a more detailed discussion of the different SKScene scale modes.

  scene.scaleMode = SKSceneScaleModeResizeFill;

The only other thing to be concerned with is getting the correct width and height to use for the scene.  I get the bounds for the mainScreen.  This always returns the dimensions based on the portrait orientation so, I simply swap the width and height to get the size in landscape.

  CGRect screenRect = [[UIScreen mainScreen] bounds];
  float landscapeWidth = screenRect.size.height;
  float landscapeHeight = screenRect.size.width;

This is a simple straightforward approach to setup a game that is only intended to support landscape orientation.  The following is a screen shot of the one of the backgrounds for the game we are working on.  This background is made up of numerous images with their positions adjusted relative to the top, bottom, left and right so that we can handle both iPhone and iPad devices.

Fillip Background

 

 

 

 

SpriteKit – Understanding SKScene scaleMode

Moving from Cocos2D to SpritKit

We have recently moved from Cocos2D to using SpriteKit as our game development framework.  In the process we have been experimenting with the various aspects of developing with SpriteKit.  This will be the first in a series of posts sharing what we have learned along the way.

What is scaleMode?

The scaleMode of a scene determines how the scene will be updated to fill the view when the iOS device is rotated.  There are four different scaleModes defined:

  • SKSceneScaleModeFill – the x and y axis are each scaled up or down so that the scene fills the view.  The content of the scene will not maintain its current aspect ratio.  This is the default if you do not specify a scaleMode for your scene.
  • SKSceneScaleModeAspectFill – both the x and y scale factors are calculated.  The larger scale factor will be used.  This ensures that the view will be filled, but will usually result in parts of the scene being cropped.  Images in the scene will usually be scaled up but will maintain the correct aspect ratio.
  • SKSceneScaleModeAspectFit – both the x and y scale factors are calculated. The smaller scale factor will be used.  This ensures that the entire scene will be visible, but will usual result in the scene being letterboxed.   Images in the scene will usually be scaled down but will maintain the correct aspect ratio.
  • SKSceneScaleModeResizeFill – The scene is not scaled.  It is simply resized so that its fits the view.  Because the scene is not scaled, the images will all remain at their original size and aspect ratio.  The content will all remain relative to the scene origin (lower left).

Examples

For these examples we will use the default spritekit template that is generated when you start a new spriteKit project. I named my project ExporingScaleMode and setup to be an iPad only project.

New project step 1 New project step 2

Next we will go to myScene.m and remove the touchBegins event handler since we are not going to be using that.  We will then add code to the initWithSize method to add two copies of the spaceship sprite.  One in the lower left corner of the screen and the other in the upper right.


SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:@”Spaceship”];
sprite.anchorPoint = CGPointMake(0.0,0.0);
sprite.position = CGPointMake(0,0);
[self addChild:sprite];

SKSpriteNode *sprite2 = [SKSpriteNode spriteNodeWithImageNamed:@”Spaceship”];
sprite2.anchorPoint = CGPointMake(1.0,1.0);
sprite2.position = CGPointMake(self.frame.size.width, self.frame.size.height);
[self addChild:sprite2];


 

Initial

Now go ahead an run the project in the iPad simulator.  You should see a screen like this, with one spaceship in the lower left, another in the upper right, and the “Hello, World!” text in the center.

Now lets try each of the different scaleModes and see what happens when we change from portrait to landscape view.  Go to the viewController.m and update the scene.scaleMode to each of the modes listed below, run the simulator, and then rotate the simulator so you are looking at the results in landscape rather then portrait.

 

 

 

SKSceneScaleModeFill – As you can see SKSceneScaleModeFill does not maintain the aspect ration when the scene is scaled.  But, it does ensure that everything in the scene is shown.

SKSceneScaleModeFill

SKSceneScaleModeFill

 

SKSceneScaleModeAspectFill – this was the mode that was originally set in the project template.  With this mode the spaceship images are slightly larger then the originals.  This is because the scene was scaled up so that the portrait width matches the landscape width.  The scene does completely fill the view but the images on the top and bottom are now cropped.

SKSceneScaleModeAspectFill2

SKSceneScaleModeAspectFill

 

SKSceneScaleModeAspectFit – This scale mode does enable you to see the entire scene.  The scene has been scaled down so that the portrait height now equals the landscape hight.  As a result the images are smaller and we now have letterboxing on the left and right of the scene.

SKSceneScaleModeAspectFit

SKSceneScaleModeAspectFit

 

SKSceneScaleModeResizeFill – This final scale mode is the only one in which the original images are the same size and aspect in both portrait and landscape.  But, as you can see the content is positioned relative to the origin (lower left) so the image that was in the upper right is now partially cropped and no longer on the far right.  Additionally the text is not centered in the screen.

SKSceneScaleModeResizeFill

SKSceneScaleModeResizeFill

 Conclusion

Well that is all very interesting but, what conclusion can we draw from this.  The first conclusion is that it would be very challenging to build a game that worked in both portrait and landscape.  It can be done but you would probably need to rearrange the content whenever the user rotated the view.  I also think that the best scale mode to be using is SKSceneScaleModeResizeFill.  This mode maintains the size and aspect ratio of your content in both portrait and landscape views.

I believe this also gives some insight in how to setup your scene when you want to maintain the landscape only view.  But, I will save that for the next post.