2017年1月10日 星期二

[C/C++自製2D遊戲]#06 使用Double page避開閃爍

在傳統以graphics.h實現遊戲時,常常會看到double buffer這個詞彙,到底是什麼意思呢?
稍後我們就會具體使用程式碼來觀察現象,並使用double buffer來解決掉。

請先將前節<bgi>目錄夾複製一份,改名為<doubleBuffer>

==========
STEP #1:

修改前節程式碼,我們使用一個1000次的for迴圈,讓畫面中的搖桿每16ms向右移動1 pixel。

#include <stdio.h>
#include <conio.h>
#include "graphics.h"
#include <stdlib.h>

int main(){
 
 initwindow(640,480,"This is my game");

 setcolor(2);
 settextstyle(SANS_SERIF_FONT, HORIZ_DIR, 0);
 outtextxy(200,300,"hello world");
 readimagefile("games.gif", 220, 120, 380, 280);
 getch();
 cleardevice();
 getch();

 for(int i; i<1000;i++){
  cleardevice();
  readimagefile("games.gif", 220+i, 120, 380+i, 280);
  delay(16);
 }

}


  1. #19清屏
  2. #20行繪製"games.gif"
按下F5運行,連按兩次Enter後可看到畫面中的搖桿向右移動,但屏幕不停地閃爍。



閃爍的原因是因為:#19行執行完畢後,畫面確實地變成了全黑,而後再繪製上了搖桿。這個繪圖過程雖然快速,但還是會出現黑屏殘像,因此產生了閃爍。
解決的方式是使用setactivepage()與setvisualpage()兩則指令,做出切換page的動作。

==================
STEP #2
修改程式如下:

#include <stdio.h>
#include <conio.h>
#include "graphics.h"
#include <stdlib.h>

int main(){
 
 int curPage =0;

 initwindow(640,480,"This is my game");

 setactivepage(curPage);
 setvisualpage(curPage);
 setcolor(2);
 settextstyle(SANS_SERIF_FONT, HORIZ_DIR, 0);
 outtextxy(200,300,"hello world");
 readimagefile("games.gif", 220, 120, 380, 280);
 getch();
 cleardevice();
 getch();

 for(int i; i<1000;i++){
  curPage = (curPage + 1) % 2;
  setactivepage(curPage);
  cleardevice();
  readimagefile("games.gif", 220+i, 120, 380+i, 280);
  setvisualpage(curPage);
  delay(16);
 }
}


  1. #8宣告了一個整數變數curPage(current page),用以紀錄目前操作中的頁面號碼。這個page可視為傳統double buffer技巧中的buffer概念。
  2. #12,#13在初始化的過程中,先使用預設頁面0來做首次繪製。
  3. #23使用餘數除法(mod,後稱餘除法)。這條指令可讓0變1,1變0,做出切換的效果。
  4. #24切換到非顯示中的頁面,隨後接著清屏並繪製games.gif
  5. #27將非顯示頁切換為顯示頁
由於在清屏與繪製的過程中,我們是針對非顯示中的頁面,因此繪製的過程也就是圖像蝶家的過程玩家是看不到的。等待繪製完畢(疊加完畢)後再一次切換為顯示屏讓玩家看清楚。如此一來就不會讓玩家看到繪製的過程,也就不會閃爍了。

按F5運行遊戲,確認此次搖桿向右移動時不會有閃爍現象。



==============
STEP #3:

剛剛使用curPage去做顯示切換實在太麻煩了(還要用餘除法,實在很累捏~~),
所以接下來我們改用超好用的swapbuffer()指令來改寫上述程式。
由於在以前的年代graphics.h只支援雙buffer(也夠了),所以整個顯示的過程是由page0與page1分工合作所完成了。
我們設計的工作流程是這樣的:
  • 在任何時刻下,其中一個page作為繪製屏,另一page做為顯示屏。繪製顯示屏必定不為同一屏。
  • 當程式在繪製屏上繪製完畢時,直接下一則swapbuffers()指令將角色轉換。如此一來繪製結果立刻可輸出。
  • 由於新繪製圖已顯示,所以前次的顯示屏在swapbuffers()後角色轉為繪製屏,其內容已不需要,固可大膽重繪之。
改寫程式如下:

#include <stdio.h>
#include <conio.h>
#include "graphics.h"
#include <stdlib.h>

int main(){
 
 initwindow(640,480,"This is my game");

 setactivepage(0);
 setvisualpage(1);
 setcolor(2);
 settextstyle(SANS_SERIF_FONT, HORIZ_DIR, 0);
 outtextxy(200,300,"hello world");
 readimagefile("games.gif", 220, 120, 380, 280);
 swapbuffers();
 getch();
 cleardevice();
 swapbuffers();
 getch();

 for(int i; i<1000;i++){
  cleardevice();
  readimagefile("games.gif", 220+i, 120, 380+i, 280);
  swapbuffers();
  delay(16);
 }
}

完成後按F5運行,確認功能與上個STEP完全相同。

* winBGIm的說明文檔在此