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


  在上一篇《Chrome自帶恐龍小游戲的源碼研究(三)》中實現了讓游戲晝夜交替,這一篇主要研究如何繪制障礙物。

  障礙物有兩種:仙人掌和翼龍。仙人掌有大小兩種類型,可以同時並列多個;翼龍按高、中、低的隨機飛行高度出現,不可並行。仙人掌和地面有着相同的速度向左移動,翼龍則快一些或慢一些,因為添加了隨機的速度修正。我們使用一個障礙物列表管理它們,當它們移出屏幕外時則將其從列表中移除。同時再用一個列表記錄它們的類型:

1 Obstacle.obstacles = [];    //存儲障礙物的數組
2 Obstacle.obstacleHistory = [];  //記錄障礙物數組中障礙物的類型

障礙物的出現不能太頻繁,也不能太稀少,太頻繁立刻就gameover了,太稀少則沒有挑戰性,因此需要一定的規則來生成障礙物。每組障礙物之間應該有一段間隔作為落腳點,新生成的障礙物在這個間隔之外生成。如示意圖所示:

因此,先定義一個最大間距系數,下面會用這個系數生成隨機間距:

Obstacle.MAX_GAP_COEFFICIENT = 1.5; //障礙物最大間距系數

另外,還需要對障礙物進行一些約束及配置:

 1 //每組障礙物的最大數量
 2 Obstacle.MAX_OBSTACLE_LENGTH = 3;   
 3 //相鄰的障礙物類型的最大重復數
 4 Obstacle.MAX_OBSTACLE_DUPLICATION = 2;
 5 
 6 Obstacle.types = [
 7     {
 8         type: 'CACTUS_SMALL', //小仙人掌
 9         width: 17,  //
10         height: 35, //
11         yPos: 105,  //在畫布上的y坐標
12         multipleSpeed: 4, 
13         minGap: 120,    //最小間距
14         minSpeed: 0    //最低速度
15     },
16     {
17         type: 'CACTUS_LARGE',   //大仙人掌
18         width: 25,
19         height: 50,
20         yPos: 90,
21         multipleSpeed: 7,
22         minGap: 120,
23         minSpeed: 0
24     },
25     {
26         type: 'PTERODACTYL',    //翼龍
27         width: 46,
28         height: 40,
29         yPos: [ 100, 75, 50 ], //有高、中、低三種高度
30         multipleSpeed: 999,
31         minSpeed: 8.5,  
32         minGap: 150,
33         numFrames: 2,   //有兩個動畫幀
34         frameRate: 1000/6,  //動畫幀的切換速率,這里為一秒6幀
35         speedOffset: .8 //速度修正
36     }
37 ];

障礙物的所有實現由構造函數Obstacle完成,下面是它的實現代碼:

  1 /**
  2  * 繪制障礙物構造函數
  3  * @param canvas
  4  * @param type 障礙物的類型
  5  * @param spriteImgPos 雪碧圖坐標
  6  * @param dimensions 屏幕尺寸
  7  * @param gapCoefficient 障礙物間隙
  8  * @param speed 障礙物移動速度
  9  * @param opt_xOffset 障礙物水平偏移量
 10  * @constructor
 11  */
 12 function Obstacle(canvas,type,spriteImgPos,dimensions,gapCoefficient,speed,opt_xOffset) {
 13     this.ctx = canvas.getContext('2d');
 14     this.spritePos = spriteImgPos;
 15     //障礙物類型(仙人掌、翼龍)
 16     this.typeConfig = type;
 17     this.gapCoefficient = gapCoefficient;
 18     //每個障礙物的數量(1~3)
 19     this.size = getRandomNum(1,Obstacle.MAX_OBSTACLE_LENGTH);
 20     this.dimensions = dimensions;
 21     //表示該障礙物是否可以被移除
 22     this.remove = false;
 23     //水平坐標
 24     this.xPos = dimensions.WIDTH + (opt_xOffset || 0);
 25     this.yPos = 0;
 26     this.width = 0;
 27     this.gap = 0;
 28     this.speedOffset = 0;   //速度修正
 29 
 30     //障礙物的動畫幀
 31     this.currentFrame = 0;
 32     //動畫幀切換的計時器
 33     this.timer = 0;
 34 
 35     this.init(speed);
 36 }
 37 ```
 38 
 39 實例方法:
 40 ```javascript
 41 Obstacle.prototype = {
 42     init:function(speed) {
 43         //如果隨機障礙物是翼龍,則只出現一只
 44         //翼龍的multipleSpeed是999,遠大於speed
 45         if (this.size > 1 && this.typeConfig.multipleSpeed > speed) {
 46             this.size = 1;
 47         }
 48         //障礙物的總寬度等於單個障礙物的寬度乘以個數
 49         this.width = this.typeConfig.width * this.size;
 50 
 51         //若障礙物的縱坐標是一個數組
 52         //則隨機選取一個
 53         if (Array.isArray(this.typeConfig.yPos))  {
 54             var yPosConfig = this.typeConfig.yPos;
 55             this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)];
 56         } else {
 57             this.yPos = this.typeConfig.yPos;
 58         }
 59 
 60         this.draw();
 61 
 62         //對翼龍的速度進行修正,讓它看起來有的飛得快一些,有些飛得慢一些
 63         if (this.typeConfig.speedOffset) {
 64             this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset :
 65                 -this.typeConfig.speedOffset;
 66         }
 67 
 68         //障礙物之間的間隙,與游戲速度有關
 69         this.gap = this.getGap(this.gapCoefficient, speed);
 70     },
 71     //障礙物之間的間隔,gapCoefficient為間隔系數
 72     getGap: function(gapCoefficient, speed) {
 73         var minGap = Math.round(this.width * speed +
 74             this.typeConfig.minGap * gapCoefficient);
 75         var maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT);
 76         return getRandomNum(minGap, maxGap);
 77     },
 78     //判斷障礙物是否移出屏幕外
 79     isVisible: function() {
 80         return this.xPos + this.width > 0;
 81     },
 82     draw:function() {
 83         //障礙物寬高
 84         var sourceWidth = this.typeConfig.width;
 85         var sourceHeight = this.typeConfig.height;
 86 
 87         //根據障礙物數量計算障礙物在雪碧圖上的x坐標
 88         //this.size的取值范圍是1~3
 89         var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1)) +
 90             this.spritePos.x;
 91 
 92         // 如果當前動畫幀大於0,說明障礙物類型是翼龍
 93         // 更新翼龍的雪碧圖x坐標使其匹配第二幀動畫
 94         if (this.currentFrame > 0) {
 95             sourceX += sourceWidth * this.currentFrame;
 96         }
 97         this.ctx.drawImage(imgSprite,
 98             sourceX, this.spritePos.y,
 99             sourceWidth * this.size, sourceHeight,
100             this.xPos, this.yPos,
101             sourceWidth * this.size, sourceHeight);
102     },
103     //單個障礙物的移動
104     update:function(deltaTime, speed) {
105         //如果障礙物還沒有移出屏幕外
106         if (!this.remove) {
107             //如果有速度修正則修正速度
108             if (this.typeConfig.speedOffset) {
109                 speed += this.speedOffset;
110             }
111             //更新x坐標
112             this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime);
113 
114             // Update frame
115             if (this.typeConfig.numFrames) {
116                 this.timer += deltaTime;
117                 if (this.timer >= this.typeConfig.frameRate) {
118                     //在兩個動畫幀之間來回切換以達到動畫效果
119                     this.currentFrame =
120                         this.currentFrame == this.typeConfig.numFrames - 1 ?
121                             0 : this.currentFrame + 1;
122                     this.timer = 0;
123                 }
124             }
125             this.draw();
126 
127             if (!this.isVisible()) {
128                 this.remove = true;
129             }
130         }
131     },
132     //管理多個障礙物移動
133     updateObstacles: function(deltaTime, currentSpeed) {
134         //保存一個障礙物列表的副本
135         var updatedObstacles = Obstacle.obstacles.slice(0);
136 
137         for (var i = 0; i < Obstacle.obstacles.length; i++) {
138             var obstacle = Obstacle.obstacles[i];
139             obstacle.update(deltaTime, currentSpeed);
140 
141             //移除被標記為刪除的障礙物
142             if (obstacle.remove) {
143                 updatedObstacles.shift();
144             }
145         }
146         Obstacle.obstacles = updatedObstacles;
147 
148         if(Obstacle.obstacles.length > 0) {
149             //獲取障礙物列表中的最后一個障礙物
150             var lastObstacle = Obstacle.obstacles[Obstacle.obstacles.length - 1];
151 
152             //若滿足條件則添加障礙物
153             if (lastObstacle &&
154                 lastObstacle.isVisible() &&
155                 (lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) <
156                 this.dimensions.WIDTH) {
157                 this.addNewObstacle(currentSpeed);
158             }
159         } else {//若障礙物列表中沒有障礙物則立即添加
160             this.addNewObstacle(currentSpeed);
161         }
162     },
163     //隨機添加障礙
164     addNewObstacle:function (currentSpeed) {
165         //隨機選取一種類型的障礙
166         var obstacleTypeIndex = getRandomNum(0,Obstacle.types.length - 1);
167         var obstacleType = Obstacle.types[obstacleTypeIndex];
168 
169         //檢查隨機取到的障礙物類型是否與前兩個重復
170         //或者檢查其速度是否合法,這樣可以保證游戲在低速時不出現翼龍
171         //如果檢查不通過,則重新再選一次直到通過為止
172         if(this.duplicateObstacleCheck(obstacleType.type) || currentSpeed < obstacleType.minSpeed) {
173             this.addNewObstacle(currentSpeed);
174         } else {
175             //檢查通過后,獲取其雪碧圖中的坐標
176             var obstacleSpritePos = this.spritePos[obstacleType.type];
177             //生成新的障礙物並存入數組
178             Obstacle.obstacles.push(new Obstacle(c,obstacleType,obstacleSpritePos,this.dimensions,
179                 this.gapCoefficient,currentSpeed,obstacleType.width));
180             //同時將障礙物的類型存入history數組
181             Obstacle.obstacleHistory.unshift(obstacleType.type);
182         }
183 
184         //若history數組的長度大於1,則清空最前面的兩個
185         if (Obstacle.obstacleHistory.length > 1) {
186             Obstacle.obstacleHistory.splice(Obstacle.MAX_OBSTACLE_DUPLICATION);
187         }
188     },
189     //檢查障礙物是否超過允許的最大重復數
190     duplicateObstacleCheck:function(nextObstacleType) {
191         var duplicateCount = 0;
192         //與history數組中的障礙物類型比較,最大只允許重得兩次
193         for(var i = 0; i < Obstacle.obstacleHistory.length; i++) {
194             duplicateCount = Obstacle.obstacleHistory[i] === nextObstacleType ? duplicateCount + 1 : 0;
195         }
196         return duplicateCount >= Obstacle.MAX_OBSTACLE_DUPLICATION;
197     }
198 };
View Code

最后在此前的基礎上添加一段測試代碼:

 1 window.onload = function () {
 2             var h = new HorizonLine(c,spriteDefinition.HORIZON);
 3             var cloud = new Cloud(c,spriteDefinition.CLOUD,DEFAULT_WIDTH);
 4             var night = new NightMode(c,spriteDefinition.MOON,DEFAULT_WIDTH);
 5             var obstacle = new Obstacle(c,Obstacle.types[0],spriteDefinition,{WIDTH:600},0.6,1);
 6             var startTime = 0;
 7             var deltaTime;
 8             var speed = 3;
 9             (function draw(time) {
10                 gameFrame++;
11                 if(speed < 13.5) {
12                     speed += 0.01;
13                 }
14                 ctx.clearRect(0,0,600,150);
15                 time = time || 0;
16                 deltaTime = time - startTime;
17                 h.update(deltaTime,speed);
18                 cloud.updateClouds(0.2);
19                 night.invert(deltaTime);
20                 obstacle.updateObstacles(deltaTime,speed);
21                 startTime = time;
22                 window.requestAnimationFrame(draw,c);
23             })();
24         };
View Code

最終得到的效果:

 


免責聲明!

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



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