国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

11.2 物理模擬

2018-02-24 15:07 更新

物理模擬

即使使用了基于定時(shí)器的動(dòng)畫來復(fù)制第10章中關(guān)鍵幀的行為,但還是會(huì)有一些本質(zhì)上的區(qū)別:在關(guān)鍵幀的實(shí)現(xiàn)中,我們提前計(jì)算了所有幀,但是在新的解決方案中,我們實(shí)際上實(shí)在按需要在計(jì)算。意義在于我們可以根據(jù)用戶輸入實(shí)時(shí)修改動(dòng)畫的邏輯,或者和別的實(shí)時(shí)動(dòng)畫系統(tǒng)例如物理引擎進(jìn)行整合。

Chipmunk

我們來基于物理學(xué)創(chuàng)建一個(gè)真實(shí)的重力模擬效果來取代當(dāng)前基于緩沖的彈性動(dòng)畫,但即使模擬2D的物理效果就已近極其復(fù)雜了,所以就不要嘗試去實(shí)現(xiàn)它了,直接用開源的物理引擎庫(kù)好了。

我們將要使用的物理引擎叫做Chipmunk。另外的2D物理引擎也同樣可以(例如Box2D),但是Chipmunk使用純C寫的,而不是C++,好處在于更容易和Objective-C項(xiàng)目整合。Chipmunk有很多版本,包括一個(gè)和Objective-C綁定的“indie”版本。C語(yǔ)言的版本是免費(fèi)的,所以我們就用它好了。在本書寫作的時(shí)候6.1.4是最新的版本;你可以從http://chipmunk-physics.net下載它。

Chipmunk完整的物理引擎相當(dāng)巨大復(fù)雜,但是我們只會(huì)使用如下幾個(gè)類:

  • cpSpace?- 這是所有的物理結(jié)構(gòu)體的容器。它有一個(gè)大小和一個(gè)可選的重力矢量
  • cpBody?- 它是一個(gè)固態(tài)無彈力的剛體。它有一個(gè)坐標(biāo),以及其他物理屬性,例如質(zhì)量,運(yùn)動(dòng)和摩擦系數(shù)等等。
  • cpShape?- 它是一個(gè)抽象的幾何形狀,用來檢測(cè)碰撞??梢越o結(jié)構(gòu)體添加一個(gè)多邊形,而且cpShape有各種子類來代表不同形狀的類型。

在例子中,我們來對(duì)一個(gè)木箱建模,然后在重力的影響下下落。我們來創(chuàng)建一個(gè)Crate類,包含屏幕上的可視效果(一個(gè)UIImageView)和一個(gè)物理模型(一個(gè)cpBody和一個(gè)cpPolyShape,一個(gè)cpShape的多邊形子類來代表矩形木箱)。

用C版本的Chipmunk會(huì)帶來一些挑戰(zhàn),因?yàn)樗F(xiàn)在并不支持Objective-C的引用計(jì)數(shù)模型,所以我們需要準(zhǔn)確的創(chuàng)建和釋放對(duì)象。為了簡(jiǎn)化,我們把cpShapecpBody的生命周期和Crate類進(jìn)行綁定,然后在木箱的-init方法中創(chuàng)建,在-dealloc中釋放。木箱物理屬性的配置很復(fù)雜,所以閱讀了Chipmunk文檔會(huì)很有意義。

視圖控制器用來管理cpSpace,還有和之前一樣的計(jì)時(shí)器邏輯。在每一步中,我們更新cpSpace(用來進(jìn)行物理計(jì)算和所有結(jié)構(gòu)體的重新擺放)然后迭代對(duì)象,然后再更新我們的木箱視圖的位置來匹配木箱的模型(在這里,實(shí)際上只有一個(gè)結(jié)構(gòu)體,但是之后我們將要添加更多)。

Chipmunk使用了一個(gè)和UIKit顛倒的坐標(biāo)系(Y軸向上為正方向)。為了使得物理模型和視圖之間的同步更簡(jiǎn)單,我們需要通過使用geometryFlipped屬性翻轉(zhuǎn)容器視圖的集合坐標(biāo)(第3章中有提到),于是模型和視圖都共享一個(gè)相同的坐標(biāo)系。

具體的代碼見清單11.3。注意到我們并沒有在任何地方釋放cpSpace對(duì)象。在這個(gè)例子中,內(nèi)存空間將會(huì)在整個(gè)app的生命周期中一直存在,所以這沒有問題。但是在現(xiàn)實(shí)世界的場(chǎng)景中,我們需要像創(chuàng)建木箱結(jié)構(gòu)體和形狀一樣去管理我們的空間,封裝在標(biāo)準(zhǔn)的Cocoa對(duì)象中,然后來管理Chipmunk對(duì)象的生命周期。圖11.1展示了掉落的木箱。

