您的位置:首页 > 其它

如何让精灵在不同的帧率下运动速度不变--Frame Rate Independent Movement

2006-05-22 00:45 323 查看
本文性质:翻译加原创,转载请保留原作者和译者信息!

行文安排:前不份是翻译,后部分是自己的原创。

FrameRateIndependentMovement
byBenDilts
翻译KevinLynx2006-5-21

第一部分:

Ihaveseencountlesspostsonthisandothermessageboards,andevenpersonalemailhasbeensenttome,allaskingthesamequestion:HowdoImakeitsomystupidobjectsmoveatthesamespeediftheframeraterisesordrops?
在各种论坛以及我收到的个人邮件里,很多人都问我相同的一个问题:我是怎样让我游戏里那些物体以相同速度运动,而无论帧率是下降还是上升?
Afterafewmonthsprogramming,IdevisedwhatIbelievetobethemostefficientandusefulwaytohandlethisproblem.Forthepurposesofthisarticle,Iwillencapsulatethisfunctionalityinitsownclass.

在几个月的编程以后,我设计了一个我认为最有效果且最有用的方法来解决这个问题。为了在这篇文章里达到这个目的,我把那些有作用的东西封装成一个类:
classframerate

{

public:

floattargetfps;

floatfps;

LARGE_INTEGERtickspersecond;

LARGE_INTEGERcurrentticks;

LARGE_INTEGERframedelay;


floatspeedfactor;


voidInit(floattfps);

voidSetSpeedFactor();

};

Ileaveallthememberspublicbecausetheyworkindependentlyofeachotherandareallusefulinsomewaytotheoutsideprogram.。
我把每一个成员都设置成public的,是因为他们都是独立工作并且对于外部程序,他们都是有用的。(和外部环境联系比较大,所以就直接设置成public的)
Hereistheimplementation,followedbysomeexplanation:
以下是执行代码和一些解释:
voidframerate::Init(floattfps)

{

targetfps=tfps;

QueryPerformanceCounter(&framedelay);

QueryPerformanceFrequency(&tickspersecond);

}

voidframerate::SetSpeedFactor()

{

QueryPerformanceCounter(¤tticks);

//Thisframe'slengthoutofdesiredlength

speedfactor=(float)(currentticks.QuadPart-framedelay.QuadPart)/((float)tickspersecond.QuadPart/targetfps);

fps=targetfps/speedfactor;

if(speedfactor<=0)

speedfactor=1;


framedelay=currentticks;

}


Someexplanation:
targetfpsispassedintotheInitfunction,butcanbesetatanytime.Itisthetargetframeratefortheprogram.ThisisusedintheSetSpeedFactorfunctiontodeterminewhatthespeedfactorwillbe.Lookdownatmyexplanationofspeedfactor.

一些解释:
targetfps是由Init函数来设置的,但是它也可以在任何时候被设置。它用来表示程序中物体的帧率。这个被用在SetSpeedFactor函数里去决定speedfactor会怎么样。看下面对于speedfactor的解释。

fpsistheactualframerateoftheprogram.Itisnotreallyneededtomakeframerateindependentmovement,butsinceitissocloselylinked,Iincludeitanyway.
fps用来表示程序真实的帧率。它没有必要去让帧率成为独立的运动,但是很接近。(不明白这句话的意思)

tickspersecondissetintheInitfunctiontobethenumberofticksthehighperformancetimerhaspersecond.

currentticksissetintheSetSpeedFactorfunctiontobethecurrenthighperformancetimerticks.

framedelayisthepreviousframecurrentticks.

tickspersecond在Init函数里被设置成每秒的tick数。
currentticks在SetSpeedFactor被设置成当前时钟ticks.
framedelay是前一帧的ticks。

speedfactoristheheartofthisclass.WhenitissetisSetSpeedFactor,itbecomesanumberthatyoumultiplyallyourmotionby.Forinstance,ifthetargetfpsis100,andtheactualfpsis85,thespeedfactorwillbesetto100/85,orabout1.2.Youthenmultiplyallyourmotionibthegame,atitslowestlevel,bythisnumber.

speedfactor是这个类的核心。当它在SetSpeedFactor中被设置时,它成为一个****的数。举个例子,如果物体的FPS是100,而真实的FPS是85,speedfactor就会被设置为100/85,或者1.2。在游戏里你把所有的动作与这个数相乘,。。。。
Forinstance,ratherthansimplycodingspaceship->MoveForward(5),Iwouldcodespaceship->MoveForward(5*framerate.speedfactor).

例如,比起简单地spaceship->MoveForeward(5),我会这样写spaceship->MoveForeward(5*frame.speedfactor);(在张勇的超级马力里有类似的方法)

Inconclusion:
Thissimpleroutinesavesawholeheapoftrouble.Justplugitintoalmostanygameorotherreal-timeprogram.CallInitatthebeginningoftheprogram,andtheneachframecallSetSpeedFactor.Thenmultiplyallyourmovementbythespeedfactor.Itisthatsimple.Nowstopfloodingthosemessageboards:)

这个简单的事例解决了很多问题,你可以在游戏里或者任何实时程序里。在程序开始时调用Init,然后在程序的每一帧里调用SetSpeedFactor。然后把所有物体的移动都乘上speedfactor。这确实很简单,以后就别来发消息给我了:)(。)

第二部分:

译者的理解:
其实以上讲的东西,在我以前看的一分超级马力的游戏里用的技术是一样的。我们不限制图象渲染的帧率,只限制精灵逻辑更新的帧率,或者说,在这里不是限制其更新间隔,而是限制其更新的属性大小------坐标改变的大小。

现在开始推导:

假设我们想把我们的游戏的FPS限制在85,85即是我们想要的帧率,这里我以want_fps表示;但是游戏真正的FPS呢?真正的FPS会随着机器性能的高低而明显不同,我用actual_fps表示;精灵sprite每次坐标增加量用coor_add表示。
那么,当actual_fps==want_fps时,sprite_coor+=coor_add;这是很理想的状态;当actual_fps<want_fps时,这个时候的表现就是,精灵比我要求动作的速度慢了----因为逻辑更新的间隔变大了,这个时候要想精灵的速度不变(或者变化很小),那么,我们就要加大coor_add,这里,coor_add显然是和actual_fps和want_fps有关系的。
于是我们可以这样做:sprite_coor+=coor_add*(want_fps/actual_fps);因为现在actual_fps<want_fps,所以其比值就大于1,这样就可以增大这个坐标增加量。
同理,当actual_fps>want_fps时,want_fps./actual_fps就小于1,这样就缩小了坐标增加量。
于是,这样就达到了在不同真实帧率下,依然可以让精灵的移动恒定。

我们所要做的,也就是得到want_fps/actual_fps的比值,也就是上文中的speedfactor,
要计算这个值,我们有两种方法:
1.按照我们以往计算FPS的方式算出实际的FPS,然后再根据希望的FPS来得到
2.这也是更为通用的方法,也是上文的方法,即通过时间间隔直接计算,反过来根据这个值来计算实际的FPS。

第一种方法很简单,而用第二种方法时,如何去直接计算呢?我们可以反向推出其计算方法----当然上文已经直接给出了计算方法

设欲求比值为speedfactor,要求达到的FPS为want_fps,实际的FPS为actual_fps,游戏主循环上次得到的时间TICK为last_tick,本次的TICK为this_tick-(其实这里可以理解为毫秒时间)。
于是:
因为每循环一次,就更新一帧,于是更新一帧用的毫秒数为:(this_tick–last_tick)=è
更新一帧用时:(this_tick–last_tick)===è于是:
actual_fps=1/[(this_tick–last_tick)/1000]
=1000/(this_tick–last_tick)====è
因为want_fps已知,根据:
speedfactor=want_fps/actual_fps===è
speedfactor=want_fps/actual_fps
=want_fps/[1000/(this_tick–last_tick)]
=want_fps*(this_tick–last_tick)/1000
=want_fps*每一帧用的秒数

最终公式也就是:
speedfactor=want_fps*(this_tick–last_tick)/1000

与上文提供的公式对比,完全一致。

OK,本文到此结束。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: