jarinosuke blog

about software engineering, mostly about iOS

SpriteSheetを使って自分の描いた絵にアニメーションをかけてみた

はじめに

f:id:jarinosuke0808:20100829160856p:image:right
突然ですが自分の描いた絵を動かしてみたい、という衝動に駆られたことはみなさんあると思います。
それを実現するための素晴らしいクラス群がcocos2dにはあります。
それらを使って自分の描いた絵に簡単なアニメーションを付けてみたので、ブログに書きました。

まずは素材の準備

僕は最近使っているCS4 Illustratorを使って描きました。まだまだ宝の持ち腐れ感がすごいですね。
ちなみに今回扱うキャラクターは「卵男」という名前です。
アニメーションに使うだけの素材を用意しましょう。
iPhoneの画面サイズ(320 * 480)以下ならサイズは適当で大丈夫です。でも、各素材でのサイズは統一した方が楽かもしれません。
また画像の名前は後で重要になるので、画像の名前は統一して、アニメーションさせたい順に名前の後に1から順番に数字を振りましょう。
もし、少し気のおかしな人で僕の画像を使いたい、という人がいましたら以下からダウンロードして使ってもらって結構です。
eggman1.png
eggman2.png
eggman3.png
eggman4.png
eggman5.png
eggman6.png
eggman7.png
eggman8.png
eggman9.png
eggman10.png
eggman11.png
eggman12.png
eggman13.png

Zwoptex

Zwoptexは、cocos2d内で用いるSpriteSheetを作る素晴らしいツールです。
早速インストールしましょう。シェアウェアですが、Demoを選択して無料で使わせてもらいましょう。
ただ、cocos2dのversionとZwoptexのそれで扱えるSpriteSheetのプロパティに差異があるので注意が必要です。
僕は最終的に、cocos2d-0.99.5-betaとZwoptex-1.0.5bで行いました。

使い方としては簡単で、アニメーションに使いたい素材画像を全てD&Dします。

そうすると、おそらく一カ所に画像が溜まってしまうのでLayoutのApplyボタンを押しましょう。整列してくれるはずです。
それでも重なっている画像があるなら、CanvasでWidthもしくはHeightを一つ大きなサイズにした後にもう一度Applyすれば整列します。

綺麗に画像が並んだら(順序は関係ありません)、SaveしてPublishしましょう。すると同じディレクトリ内にpngファイルとplistファイルができるはずです。

これで素材作成は終わりです、以下からプロジェクトを作成してコードを書いていきます。
f:id:jarinosuke0808:20100829160857p:image

アニメーション実装

まずはXcodeを起動し、テンプレートに「cocos2d Application」を選択して新規プロジェクトを作成しましょう。プロジェクト名は「AnimEggMan」にします。
f:id:jarinosuke0808:20100829160858p:image:right
次にさきほどZwoptexを使って作成したplistファイルとpngファイルをResourcesフォルダに追加します。
これで準備は万全です、コードを書いていきましょう。

まずは、これから必要となるメンバ変数を定義していきます。HelloWorldScene.hを開いて以下を追加して下さい。

CCSprite *_eggman;
CCAction *_walkAction;
CCAction *_moveAction;
BOOL _moving;

@property (nonatomic, retain) CCSprite *eggman;
@property (nonatomic, retain) CCAction *walkAction;
@property (nonatomic, retain) CCAction *moveAction;

CCSpriteは画像を保持するためのオブジェクトです。
CCActionには、これから定義するアクションを保持し、いつでもそのアクションを呼び出せるようにするための変数です。

同様にして、HelloWorldScene.mにもsynthesizeを書きましょう。
以下からは全てimplementationになります。

@synthesize eggman = _eggman;
@synthesize moveAction = _moveAction;
@synthesize walkAction = _walkAction;

dealloc内にreleaseも忘れずに追加しましょう。

self.eggman = nil;
self.walkAction = nil;

次にinit内を以下のように修正しましょう。

-(id) init
{
     if( (self=[super init] )) {
          self.isTouchEnabled = YES;
       
          [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:
           @"eggman.plist"];
       
          CCSpriteSheet *spriteSheet = [CCSpriteSheet spriteSheetWithFile:@"eggman.png"];
          [self addChild:spriteSheet];
       
          NSMutableArray *walkAnimFrames = [NSMutableArray array];
          for(int i = 1; i <= 13; ++i) {
               [walkAnimFrames addObject:
                [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
                 [NSString stringWithFormat:@"eggman%d.png", i]]];
          }
       
          CCAnimation *walkAnim = [CCAnimation animationWithName:@"walk"
                                                                       delay:0.1f frames:walkAnimFrames];
       
          CGSize winSize = [CCDirector sharedDirector].winSize;
          self.eggman = [CCSprite spriteWithSpriteFrameName:@"eggman1.png"];       
          _eggman.position = ccp(winSize.width/2, winSize.height/2);
          self.walkAction = [CCRepeatForever actionWithAction:
                                 [CCAnimate actionWithAnimation:walkAnim restoreOriginalFrame:NO]];
          [_eggman runAction:_walkAction];
          [spriteSheet addChild:_eggman];
       
     }
     return self;
}

まず初めにタッチ操作を有効にしていますね。後でタッチイベント周りの操作を追加することになるので、そのためです。

次にCCSpriteFrameCacheというシングルトンクラスに先ほど作成したplistファイルを加えています。
あのplistファイルを直接見てみると分かりますが、中には各画像一枚一枚の情報が書かれています。
そのメタ情報をキャッシュとして保持することで、一枚の大きな画像から高速で呼び出しを行うことが可能になります。

その後はNSMutableArrayに、CCSpriteFrameCacheクラスから一枚一枚の画像を名前で呼び出して保存しています。

次はwalkAnimというCCAnimationオブジェクトに、先ほどの画像の配列walkAnimFramesをフレーム引数として与えてアニメーションを作っています。

最後の部分で、実際に画像をSpriteFrameから一枚指定して画面の真ん中に加えています。

これでビルドしてみましょう。卵男がその場で足を空回りしていれば成功です。
でもこれだけでは物足りないので、タッチした位置に卵男が歩いて行くようにしてみましょう。

タッチイベントも付けよう

まずは先ほどinit内でrunActionを走らせたままにしていたので、コメントアウトしましょう。

// [_eggman runAction:_walkAction];

では以下にタッチイベントを取得する関数をバァっと貼り付けました。
タッチイベントについては過去に何回か扱っているので、基本的にな所は以前の記事であるcocos2dを用いてiPhone上で至極シンプルなゲーム作成チュートリアルを先に見てもらえると分かるような気がします。

-(void) registerWithTouchDispatcher
{
    [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0
                                                         swallowsTouches:YES];
}

-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
     return YES;
}

-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event { 
  
     CGPoint touchLocation = [touch locationInView: [touch view]];       
     touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];
     touchLocation = [self convertToNodeSpace:touchLocation];
  
     float bearVelocity = 480.0/3.0;
  
     CGPoint moveDifference = ccpSub(touchLocation, _eggman.position);
  
     float distanceToMove = ccpLength(moveDifference);
  
     float moveDuration = distanceToMove / bearVelocity;
  
     if (moveDifference.x < 0) {
          _eggman.flipX = NO;
     } else {
          _eggman.flipX = YES;
     }
  
     [_eggman stopAction:_moveAction];
  
     if (!_moving) {
          [_eggman runAction:_walkAction];
     }
  
     self.moveAction = [CCSequence actions:                       
                            [CCMoveTo actionWithDuration:moveDuration position:touchLocation],
                            [CCCallFunc actionWithTarget:self selector:@selector(eggmanMoveEnded)],
                            nil];
  
     [_eggman runAction:_moveAction];
     _moving = TRUE;
}

-(void)eggmanMoveEnded {
    [_eggman stopAction:_walkAction];
    _moving = FALSE;
}

いくつか説明すると、途中の_eggmanというCCSpriteオブジェクトのプロパティflipXにブール値を代入していますが、これをYESにするとX軸方向にSpriteが反転します。
タッチされた位置とキャラクターの位置から移動距離や時間間隔などを計算しており、それらをCCMoveToとして与えています。
あえてCCSequenceを使ってCCMoveToだけでなく、CCCalcFuncも入れて、stopActionを含めたコールバック関数も与えているのがスマートだな、と自分で思っていたりします。

これであなたのiPhone上で自分の描いたキャラクターが縦横無尽に足を動かし歩き回ってくれることでしょう。
大勝利!やりましたね!
f:id:jarinosuke0808:20100829161628p:image