




已閱讀5頁,還剩9頁未讀, 繼續(xù)免費閱讀
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
SDL入門教程(十三):1、多線程,從動畫說起 作者:龍飛1.1:簡單動畫游戲離不開動畫。我們考慮最簡單的情況:將一個角色從一個位置移動到另外一個位置。這個行為表述給電腦就是,將一個surface不斷的blit(),從起始位置的坐標,移動到結(jié)束位置的坐標。移動速度取決于每次blit()的坐標差和blit()的時間間隔(v = ds/dt )。我們來設計一個函數(shù)實現(xiàn)這個簡單的動畫。我們需要的數(shù)據(jù)有:起始坐標(int beginX, int beginY),結(jié)束坐標(int endX, int endY),以及作為SDL基礎(chǔ)的ScreenSurface窗口(const ScreenSurface& screen)。一般的考慮是,將這5個數(shù)據(jù)以參數(shù)的方式傳入函數(shù);但是一種更加通用一點的方式是,將這5種數(shù)據(jù)合并成一個結(jié)構(gòu),這樣函數(shù)的參數(shù)形式會更加的統(tǒng)一,這正是觸發(fā)多線程的函數(shù)所需要的。在SDL中,我們通過函數(shù):SDL_Thread *SDL_CreateThread(int (*fn)(void *), void *data);觸發(fā)多線程,其中所需要的函數(shù)指針形式為:typedef int (*fn)(void*);而void*類型的data就是函數(shù)(*fn)()需要的的數(shù)據(jù)。我們可以將任意的結(jié)構(gòu)體指針,轉(zhuǎn)化為void*,作為這個函數(shù)的第二個參數(shù)需要。因此,我們可以為我們需要的動畫函數(shù)定義一個結(jié)構(gòu)作為傳遞所有數(shù)據(jù)的載體:struct AmnArgint beginX;int beginY;int endX;int endY;const ScreenSurface& screen;AmnArg(int begin_x, int begin_y, int end_x, int end_y, const ScreenSurface& _screen): beginX(begin_x), beginY(begin_y), endX(end_x), endY(end_y), screen(_screen);這樣,我們可以將AmnArg對象的指針傳遞給動畫函數(shù)考慮到多線程函數(shù)的需要,我們再曲折一點:先將AmnArg*轉(zhuǎn)換成void*傳遞給函數(shù),在函數(shù)內(nèi)部再將其轉(zhuǎn)換回來以供調(diào)用。int amn(void* data)AmnArg* pData = (AmnArg*)data;PictureSurface stand(./images/am01.png, pData-screen);stand.colorKey();PictureSurface bg(./images/background.png, pData-screen);const int SPEED_CTRL = 300;int speedX = (pData-endX - pData-beginX) / SPEED_CTRL;int speedY = (pData-endY - pData-beginY) / SPEED_CTRL;for ( int i = 0; i beginX += speedX;pData-beginY += speedY;bg.blit(pData-beginX, pData-beginY, pData-beginX, pData-beginY, stand.point()-w, stand.point()-h, 2, 2);stand.blit(pData-beginX, pData-beginY);pData-screen.flip();return 0;注意:我們這里僅僅設定了每次blit()的位移差(ds)而沒有設定時間差(dt)。這并不意味著dt = 0,事實上,電腦處理數(shù)據(jù)是需要時間的,包括運算和顯示。我們這里事實上將dt的設定交給了電腦,也就是說,讓電腦以其最快的速度來完成。為什么要這么做呢?這是為了演示多線程的一個現(xiàn)象,賣個關(guān)子,后面解釋。:)1.2:動畫函數(shù)在主程序中的調(diào)用#include SurfaceClass.hpp#include amn.hppint main(int argc ,char* argv)/Create a SDL screen.const int SCREEN_WIDTH = 640;const int SCREEN_HEIGHT = 480;const Uint32 SCREEN_FLAGS = 0; /SDL_FULLSCREEN | SDL_DOUBLEBUF | SDL_HWSURFACEconst std:string WINDOW_NAME = Amn Test;ScreenSurface screen(SCREEN_WIDTH, SCREEN_HEIGHT, WINDOW_NAME, 0, SCREEN_FLAGS);PictureSurface bg(./images/background.png, screen);bg.blit(0);screen.flip();AmnArg test1(0, 250, 600, 250, screen);amn(void*)&test1);SDL_Event gameEvent;bool gameOver = false;while ( gameOver = false )while ( SDL_PollEvent(&gameEvent) != 0 )if ( gameEvent.type = SDL_QUIT )gameOver = true;if ( gameEvent.type = SDL_KEYDOWN )if ( gameEvent.key.keysym.sym = SDLK_ESCAPE )gameOver = true;screen.flip();return 0;當這個程序運行的時候,我們會發(fā)現(xiàn)一些很明顯的問題:1、圖片移動的時候,界面不接受任何信息。這是因為必須把amn()執(zhí)行完畢才會運行到有事件響應的事件輪詢循環(huán)。2、如果我們需要另外一張圖片移動起來,我們唯一能做的事情,是修改amn()函數(shù),而不是把amn()以不同的參數(shù)調(diào)用兩次如果以不同的參數(shù)調(diào)用兩次,那么移動總是有先后的是不可能完成“同時”移動的。1.3:創(chuàng)建線程如果要將這個程序從主線程(主進程)調(diào)用函數(shù)修改為通過新創(chuàng)建的線程調(diào)用函數(shù),只需要做很小的修改,即將amn(void*)&test1);修改為:SDL_Thread* thread1 = SDL_CreateThread(amn, (void*)&test1);然后在return 0;之前加入清理線程的語句:SDL_KillThread(thread1);這樣,程序在執(zhí)行動畫的同時,事件輪詢就已經(jīng)開始,我們可以隨時結(jié)束程序,SDL界面也不會出現(xiàn)不響應的情況。SDL入門教程(十三):2、初識多線程 作者:龍飛2.1:競爭條件(Race Conditions)我們在前面將一個普通函數(shù)調(diào)用轉(zhuǎn)換成了用線程調(diào)用,這意味著我們可以“同時”調(diào)用兩個以上的線程。例如,我們希望在屏幕的另外一個位置也播放這段簡單的動畫,我們只需要添加一個線程的調(diào)用就可以了。int main(int argc ,char* argv)/Create a SDL screen.const int SCREEN_WIDTH = 640;const int SCREEN_HEIGHT = 480;const Uint32 SCREEN_FLAGS = 0; /SDL_FULLSCREEN | SDL_DOUBLEBUF | SDL_HWSURFACEconst std:string WINDOW_NAME = Amn Test;ScreenSurface screen(SCREEN_WIDTH, SCREEN_HEIGHT, WINDOW_NAME, 0, SCREEN_FLAGS);PictureSurface bg(./images/background.png, screen);bg.blit(0);screen.flip();AmnArg test1(0, 250, 600, 250, screen);SDL_Thread* thread1 = SDL_CreateThread(amn, (void*)&test1);AmnArg test2(0, 0, 600, 0, screen);SDL_Thread* thread2 = SDL_CreateThread(amn, (void*)&test2);SDL_Event gameEvent;bool gameOver = false;while ( gameOver = false )while ( SDL_PollEvent(&gameEvent) != 0 )if ( gameEvent.type = SDL_QUIT )gameOver = true;if ( gameEvent.type = SDL_KEYDOWN )if ( gameEvent.key.keysym.sym = SDLK_ESCAPE )gameOver = true;screen.flip();SDL_KillThread(thread1);SDL_KillThread(thread2);return 0;這段程序看起來似乎沒有什么問題,但是運行的時候,不可預知的情況出現(xiàn)了:理論上我們幾乎同時調(diào)用了兩個線程,動畫似乎應該是同步播放的,但是實際上,兩段動畫的播放并不同步,并且每次執(zhí)行的效果都不一樣有時候上面的圖片移動快,有時候下面的圖片移動快,并且速度不均勻。這就是典型的race conditions的表現(xiàn)。還記得我說過沒有定義dt嗎,我們讓電腦以其所能達到的最快速度決定dt,換句話說,我們每一個線程都試圖“咬死”CPU的運算,當然,在實際中多任務的OS會幫助CPU分配任務,但是如何分配卻是不確定的,因為OS并不知道哪些任務需要優(yōu)先執(zhí)行,所以,兩個線程實際上在競爭電腦的性能資源,產(chǎn)生的結(jié)果就是不確定的。2.2:松開“死咬”的CPUvoid SDL_Delay(Uint32 ms);解決race conditions的方法就是給CPU足夠的時間“休息”,而這正好也是我們自己定義dt所需要的。SDL_Delay()在這個時候就顯得意義重大了。當今電腦的運算速度非常非???,以至于哪怕我們僅僅給電腦0.01秒的時間“休息”(每次循環(huán)中),電腦都會顯得很輕松了。int amn(void* data)AmnArg* pData = (AmnArg*)data;PictureSurface stand(./images/am01.png, pData-screen);stand.colorKey();PictureSurface bg(./images/background.png, pData-screen);const int SPEED_CTRL = 300;int speedX = (pData-endX - pData-beginX) / SPEED_CTRL;int speedY = (pData-endY - pData-beginY) / SPEED_CTRL;for ( int i = 0; i beginX += speedX;pData-beginY += speedY;bg.blit(pData-beginX, pData-beginY, pData-beginX, pData-beginY, stand.point()-w, stand.point()-h, 2, 2);stand.blit(pData-beginX, pData-beginY);pData-screen.flip();SDL_Delay(10);return 0;說到這里,我們不得不提及之前一直所忽略的一個問題:我們之前凡是涉及循環(huán)等待事件輪詢的程序總是占用100%的CPU,這并不是因為我們真正用到了100%的CPU性能,而是我們讓CPU陷入了“空等”(Busy Waiting)的尷尬境地。輪詢事件得到響應相對于循環(huán)等待來說,是發(fā)生得非常緩慢的事情,我們在循環(huán)中,哪怕是讓電腦休息0.01秒,事情都會發(fā)生本質(zhì)性的改變:while ( gameOver = false )while ( SDL_PollEvent(&gameEvent) != 0 )if ( gameEvent.type = SDL_QUIT )gameOver = true;if ( gameEvent.type = SDL_KEYDOWN )if ( gameEvent.key.keysym.sym = SDLK_ESCAPE )gameOver = true;screen.flip();SDL_Delay(10);當我們重新運行新程序的時候,我們可以看到程序?qū)PU的占用從100%驟降到了0%!這當然并不意味著程序就用不上CPU了,而是說,這些運算對于我們的CPU來說,實在是小菜一碟了,或者從數(shù)據(jù)上說,處理這些運算的時間與0.01秒來比較,都幾乎可以忽略不計!2.3:GUI線程與worker線程我們的另外一項試驗是將事件輪詢放到動畫線程中,程序就不多寫了,大家可以自己試下。我直接說結(jié)論:動畫線程中無法響應事件輪詢。一般提倡的模式,是將GUI事件都寫在主線程中,而將純粹的運算才寫到由主線程創(chuàng)建的線程中,后者也就是所謂的worker線程。從另外一個概念看,只有主線程控制著“當前窗口”,其它線程也許在后臺,也許雖然也是在前臺但是并非是我們可見的,所以,輪詢事件找不到接口。對于拋出的線程與主線程之間的通訊,我們可以通過他們共享的數(shù)據(jù)來進行控制,所以,盡管事件輪詢不能直接影響worker線程,但是我們?nèi)匀皇强梢酝ㄟ^主線程進行間接影響的。SDL入門教程(十三):3、封裝多線程 作者:龍飛SDL創(chuàng)建多線程的函數(shù)SDL_CreateThread()所調(diào)用的是函數(shù)指針,這意味著我們不可以傳入(非靜態(tài))成員函數(shù)的指針。關(guān)于兩種函數(shù)指針我們之前已經(jīng)討論過:函數(shù)指針與成員函數(shù)指針,我們可以有兩種方法能讓具有普通函數(shù)指針(函數(shù)指針以及靜態(tài)成員函數(shù)指針)的函數(shù)調(diào)用類的私有成員,一是友元函數(shù),另外就是靜態(tài)成員函數(shù)。而能夠受到類私有保護的,只有靜態(tài)成員函數(shù)。所以,我們可以通過靜態(tài)成員函數(shù)調(diào)用一個對象數(shù)據(jù)的形式,實現(xiàn)對于創(chuàng)建多線程函數(shù)的封裝。另外,我們希望測試在主線程中讀寫線程數(shù)據(jù)的效果,所以添加了兩個方法show() 和reset(),多線程演示的類源代碼如下:#include #include SurfaceClass.hppclass AmnArgprivate:int beginX;int beginY;int endX;int endY;const ScreenSurface& screen;/static int amn(void* pThat);public:AmnArg(int begin_x, int begin_y, int end_x, int end_y, const ScreenSurface& _screen);SDL_Thread* createThrd();void show() const;void reset();其中SurfaceClass.hpp請參考:/lf426/archive/2008/04/14/47038.html實現(xiàn)函數(shù)如下:#include amn.hppAmnArg:AmnArg(int begin_x, int begin_y, int end_x, int end_y, const ScreenSurface& _screen):beginX(begin_x), beginY(begin_y), endX(end_x), endY(end_y), screen(_screen)SDL_Thread* AmnArg:createThrd()return SDL_CreateThread(amn, (void*)this);void AmnArg:show() conststd:cout Now x at: beginX screen);stand.colorKey();PictureSurface bg(./images/background.png, pData-screen);const int SPEED_CTRL = 300;int speedX = (pData-endX - pData-beginX) / SPEED_CTRL;int speedY = (pData-endY - pData-beginY) / SPEED_CTRL;for ( int i = 0; i beginX += speedX;pData-beginY += speedY;bg.blit(pData-beginX, pData-beginY, pData-beginX, pData-beginY, stand.point()-w, stand.point()-h, 2, 2);stand.blit(pData-beginX, pData-beginY);pData-screen.flip();SDL_Delay(10);return 0;最后,我們修改了主演示程序,并測試了show()和reset()的效果。我們可以看到,直接修改線程數(shù)據(jù)的reset()的結(jié)果也是不可預知的,所以,我們似乎更應該通過改變線程“流”的效果,而不是直接對數(shù)據(jù)進行修改。這個我們以后再討論了。#include SurfaceClass.hpp#include amn.hppint game(int argc ,char* argv);int main(int argc ,char* argv)int mainRtn = 0;try mainRtn = game(argc, argv);catch ( const ErrorInfo& info ) info.show();return -1;catch ( const char* s ) std:cerr s std:endl;return -1;return mainRtn;int game(int argc ,char* argv)/Create a SDL screen.const int SCREEN_WIDTH = 640;const int SCREE
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 過敏性紫癜腎炎的護理
- 高端車型抵押貸款服務協(xié)議
- 高速路口立柱廣告牌年度租賃服務協(xié)議
- 多發(fā)傷急救與護理
- 環(huán)保標準國際貨物運輸代理協(xié)議
- 廠房租賃合同續(xù)約及押金退還協(xié)議
- 財務部門負責人保密義務及離職后競業(yè)限制協(xié)議
- 餐飲連鎖店簽單掛賬顧客滿意度調(diào)查合同
- 生態(tài)濕地草花苗木養(yǎng)護與采購一體化合同
- 澳大利亞雇主擔保移民項目合作協(xié)議
- 拉刀設計計算說明書
- 《快遞企業(yè)安全管理》課件
- 大學化學期末考試卷(含答案)
- 轉(zhuǎn)向系統(tǒng)開發(fā)手冊
- (完整word版)勞動合同書(電子版)正規(guī)范本(通用版)
- 專題1.3 新定義問題(壓軸題專項講練)2023-2024學年七年級數(shù)學上冊壓軸題專項講練系列(人教版)(解析版)
- 科目一3小時速成璇姐筆記
- урок-9-俄語名詞的性
- 江蘇省建設工程施工現(xiàn)場安全文明施工措施費監(jiān)督檢查表【模板】
- 鋼結(jié)構(gòu)安裝合同模板(六篇)
- 2023-2024學年江蘇省張家港市小學語文五年級期末自測模擬考試題詳細參考答案解析
評論
0/150
提交評論