//nstimer
//[self timerTest];
//cadisplaylink
// [self displayLinkTest];
//chipmunk
//[self chipmunkTest];
定時(shí)幀
//iOS按照每秒60次刷新屏幕,然后CAAnimation計(jì)算出需要展示的新的幀,然后在每次屏幕更新的時(shí)候同步繪制上去,CAAnimation最機(jī)智的地方在于每次刷新需要展示的時(shí)候去計(jì)算插值和緩沖。
//NSTimer
- (void)timerTest {
containerView = [[UIView alloc] init];
containerView.frame = CGRectMake(20.f, 64.0f, 300.0f, 300.0f);
containerView.backgroundColor = [UIColor blackColor];
[self.view addSubview:containerView];
//add image view
imageView = [[UIImageView alloc] init];
imageView.frame = CGRectMake(0.0, 0.0f, 30.0, 30.0f);
imageView.image = [UIImage imageNamed:@"Star"];
[containerView addSubview:imageView];
//animate
// [self pointToLineAnimation];
[self timerAnimation];
}
- (void)timerAnimation {
//reset ball to top of screen
imageView.center = CGPointMake(150, 32);
//configure the animation
duration = 1.0;
timeOffset = 0.0;
fromValue = [NSValue valueWithCGPoint:CGPointMake(150, 32)];
toValue = [NSValue valueWithCGPoint:CGPointMake(150, 268)];
//stop the timer if it's already running
[timer invalidate];
//start the timer
timer = [NSTimer scheduledTimerWithTimeInterval:1/60.0f
target:self
selector:@selector(timerAction:)
userInfo:nil
repeats:YES];
}
- (void)timerAction:(NSTimer *)step {
//update time offset
timeOffset = MIN(timeOffset + 1/60.0, duration);
//get normalized time offset (in range 0-1)
float time = timeOffset / duration;
//apply easing
time = bounceEaseOut(time);
//interpolate position
id position = [self interpolateFromValue:fromValue
toValure:toValue
time:time];
//move ball view to new position
imageView.center = [position CGPointValue];
//stop the timer if we've reached the end of the animation
if (timeOffset >= duration) {
[timer invalidate];
timer = nil;
}
}
//對(duì)主線程,這些任務(wù)包含如下幾項(xiàng)
//處理觸摸事件
//發(fā)送和接受網(wǎng)絡(luò)數(shù)據(jù)包
//執(zhí)行使用gcd的代碼
//處理計(jì)時(shí)器行為
//屏幕重繪
//我們可以通過(guò)一些途徑來(lái)優(yōu)化:
//我們可以用CADisplayLink讓更新頻率嚴(yán)格控制在每次屏幕刷新之后。
//基于真實(shí)幀的持續(xù)時(shí)間而不是假設(shè)的更新頻率來(lái)做動(dòng)畫。
//調(diào)整動(dòng)畫計(jì)時(shí)器的run loop模式,這樣就不會(huì)被別的事件干擾。
CADisplayLink
//CADisplayLink是CoreAnimation提供的另一個(gè)類似于NSTimer的類,它總是在屏幕完成一次更新之前啟動(dòng)
//CADisplayLink有一個(gè)整型的frameInterval屬性,指定了間隔多少幀之后才執(zhí)行。默認(rèn)值是1,意味著每次屏幕更新之前都會(huì)執(zhí)行一次,如果動(dòng)畫的代碼執(zhí)行起來(lái)超過(guò)了六十分之一秒,你可以指定frameInterval為2,就是說(shuō)動(dòng)畫每隔一幀執(zhí)行一次(一秒鐘30幀)或者3,也就是一秒鐘20次
//當(dāng)使用NSTimer的時(shí)候,一旦有機(jī)會(huì)計(jì)時(shí)器就會(huì)開(kāi)啟,但是CADisplayLink卻不一樣:如果它丟失了幀,就會(huì)直接忽略它們,然后在下一次更新的時(shí)候接著運(yùn)行。
計(jì)算幀的持續(xù)時(shí)間
//我們可以在每幀開(kāi)始刷新的時(shí)候用CACurrentMediaTime()記錄當(dāng)前時(shí)間,然后和上一幀記錄的時(shí)間去比較。
- (void)displayLinkTest {
containerView = [[UIView alloc] init];
containerView.frame = CGRectMake(20.f, 64.0f, 300.0f, 300.0f);
containerView.backgroundColor = [UIColor blackColor];
[self.view addSubview:containerView];
//add image view
imageView = [[UIImageView alloc] init];
imageView.frame = CGRectMake(0.0, 0.0f, 30.0, 30.0f);
imageView.image = [UIImage imageNamed:@"Star"];
[containerView addSubview:imageView];
//animate
//[self timerAnimation];
[self displayLinkAnimation];
}
- (void)displayLinkAnimation {
//reset ball to top of screen
imageView.center = CGPointMake(150.0, 32);
//configure the animation
duration2 = 1.0f;timeOffset2 = 0.0;
fromValue = [NSValue valueWithCGPoint:CGPointMake(150, 32)];
toValue = [NSValue valueWithCGPoint:CGPointMake(150, 268)];
//stop the timer if it's already running
[timer2 invalidate];
//start the timer2
lastStep = CACurrentMediaTime();
timer2 = [CADisplayLink displayLinkWithTarget:self
selector:@selector(timer2Action:)];
[timer2 addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)timer2Action:(CADisplayLink *)timer {
//calculate time delta
CFTimeInterval thisStep = CACurrentMediaTime();
CFTimeInterval stepDuration = thisStep - lastStep;
lastStep = thisStep;
//upadate time offset
timeOffset2 = MIN(timeOffset2 + stepDuration, duration2);
//get normalized time offset (in range 0 - 1)
float time = timeOffset2 / duration2;
//apply easing
time = bounceEaseOut(time);
//interpolate position
id position = [self interpolateFromValue:fromValue toValure:toValue time:time];
//move ball view to new position
imageView.center = [position CGPointValue];
//stop the timer if we've reached the end of the animation
if (timeOffset2 >= duration2) {
[timer2 invalidate];
timer2 = nil;
}
}
Run Loop 模式
//run loop模式如下
//NSDefaultRunLoopMode - 標(biāo)準(zhǔn)優(yōu)先級(jí)
//NSRunLoopCommonModes - 高優(yōu)先級(jí)
//UITrackingRunLoopMode - 用于UIScrollView和別的控件的動(dòng)畫
//于是我們可以同時(shí)加入NSDefaultRunLoopMode和UITrackingRunLoopMode來(lái)保證它不會(huì)被滑動(dòng)打斷,也不會(huì)被其他UIKit控件動(dòng)畫影響性能,像這樣:
//self.timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(step:)];
//[self.timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
//[self.timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode];
//NSTimer同樣也可以使用不同的run loop模式配置,通過(guò)self.timer = [NSTimer timerWithTimeInterval:1/60.0 target:self selector:@selector(step:) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
物理模擬
//Chipmunk:我們將要使用的物理引擎叫做Chipmunk。Chipmunk使用純C寫的,而不是C++,好處在于更容易和Objective-C項(xiàng)目整合
//包括一個(gè)和Objective-C綁定的“indie”版本 http://chipmunk-physics.net下載它
//cpSpace - 這是所有的物理結(jié)構(gòu)體的容器。它有一個(gè)大小和一個(gè)可選的重力矢量
//cpBody - 它是一個(gè)固態(tài)無(wú)彈力的剛體。它有一個(gè)坐標(biāo),以及其他物理屬性,例如質(zhì)量,運(yùn)動(dòng)和摩擦系數(shù)等等。
//cpShape - 它是一個(gè)抽象的幾何形狀,用來(lái)檢測(cè)碰撞??梢越o結(jié)構(gòu)體添加一個(gè)多邊形,而且cpShape有各種子類來(lái)代表不同形狀的類型。 在例子中,我們來(lái)對(duì)一個(gè)木箱建模,然后在重力的影響下下落。我們來(lái)創(chuàng)建一個(gè)Crate類,包含屏幕上的可視效果(一個(gè)UIImageView)和一個(gè)物理模型(一個(gè)cpBody和一個(gè)cpPolyShape,一個(gè)cpShape的多邊形子類來(lái)代表矩形木箱)。
//Chipmunk使用了一個(gè)和UIKit顛倒的坐標(biāo)系(Y軸向上為正方向)。為了使得物理模型和視圖之間的同步更簡(jiǎn)單,我們需要通過(guò)使用geometryFlipped屬性翻轉(zhuǎn)容器視圖的集合坐標(biāo)(第3章中有提到),于是模型和視圖都共享一個(gè)相同的坐標(biāo)系。
/*
#define GRAVITY 1000
- (void)chipmunkTest {
containerView = [[UIView alloc] init];
containerView.frame = CGRectMake(20.f, 64.0f, 300.0f, 300.0f);
containerView.backgroundColor = [UIColor blackColor];
[self.view addSubview:containerView];
//invert view coordinate system to match physics
containerView.layer.geometryFlipped = YES;
//set up physics space
space = cpSpaceNew();
cpSpaceSetGravity(space, cpv(0, -GRAVITY));
//add a crate
Crate *crate = [[Crate alloc] initWithFrame:CGRectMake(100, 0, 100, 100)];
[containerView addSubview:crate];
cpSpaceAddBody(space, crate.body);
cpSpaceAddShape(space, crate.shape);
//start the timer
lastStep = CACurrentMediaTime();
timer2 = [CADisplayLink displayLinkWithTarget:self selector:@selector(step:)];
[timer2 addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
void updateShape(cpShape *shape, void *unused) {
//get the create object associated with the shape
// Crate *crate = (__bridge Crate *)shape->data;
Crate *crate = (__bridge Crate *)shape;
//update crate view position and angle to match physics shape
// cpBody *body = shape->body;
cpBody *body = crate.body;
// crate.center = cpBodyGetPos(body);
crate.center = CGPointMake(0, 0);
crate.transform = CGAffineTransformMakeRotation(cpBodyGetAngle(body));
}
- (void)step:(CADisplayLink *)timer
{
//calculate step duration
CFTimeInterval thisStep = CACurrentMediaTime();
CFTimeInterval stepDuration = thisStep - lastStep;
lastStep = thisStep;
//update physics
cpSpaceStep(space, stepDuration);
//update all the shapes
cpSpaceEachShape(space, &updateShape, NULL);
}
*/
添加用戶交互
//不想讓這個(gè)邊框矩形滑出屏幕或者被一個(gè)下落的木箱擊中而消失,可以通過(guò)給cpSpace添加四個(gè)cpSegmentShape對(duì)象(cpSegmentShape代表一條直線,所以四個(gè)拼起來(lái)就是一個(gè)矩形)。然后賦給空間的staticBody屬性(一個(gè)不被重力影響的結(jié)構(gòu)體),而不是像木箱那樣一個(gè)新的cpBody實(shí)例
/*
- (void)addCrateWithFrame:(CGRect)frame
{
Crate *crate = [[Crate alloc] initWithFrame:frame];
[self.containerView addSubview:crate];
cpSpaceAddBody(self.space, crate.body);
cpSpaceAddShape(self.space, crate.shape);
}
- (void)addWallShapeWithStart:(cpVect)start end:(cpVect)end
{
cpShape *wall = cpSegmentShapeNew(self.space->staticBody, start, end, 1);
cpShapeSetCollisionType(wall, 2);
cpShapeSetFriction(wall, 0.5);
cpShapeSetElasticity(wall, 0.8);
cpSpaceAddStaticShape(self.space, wall);
}
- (void)chipmunkTest {
containerView = [[UIView alloc] init];
containerView.frame = CGRectMake(20.f, 64.0f, 300.0f, 300.0f);
containerView.backgroundColor = [UIColor blackColor];
[self.view addSubview:containerView];
//invert view coordinate system to match physics
self.containerView.layer.geometryFlipped = YES;
//set up physics space
self.space = cpSpaceNew();
cpSpaceSetGravity(self.space, cpv(0, -GRAVITY));
//add wall around edge of view
[self addWallShapeWithStart:cpv(0, 0) end:cpv(300, 0)];
[self addWallShapeWithStart:cpv(300, 0) end:cpv(300, 300)];
[self addWallShapeWithStart:cpv(300, 300) end:cpv(0, 300)];
[self addWallShapeWithStart:cpv(0, 300) end:cpv(0, 0)];
//add a crates
[self addCrateWithFrame:CGRectMake(0, 0, 32, 32)];
[self addCrateWithFrame:CGRectMake(32, 0, 32, 32)];
[self addCrateWithFrame:CGRectMake(64, 0, 64, 64)];
[self addCrateWithFrame:CGRectMake(128, 0, 32, 32)];
[self addCrateWithFrame:CGRectMake(0, 32, 64, 64)];
//start the timer
self.lastStep = CACurrentMediaTime();
self.timer = [CADisplayLink displayLinkWithTarget:self
selector:@selector(step:)];
[self.timer addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSDefaultRunLoopMode];
//update gravity using accelerometer
[UIAccelerometer sharedAccelerometer].delegate = self;
[UIAccelerometer sharedAccelerometer].updateInterval = 1/60.0;
}
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
//update gravity
cpSpaceSetGravity(self.space, cpv(acceleration.y * GRAVITY, -acceleration.x * GRAVITY));
}
*/
模擬時(shí)間以及固定的時(shí)間步長(zhǎng)
//通過(guò)每次CADisplayLink的啟動(dòng)來(lái)通知屏幕將要刷新,然后記錄下當(dāng)前的CACurrentMediaTime()。
/*
#define SIMULATION_STEP (1/120.0)
- (void)step:(CADisplayLink *)timer
{
//calculate frame step duration
CFTimeInterval frameTime = CACurrentMediaTime();
//update simulation
while (lastStep < frameTime) {
cpSpaceStep(space, SIMULATION_STEP);
lastStep += SIMULATION_STEP;
}
//update all the shapes
cpSpaceEachShape(space, &updateShape, NULL);
}
*/
避免死亡螺旋
//如果幀刷新的時(shí)間延遲的話會(huì)變得很糟糕,我們的模擬需要執(zhí)行更多的次數(shù)來(lái)同步真實(shí)的時(shí)間。這些額外的步驟就會(huì)繼續(xù)延遲幀的更新,等等。這就是所謂的死亡螺旋,因?yàn)樽詈蟮慕Y(jié)果就是幀率變得越來(lái)越慢,直到最后應(yīng)用程序卡死了。
//只要保證你給容錯(cuò)留下足夠的邊長(zhǎng),然后在期望支持的最慢的設(shè)備上進(jìn)行測(cè)試就可以了。如果物理計(jì)算超過(guò)了模擬時(shí)間的50%,就需要考慮增加模擬時(shí)間步長(zhǎng)(或者簡(jiǎn)化場(chǎng)景)。如果模擬時(shí)間步長(zhǎng)增加到超過(guò)1/60秒(一個(gè)完整的屏幕更新時(shí)間),你就需要減少動(dòng)畫幀率到一秒30幀或者增加CADisplayLink的frameInterval來(lái)保證不會(huì)隨機(jī)丟幀,不然你的動(dòng)畫將會(huì)看起來(lái)不平滑。