Quantcast
Channel: Envato Tuts+ Code - Mobile Development
Viewing all articles
Browse latest Browse all 1836

Working with SKTransition

$
0
0

This tutorial will teach you to combine UIKit views and the SKTransition class to create beautiful, custom transitions between different SKScenes. Read on!


Final Preview

Figure 1: Final Result!
Illustration of Final Result.

1. Transitions Between Scenes

Scenes are interfaces that hold several view objects. Normally, you design several scenes for each part of your game, and then use transitions between the scenes as necessary. For example, you might create different scene classes to represent any or all of the following concepts:

  • A main menu scene to choose the difficulty of a specific level the user wants to play.
  • A scene to configure the details of the sound and music effects of a game.
  • A scene that provides the game-play interface.
  • A scene that provides the online leader board interface.
  • A scene that provides current and overall game achievements.

As you can see, a scene can be anything that the programmer would like to create. Typically, you transition to a new scene based on game-play objectives or direct user input. For example, if the timer runs out, a new scene presenting a “game over” may be presented. Alternatively, if a user presses an options button, you might transition to a new scene to configure the game settings. Note that, at the transition step, the scene property is immediately updated to point to the new scene. After this, the transition occurs.

A transition can also have a transition object (an effect or an animation). That object will create a dynamic, beautiful presentation when the transition occurs. Several objects exist, and for a complete and official reference you should consult the SKTransition class reference.


2. Class Methods

The SKTransition class presents several methods and two properties (more about those later). The goal of all methods is to create a shiny and dynamic transition between scenes. The class methods can be divided into four main sections:

  • With Duration: Transitions that will occur over a defined period of time.
  • With Color: Transitions that will use a UIColor object to color the inherent transition.
  • With Direction: Transitions that will be made from a specified direction.
  • With CIFilter: Transitions that will use a custom filter to produce a visual effect in the transition.

Duration and Color are simple objects, but both Direction and CIFilter are not.

As the name suggestions, the direction property means that the transition will occur in a specific direction. The direction property can have one of four constants. These constants are declared as an NS_ENUM, like this:

typedef NS_ENUM(NSInteger,
   SKTransitionDirection) {
   SKTransitionDirectionUp,
   SKTransitionDirectionDown,
   SKTransitionDirectionRight,
   SKTransitionDirectionLeft,
};

CIFilter is even more robust than Direction since it is also a reference class with class methods, instance methods, and properties. This tutorial will not cover the CIFilter class in-depth, but it will present an example of how to use it to create a custom filter and the inherent SKTransition. An additional note regarding the CIFilter class: it supports dozens of effects, but not all are supported by the latest version of iOS. You should consult the Core Image Filter Reference to see the compatibility list.

Note that you can use several “grouped methods” in order to create an SKTransiton. But how do you know which ones can be combined? For this, you must look to the method signature and determine the properties accepted by each method.

For instance, let’s analyze three methods to see what they can handle:

  • doorsOpenVerticalWithDuration:
  • fadeWithColor:duration:
  • revealWithDirection:duration:

As previously stated, the methods names allow you to quickly understand what each method can do. To elaborate, the doorsOpenVerticalWithDuration: method will only take into consideration a duration The fadeWithColor:duration: method uses both a color and a duration. You must first define a UIColor object and then a duration time. The revealWithDirection:duration: method will only use a direction, which, in turn, can be one of four properties. Note that you can also extend the SKTransition class to create custom transitions and join Duration, Color, Direction, and CIFilter.

Figure 2: revealWithDirection
revealWithDirection (UP) illustration

3. Class Properties

The SKTransition class only has two properties: pausesIncomingScene and pausesOutgoingScene. Both configure whether animations play during the transition and both properties are Boolean values. The difference is as follows:

  • pausesIncomingScene determines whether the incoming scene is paused during the transition.
  • pausesOutgoingScene determines whether the outgoing scene is paused during the transition.

