我會將閱讀文章的人視作對 c / obj-c 以及 iOS有一定的認識,因此在obj-c/c的一些使用內容將不會有太多的解釋。另外會以AS3為對照的語言,作一些相關的引用跟對照。
首先先新增一個Class,命名為Sprite 繼承自 NSObject。
Sprite.h:
@interface Sprite: NSObject { CGFloat x; CGFloat y; CGFloat width; CGFloat height; CGFloat red; CGFloat blue; CGFloat green; CGFloat alpha; } @property (assign) CGFloat x, y; @property (assign) CGFloat width, height; @property (assign) CGFloat red, green, blue, alpha; -(void) draw:(CGContextRef) context; @end在Sprite.h內,我們知道這個Class會有(x,y)座標、長、寬以及RGBA等參數,並宣告了一個實體method draw;接下來我們開始實做這Class。
Sprite.m:
@implementation Sprite @synthesize x, y; @synthesize width, height; @synthesize red, green, blue, alpha; -(id)init { self = [super init]; if(self) { x = y = 0.0; width = height = 0.0; red = green = blue = 0.0; alpha = 0.0; } } -(void) draw:(CGContextRef) context { CGContextSaveGState(context); CGContextBeginPath(context); CGContextSetRGBFillColor(context, red, green, blue, alpha); CGContextAddRect(context, CGRectMake(x, y, width, height) ); CGContextClosePath(context); CGContextDrawPath(context, kCGPathFill); CGContextRestoreGState(context); }在上面,你會注意到兩行(19跟27)分別被我highlight起來;在這邊會需要將他的狀況保存起來是因為我們之後將會對這個Class去進行擴充,所以要將這個Class(或者說被建立出來的實體)本身的CTM去獨立的保存與釋放;如果不這麼作,可能會遇到圖形變形或者是座標...等等出現問題;大家可以在後面進行縮放跟旋轉擴充後,在去測試將這個動作移除後的差異。
然後回到Quartz2DView的部分,分別對 .h 以及 .m 作下面的修改:
//-----Quartz2DView.h #import "Sprite.h" @interface Quartz2DView : UIView { Sprite *mySprite0; NSTimer *timer; } -(void) dt:(NSTimer*) theTimer @end //Quartz2DView.m -(id) initWithFrame:(CGRect) frame { self = [super initWithFrame:frame]; if(self) { mySprite0 = [[Sprite alloc] init]; mySprite0.width = 100; mySprite0.height = 100; timer = [NSTimer scheduledTimerWithTimeInterval:1/30.0 target:self selector:@selector(dt:) userInfo:nil repeats:YES]; } } -(void) dt:(NSTimer*) theTimer { mySprite0.y+=1; [self setNeedsDisplay]; } -(void) drawRect:(CGRect) rect{ CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSaveGState(context); CGAffineTransform t0 = CGContextGetCTM( context ); t0 = CGAffineTransformInvert(t0); CGContextConcatCTM(context, t0); [mySprite0 draw:context]; CGContextRestoreGState(context); }到這邊就修改完畢了,執行的結果,應該是上一篇最後執行的結果,沿著Y軸移動。
接下來我們將要進行Sprite的擴充,希望讓Sprite有旋轉和縮放的能力,這就要使用到上一篇提到的轉置矩陣,轉置矩陣可以參考一下AS3的Matrix;那麼現在,我們就開始修改Sprite的內容。
Sprite.m
@interface Sprite: NSObject { CGFloat x; CGFloat y; CGFloat width; CGFloat height; CGFloat red; CGFloat blue; CGFloat green; CGFloat alpha; CGFloat rotation; CGFloat scaleX; CGFloat scaleY; CGRect box; } @property (assign) CGFloat x, y; @property (assign) CGFloat width, height; @property (assign) CGFloat red, green, blue, alpha; @property (assign) CGFloat rotation; @property (assign) CGFloat scaleX,scaleY; @property (assign) CGRect box; -(void) drawOutlinePath:(CGContextRef) context; -(void) drawBody:(CGContextRef) context; -(void) draw:(CGContextRef) context; -(void) updateBox; @end上面我們對Sprite Class增加了旋轉(rotation)及縮放(scaleX,scaleY)的參數,以及drawBody、drawOutlinePath兩個instance method,box則是用來記錄Sprite在畫面上的起點跟大小,updateBox則是用來更新box的內容後面會有更詳細的解釋;這邊我們會注意當我們在旋轉的時候,會以Sprite的原點作旋轉,而現在的原點,在左下角如下圖所示。
如果依照上圖,以左下角原點做旋轉的話,在大多的時候不適用在我們的物件上,所以我們希望將員點移到物件的中心點,如下圖一所示。這樣才能達到理想中的旋轉情況,如圖二所示。
因此我們在Sprite.m 的 drawOutlinePath 中將Sprite的原點做改變
Sprite.m
-(void) drawOutlinePath:(CGContextRef) context { CGFloat w2 = box.size.width * 0.5;// 半寬 CGFloat h2 = box.size.height * 0.5;// 半高 CGContextBeginPath( context ); CGContextMoveToPoint( context, -w2, h2); CGContextAddLineToPoint( context, w2, h2); CGContextAddLineToPoint( context, w2, -h2); CGContextAddLineToPoint( context, -w2, -h2); CGContextAddLineToPoint( context, -w2, h2); CGContextClosePath( context ); }上面的程式碼中,如果你熟悉AS3應該會覺得CGContextMoveToPoint、CGContextAddLineToPoint很像是AS3中的graphic.MoveTo() 跟 graphic.LineTo(),其實非常的類似;也就是先將起始點移到(-w2,h2),然後再依照寬高將外框的線畫出來並畫回起始點;這樣,物件的原點與中心點就重合到在一起了(上圖一)。
(其實這邊也可以用簡單的CGContextAddRect去繪製矩形,不過在這邊介紹另一個用法給大家參考)
接下來我們繼續將 drawBody 、 draw 兩個method完成:
//Sprite.m -(void) drawBody:(CGContextRef) context { CGContextSetRGBFillColor( context , red, green, alpha); [self drawOutlinePath:context]; CGContextDrawPath( context ); } -(void) draw:(CGContextRef) context { CGContextSaveGState(context); CGAffineTransform t0 = CGAffineTransformIsIdentity; t0 = CGAffineTransformTranslate( context, x, y); t0 = CGAffineTransformRotate( context, rotation); t0 = CGAffineTransformScale ( context, scaleX, scaleY); CGContextConcatCTM( context, t0); [self drawBody:context]; CGContextRestoreGState(context); }上面被Highlight的部分,就是對Sprite Class本身去進行旋轉、縮放以及位移的動作,CGAffineTransformTranslate、CGAffineTransformRotate跟CGAffineTransformScale 使用順序可能要特別注意,不然出來的結果可能會跟你想的很不一樣。
接著再來講解box以及updateBox內容,你也許在上面drawOutlinePath已經有注意到box有被使用到,但是我們並沒有去設定box相關的內容,那他的寬、高是如何得到的?我們配合下面的程式碼以及圖三來進行解釋:
//Sprite.m -(void)updateBox { // 取得現在的寬高 CGFloat w0 = width * scaleX; CGFloat h0 = height * scaleY; // 求出半高及半寬 CGFloat w2 = w0 * 0.5; CGFloat h2 = h0 * 0.5; CGSize size = box.size; CGPoint origin = box.origin; //更新圖形的寬高以及起始點 size.width = w0; size.height = h0; origin.x = x - w2; origin.y = y - h2; box.size = size; box.origin = origin; }上面的程式碼,主要事要隨著改變Sprite的寬、高以及對x、y方向的縮放,去調整實際上Sprite物件的大小,並將線在範圍重新定位。
最後因為Quartz 2D的旋轉是以弧度來計算所以在 rotation 的 setter & getter 要再作一些修改:
-(void)setRotation:(CGFloat) degree { rotation = degree * 3.141592 / 180; } -(CGFloat) rotation { return rotation * 180 / 3.141592; }最後,我們將Quartz2DView的mySprite作一些修改
-(id) initWithFrame:(CGRect) frame { self = [super initWithFrame:frame]; if(self) { mySprite0 = [[Sprite alloc] init]; mySprite0.width = 100; mySprite0.height = 100; mySprite0.red = 1; mySprite0.rotation = 30; mySprite0.height = 100; mySprite0.scaleX = 1.5; //timer = [NSTimer scheduledTimerWithTimeInterval:1/30.0 // target:self // selector:@selector(dt:) // userInfo:nil // repeats:YES]; } }執行後的結果
到這篇,這一篇就告一段落了,內容比我想像中的多了很多 -.-",看來應該是我沒有掌控好,不過內容也大多是被程式碼給佔據就是;最後希望這篇對大家有幫助,也跟大家一起共勉求進步。對於內容有任何錯誤或者建議,也請大家不要吝嗇,謝謝。
參考資料:
iOS Developer Library CGContext Reference
iOS Developer Library CGAffineTransform Reference
ActionScript3.0 Matrix
ActionScript3.0 Graphics
範例下載:Sample2.zip
上一篇相關文章:iOS SDK 認識 Quartz 2D(0)
沒有留言:
張貼留言