2011年4月26日

iOS SDK 認識 Quartz 2D (2)

Hi everyone,在上一篇 iOS SDK 認識 Quartz 2D (1),我們成功的將冗長的程式碼包裝成一個class,並賦予這個class讓其可以有旋轉以及縮放的功能;接著這次要介紹的功能是如果播放動態,介紹其相關的原理。


我會將閱讀文章的人視作對 c / obj-c 以及 iOS有一定的認識,因此在obj-c/c的一些使用內容將不會有太多的解釋。另外會以AS3為對照的語言,作一些相關的引用跟對照。

此篇將延續上兩篇使用的專案


動態製作的原理,是依照連環圖動畫來製作(filpnook animation),使用一張一張圖讓他去播放(可以參考前面連結的影片),在瞭解動態如何製作之後,我們假設現在要製作一個數字跳動的動態,我希望能已30fps重複播放六張圖片,但如果這樣製作六張分開的圖片讓程式載入,不只在檔案上會增加很多圖檔,程式也必需要一直載入圖片來播放,這樣管理起又變得麻煩。 所以我們將會把一個物件(人物、角色...)的動態坐在一張檔案裡,圖片製作的方式如下:
一個Row, 可以做一方向(狀態)的動態, 將物件所有的動態做成一張圖(atlas 地圖集),用來載入程式來動作。

接著我們建立一個新的Class,繼承 Sprite 命名為 AtlasSprite
AltasSprite.h:
#import "Sprite.h"
@interface AtlasSprite : Sprite {
    UIImage *atlas;//地圖集
    CGImageRef image;//Quartz 圖片格式
    CGFloat atlasWidth;//寬
    CGFloat atlasHeight;//高度
    CGRect clipRect;//紀錄圖片位置
    int rows;//地圖集有幾行
    int columns;//幾欄
    int frame;//用來紀錄播放動態的影格
}
@property (retain, nonatomic) UIImage *atlas;
@property (assign) CGFloat atlasWidth,atlasHeight;
@property (assign) CGRect clipRect;
@property (assign) CGImageRef image;
@property (assign) int rows,columns,frame;
- (id) initWithFile:(NSString*) fileName withRows:(int)atlasRows withColumns:(int) atlasColumns;
在AltasSprite中,我們宣告了八個新參數以及一個Method,接著我們進行Class的實作,這邊我會將method分開解釋,程式碼方面會比較零散,請大家自己注意一下
AltasSprite.m
- (id) initWithFile:(NSString*) fileName withRows:(int)atlasRows withColumns:(int) atlasColumns;
{
    self = [super init];
    if(self)
    {
        atlas = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle]
                                                           pathForResource:fileName ofType:nil]
                   ];
        CGImageRef img = [atlas CGImage];
        image = img;
        int width0 = CGImageGetWidth(image);
        int height0 = CGImageGetHeight(image);
        if(atlasRows < 1) atlasRows = 1;
        if(atlasColumns < 1) atlasColumns = 1;
        atlasWidth = width0;
        atlasHeight = height0;
        rows = atlasRows;
        columns = atlasColumns;
        width = round(width0/columns);
        height = round(height0/rows);
        CGFloat w2 = width*0.5;
        CGFloat h2 = height*0.5;
        clipRect = CGRectMake(-width*0.5, -height*0.5, width, height);
        frame = 0;
    }
    return self;
}
21行highlight的部份,將我們原點轉移到中心點, 這就是之後我們呈現圖案的範圍大小
接著我們覆寫drawBody Method
-(void) drawBody:(CGContextRef)context
{
    int r0 = floor(frame/columns);
    int c0 = frame-columns*r0;
    CGFloat w2 = width*0.5;
    CGFloat h2 = height*0.5;
    CGFloat u = c0*width+w2;
    CGFloat v = atlasHeight - (r0*height+h2);
    
    CGContextBeginPath(context);
    CGContextAddRect(context, clipRect);
    CGContextClip(context);
    CGContextDrawImage(context, CGRectMake(-u, -v, atlasWidth, atlasHeight), image);
}
在3-4行程式碼的地方我們先求出我們要對應的row & column 接著運算出對應的在地圖集上的坐標,接著我們增加即將製作圖形的大小方塊,接著使用 CGContextDrawImage 的方式,在地圖集上將我們想呈現的出區塊畫出來(參考下圖)。
最後別忘了要釋放我們使用的圖片資源 AtlasSprite.m
-(void)dealloc
{
    [atlas release];
    CGImageRelease(image);
    [super dealloc];
}
接著回到Quartz2DView進行修改:
//Quartz2Dview.h
#import "Sprite.h"
#import "AtlasSprite.h"
@interface Quartz2DView : UIView {
    
