青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

posts - 23,  comments - 94,  trackbacks - 0

原文地址

http://dewitters.koonsolo.com/gameloop.html

游戲主循環

引言

游戲主循環是每個游戲的心跳,輸送著整個游戲需要的養分。不幸的是沒有任何一篇好的文章來指導一個菜鳥游戲程序員如何為自己的程序供養。不過不用擔心,因為你剛好不小心看到了這篇,也是唯一一篇給予這個話題足夠重視的文章。

由于我身為游戲程序員,我見過許許多多的手機小游戲的代碼。這些代碼給我展示了五彩繽紛的游戲主循環實現方法。你可能要問:“這么簡單的一個小玩意還能做到千奇百怪?” 事實就是這樣,我就會在此文中討論一些主流實現的優缺點,并且給你介紹在我看來最好的輸送養分的解決方案。


游戲主循環


每一個游戲都是由獲得用戶輸入,更新游戲狀態,處理AI,播放音樂和音效,還有畫面顯示這些行為組成。游戲主循環就是用來處理這個行為序列。如我在引言中所說,游戲主循環是每一個游戲的心跳。在此文中我不會深入講解上面提到的任何一個行為,而只詳細介紹游戲主循環。所以我把這些行為簡化為了兩個函數:

 update_game();  //更新游戲狀態 (后文可能翻譯為邏輯幀)
 display_game(); //更新顯示     (顯示幀)

下面是最簡單的游戲主循環:

    bool game_is_running = true;

    while( game_is_running ) {
        update_game();
        display_game();
    }
 
這個簡單循環的主要問題是它忽略了時間,游戲會盡情的飛奔。在小霸王機器上運行會使玩家有極強的挫敗感,在牛逼的機器上運行則會要求玩家有超人的判斷力和APM(原意為慢的機器上運行慢,快的機器上運行快……)。在遠古時代,硬件的速度已知的情況下,這不算什么,但是目前有如此多的硬件平臺使得我們不得不去處理時間這個重要因素。對于時間的處理有很多的方法,接下來我會一一奉上。

首先我會解釋兩個貫穿全文的術語:

每秒幀數(后簡稱FPS)

FPS是Frames Per Second的縮寫。在此文的上下文中它意味著display_game()每秒被調用的次數。

游戲速度

游戲速度是每秒更新游戲狀態的速度,換言之,即update_game()每秒被調用的次數。

FPS依賴于恒定的游戲速度

實現

一個讓游戲每秒穩定運行在25幀的解決方案如下:

    const int FRAMES_PER_SECOND = 25;
    const int SKIP_TICKS = 1000 / FRAMES_PER_SECOND;

    DWORD next_game_tick = GetTickCount();
    // GetTickCount() returns the current number of milliseconds
    // that have elapsed since the system was started

    int sleep_time = 0;

    bool game_is_running = true;

    while( game_is_running ) {
        update_game();
        display_game();

        next_game_tick += SKIP_TICKS;
        sleep_time = next_game_tick - GetTickCount();
        if( sleep_time >= 0 ) {
            Sleep( sleep_time );
        }
        else {
            // Shit, we are running behind!
        }
    }
 
這個方案有一個非常大的優點:簡單!因為你知道update_game()每秒被調用25次,那么你的游戲的邏輯部分代碼編寫將非常直白。比如說在這種主循環實現的游戲中實現一個 重放 函數將非常簡單(譯者注:因為每幀的間隔時間已知,只需要記錄每一幀游戲的狀態,回放時按照恒定的速度播放即可。就像電影膠片一樣)。如果在游戲中沒有受到隨機值的影響,只需要記錄玩家的輸入就可以實現重放。

在你實現這個循環的硬件上你可以按需要調整FRAMES_PER_SECOND到一個理想的值,但是這個游戲主循環實現會在各種硬件上表現得怎么樣呢?

小霸王機

如果硬件可以應付指定的FPS,那么不會有什么事情發生。但是小霸王通常是應付不了的,游戲就會卡。在極端情況下就會卡得掉渣,或者一步十卡、一卡十步(原意為某些情況下游戲速度很慢,有一些情況下又比較正常)。這樣的問題會毀掉你的游戲,使得玩家及其挫敗。

牛逼的機器

在牛逼的機器上似乎不會有任何問題,但是這樣的游戲主循環浪費大量的時鐘循環!牛逼的機器運行這個游戲可以輕松的跑到300幀,卻每秒只運行了25或者30幀~ 那么這個主循環實現會讓擁有牛逼硬件的玩家無法盡情發揮其硬件效果產生極大的挫敗感(原意為這樣的實現會讓你的視覺效果受到影響,尤其是高速移動物體)。

從另外一個角度來說,在移動設備上,這一點可能會是一個優點。游戲持續的高速運行會很快地消耗電池……

結論

基于恒定游戲速度的FPS的主循環實現方案簡單易學。但是存在一些問題,比如定義的FPS太高會使得老爺機不堪重負,定義的FPS太低則會使得高端硬件損失太多視覺效果。

基于可變FPS的游戲速度

實現

另外一種游戲實現可以讓游戲盡可能的飛奔,并且讓依據FPS來決定游戲速度。游戲狀態會根據每一顯示幀消耗的時間來進行更新。

    DWORD prev_frame_tick;
    DWORD curr_frame_tick = GetTickCount();

    bool game_is_running = true;
    while( game_is_running ) {
        prev_frame_tick = curr_frame_tick;
        curr_frame_tick = GetTickCount();

        update_game( curr_frame_tick - prev_frame_tick );
        display_game();
    }
 
這個游戲主循環的代碼比起之前稍微復雜一些,因為我們必須去考慮兩次update_game()調用之間的時間差。不過,好在這并不算復雜。
初窺這個實現的代碼好像是一個理想的實現方案。我已經見過許多聰明的游戲程序員用這種方式來書寫游戲主循環。但是我會給你展示這個實現方案在小霸王和牛逼的機器上的嚴重問題!是的,包括非常職業非常嫻熟非常牛逼的玩家的機器。

小霸王

小霸王會在某些運算復雜的地方出現卡的情況,尤其在3D游戲中的復雜場景更是如此。幀率的降低會影響游戲輸入的響應,同時降低玩家的反應速度。游戲狀態更新也會因此突然受到影響。這樣的情況會使得玩家和AI的反應速度減慢,造成玩家挫敗感加劇。比如一個在正常幀率下可以輕松越過的障礙會在低幀率下無法逾越。更嚴重的問題是在小霸王上會經常發生一些違反物理規律的怪事,如果這些運算涉及到物理模擬的話。

牛逼的機器

你可能會好奇,為什么剛才的游戲循環在飛快的機器上會出現問題。不幸的是,這個方案的確如此,首先,讓我給你介紹一些計算機數學知識。

浮點數類型占用內存大小是有限的,那么有一些數值就無法被呈現。比如0.1就不能用2進制表示,所以會被近似的存儲在一個浮點數中。我用python給你們展示一下。

>>> 0.1
0.10000000000000001

這個問題本身并不怎么具有戲劇性,但是這樣的后果卻截然相反。比方說你的賽車的速度是0.001個單元每微秒。那么正確的結果是在10秒后你的賽車會移動10個單位,那么我們這樣來實現一下:

>>> def get_distance( fps ):
...     skip_ticks = 1000 / fps
...     total_ticks = 0
...     distance = 0.0
...     speed_per_tick = 0.001
...     while total_ticks < 10000:
...             distance += speed_per_tick * skip_ticks
...             total_ticks += skip_ticks
...     return distance


現在我們來得到40幀每秒時運行10秒后的結果
>>> get_distance( 40 )
10.000000000000075

等等~怎么不是10呢?發生了什么?嗯,400次加法后的誤差就有這么大,每秒運行100次加法后又會是怎么一個樣子呢?

>>> get_distance( 100 )
9.9999999999998312

誤差越來越大了!那么,40幀每秒的結果和100幀每秒之間誤差差距是多大呢?

>>> get_distance( 40 ) - get_distance( 100 )
2.4336088699783431e-13

你可能會想這樣的誤差可以忽略。但是真正的問題出現在你使用這些錯誤的值去進行更多的運算。小的誤差會被擴大為致命的錯誤!然后這些錯誤會在游戲飛奔的同時毀掉它!這些問題發生的幾率絕對大到足夠引起你的注意。我有見過因為這個原因在高幀率出現問題得游戲。之后那個游戲程序員發現這些問題出現在游戲的核心部分,只有重寫大部分代碼才能修復它。

結論

這樣的游戲主循環看上起不錯,但是并不怎么樣。不管運行它的硬件怎樣,都可能出現嚴重的問題。另外,游戲實現的代碼相對于固定游戲速度的主循環而言更加復雜,那你還有什么使用它的理由呢?

最大FPS和恒定游戲速度

實現

我們的第一個實現中,FPS依賴于恒定的游戲速度,在低端的機器上會出現問題。游戲速度和游戲顯示都會出現掉幀。一個可行的解決方案是犧牲顯示幀率的來保持恒定的游戲速度。下面就實現了這種方案:

   
    const int TICKS_PER_SECOND = 50;
    const int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
    const int MAX_FRAMESKIP = 10;
       
    DWORD next_game_tick = GetTickCount();
    int loops;
   
    bool game_is_running = true;
    while( game_is_running ) {

        loops = 0;
        while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP) {
            update_game();
           
            next_game_tick += SKIP_TICKS;
            loops++;
        }
       
        display_game();
    }

游戲會以穩定的50(邏輯)幀每秒的速度更新,渲染速度也盡可能的快。需要注意的是,如果渲染速度超過了50幀每秒的話,有一些幀的畫面將會是完全相同的。所以顯示幀率實際上也等同于最快每秒50幀。在小霸王上運行的話,顯示幀率會在更新游戲狀態循環達到MAX_FRAMESKIP時下降。從上面這個例子來說就是當渲染幀率下降到5(FRAMES_PER_SECOND / MAX_FRAMESKIP)以下時,游戲速度會變慢。

小霸王

在小霸王上運行這樣的游戲循環會出現掉幀,但是游戲速度不受到影響。如果硬件還是沒有辦法處理這樣的循環,那么游戲速度和游戲幀率都會受到影響。

牛逼的機器

在牛逼的機器上這個游戲循環不會出現問題,但是如同第一個解決方案一樣,還是浪費了太多的時鐘周期。找到一個快速更新并且依然能夠在小霸王上運行游戲的平衡點是至關重要的!

結論

使用上面的這個方案可以使游戲的實現代碼比較簡單。但是仍然有一些問題:如果定義了一個過高的FPS會讓小霸王吃不消,如果過低則會讓牛逼的機器難以發揮性能。


獨立的可變顯示幀率和恒定的游戲速度

實現

有沒有可能對之前的那種方案進行優化使得它在各種平臺上都有足夠好的表現呢?當然是有的!游戲狀態本身并不需要每秒更新60次。玩家輸入,AI信息等都不需要如此高的幀率來進行更新,大約每秒25次就足夠了。所以,我們可以試著讓update_game()每秒不多不少的被調用25次。渲染則放任不管,讓其飛奔。但是不能讓小霸王的低渲染幀率影響到游戲狀態更新的速度。下面就是這個方案的實現:

    const int TICKS_PER_SECOND = 25;
    const int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
    const int MAX_FRAMESKIP = 5;

    DWORD next_game_tick = GetTickCount();
    int loops;
    float interpolation;

    bool game_is_running = true;
    while( game_is_running ) {

        loops = 0;
        while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP) {
            update_game();

            next_game_tick += SKIP_TICKS;
            loops++;
        }

        interpolation = float( GetTickCount() + SKIP_TICKS - next_game_tick )
                        / float( SKIP_TICKS );
        display_game( interpolation );
    }

使用這種方案的update_game()實現會比較簡單,相對而言,display_game()則會變得稍許復雜。你需要實現一個接收插值參數的預言函數,這并不是什么難事,只是需要做一些額外的工作。我會接著解釋這個預言函數是如何工作的,不過首先讓我告訴你為什么需要這樣的一個函數。

游戲狀態每秒被更新25次,如果你渲染的時候不使用插值計算,渲染幀率就會被限定在25幀。需要注意的是,25幀并沒有人們想象中的糟糕,電影畫面在每秒24幀的情況下依然流暢。所以25幀可以很好的展示游戲畫面,不過對于高速移動的物體,更高的幀率會帶來更好的效果。所以我們要做的是,在顯示幀之間讓高速移動的物體平滑過度。這就是我們需要一個插值和預言函數的原因。

插值和預言函數

如我之前所說,游戲狀態更新在一個恒定的幀率下運行著,當你渲染畫面的時刻,很有可能就在兩個邏輯幀之間。假設你已經第10次更新了你的游戲狀態,現在你需要渲染你的場景。這次渲染就會出現在第10次和第11次邏輯幀之間。很有可能出現在第10.3幀的位置。那么插值的值就是0.3。舉個例子說,我的一輛賽車以下面的方式計算位置。

    position = position + speed;

如果第10次邏輯幀后賽車的位置是500,速度是100,那么第11幀的位置就會是600. 那么在10.3幀的時候你會在什么位置渲染你的賽車呢?顯而易見,應該像下面這樣:

    view_position = position + (speed * interpolation)

現在,賽車將會被正確地渲染在530這個位置。

基本上,插值的值就是渲染發生在前一幀和后一幀中的位置。你需要做的就是寫出預言函數來預計你的賽車/攝像機或者其他物件在渲染時刻的正確位置。你可以根據物件的速度來計算預計的位置。這些并不復雜。對于某些預計后的幀中出現的錯誤現象,如某個物體被渲染到了某個物體之中的情況的確會出現。由于游戲速度恒定在每秒更新25次狀態,那么這種錯誤停留在畫面上的時間極短,難以發現,并無大礙。

小霸王

大多數情況下,update_game()執行需要的時間比display_game()少得多。實際上,我們可以假設在小霸王上update_game()每秒還是能運行25次。所以游戲的邏輯狀態不會受到太大的影響,即使FPS非常低。

牛逼的機器

在牛逼的硬件上,游戲速度會保持每秒25次,屏幕更新卻可以非常快。插值的方案可以讓游戲在高幀率中有更好的畫面表現。但實質上游戲的狀態每秒只更新了25次。

結論

使游戲狀態的更新獨立于FPS的解決方案似乎是最好的游戲主循環實現。不過,你必須實現一個插值計算函數。

整體總結

游戲主循環對游戲的影響遠遠超乎你的想象。我們討論了4個可能的實現方法,其中有一個方案是要堅決避免的,那就是可變幀率來決定游戲速度的方案。

一個恒定的幀率對移動設備而言可能是一個很好的實現,如果你想展示你的硬件全部的實力,那么最好使用FPS獨立于游戲速度的實現方案。

如果你不想麻煩的實現一個預言函數,那么可以使用最大幀率的實現方案,只是要找到一個幀率大小的平衡點。

現在,你可以著手編寫你夢寐以求的游戲了!

Koen Witters

posted on 2009-08-25 21:59 Charlie 侯杰 閱讀(4712) 評論(9)  編輯 收藏 引用
by Charlie
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            久久久亚洲高清| 一区二区动漫| 欧美高清视频| 免费中文字幕日韩欧美| 亚洲一区二区三区视频播放| 亚洲视频日本| 欧美一级黄色录像| 久久久久久久97| 久久婷婷久久| 亚洲国内自拍| 亚洲免费精品| 午夜精品在线| 久久噜噜噜精品国产亚洲综合| 欧美www在线| 国产精品久久久久久久久久免费 | 久久综合给合久久狠狠色 | 欧美成人一区二区| 亚洲国产精品专区久久| 亚洲片在线资源| 亚洲综合视频一区| 欧美.www| 激情成人在线视频| 亚洲淫性视频| 欧美激情一区二区三区四区| 亚洲女性喷水在线观看一区| 老司机精品视频网站| 国产精品美腿一区在线看 | 亚洲每日在线| 久久久久久黄| 亚洲一区二区三区在线视频| 欧美肥婆在线| 在线精品国产欧美| 欧美中文字幕第一页| 亚洲韩国日本中文字幕| 久久久久成人网| 国产婷婷色一区二区三区| 亚洲网友自拍| 亚洲精品午夜| 欧美电影打屁股sp| 亚洲第一区在线| 久久黄金**| 亚洲自拍电影| 国产精品久久二区| 亚洲一区二区三区在线播放| 91久久综合亚洲鲁鲁五月天| 麻豆精品视频在线| 亚洲成色777777女色窝| 久久久久久久激情视频| 亚洲一区二区3| 国产精品每日更新| 亚洲欧美日韩另类精品一区二区三区| 亚洲国产日韩在线一区模特| 美女主播一区| 亚洲日本精品国产第一区| 欧美国产日韩一区二区三区| 久久免费视频在线观看| **性色生活片久久毛片| 999在线观看精品免费不卡网站| 久久久亚洲欧洲日产国码αv | 欧美日韩大片| 99精品视频免费全部在线| 亚洲国产欧美一区| 欧美精品性视频| 一区二区日韩| 亚洲色无码播放| 国产精品亚洲欧美| 久久高清福利视频| 久久久亚洲国产天美传媒修理工| 黄色成人在线观看| 欧美国产一区二区| 欧美日韩高清在线观看| 亚洲欧美日韩精品久久久久| 性做久久久久久久久| 精品不卡一区| 亚洲人成亚洲人成在线观看| 欧美日韩亚洲一区在线观看| 亚洲免费视频观看| 久久成人亚洲| 亚洲免费观看在线视频| 中文精品视频| 国内一区二区三区| 亚洲国产一区二区a毛片| 欧美三级第一页| 久久精品欧美日韩精品| 欧美91视频| 亚洲欧美综合| 麻豆精品在线视频| 亚洲综合精品四区| 久久久综合视频| 亚洲欧美春色| 久久综合亚洲社区| 亚洲一二三区精品| 久久久亚洲精品一区二区三区 | 一区二区三区欧美亚洲| 亚洲免费视频在线观看| 伊人久久大香线蕉av超碰演员| 亚洲激情六月丁香| 国产日韩欧美电影在线观看| 亚洲第一天堂av| 国产欧美大片| 亚洲精品乱码久久久久久黑人| 国产精品一二一区| 亚洲黄色影院| 极品少妇一区二区| 亚洲尤物在线视频观看| 亚洲另类春色国产| 久久精品青青大伊人av| 亚洲图片欧美日产| 欧美成人情趣视频| 久久只有精品| 国产亚洲精品久久久久动| 日韩午夜黄色| 亚洲国产日韩综合一区| 欧美一区二区三区播放老司机| 亚洲视频碰碰| 欧美激情五月| 欧美激情在线播放| 久热精品视频在线观看| 亚洲欧美综合精品久久成人 | 亚洲淫性视频| 亚洲麻豆视频| 欧美国产在线观看| 欧美成年人网| 激情自拍一区| 久久成人18免费网站| 欧美在线观看视频在线| 国产精品久久久久久久9999| a4yy欧美一区二区三区| 中文国产成人精品久久一| 欧美经典一区二区| 亚洲精品久久| 一本大道久久a久久精品综合| 狂野欧美性猛交xxxx巴西| 免费成人av| 亚洲国产清纯| 久久人人爽人人爽| 欧美国产极速在线| 亚洲欧洲一区二区三区| 欧美成人自拍| 亚洲精品国产拍免费91在线| 99精品欧美一区二区三区| 欧美日产一区二区三区在线观看| 亚洲精品欧美日韩专区| 亚洲午夜精品| 国产欧美精品一区aⅴ影院| 小黄鸭精品aⅴ导航网站入口| 久久精品1区| 亚洲国产成人精品视频| 欧美国产精品va在线观看| 99精品欧美| 久久精品成人欧美大片古装| 一区二区三区在线免费视频 | 好看的av在线不卡观看| 久久免费的精品国产v∧| 欧美激情视频免费观看| 妖精视频成人观看www| 国产精品久久久久秋霞鲁丝| 性做久久久久久久免费看| 免费影视亚洲| 亚洲天堂免费观看| 国产日韩亚洲欧美综合| 老司机67194精品线观看| 亚洲老司机av| 久久久亚洲一区| 亚洲最新在线| 狠狠色丁香久久婷婷综合_中| 免费成年人欧美视频| 日韩一二三区视频| 久久亚洲精品欧美| 一本色道久久综合亚洲精品不| 国产精品日日摸夜夜摸av| 久久青青草原一区二区| 亚洲视频一区二区免费在线观看| 久久免费精品日本久久中文字幕| 亚洲精品日韩激情在线电影| 国产欧美高清| 欧美日韩系列| 玖玖综合伊人| 香蕉久久夜色精品国产| 日韩系列在线| 欧美激情精品久久久久久久变态 | 欧美视频福利| 尤物九九久久国产精品的特点| 久久综合国产精品台湾中文娱乐网| 亚洲日本视频| 国内成人精品视频| 国产精品久久久久久久久久免费| 免费精品视频| 国产乱码精品1区2区3区| 欧美在线视频观看免费网站| 亚洲最新视频在线| 亚洲人成在线播放| 狠狠入ady亚洲精品| 国产欧美日韩视频在线观看| 欧美日韩国产综合视频在线观看中文 | 亚洲国产精品99久久久久久久久| 国产精品影片在线观看| 欧美成人情趣视频| 一区二区三区毛片| 在线观看亚洲精品|