Since both are Boolean values, the definition is easy and can be understood in the next snippet:

  // other code ...
  transitionCrossFade = [SKTransition crossFadeWithDuration:1];
  transitionCrossFade.pausesIncomingScene = TRUE;
  transitionDoorsCloseHorizontal = [SKTransition doorsCloseHorizontalWithDuration:2];
  transitionDoorsCloseHorizontal.pausesOutgoingScene = FALSE;

The pausesIncomingScene and pausesOutgoingScene properties on the transition object define which animations are played during the transition. By default, both scenes continue to process animation during the transition. However, you might want to pause one or both of the scenes until the transition completes.


4. The Tutorial Project

Now that you know the basics of the SKTransition class, you can now start the programming phase.

Step 1

The first step is to open Xcode and start a new SpriteKit project. Next, you should add another Objective-C class named TransitionResult and a super-class of SKScene.

The objective of this project is to have two classes that will be swapped between them. The first (MyScene already defined by the Xcode) will contain a UITableView that will hold the reference to each SKTransition. The second (TransitionResult) will be the destination scene after a transition.

When the user taps the screen after a transition it will once again be transferred to MyScene.

Step 2

In MyScene.h, you will declare 3 objects: a UITableView, UISlider, and an NSArray. The UITableView will display the names of each transition, the UISlider will define the Duration of that transition, and the NSArray will contain the names of each SKTransition. The final MyScene.h code will be similar to this snippet:

@property (retain, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic, retain) IBOutlet UISlider *sliderTimer;
@property (strong, nonatomic) NSArray *transitionsArray;

Step 3

Now, focus your attention in the MyScene implementation file.

The first step is to use the -(id)initWithSize:(CGSize)size method to initialize the aforementioned objects. One possible configuration setup is the following:

  _tableView = [[UITableView alloc] initWithFrame:CGRectMake(CGRectGetMinX(self.frame), CGRectGetMinY(self.frame)+20, CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame)-80)];
  _tableView.dataSource = self;
  _tableView.delegate = self;
  _sliderTimer = [[UISlider alloc] initWithFrame:CGRectMake(CGRectGetMidX(self.frame)-70, CGRectGetMaxY(self.frame)-40, 140, 3)];
  [_sliderTimer addTarget:self action:@selector(sliderAction) forControlEvents:UIControlEventValueChanged];
  [_sliderTimer setBackgroundColor:[UIColor clearColor]];
  _sliderTimer.minimumValue = 1;
  _sliderTimer.maximumValue = 4;
  _sliderTimer.continuous = YES;
  _sliderTimer.value = 1;
  _transitionsArray = [[NSArray alloc] initWithObjects:
                             @"crossFadeWithDuration",
                             @"doorsCloseHorizontalWithDuration",
                             @"doorsCloseVerticalWithDuration",
                             @"doorsOpenHorizontalWithDuration",
                             @"doorsOpenVerticalWithDuration",
                             @"doorwayWithDuration",
                             @"fadeWithColor:duration",
                             @"fadeWithDuration",
                             @"flipHorizontalWithDuration",
                             @"flipVerticalWithDuration",
                             @"moveInWithDirectionDown:duration",
                             @"moveInWithDirectionUp:duration",
                             @"moveInWithDirectionLeft:duration",
                             @"moveInWithDirectionRight:duration",
                             @"pushWithDirection:duration",
                             @"revealWithDirection:duration",
                             @"transitionWithCIFilter:duration",
                             nil];

However, a warning is presented since the sliderAction is missing. The method will update in real time the transitionTimerText taking into account the UISlider value.

-(void)sliderAction{
    transitionTimerText.text = [[NSString alloc] initWithFormat:@"Transition Duration: %f", _sliderTimer.value];
}

Note that the views, location, configuration, and layout are completely configurable. If you want, you can tune this for your best interests. Additionally, you will add a SKLabelNode to store and display the UISlider value. That value will be representative of the SKTransition Duration effects. Add the SKLabelNode* transitionTimerText to your implementation file and the corresponding initialization will be:

  transitionTimerText = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
  transitionTimerText.text = [[NSString alloc] initWithFormat:@"Transition Duration: %f", _sliderTimer.value];
  transitionTimerText.fontSize = 10;
  transitionTimerText.color = [SKColor colorWithRed:0 green:0 blue:0 alpha:1];
  transitionTimerText.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMinY(self.frame)+45);

Step 4

Now that you have the objects configured you just need to add them to the scene. For that, you will use the -(void) didMoveToView:(SKView *)view method. Add it to your file and inside add the aforementioned views to the main view:

-(void) didMoveToView:(SKView *)view{
    [self addChild:transitionTimerText];
    [self.scene.view addSubview:_sliderTimer];
    [self.scene.view addSubview:_tableView];
}

If you run the project now you will see two objects on the screen: a UISlider and a SKLabelNode.

Step 5

The next step is to display the SKTransition methods in the UITableView. For this, you need to modify your MyScene.h file and extend your class with the <UITableViewDataSource, UITableViewDelegate> protocols. The final MyScene.h should look like this:

  @interface MyScene : SKScene <UITableViewDataSource, UITableViewDelegate>

Go back to the implementation file and you will be presented with a warning. That warning says that you need to implement additional methods inherent to the UITableView. The methods needed are: -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section and -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath. The first will return the number of rows in the table, while the second will handle the logic for each cell inside the table. For additional notes regarding the UITableView class, you should consult the official reference class.

The first method is simple and is only a single line:

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [_transitionsArray count];
}

The second method is more complex since we must define the table cell properties and configurations (cell contents). In this example, you will use a simple UITableViewCellStyleSubtitle. If you have problems writing the method, the full version is presented bellow:

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSString *transitions = [_transitionsArray objectAtIndex:indexPath.row];
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Identifier"];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"Identifier"];
    }
    [cell.textLabel setText:transitions];
    return cell;
}

Run your code now and you should see each cell line with a unique name. Each name represents the inherent SKTransiton used if the user taps that cell. You will also note that the UITableView does not have a title. Let’s fix that!

Step 6

Add the following method: - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section. Within this method, add the following code:

NSString *sectionName;
    switch (section)
    {
        case 0:
            sectionName = NSLocalizedString(@"SKTransition List", @"SKTransition List");
            break;
        default:
            sectionName = @"";
            break;
    }
    return sectionName;

This method is permitted to have multiple titles for multiple UITableView sections. However, we will only have one section, so the title will be “SKTransition List” (or any other of your choice).

Step 7

At this point, you need to add user interaction to the cells. To do this, another additional method is needed. This time the -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath method should be called. This method is long, but it is simple to understand. You will allocate the necessary resources for each SKTransition and, based on the cell tapped, you will present another SKScene.

The first step is to import the TransitionResult header file, then define a TransitionResult object for the resulting SKScene. Take a look at the following to see this in action:

  TransitionResult* transitionResult;
  SKTransition* transitionCrossFade;
  SKTransition* transitionDoorsCloseHorizontal;
  SKTransition* transitionDoorsCloseVertical;
  SKTransition* transitiondoorsOpenHorizontal;
  SKTransition* transitionDoorsOpenVertical;
  SKTransition* transitionDoorway;
  SKTransition* transitionFadeWithColor;
  SKTransition* transitionFadeWithDuration;
  SKTransition* transitionFlipHorizontal;
  SKTransition* transitionFlipVertical;
  SKTransition* transitionMoveInWithDirectionDown;
  SKTransition* transitionMoveInWithDirectionUp;
  SKTransition* transitionMoveInWithDirectionLeft;
  SKTransition* transitionMoveInWithDirectionRight;
  SKTransition* transitionPushWithDirection;
  SKTransition* transitionRevealWithDirectionUp;
  SKTransition* transitionWithCIFilter;

Now, in the -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath method, it is time to allocate the necessary resources. The complete code is:

transitionCrossFade = [SKTransition crossFadeWithDuration:_sliderTimer.value];
    transitionCrossFade.pausesIncomingScene = TRUE;
    transitionDoorsCloseHorizontal = [SKTransition doorsCloseHorizontalWithDuration:_sliderTimer.value];
    transitionDoorsCloseHorizontal.pausesOutgoingScene = FALSE;
    transitionDoorsCloseVertical = [SKTransition doorsCloseVerticalWithDuration:_sliderTimer.value];
    transitiondoorsOpenHorizontal = [SKTransition doorsOpenHorizontalWithDuration:_sliderTimer.value];
    transitionDoorsOpenVertical = [SKTransition doorsOpenVerticalWithDuration:_sliderTimer.value];
    transitionDoorway = [SKTransition doorwayWithDuration:_sliderTimer.value];
    transitionFadeWithColor = [SKTransition fadeWithColor:[UIColor yellowColor] duration:_sliderTimer.value];
    transitionFadeWithDuration = [SKTransition fadeWithDuration:_sliderTimer.value];
    transitionFlipHorizontal = [SKTransition flipHorizontalWithDuration:_sliderTimer.value];
    transitionFlipVertical = [SKTransition flipVerticalWithDuration:_sliderTimer.value];
    transitionMoveInWithDirectionDown = [SKTransition moveInWithDirection:SKTransitionDirectionDown duration:_sliderTimer.value];
    transitionMoveInWithDirectionUp = [SKTransition moveInWithDirection:SKTransitionDirectionUp duration:_sliderTimer.value];
    transitionMoveInWithDirectionLeft = [SKTransition moveInWithDirection:SKTransitionDirectionLeft duration:_sliderTimer.value];
    transitionMoveInWithDirectionRight = [SKTransition moveInWithDirection:SKTransitionDirectionRight duration:_sliderTimer.value];
    transitionPushWithDirection = [SKTransition pushWithDirection:SKTransitionDirectionDown duration:_sliderTimer.value];
    transitionRevealWithDirectionUp = [SKTransition revealWithDirection:SKTransitionDirectionUp duration:_sliderTimer.value];
    CGRect screenRect = [[UIScreen mainScreen] bounds];
    CIVector  *extent = [CIVector vectorWithX:0  Y:0  Z:screenRect.size.width  W:screenRect.size.height];
    transitionWithCIFilter = [SKTransition transitionWithCIFilter:[CIFilter filterWithName: @"CIFlashTransition"
                                                                             keysAndValues: @"inputExtent", extent,
                                                                   @"inputCenter",[CIVector vectorWithX:0.3*screenRect.size.width Y:0.7*screenRect.size.height],
                                                                   @"inputColor", [CIColor colorWithRed:1.0 green:0.8 blue:0.6 alpha:1],
                                                                   @"inputMaxStriationRadius", @2.5,
                                                                   @"inputStriationStrength", @0.5,
                                                                   @"inputStriationContrast", @1.37,
                                                                   @"inputFadeThreshold", @0.85, nil] duration:_sliderTimer.value];
    transitionResult = [[TransitionResult alloc] initWithSize:CGSizeMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame))];
    switch (indexPath.row) {
        case 0:
            [self.scene.view presentScene:transitionResult transition:transitionCrossFade];
            [self removeUIKitViews];
            break;
        case 1:
            [self.scene.view presentScene:transitionResult transition:transitionDoorsCloseHorizontal];
            [self removeUIKitViews];
            break;
        case 2:
            [self.scene.view presentScene:transitionResult transition:transitionDoorsCloseVertical];
            [self removeUIKitViews];
            break;
        case 3:
            [self.scene.view presentScene:transitionResult transition:transitiondoorsOpenHorizontal];
            [self removeUIKitViews];
            break;
        case 4:
            [self.scene.view presentScene:transitionResult transition:transitionDoorsOpenVertical];
            [self removeUIKitViews];
            break;
        case 5:
            [self.scene.view presentScene:transitionResult transition:transitionDoorway];
            [self removeUIKitViews];
            break;
        case 6:
            [self.scene.view presentScene:transitionResult transition:transitionFadeWithColor];
            [self removeUIKitViews];
            break;
        case 7:
            [self.scene.view presentScene:transitionResult transition:transitionFadeWithDuration];
            [self removeUIKitViews];
            break;
        case 8:
            [self.scene.view presentScene:transitionResult transition:transitionFlipHorizontal];
            [self removeUIKitViews];
            break;
        case 9:
            [self.scene.view presentScene:transitionResult transition:transitionFlipVertical];
            [self removeUIKitViews];
            break;
        case 10:
            [self.scene.view presentScene:transitionResult transition:transitionMoveInWithDirectionDown];
            [self removeUIKitViews];
            break;
        case 11:
            [self.scene.view presentScene:transitionResult transition:transitionMoveInWithDirectionUp];
            [self removeUIKitViews];
            break;
        case 12:
            [self.scene.view presentScene:transitionResult transition:transitionMoveInWithDirectionLeft];
            [self removeUIKitViews];
            break;
        case 13:
            [self.scene.view presentScene:transitionResult transition:transitionMoveInWithDirectionRight];
            [self removeUIKitViews];
            break;
        case 14:
            [self.scene.view presentScene:transitionResult transition:transitionPushWithDirection];
            [self removeUIKitViews];
            break;
        case 15:
            [self.scene.view presentScene:transitionResult transition:transitionRevealWithDirectionUp];
            [self removeUIKitViews];
            break;
        case 16:
            [self.scene.view presentScene:transitionResult transition:transitionWithCIFilter];
            [self removeUIKitViews];
            break;
        default:
            break;
    }

