[iOS] SpriteKitとUIKitを組み合わせて、ちょっとリッチなウォークスルー画面 を作りたい

2015.02.20

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

先日、iOS オールスターズ勉強会に参加しました。すごく良かったです!どれも勉強になりました。

そのなかで、まだiOSでリッチな演出に疲弊してるの?という発表があったのですが、そこでUIKitにSpriteKitのを入れられることを知りました。(´-`).。oO(しかも、思ったより簡単そう・・・)

だがしかし、パーティクルの使いドコロって、ゲームじゃないアプリだと使いドコロがが難しいです。 パーティクル上級者が実装すれば素敵な感じになると思いますが、自分のような初心者が通常の画面で多用すると、ユーザビリティの低下を起こしそうなのです。

でも、使ってみたい!\\\\(۶•̀ᴗ•́)۶////

そこで、考えました。入れてもそこまで影響が無さそうな画面を。アプリを初回起動した時によく出てくる、簡単な操作説明や、ようこそ的画面(ウォークスルー画面という名前みたいです)に入れれば、パッと目を引くし素敵な感じになるのではと・・・。

はい。前置きが長くなりましたが、今回はSpriteKitとUIKitを組み合わせてウォークスルー画面作りたいと思います。 Swiftのデモは素敵なのが *1あるので、時代を逆行している感はありますがObjectice-Cで今回はやっていきます。

サンプルプロジェクトの作成

サンプルでは、ようこそ画面における温泉の湯けむりを演出します。(´-`).。oO(Smokeパーティクル使いたい・・・)

環境

  • Xcode 6.1.1
  • iOS SDK 8.1

演出を入れる前の画面を作成する

演出を入れる前のウォークスルー画面を作っていきます。 プロジェクトテンプレートよりSingle View Applicationを選択します。

次にMain.storyboard上にウォークスルーの画面を作成します。ベースはUIViewControllerです。

Main_storyboard

そして、UIImagerViewUIScrollViewUIPageControlの順に置いていきます。UIImagerViewには背景画像を表示させます。

演出入れる前のStoryboard

ウォークスルー画面のクラスを作成します。今回はWalkThroughViewControllerというクラスを作成しました。

WalkThroughViewController.m

#import "WalkThroughViewController.h"
#import "WalkThroughPageView.h"

@interface WalkThroughViewController () <UIScrollViewDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (weak, nonatomic) IBOutlet UIPageControl *pageControl;
@end

@implementation WalkThroughViewController

#pragma mark - Lifecycle Methods

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self setupScrollView];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}

#pragma mark - scrollView Delegate method

- (void)scrollViewDidScroll:(UIScrollView*)scrollView
{
    // 縦方向のスクロールをキャンセルする
    CGPoint point = scrollView.contentOffset;
    point.y = 0;
    self.scrollView.contentOffset = point;
    // ページャ設定
    CGFloat pageWidth = scrollView.frame.size.width;
    float fractionalPage = scrollView.contentOffset.x / pageWidth;
    NSInteger page = lround(fractionalPage);
    if (self.pageControl.currentPage != page) {
        self.pageControl.currentPage = page;
    }
}

#pragma mark - Private methods

- (void)setupScrollView
{
    // スクローラーの設定
    self.scrollView.pagingEnabled = YES;
    self.scrollView.showsHorizontalScrollIndicator = NO;
    self.scrollView.showsVerticalScrollIndicator = NO;
    self.scrollView.delegate = self;
    self.scrollView.userInteractionEnabled = YES;
    
    // ページの中身を入れる処理(コード省略)
    ・
    ・
    ・
    
    // pageControl設定
    self.pageControl.numberOfPages = titles.count;
    self.pageControl.currentPage = 0;
    self.pageControl.userInteractionEnabled = NO; // タップを無効
}
@end

※ページの中身を作成しているコードは省略しています

サンプルアプリなので、実行時に問答無用で呼び出すようにします。

ViewController.m

#import "ViewController.h"
#import "WalkThroughViewController.h"

@interface ViewController ()
@end

@implementation ViewController

#pragma mark - Lifecycle Methods

- (void)viewDidLoad
{
    [super viewDidLoad];
}
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    WalkThroughViewController *walkThroughViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"WalkThroughViewController"];
    [self presentViewController:walkThroughViewController animated:YES completion:nil];
}
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}
@end

説明を色々と端折りましたが、アプリ起動時にウォークスルー画面が表示されれば準備は完了です。

パーティクル作成

パーティクルの作成については以下のページを参考に作成しました。

今回は湯けむりなので、Smokeパーティクルを選択します。 そしてSmokeParticle.sksファイルを作成しました。

SmokeParticle_sks

それっぽく見えるように調整します。パーティクルのパラメータの解説については、下記サイトにまとめられていたので参考になりました。

SpriteKitの準備

パーティクルを呼び出すには、SpriteKitSceneが必要になるので作成します。 ResourceからSpriteKitSceneを選択して作成します。

SpriteKitSceneを作成

また、SpriteKitScene用のクラスファイルを作成します。サブクラスはSKSceneとします。