    //Sprite *mySprite;
    AtlasSprite *atlasSprite;
    NSTimer *timer;
    int aframe;
}
-(void) dt:(NSTimer *) theTimer;
@end

//Quartz2DView.m
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        self.backgroundColor = [UIColor clearColor];
        aframe = 0;
        atlasSprite = [[AtlasSprite alloc] initWithFile:@"sample.png" 
                                               withRows:7 
                                               withColumns:7
                      ];
        atlasSprite.x = 100;
        atlasSprite.y = 100;
        atlasSprite.frame = 1;//使呈現的圖案=01
    }
    return self;
}
-(void) drawRect:(CGRect)rect
{
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    CGContextSaveGState(context);
    
    CGAffineTransform t0 = CGContextGetCTM(context);
    t0 = CGAffineTransformInvert(t0);
    CGContextConcatCTM(context, t0);
    
    [atlasSprite updateBox];
    [atlasSprite draw:context];
    
    CGContextRestoreGState(context); 
}
執行後的結果如下圖:
到這邊整個Class已經完成了,接下來要做的就是讓動態可以跑起來,我們建立一個 NSTimer 來處理動態的動作
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        self.backgroundColor = [UIColor clearColor];
        aframe = 0;
        atlasSprite = [[AtlasSprite alloc] initWithFile:@"sample.png" 
                                               withRows:7 
                                               withColumns:7
                      ];
        atlasSprite.x = 100;
        atlasSprite.y = 100;
        //atlasSprite.frame = 1;//使呈現的圖案=01
        timer = [NSTimer scheduledTimerWithTimeInterval:1/30.0
                                               target:self
                                             selector:@selector(dt:)
                                             userInfo:nil
                ];
    }
    return self;
}
-(void) dt:(NSTimer *) theTimer
{
    aframe+1 < atlasSprite.columns ? ++aframe : (aframe=0);
    atlasSprite.frame = aframe;
    [self setNeedsDisplay];
}
執行出現的話面就不在提供了,畫面的紅色區塊將會00 ~ 06之間重複播放。

那麼,這篇文章到這邊也就結束了,可能大家對於AltasSprite.m 的 drawBody會有較多的疑問, 這邊牽扯到了一些數學計算,讀起來可能會比較不容易瞭解,所以附上了兩個圖片來讓大家理解。
總算是打完這篇了,這篇的文字敘述越來越少了,大多數的內容也都是使用程式碼和圖片來帶過 囧rz ..(變懶了);自己最近做練習,其實也是跌跌撞撞,debug花了2hrs才發現是使用到了method不是當前需要的,其實程式碼根本沒有錯誤,只能希望自己盡量小心不要再犯一樣的錯誤;最後在這邊希望這篇也能幫助到大家,如果內容有任何錯誤或者是指教,也請不要吝嗇,謝謝。

參考資料:
iOS Developer Library CGContext Reference
iOS Developer Library CGImage Reference
使用單張影像來製作flipbook動畫效果
iOS SDK Tutorial: Flip Book Style Animation, part 1 of 2
iOS SDK Tutorial: Flip Book Style Animation, part 2 of 2

範例下載: Sample3.zip

相關文章:
iOS SDK 認識 Quartz 2D(0)
iOS SDK 認識 Quartz 2D(1)

沒有留言:

張貼留言