清單11.3 使用物理學(xué)來對(duì)掉落的木箱建模

#import "ViewController.h" 
#import 
#import "chipmunk.h"

@interface Crate : UIImageView

@property (nonatomic, assign) cpBody *body;
@property (nonatomic, assign) cpShape *shape;

@end

@implementation Crate

#define MASS 100

- (id)initWithFrame:(CGRect)frame
{
    if ((self = [super initWithFrame:frame])) {
        //set image
        self.image = [UIImage imageNamed:@"Crate.png"];
        self.contentMode = UIViewContentModeScaleAspectFill;
        //create the body
        self.body = cpBodyNew(MASS, cpMomentForBox(MASS, frame.size.width, frame.size.height));
        //create the shape
        cpVect corners[] = {
            cpv(0, 0),
            cpv(0, frame.size.height),
            cpv(frame.size.width, frame.size.height),
            cpv(frame.size.width, 0),
        };
        self.shape = cpPolyShapeNew(self.body, 4, corners, cpv(-frame.size.width/2, -frame.size.height/2));
        //set shape friction & elasticity
        cpShapeSetFriction(self.shape, 0.5);
        cpShapeSetElasticity(self.shape, 0.8);
        //link the crate to the shape
        //so we can refer to crate from callback later on
        self.shape->data = (__bridge void *)self;
        //set the body position to match view
        cpBodySetPos(self.body, cpv(frame.origin.x + frame.size.width/2, 300 - frame.origin.y - frame.size.height/2));
    }
    return self;
}

- (void)dealloc
{
    //release shape and body
    cpShapeFree(_shape);
    cpBodyFree(_body);
}

@end

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, assign) cpSpace *space;
@property (nonatomic, strong) CADisplayLink *timer;
@property (nonatomic, assign) CFTimeInterval lastStep;

@end

@implementation ViewController

#define GRAVITY 1000

- (void)viewDidLoad
{
    //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 a crate
    Crate *crate = [[Crate alloc] initWithFrame:CGRectMake(100, 0, 100, 100)];
    [self.containerView addSubview:crate];
    cpSpaceAddBody(self.space, crate.body);
    cpSpaceAddShape(self.space, crate.shape);
    //start the timer
    self.lastStep = CACurrentMediaTime();
    self.timer = [CADisplayLink displayLinkWithTarget:self
                                             selector:@selector(step:)];
    [self.timer addToRunLoop:[NSRunLoop mainRunLoop]
                     forMode:NSDefaultRunLoopMode];
}

void updateShape(cpShape *shape, void *unused)
{
    //get the crate object associated with the shape
    Crate *crate = (__bridge Crate *)shape->data;
    //update crate view position and angle to match physics shape
    cpBody *body = shape->body;
    crate.center = cpBodyGetPos(body);
    crate.transform = CGAffineTransformMakeRotation(cpBodyGetAngle(body));
}

- (void)step:(CADisplayLink *)timer
{
    //calculate step duration
    CFTimeInterval thisStep = CACurrentMediaTime();
    CFTimeInterval stepDuration = thisStep - self.lastStep;
    self.lastStep = thisStep;
    //update physics
    cpSpaceStep(self.space, stepDuration);
    //update all the shapes
    cpSpaceEachShape(self.space, &updateShape, NULL);
}

@end

圖11.1 真實(shí)引力場(chǎng)下的木箱交互

模擬時(shí)間以及固定的時(shí)間步長(zhǎng)

對(duì)于實(shí)現(xiàn)動(dòng)畫的緩沖效果來說,計(jì)算每幀持續(xù)的時(shí)間是一個(gè)很好的解決方案,但是對(duì)模擬物理效果并不理想。通過一個(gè)可變的時(shí)間步長(zhǎng)來實(shí)現(xiàn)有著兩個(gè)弊端:

  • 如果時(shí)間步長(zhǎng)不是固定的,精確的值,物理效果的模擬也就隨之不確定。這意味著即使是傳入相同的輸入值,也可能在不同場(chǎng)合下有著不同的效果。有時(shí)候沒多大影響,但是在基于物理引擎的游戲下,玩家就會(huì)由于相同的操作行為導(dǎo)致不同的結(jié)果而感到困惑。同樣也會(huì)讓測(cè)試變得麻煩。

  • 由于性能故常造成的丟幀或者像電話呼入的中斷都可能會(huì)造成不正確的結(jié)果。考慮一個(gè)像子彈那樣快速移動(dòng)物體,每一幀的更新都需要移動(dòng)子彈,檢測(cè)碰撞。如果兩幀之間的時(shí)間加長(zhǎng)了,子彈就會(huì)在這一步移動(dòng)更遠(yuǎn)的距離,穿過圍墻或者是別的障礙,這樣就丟失了碰撞。

