技術

SpriteKitを使ってみる その1

iOS 7 SDKにSpriteKitなるものが追加された。
これは2Dスプライトを簡単に扱えるようにするためのもので、アニメーションの機能や物理エンジンやパーティクルシステムなども含まれているみたい。

実際にひと通り使ってみないと使い勝手は分からないけど、簡単な2DゲームならSpriteKitで事足りるんではなかろうかと思う。
ということで、とりあえず今回はXcode 5にあらかじめ用意されている、SpriteKit Gameプロジェクトテンプレートを使ってプロジェクトを作成し、自動作成されるソースとアプリの動きを見てみる。

まず、SpriteKit Gameテンプレートを選択。
スクリーンショット 2013-09-19 13.27.14
実際にやる前は、OpenGL Gameテンプレートを選んでからSpriteKitのフレームワークを追加するようなやり方かなと思っていたけど、テンプレート自体別になってるし、自動で作成されるプロジェクトはSpriteKitのフレームワークを参照してはいるがOpenGLのフレームワークを参照していない。
これは想像だけど、SpriteKitはOpenGLの上に載った薄い層のはず。
素のOpenGLと比べてどのくらい速度的なオーバーヘッドがあるのか、そして素のOpenGLとどうやって連携させるか(そもそもそういう事が可能か)余裕があれば別途確認してみたい。

さて、自動生成されたソースの構成としては、基本的にはAppDelegateとViewController(とストーリーボード)からなる普通の最小限のアプリと同じであるが、それに加えてひとつだけMySceneというクラスが追加されている。

ソースを見るまえに、自動生成されたプロジェクトがどういうアプリなのか実行して確かめてみる。
スクリーンショット 2013-09-19 13.36.54
タップした箇所に一定速度で回転する飛行機(宇宙船?)のスプライトが表示されるアプリのようである。
どんどんタップすると飛行機も増えていく。
画面下部にノード数(スプライト数)とfpsが表示されている点に注意。

さて、ソースであるが、AppDelegateクラスには特にSpriteKit関連の記述はないので、ViewControllerから見ていく。

ViewController自体はUIViewControllerを継承しているごく普通のViewControllerとなっている。
ヘッダのimportを除けば、SpriteKitに関係ある部分はviewDidLoadメソッドだけである。
以下、ViewController.mの-(void)viewDidLoad。

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Configure the view.
    SKView * skView = (SKView *)self.view;
    skView.showsFPS = YES;
    skView.showsNodeCount = YES;
    
    // Create and configure the scene.
    SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
    scene.scaleMode = SKSceneScaleModeAspectFill;
    
    // Present the scene.
    [skView presentScene:scene];
}

いきなりself.viewをSKView*にキャストしてるが、これはストーリーボードでviewのクラスとしてSKViewが設定されているため可能となっている。
上記のスクリーンショットにあったノード数とfpsの表示であるが、それぞれ1行でオンに出来るようである。
これは楽だ。

MyScene(SKSceneを継承したクラス。後ほど説明する。)を生成している。
SKSceneはSKNodeを継承(したSKEffectNodeをさらに継承)したクラスで、まぁ大雑把には画面単位でノードを取りまとめるためのクラスのようである。
ちなみにSKNodeはUIResponderを継承しているのでUIKit関連のクラスと同じように操作出来るんではないかと思われる。

[skView presentScene:scene]によってシーンがviewに表示される。
すでに表示中のシーンがあれば置き換えられる。

次にMySceneのソースについてであるが、中身があるメソッドは以下の2つだけとなっている。

-(id)initWithSize:(CGSize)size {    
    if (self = [super initWithSize:size]) {
        /* Setup your scene here */
        
        self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
        
        SKLabelNode *myLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
        
        myLabel.text = @"Hello, World!";
        myLabel.fontSize = 30;
        myLabel.position = CGPointMake(CGRectGetMidX(self.frame),
                                       CGRectGetMidY(self.frame));
        
        [self addChild:myLabel];
    }
    return self;
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    /* Called when a touch begins */
    
    for (UITouch *touch in touches) {
        CGPoint location = [touch locationInNode:self];
        
        SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:@"Spaceship"];
        
        sprite.position = location;
        
        SKAction *action = [SKAction rotateByAngle:M_PI duration:1];
        
        [sprite runAction:[SKAction repeatActionForever:action]];
        
        [self addChild:sprite];
    }
}

まず、-(id)initWithSize:(CGSize)sizeであるが、これはViewController.mで呼んでいるsceneWithSizeから内部的に呼ばれる。
やってることは簡単で、背景色を指定してラベル(SKLabelNode)をひとつ追加してるだけである。
ラベルの操作については見たまんま分かりやすい。
ちなみにSKLabelNodeもSKNodeのサブクラスである。

SKColorについては「うわ、また新しい色クラスか」と思うかもしれないが実際は単なる以下のようなマクロである。

#if TARGET_OS_IPHONE
#define SKColor UIColor
#else
#define SKColor NSColor
#endif

次に、-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)eventであるが、UIViewなどと同じくタッチイベントを受け取るためのメソッドである。

見どころは2つあって、まずは[SKSpriteNode spriteNodeWithImageNamed:@”Spaceship”]の部分だろう。
たった1行で、画像ファイルからスプライトノードの生成が可能となっている。
これは楽だ。
もちろんSKSpriteNodeもSKNodeのサブクラスである。

もう1つの見どころはSKAction関連の部分だろう。
文字通りノードの動作を表すクラスで、内部的にはシーンに配置したノードが処理される度に、そのノードに関連付けられたアクションが評価されるという仕組みになっている。
つまりシーンに追加されているノード(もしくはこれからシーンに追加しようとしているノード)に一度アクションを設定したら、あとは自動的にそのアクションが実行される。

上記のコードではまず、[SKAction rotateByAngle:M_PI duration:1]で1秒で180度回転するアクションを生成している。
正の角度で反時計回り、負の角度で時計回りとなる。
durationを秒単位で指定することからも分かるが、アクションの速度はfpsに依存していない。
つまりfpsが低くなればアクションはカクカクにはなるが、指定されたdurationで指定されたアクションを行うように動作する。
(とはいっても、実際にはfpsが極端に下がるほど重い場面ではアクションの実行もそれなりに影響を受けるはずである。)

次に、[sprite runAction:[SKAction repeatActionForever:action]]で、1秒で180度回転するアクションを永遠に繰り返すアクションを生成し、ノードに設定している。
これから分かるとおり、アクションを繰り返すためには、アクションのプロパティなどで設定するのではなく、アクションを入れ子にする必要があるみたい。

ざっとSKAction.hを見たところアクションには色々あって、移動、回転、拡縮、フェードイン・アウト、複数の画像を切替える事によるアニメーション、音の再生、ブロック(いわゆるクロージャやラムダ式のようなもの)の実行などがある。
さらにこれらを繰り返したり、順に実行するようなアクションも作成出来る。

簡単なゲームなら、ほとんどアクションの記述だけで作れそうな気がする。

ということで、今回はここまで。
タイトルに「その1」と付いてる通り、「その2」「その3」と続けるつもりである。

「その2」で物理エンジンについて、「その3」でパーティクルについて簡単に見ていこうかなと思っているけど、長くなりそうなら分割するかも。

コメントを残す

メールアドレスが公開されることはありません。



※画像をクリックして別の画像を表示

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください