Chrome自帶恐龍小游戲的源碼研究(五)


  在上一篇《Chrome自帶恐龍小游戲的源碼研究(四)》中實現了障礙物的繪制及移動,從這一篇開始主要研究恐龍的繪制及一系列鍵盤動作的實現。

會眨眼睛的恐龍

  在游戲開始前的待機界面,如果仔細觀察會發現恐龍會時不時地眨眼睛。這是通過交替繪制這兩個圖像實現的: 

可以通過一張圖片來了解這個過程: 

 

  為實現圖片的切換,需要一個計時器timer,並且需要知道兩張圖片切換的時間間隔msPerFrame。當計時器timer的時間大於切換的時間間隔msPerFrame時,將圖片切換到下一張,到達最后一張時又從第一張開始,如此反復。下面是實現代碼:

 1 Trex.config = {
 2     BLINK_TIMING:3000,  //眨眼間隔
 3     WIDTH: 44,        //站立時寬度
 4     WIDTH_DUCK: 59,    //閃避時寬度
 5     HEIGHT: 47,    //站立時高度
 6     BOTTOM_PAD: 10,
 7     MIN_JUMP_HEIGHT: 30 //最小起跳高度
 8 };
 9 //狀態
10 Trex.status = {
11     CRASHED: 'CRASHED',    //與障礙物發生碰撞
12     DUCKING: 'DUCKING',    //閃避
13     JUMPING: 'JUMPING',    //跳躍
14     RUNNING: 'RUNNING',    //跑動
15     WAITING: 'WAITING'    //待機
16 };
17 //元數據(metadata),記錄各個狀態的動畫幀和幀率
18 Trex.animFrames = {
19     WAITING: {//待機狀態
20         frames: [44, 0],//動畫幀x坐標在44和0之間切換,由於在雪碧圖中的y坐標是0所以不用記錄
21         msPerFrame: 1000 / 3    //一秒3幀
22     },
23     RUNNING: {
24         frames: [88, 132],
25         msPerFrame: 1000 / 12
26     },
27     CRASHED: {
28         frames: [220],
29         msPerFrame: 1000 / 60
30     },
31     JUMPING: {
32         frames: [0],
33         msPerFrame: 1000 / 60
34     },
35     DUCKING: {
36         frames: [262, 321],
37         msPerFrame: 1000 / 8
38     }
39 };
40 
41 function Trex(canvas,spritePos){
42     this.canvas = canvas;
43     this.ctx = canvas.getContext('2d');
44     this.spritePos = spritePos; //在雪碧圖中的位置
45     this.xPos = 0;  //在畫布中的x坐標
46     this.yPos = 0;  //在畫布中的y坐標
47     this.groundYPos = 0;    //初始化地面的高度
48     this.currentFrame = 0;  //初始化動畫幀
49     this.currentAnimFrames = [];    //記錄當前狀態的動畫幀
50     this.blinkDelay = 0;    //眨眼延遲(隨機)
51     this.animStartTime = 0; //動畫開始的時間
52     this.timer = 0; //計時器
53     this.msPerFrame = 1000 / FPS;   //默認幀率
54     this.config = Trex.config;  //拷貝一個配置的副本方便以后使用
55     this.jumpVelocity = 0;  //跳躍的初始速度
56 
57     this.status = Trex.status.WAITING;  //初始化默認狀態為待機狀態
58 
59     //為各種狀態建立標識
60     this.jumping = false;    //角色是否處於跳躍中
61     this.ducking = false;    //角色是否處於閃避中
62     this.reachedMinHeight = false;  //是否到達最小跳躍高度
63     this.speedDrop = false; //是否加速降落
64     this.jumpCount = 0;     //跳躍次數
65 
66     this.init();
67 }
View Code

  首先還是和以往一樣,對Trex這個構造函數進行基本的配置,然后在原型鏈中添加操作方法:

 1 Trex.prototype = {
 2     init:function() {
 3         this.groundYPos = DEFAULT_HEIGHT - this.config.HEIGHT - this.config.BOTTOM_PAD;
 4         this.yPos = this.groundYPos;
 5         //計算出最小起跳高度
 6         this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT;
 7 
 8         this.draw(0,0);
 9         this.update(0,Trex.status.WAITING);
10     },
11     setBlinkDelay:function () {
12         //設置隨機眨眼間隔時間
13         this.blinkDelay = Math.ceil(Math.random() * Trex.config.BLINK_TIMING);
14     },
15     update:function (deltaTime,opt_status) {
16         this.timer += deltaTime;
17 
18         if(opt_status) {
19             this.status = opt_status;
20             this.currentFrame = 0;
21             //得到對應狀態的幀率 e.g. WAITING 1000ms / 3fps = 333ms/fps
22             this.msPerFrame = Trex.animFrames[opt_status].msPerFrame;
23             //對應狀態的動畫幀 e.g. WAITING [44,0]
24             this.currentAnimFrames = Trex.animFrames[opt_status].frames;
25 
26             if(opt_status === Trex.status.WAITING) {
27                 //開始計y時
28                 this.animStartTime = getTimeStamp();
29                 //設置延時
30                 this.setBlinkDelay();
31             }
32         }
33 
34         //計時器超過一幀的運行時間,切換到下一幀
35         if (this.timer >= this.msPerFrame) {
36             this.currentFrame = this.currentFrame === this.currentAnimFrames.length - 1 ?
37                  0 : this.currentFrame + 1;
38             this.timer = 0; //重置計時器
39         }
40 
41         //待機狀態
42         if(this.status === Trex.status.WAITING) {
43             //執行眨眼動作
44             this.blink(getTimeStamp());
45         }
46     },
47     blink:function (time) {
48         var deltaTime = time - this.animStartTime;
49 
50         if(deltaTime >= this.blinkDelay) {
51             this.draw(this.currentAnimFrames[this.currentFrame],0);
52 
53             if (this.currentFrame === 1) {//0閉眼 1睜眼
54                 //設置新的眨眼間隔時間
55                 this.setBlinkDelay();
56                 this.animStartTime = time;
57             }
58         }
59     },
60     draw:function (x,y) {
61         var sourceX = x;
62         var sourceY = y;
63         var sourceWidth = this.ducking && this.status != Trex.status.CRASHED ?
64             this.config.WIDTH_DUCK : this.config.WIDTH;
65         var sourceHeight = this.config.HEIGHT;
66         sourceX += this.spritePos.x;
67         sourceY += this.spritePos.y;
68 
69         this.ctx.drawImage(imgSprite,
70             sourceX, sourceY,
71             sourceWidth, sourceHeight,
72             this.xPos, this.yPos,
73             this.config.WIDTH, this.config.HEIGHT);
74     }
75 };
View Code

  先來看update方法中的這段代碼:

1 if (this.timer >= this.msPerFrame) {
2     this.currentFrame = this.currentFrame === this.currentAnimFrames.length - 1 ?
3          0 : this.currentFrame + 1;
4     this.timer = 0;
5 }

  這段代碼實現了兩個幀之間的切換,但如果只是單純地以相同時間間隔來切換兩張圖片,那么得到的效果是不正確的,會出現頻繁眨眼的情況。而實際情況是,閉眼只是一瞬間,睜開眼睛的時間則比較長。Chrome開發人員非常巧妙地解決了這個問題:

 1 blink:function (time) {
 2     var deltaTime = time - this.animStartTime;
 3 
 4     if(deltaTime >= this.blinkDelay) {
 5         this.draw(this.currentAnimFrames[this.currentFrame],0);
 6 
 7         if (this.currentFrame === 1) {//0閉眼 1睜眼
 8             //設置新的眨眼間隔時間
 9             this.setBlinkDelay();
10             this.animStartTime = time;
11         }
12     }
13 }
View Code

  只要計時器沒有超過blinkDelay就不繪制新的圖片,這樣圖片就會停留在上一次繪制的狀態,恐龍此時是睜着眼睛的。當時間超過了blinkDelay,即執行眨眼的時間到了,這時會繪制this.currentFrame這一幀。如果這一幀是0(閉眼),由於之前設置了this.timer >= this.msPerFrame時會切換幀,當時間再次超過blinkDelay時,這時就會繪制幀1(睜眼),我們看到的效果就是眼睛閉上只有一瞬然后立刻睜開了。 如果當前幀是1(睜眼),重新設置blinkDelay,於是在deltaTime沒有超過重新設置blinkDelay的情況下,都不會繪制新圖片(始終保持在幀1(睜眼)),這樣我們看到的效果就是睜眼的時間稍長。

下面是運行后的效果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM