[GEiv]第二章:動態繪製 讓畫面動起來

動態繪製 

讓畫面動起來

        這篇文章主要介紹圖元動畫的基本實現。

[畫面、動畫與遊戲]

        在上一章節中,主要講解了靜態繪製的諸多方法。這些API終究是對OPENGL API的簡單封裝,這離遊戲製作尚有一段距離。

        什麼是動畫呢?動畫是一系列畫面的有序播放,當播放速度快於人眼視覺暫留時,我們便誤以爲畫面中的物體在移動了,對於視頻、動畫來講,這個播放速度大於24幀時,已經達到了不錯的效果。

        什麼又是遊戲呢?遊戲是在玩家交互過程中變化的動畫,對於計算機遊戲來講,這個交互過程主要依賴IO系統來完成。由於遊戲對象根據IO的變化作出及時反應並來讓玩家產生實在的控制感,24幀每秒的繪製速度雖然不會影響視覺暫留,但對於“控制感”來講卻會有一些滯後(lag),因此遊戲普遍要求幀數在40以上。而Geiv的默認繪製速率被固定在60FPS。也就是說,前一章的靜態繪製,實際上是每秒鐘繪製60幅相同的畫面而已。

[讓畫面動起來]

例子:

UESI UES = new R();
Obj rect = UES.creatObj(UESI.BGIndex);
rect.addGLRect("00FF00",0,0,200f,200f);//畫一個矩形
rect.setGLFill(true);
rect.setPosition(CANExPos.POS_Y_CENTER);       //放在Y軸中間位置
rect.show();
while(rect.getDx() + rect.getWidth() <UES.getScreenWidth()){
       rect.setDx(rect.getDx()+ 1f);//取出Dx,並加上1
       UES.wait(3,17);//延時17ms,約爲1000/60
}


結果:

        

但是上面的寫法極其不科學!!!

        首先,引擎自己有自己的繪製線程,是基於Timer、及其準確的每秒鐘執行60次繪製;而main方法相對於繪製來講,是一個異步過程。延時17ms並不會準確的落在繪製線程的延時部分。

        第二,線程總是有限的,在執行while循環時,整個main線程都在爲這個動畫提供服務,無法進行其他運算,如果此時需要5種不同的動畫,爲他們開闢5個新線程是及其不合理的,縱使引擎組建中有完善的同步鎖保證數據安全,但我們也不應該提高無用的併發度。

[幀邏輯]

[interface geivcore.SerialTask]

        SerialTask,字面意義是“串行任務”,這個名字起得不是很好(又是歷史原因恩恩),實際的意義,是插入到繪製線程完成繪製之後的額外自定義邏輯。

例子:

//到了這裏,我十分建議將類分開寫,一個Main類已經滿足不了需求了。
//Main.java:
public class Main{
       public static void main(String[] args) {
              UESI UES = new R();
              new MoveableRect(UES);
       }
}

//MoveableRect.java:
public class MoveableRect implements SerialTask{
       Obj rect;
       UESI UES;
       public MoveableRect(UESI UES){
              this.UES = UES;
              rect = UES.creatObj(UESI.BGIndex);
              rect.addGLRect("00FF00",0,0,200f,200f);
              rect.setGLFill(true);
              rect.setPosition(CANExPos.POS_Y_CENTER);
              rect.show();
             
              UES.addSerialTask(this);
       }
       @Override
       public void Serial(int arg0) {
              if(rect.getDx() + rect.getWidth()< UES.getScreenWidth()){
                     rect.setDx(rect.getDx() +1f);
              }
       }
}


        首先說SerialTask接口,它當中只有一個方法:

        public void Serial(int arg0);

        其中,參數是唯一的繪製時序信號,可用可不用。

        之後MoveableRect類實現了Serial方法,在每次繪製過後,將rect的橫座標+1,也就是每秒60像素的速度。

        最後,在MoveableRect的構造方法中,使用UESI爲參數,並保留一個引用,在構造方法的最後部分,它調用了UES.addSerialTask(this);方法,將這個自定義邏輯(Serial的實現)插入了繪製線程的隊列末尾。

        這樣,在每次繪製完成後,會精確的執行一次Serial方法,改變座標,在下次繪製時圖元便會移動一小段距離,依次類推,圖元便移動了起來。

結果:

        

        如果您觀察仔細的話,應該能看出來,使用SerialTask實現的動畫要比使用其他線程的異步調用相比平滑的多。

SerialTask的注意事項:

        引擎的動態部分應該主要由SerialTask完成。SerialTask的Serial方法中,能夠書寫的語句比較有限:這點非常重要,所有的Serial實現都要在繪製間隔裏執行完畢(17ms),在一個遊戲中,Serial的總數量可以成百上千,因此有如下限制:

        1)阻塞方法一定不要有,例如sleep,如果以前進行過awt編程,在onclick方法裏調用阻塞方法會讓整個繪製線程停止,這裏的Serial也是一樣。

        2)阻塞IO過程也一定不能有,總共17ms的執行實現,使用Serial調用阻塞IO時間來不及,諸如讀取文件、網絡訪問等不適宜寫入Serial。

        3)堆區分配請求過程不能過多,也就是說,儘量不要new對象,但是一些輕量化的數據對象還是可以接受的。

[總結]

        SerialTask接口用來向繪製線程動態地添加一部分自定義運算邏輯,自定義類通過實現Serial方法,適當地進行圖元變換以實現各種動態效果。

        Serial儘可能只負責數值運算與類之間的消息傳遞,不要寫費時間的東西。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章