我會將閱讀文章的人視作對 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)

沒有留言:
張貼留言