You will receive a warning that says that a method (removeUIKitViews) is missing. That method is a simple call to remove some views from the parent and super view. While simplistic, the necessary code is:

-(void) removeUIKitViews
{
    [transitionTimerText removeFromParent];
    [_tableView removeFromSuperview];
    [_sliderTimer removeFromSuperview];
}

Now for several notes regarding the -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath method.

  • The SKTransition transition initialization is similar to all transitions.
  • The filter defined is a customized one. As stated above, you can re-configure it or define a totally new filter.
  • The Duration time is defined by the UISlider value.

Step 8

In order to run the code and test the transitions, you will need to populate the TransitionResult class. Move that class and add the -(id)initWithSize:(CGSize)size method. It is similar to the MyScene.m method. You can try to write it yourself. Copy-and-paste from the other class to make it look like the next method:

-(id)initWithSize:(CGSize)size {
    if (self = [super initWithSize:size]) {
        self.backgroundColor = [SKColor colorWithRed:0.35 green:0.45 blue:0.23 alpha:1.0];
        SKLabelNode *myLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
        myLabel.text = @"Tap go back";
        myLabel.fontSize = 15;
        myLabel.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
        [self addChild:myLabel];
    }
    return self;
}

You can now run the code and test the transitions. Go ahead and try them!

Step 9

As you may have already noticed, every time you want to test a new transition you need to run the code again. So, let’s modify the TransitionResult.m file that enables an infinite navigation. Every time the user taps the screen it will be moved to the initial scene.

You will need the -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event method and you will need to import the MyScene.h class. So, the final step is to allocate and initiate a class object and swap the scenes. The next snippet will help you do just that:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        MyScene* home = [[MyScene alloc] initWithSize:CGSizeMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame))];
        [self.scene.view presentScene:home];
}

Finally, run your program and test all the SKTransitions. The next image represents one of the transitions:

Figure 3: SKTRansition
Another SKTRansition effect illustration

Conclusion

Over the course of this SKTransition tutorial, we have covered the following:

  • A complete overview of the SKTransition class.
  • How to create and configure all of the SKTransition options.
  • Working with SKTransition properties.
  • How to create and configure a UITableView and UISlider and use them in parallel with Sprite Kit.

If you have any questions or comments, please feel free to leave them below!


Viewing all articles
Browse latest Browse all 1836

Trending Articles