以下、3ファイルを作りました。

  • SmokeScene.sks
  • SmokeScene.h
  • SmokeScene.m

SmokeScene_h

SmokeScene.hですが、デフォルトではSpriteKitをインポートしてませんでした。

SmokeScene.h

#import <UIKit/UIKit.h>

#import <SpriteKit/SpriteKit.h>

に変更します。

また、SmokeScene.mにパーティクルを呼び出すコードを追加します。

SmokeScene.m

#import "SmokeScene.h"
@implementation SmokeScene
- (void)didMoveToView:(SKView *)view
{
    [self setBackgroundColor:[UIColor clearColor]];
    
    if (self.children.count == 0) {
        NSString *path = [[NSBundle mainBundle] pathForResource:@"SmokeParticle" ofType:@"sks"];
        SKEmitterNode *particle = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
        particle.position = CGPointMake(CGRectGetMidX(self.frame), 0.0f);
        [self addChild:particle];
    }
}
@end

今回は任意のタイミングではなく、このシーンが表示されたらパーティクルを表示するようにしてます。

particle.positionで呼び出したい場所を指定します。今回は画面下から出てくるイメージなので、y座標を0にします。(左下が(0,0)になるので・・・)

これでSpriteKitの準備は完了です。

UIKitに入れる

先ほど作成したMain.storyboardのウォークスルー画面にSpriteKit用のViewを追加します。種類はUIView、位置は背景画像とScrollViewの間に入れます。

Main_storyboard_—_Edited

ここでポイントなのが、追加したUIViewのクラスをSKViewにします。またBackgroundを透明にしておきます。

Main_storyboard_—_Edited 2

また、パーティクルを入れたいViewController(ここではWalkThroughViewController)にSKViewを呼び出すコードを入れます。追加したのは色を付けた部分です。Storyboardに追加したSKViewもアウトレット化しておきます。

WalkThroughViewController.m

#import "WalkThroughViewController.h"
#import "WalkThroughPageView.h"
#import "SmokeScene.h"

@implementation SKScene (Unarchive)
+ (instancetype)unarchiveFromFile:(NSString *)file
{
    // アプリケーションバンドルからシーンファイルのパスを取得
    NSString *nodePath = [[NSBundle mainBundle] pathForResource:file ofType:@"sks"];
    NSData *data = [NSData dataWithContentsOfFile:nodePath
                                          options:NSDataReadingMappedIfSafe
                                            error:nil];
    NSKeyedUnarchiver *arch = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    [arch setClass:self forClassName:@"SKScene"];
    SKScene *scene = [arch decodeObjectForKey:NSKeyedArchiveRootObjectKey];
    [arch finishDecoding];
    
    return scene;
}
@end

@interface WalkThroughViewController () <UIScrollViewDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (weak, nonatomic) IBOutlet UIPageControl *pageControl;
@property (weak, nonatomic) IBOutlet SKView *skView;
@end

@implementation WalkThroughViewController

#pragma mark - Lifecycle Methods

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self setupScrollView];
    [self setupParticle];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}

#pragma mark - scrollView Delegate method

- (void)scrollViewDidScroll:(UIScrollView*)scrollView
{
    // 縦方向のスクロールをキャンセルする
    CGPoint point = scrollView.contentOffset;
    point.y = 0;
    self.scrollView.contentOffset = point;
    // ページャ設定
    CGFloat pageWidth = scrollView.frame.size.width;
    float fractionalPage = scrollView.contentOffset.x / pageWidth;
    NSInteger page = lround(fractionalPage);
    if (self.pageControl.currentPage != page) {
        self.pageControl.currentPage = page;
    }
}

#pragma mark - Private methods

- (void)setupScrollView
{
    // スクローラーの設定
    self.scrollView.pagingEnabled = YES;
    self.scrollView.showsHorizontalScrollIndicator = NO;
    self.scrollView.showsVerticalScrollIndicator = NO;
    self.scrollView.delegate = self;
    self.scrollView.userInteractionEnabled = YES;
    
    // ページの中身を入れる処理(コード省略)
    ・
    ・
    ・
    
    // pageControl設定
    self.pageControl.numberOfPages = titles.count;
    self.pageControl.currentPage = 0;
    self.pageControl.userInteractionEnabled = NO; // タップを無効
}

- (void)setupParticle
{
    self.skView.userInteractionEnabled = NO;
    self.skView.allowsTransparency = YES;
    
    SmokeScene *scene = [SmokeScene unarchiveFromFile:@"SmokeScene"];
    scene.scaleMode = SKSceneScaleModeAspectFill;
    [self.skView presentScene:scene];
}
@end

出来上がったもの

実行してみました。 GIFにしたら画質が悪くなってしまいましたが、下記のような湯けむりを演出することが出来ました。

さいごに

SpriteKitを扱うのが初めてだったので、手探りつつやってみましたが、思っていたよりも手軽に出来るなと思いました。実際の案件ではあまり使う事は無さそうな気もしますが、こういった要望がきた時には、さっと実装できるとカッコ良いです。(´◡`人)

今回の記事の中で使用している画像は下記からお借りしています。

脚注

  1. https://github.com/ryusukefuda/SpriteKit-Demo