我們想得到的理想的效果就是通過固定的時(shí)間步長(zhǎng)來計(jì)算物理效果,但是在屏幕發(fā)生重繪的時(shí)候仍然能夠同步更新視圖(可能會(huì)由于在我們控制范圍之外造成不可預(yù)知的效果)。

幸運(yùn)的是,由于我們的模型(在這個(gè)例子中就是Chipmunk的cpSpace中的cpBody)被視圖(就是屏幕上代表木箱的UIView對(duì)象)分離,于是就很簡(jiǎn)單了。我們只需要根據(jù)屏幕刷新的時(shí)間跟蹤時(shí)間步長(zhǎng),然后根據(jù)每幀去計(jì)算一個(gè)或者多個(gè)模擬出來的效果。

我們可以通過一個(gè)簡(jiǎn)單的循環(huán)來實(shí)現(xiàn)。通過每次CADisplayLink的啟動(dòng)來通知屏幕將要刷新,然后記錄下當(dāng)前的CACurrentMediaTime()。我們需要在一個(gè)小增量中提前重復(fù)物理模擬(這里用120分之一秒)直到趕上顯示的時(shí)間。然后更新我們的視圖,在屏幕刷新的時(shí)候匹配當(dāng)前物理結(jié)構(gòu)體的顯示位置。

清單11.5展示了固定時(shí)間步長(zhǎng)版本的代碼

清單11.5 固定時(shí)間步長(zhǎng)的木箱模擬

#define SIMULATION_STEP (1/120.0)

- (void)step:(CADisplayLink *)timer
{
    //calculate frame step duration
    CFTimeInterval frameTime = CACurrentMediaTime();
    //update simulation
    while (self.lastStep < frameTime) {
        cpSpaceStep(self.space, SIMULATION_STEP);
        self.lastStep += SIMULATION_STEP;
    }
    ?
    //update all the shapes
    cpSpaceEachShape(self.space, &updateShape, NULL);
}

避免死亡螺旋

當(dāng)使用固定的模擬時(shí)間步長(zhǎng)時(shí)候,有一件事情一定要注意,就是用來計(jì)算物理效果的現(xiàn)實(shí)世界的時(shí)間并不會(huì)加速模擬時(shí)間步長(zhǎng)。在我們的例子中,我們隨意選擇了120分之一秒來模擬物理效果。Chipmunk很快,我們的例子也很簡(jiǎn)單,所以cpSpaceStep()會(huì)完成的很好,不會(huì)延遲幀的更新。

但是如果場(chǎng)景很復(fù)雜,比如有上百個(gè)物體之間的交互,物理計(jì)算就會(huì)很復(fù)雜,cpSpaceStep()的計(jì)算也可能會(huì)超出1/120秒。我們沒有測(cè)量出物理步長(zhǎng)的時(shí)間,因?yàn)槲覀兗僭O(shè)了相對(duì)于幀刷新來說并不重要,但是如果模擬步長(zhǎng)更久的話,就會(huì)延遲幀率。

如果幀刷新的時(shí)間延遲的話會(huì)變得很糟糕,我們的模擬需要執(zhí)行更多的次數(shù)來同步真實(shí)的時(shí)間。這些額外的步驟就會(huì)繼續(xù)延遲幀的更新,等等。這就是所謂的死亡螺旋,因?yàn)樽詈蟮慕Y(jié)果就是幀率變得越來越慢,直到最后應(yīng)用程序卡死了。

我們可以通過添加一些代碼在設(shè)備上來對(duì)物理步驟計(jì)算真實(shí)世界的時(shí)間,然后自動(dòng)調(diào)整固定時(shí)間步長(zhǎng),但是實(shí)際上它不可行。其實(shí)只要保證你給容錯(cuò)留下足夠的邊長(zhǎng),然后在期望支持的最慢的設(shè)備上進(jìn)行測(cè)試就可以了。如果物理計(jì)算超過了模擬時(shí)間的50%,就需要考慮增加模擬時(shí)間步長(zhǎng)(或者簡(jiǎn)化場(chǎng)景)。如果模擬時(shí)間步長(zhǎng)增加到超過1/60秒(一個(gè)完整的屏幕更新時(shí)間),你就需要減少動(dòng)畫幀率到一秒30幀或者增加CADisplayLinkframeInterval來保證不會(huì)隨機(jī)丟幀,不然你的動(dòng)畫將會(huì)看起來不平滑。

物理模擬

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)