Chrome自带恐龙小游戏的源码研究(完)

  在上一篇《Chrome自带恐龙小游戏的源码研究(七)》中研究了恐龙与障碍物的碰撞检测,这一篇主要研究组成游戏的其它要素。

游戏分数记录

  如图所示,分数及最高分记录显示在游戏界面的右上角,每达到100分就会出现闪烁特效,游戏第一次gameover时显示历史最高分。分数记录器由DistanceMeter构造函数实现,以下是它的全部代码:

  1 DistanceMeter.dimensions = {
  2     WIDTH: 10,    //每个字符的宽度
  3     HEIGHT: 13,    //每个字符的高
  4     DEST_WIDTH: 11 //间隙
  5 };
  6 DistanceMeter.config = {
  7     // 初始时记录的分数上限为5位数,即99999
  8     MAX_DISTANCE_UNITS: 5,
  9
 10     // 每隔100米距离记录器的数字出现闪动特效
 11     ACHIEVEMENT_DISTANCE: 100,
 12
 13     // 将移动距离转化为合理的数值所用的转化系数
 14     COEFFICIENT: 0.025,
 15
 16     // 每250ms闪动一次
 17     FLASH_DURATION: 1000 / 4,
 18
 19     // 闪动次数
 20     FLASH_ITERATIONS: 3
 21 };
 22 /**
 23          * 距离记录器
 24          * @param {HTMLCanvasElement} canvas
 25          * @param {Object} spritePos 雪碧图上的坐标.
 26          * @param {number} canvasWidth
 27          * @constructor
 28          */
 29 function DistanceMeter(canvas, spritePos, canvasWidth) {
 30     this.canvas = canvas;
 31     this.canvasCtx = canvas.getContext(‘2d‘);
 32     this.image = imgSprite;
 33     this.spritePos = spritePos;
 34     //相对坐标
 35     this.x = 0;
 36     this.y = 5;
 37
 38     //最大分数
 39     this.maxScore = 0;
 40     //高分榜
 41     this.highScore = 0;
 42
 43     this.digits = [];
 44     //是否进行闪动特效
 45     this.acheivement = false;
 46     this.defaultString = ‘‘;
 47     //闪动特效计时器
 48     this.flashTimer = 0;
 49     //闪动计数器
 50     this.flashIterations = 0;
 51     this.invertTrigger = false;
 52
 53     this.config = DistanceMeter.config;
 54     //最大记录为万位数
 55     this.maxScoreUnits = this.config.MAX_DISTANCE_UNITS;
 56     this.init(canvasWidth);
 57 }
 58
 59 DistanceMeter.prototype = {
 60     /**
 61              * 初始化距离记录器为00000
 62              * @param canvasWidth canvas的宽度
 63              */
 64     init: function(canvasWidth) {
 65         var maxDistanceStr = ‘‘;
 66
 67         this.calcXPos(canvasWidth);
 68         for (var i = 0; i < this.maxScoreUnits; i++) {
 69             this.draw(i, 0);
 70             this.defaultString += ‘0‘;
 71             maxDistanceStr += ‘9‘;
 72         }
 73
 74         //99999
 75         this.maxScore = parseInt(maxDistanceStr);
 76     },
 77     /**
 78              * 计算出xPos
 79              * @param canvasWidth
 80              */
 81     calcXPos: function(canvasWidth) {
 82         this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH * (this.maxScoreUnits + 1));
 83     },
 84     draw: function(digitPos, value, opt_highScore) {
 85         var sourceWidth = DistanceMeter.dimensions.WIDTH;
 86         var sourceHeight = DistanceMeter.dimensions.HEIGHT;
 87         var sourceX = DistanceMeter.dimensions.WIDTH * value;
 88         var sourceY = 0;
 89
 90         var targetX = digitPos * DistanceMeter.dimensions.DEST_WIDTH;
 91         var targetY = this.y;
 92         var targetWidth = DistanceMeter.dimensions.WIDTH;
 93         var targetHeight = DistanceMeter.dimensions.HEIGHT;
 94
 95         sourceX += this.spritePos.x;
 96         sourceY += this.spritePos.y;
 97
 98         this.canvasCtx.save();
 99
100         if (opt_highScore) {
101             // 将最高分放至当前分数的左边
102             var highScoreX = this.x - (this.maxScoreUnits * 2) * DistanceMeter.dimensions.WIDTH;
103             this.canvasCtx.translate(highScoreX, this.y);
104         } else {
105             this.canvasCtx.translate(this.x, this.y);
106         }
107
108         this.canvasCtx.drawImage(this.image, sourceX, sourceY, sourceWidth, sourceHeight, targetX, targetY, targetWidth, targetHeight);
109
110         this.canvasCtx.restore();
111     },
112     /**
113              * 将像素距离转化为“真实距离”
114              * @param distance  像素距离
115              * @returns {number} “真实距离”
116              */
117     getActualDistance: function(distance) {
118         return distance ? Math.round(distance * this.config.COEFFICIENT) : 0;
119     },
120     /**
121              * 更新距离记录器
122              * @param {number} deltaTime
123              * @param {number} distance
124              * @returns {boolean} 是否播放声音
125              */
126     update: function(deltaTime, distance) {
127         var paint = true;
128         var playSound = false;
129
130         if (!this.acheivement) {
131             distance = this.getActualDistance(distance);
132             // 分数超过最大分数时增加至十万位999999
133             if (distance > this.maxScore && this.maxScoreUnits === this.config.MAX_DISTANCE_UNITS) {
134                 this.maxScoreUnits++;
135                 this.maxScore = parseInt(this.maxScore + ‘9‘);
136             }
137
138             if (distance > 0) {
139                 // 每100距离开始闪动特效并播放声音
140                 if (distance % this.config.ACHIEVEMENT_DISTANCE === 0) {
141                     this.acheivement = true;
142                     this.flashTimer = 0;
143                     playSound = true;
144                 }
145
146                 // 让数字以0开头
147                 var distanceStr = (this.defaultString + distance).substr( - this.maxScoreUnits);
148                 this.digits = distanceStr.split(‘‘);
149             } else {
150                 this.digits = this.defaultString.split(‘‘);
151             }
152         } else {
153             // 到达目标分数时闪动分数
154             if (this.flashIterations <= this.config.FLASH_ITERATIONS) {
155                 this.flashTimer += deltaTime;
156
157                 if (this.flashTimer < this.config.FLASH_DURATION) {
158                     paint = false;
159                 } else if (this.flashTimer > this.config.FLASH_DURATION * 2) {
160                     this.flashTimer = 0;
161                     this.flashIterations++;
162                 }
163             } else {
164                 this.acheivement = false;
165                 this.flashIterations = 0;
166                 this.flashTimer = 0;
167             }
168         }
169
170         // 非闪动时绘制分数
171         if (paint) {
172             for (var i = this.digits.length - 1; i >= 0; i--) {
173                 this.draw(i, parseInt(this.digits[i]));
174             }
175         }
176
177         this.drawHighScore();
178         return playSound;
179     },
180     //绘制高分榜
181     drawHighScore: function() {
182         this.canvasCtx.save();
183         this.canvasCtx.globalAlpha = .8; //让字符看起来颜色稍浅
184         for (var i = this.highScore.length - 1; i >= 0; i--) {
185             this.draw(i, parseInt(this.highScore[i], 10), true);
186         }
187         this.canvasCtx.restore();
188     },
189     setHighScore: function(distance) {
190         distance = this.getActualDistance(distance);
191         var highScoreStr = (this.defaultString + distance).substr( - this.maxScoreUnits);
192         //10和11分别对应雪碧图中的H、I
193         this.highScore = [‘10‘, ‘11‘, ‘‘].concat(highScoreStr.split(‘‘));
194     },
195     //重置记录器为00000
196     reset: function() {
197         this.update(0);
198         this.acheivement = false;
199     }
200 };

GameOver

  恐龙和障碍物碰撞后,游戏结束,游戏界面显示gameover面板,该功能由GameOverPanel构造函数实现:

 1 GameOverPanel.dimensions = {
 2     TEXT_X: 0,
 3     TEXT_Y: 13,
 4     TEXT_WIDTH: 191,
 5     TEXT_HEIGHT: 11,
 6     RESTART_WIDTH: 36,
 7     RESTART_HEIGHT: 32
 8 };
 9
10 function GameOverPanel(canvas, textImgPos, restartImgPos, dimensions) {
11     this.canvas = canvas;
12     this.canvasCtx = canvas.getContext(‘2d‘);
13     this.canvasDimensions = dimensions;
14     this.textImgPos = textImgPos;
15     this.restartImgPos = restartImgPos;
16     this.draw();
17 }
18
19 GameOverPanel.prototype = {
20     draw: function() {
21         var dimensions = GameOverPanel.dimensions;
22
23         var centerX = this.canvasDimensions.WIDTH / 2;
24
25         // Game over text
26         var textSourceX = dimensions.TEXT_X;
27         var textSourceY = dimensions.TEXT_Y;
28         var textSourceWidth = dimensions.TEXT_WIDTH;
29         var textSourceHeight = dimensions.TEXT_HEIGHT;
30
31         var textTargetX = Math.round(centerX - (dimensions.TEXT_WIDTH / 2));
32         var textTargetY = Math.round((this.canvasDimensions.HEIGHT - 25) / 3);
33         var textTargetWidth = dimensions.TEXT_WIDTH;
34         var textTargetHeight = dimensions.TEXT_HEIGHT;
35
36         var restartSourceWidth = dimensions.RESTART_WIDTH;
37         var restartSourceHeight = dimensions.RESTART_HEIGHT;
38         var restartTargetX = centerX - (dimensions.RESTART_WIDTH / 2);
39         var restartTargetY = this.canvasDimensions.HEIGHT / 2;
40
41         textSourceX += this.textImgPos.x;
42         textSourceY += this.textImgPos.y;
43
44         // Game over text from sprite.
45         this.canvasCtx.drawImage(imgSprite, textSourceX, textSourceY, textSourceWidth, textSourceHeight, textTargetX, textTargetY, textTargetWidth, textTargetHeight);
46
47         // Restart button.
48         this.canvasCtx.drawImage(imgSprite, this.restartImgPos.x, this.restartImgPos.y, restartSourceWidth, restartSourceHeight, restartTargetX, restartTargetY, dimensions.RESTART_WIDTH, dimensions.RESTART_HEIGHT);
49     }
50 };

 1 function gameOver() {
 2     cancelAnimationFrame(raq);
 3     raq = 0;
 4     crashed = true;
 5     trex.update(0, Trex.status.CRASHED);
 6
 7     distanceMeter.acheivement = false;
 8     if (distanceRan > highestScore) {
 9         highestScore = Math.ceil(distanceRan);
10         distanceMeter.setHighScore(highestScore);
11     }
12
13     if (!gameOverPanel) {
14         gameOverPanel = new GameOverPanel(c, spriteDefinition.TEXT_SPRITE, spriteDefinition.RESTART, dimensions);
15     } else {
16         gameOverPanel.draw();
17     }
18 }

游戏重新开始

  GameOver后,按下Spacebar游戏重新开始,restart方法负责将游戏各个元素或数据重置:

 1 function restart() {
 2     trex.reset();
 3     Obstacle.obstacles = [];
 4     h.reset();
 5     night.reset();
 6     crashed = false;
 7     time = performance.now();
 8     distanceRan = 0;
 9     ctx.clearRect(0, 0, 600, 150);
10     distanceMeter.reset();
11     raq = requestAnimationFrame(draw, c);
12 }

游戏暂停

  当游戏窗口失去焦点时,游戏暂停,得到焦点时游戏继续。游戏通过注册三个事件来实现:

document.addEventListener(‘visibilitychange‘,onVisibilityChange);
window.addEventListener(‘blur‘,onVisibilityChange);
window.addEventListener(‘focus‘,onVisibilityChange);

 1 onVisibilityChange: function(e) {
 2     if (document.hidden || document.webkitHidden || e.type == ‘blur‘ || document.visibilityState != ‘visible‘) {
 3         this.stop();
 4     } else if (!this.crashed) {
 5         this.tRex.reset();
 6         this.play();
 7     }
 8 },
 9 stop: function() {
10     this.activated = false;
11     this.paused = true;
12     cancelAnimationFrame(this.raqId);
13     this.raqId = 0;
14 },
15 play: function() {
16     if (!this.crashed) {
17         this.activated = true;
18         this.paused = false;
19         this.tRex.update(0, Trex.status.RUNNING);
20         this.time = getTimeStamp();
21         this.update();
22     }
23 }

开场动画

  第一次开始游戏时,会有一个过渡动画,效果是地面逐渐展开,并且恐龙向前移动50像素。

 1 // CSS animation definition.
 2 var keyframes = ‘@-webkit-keyframes intro { ‘ + ‘from { width:‘ + Trex.config.WIDTH + ‘px }‘ + ‘to { width: ‘ + this.dimensions.WIDTH + ‘px }‘ + ‘}‘;
 3 document.styleSheets[0].insertRule(keyframes, 0);
 4
 5 this.containerEl.addEventListener(‘webkitAnimationEnd‘, this.startGame.bind(this));
 6
 7 this.containerEl.style.webkitAnimation = ‘intro .4s ease-out 1 both‘;
 8 this.containerEl.style.width = this.dimensions.WIDTH + ‘px‘;
 9
10
11 //向前移动50像素
12 if (this.playingIntro && this.xPos < this.config.START_X_POS) {
13     this.xPos += Math.round((this.config.START_X_POS / this.config.INTRO_DURATION) * deltaTime);
14 }

游戏音效

  游戏准备了三种音效,分别是游戏点击空格键开始时、与障碍物碰撞时、每到达100分时。游戏在代码中放置了三个audio标签来存放音效,并且是base64形式,所以在播放时要经过解码,可以查阅文档了解AudioContext API的用法:

 1 function decodeBase64ToArrayBuffer(base64String) {
 2     var len = (base64String.length / 4) * 3;
 3     var str = atob(base64String);
 4     var arrayBuffer = new ArrayBuffer(len);
 5     var bytes = new Uint8Array(arrayBuffer);
 6
 7     for (var i = 0; i < len; i++) {
 8         bytes[i] = str.charCodeAt(i);
 9     }
10     return bytes.buffer;
11 }

 1 var data = ‘........base64String.......‘;
 2 var soundFx = {};
 3 var soundSrc = data.substr(data.indexOf(‘,‘)+1);
 4 var buffer = decodeBase64ToArrayBuffer(soundSrc);
 5 var audioContext = new AudioContext();
 6 audioContext.decodeAudioData(buffer,function(index,audioData) {
 7     soundFx[index] = audioData;
 8 }.bind(this,‘audio1‘));
 9
10 function playSound(soundBuffer) {
11     if (soundBuffer) {
12         var sourceNode = audioContext.createBufferSource();
13         sourceNode.buffer = soundBuffer;
14         sourceNode.connect(audioContext.destination);
15         sourceNode.start(0);
16     }
17 }
18
19 window.onload = function() {
20     playSound(soundFx[‘audio1‘]);
21 };

对移动设备的处理  

  游戏还专门对移动设备进行了处理,包括屏幕大小的自适应,游戏速度调节,为高清屏加载高清素材等等。具体代码就不一一列出了。

  至此,对这个小游戏的代码研究结束,下面是完整的游戏:

总结

  通过对这个游戏的源码进行研究,从中收获了不少干货,对2d游戏的制作思路有一定的启发,特别是基于时间的运动有了进一步的认识。游戏大致可以划分为以下功能:

大部分构造函数里都包含了一个名为update的方法,在每次GameLoop里调用以更新该游戏元件的状态,并根据条件判断是否在画布上绘制(draw)。

暂时就想到这么多,接下来就是以开发一个2D游戏为目标努力了。

时间: 2024-10-12 22:42:01

Chrome自带恐龙小游戏的源码研究(完)的相关文章

Chrome自带恐龙小游戏的源码研究(七)

在上一篇<Chrome自带恐龙小游戏的源码研究(六)>中研究了恐龙的跳跃过程,这一篇研究恐龙与障碍物之间的碰撞检测. 碰撞盒子 游戏中采用的是矩形(非旋转矩形)碰撞.这类碰撞优点是计算比较简单,缺点是对不规则物体的检测不够精确.如果不做更为精细的处理,结果会像下图: 如图所示,两个盒子虽然有重叠部分,但实际情况是恐龙和仙人掌之间并未发生碰撞.为了解决这个问题,需要建立多个碰撞盒子: 不过这样还是有问题,观察图片,恐龙和仙人掌都有四个碰撞盒子,如果每次Game Loop里都对这些盒子进行碰撞检测

Chrome自带恐龙小游戏的源码研究(五)

在上一篇<Chrome自带恐龙小游戏的源码研究(四)>中实现了障碍物的绘制及移动,从这一篇开始主要研究恐龙的绘制及一系列键盘动作的实现. 会眨眼睛的恐龙 在游戏开始前的待机界面,如果仔细观察会发现恐龙会时不时地眨眼睛.这是通过交替绘制这两个图像实现的: 可以通过一张图片来了解这个过程: 为实现图片的切换,需要一个计时器timer,并且需要知道两张图片切换的时间间隔msPerFrame.当计时器timer的时间大于切换的时间间隔msPerFrame时,将图片切换到下一张,到达最后一张时又从第一张

Chrome自带恐龙小游戏的源码研究(六)

在上一篇<Chrome自带恐龙小游戏的源码研究(五)>中实现了眨眼睛的恐龙,这一篇主要研究恐龙的跳跃. 恐龙的跳跃 游戏通过敲击键盘的Spacebar或者Up来实现恐龙的跳跃.先用一张图来表示整个跳跃的过程: 首先规定向下为正方向,即重力加速度(g)为正,起跳的速度(v)为负,恐龙距离画布上方的距离为yPos: 每一帧动画中,速度都会与重力加速度相加得到新的速度,再用新的速度与yPos相加得到新的yPos,改变恐龙的位置为新的yPos,表现出来为yPos不断减小: 当恐龙升至最高点,此时速度为

Chrome自带恐龙小游戏的源码研究(四)

在上一篇<Chrome自带恐龙小游戏的源码研究(三)>中实现了让游戏昼夜交替,这一篇主要研究如何绘制障碍物. 障碍物有两种:仙人掌和翼龙.仙人掌有大小两种类型,可以同时并列多个:翼龙按高.中.低的随机飞行高度出现,不可并行.仙人掌和地面有着相同的速度向左移动,翼龙则快一些或慢一些,因为添加了随机的速度修正.我们使用一个障碍物列表管理它们,当它们移出屏幕外时则将其从列表中移除.同时再用一个列表记录它们的类型: 1 Obstacle.obstacles = []; //存储障碍物的数组 2 Obs

Chrome自带恐龙小游戏的源码研究(二)

在上一篇<Chrome自带恐龙小游戏的源码研究(一)>中实现了地面的绘制和运动,这一篇主要研究云朵的绘制. 云朵的绘制通过Cloud构造函数完成.Cloud实现代码如下: 1 Cloud.config = { 2 HEIGHT:14, //云朵sprite的高度 3 MAX_CLOUD_GAP:400, //两朵云之间的最大间隙 4 MAX_SKY_LEVEL:30, //云朵的最大高度 5 MIN_CLOUD_GAP:100, //两朵云之间的最小间隙 6 MIN_SKY_LEVEL:71,

Chrome自带恐龙小游戏的源码研究(一)

众所周知,Chrome浏览器在网络不通的情况下,会出现一个霸王龙翻越障碍的小游戏:  这个游戏做得小巧精致,于是探究了一下它的源码,发现代码写得相当严谨并且富有技巧性,用来学习再好不过了. 游戏虽然看起来简单,但也有几千行的代码量.主要包括五个构造函数: 游戏逻辑控制函数Runner 背景管理函数Horizon 地面 (HorizonLine) 云朵 (Cloud) 昼夜更替 (NightMode) 障碍物 (Obstacle) 霸王龙函数Trex 分数记录函数DistanceMeter 游戏结

github下载下来的C#控制台小游戏[含源码]

早就听说了github是世界最大的源码库,但自己却不是很懂,今天去研究了下,注册了一个帐号,然后在上面搜索了一下C# game,然后发现有许多的游戏. 随意地选择了一个,感觉比较简单,于是就下载了下来.这个解决方案包含了5个项目,每个项目都是一个小的控制台游戏. 我打开运行了了下,有2个项目报错,但是汽车和乒乓可以运行. 看了下代码,感觉还不错,有许多值得学习的地方. 这个代码库是一个美国人提供的,瞬间感觉自己也变得洋气了起来! 每个项目都只有一个文件,真是够简单. 贴出乒乓的代码看看 usin

c#实现简单金山打字小游戏(源码)

using GameDemo.Utils;using System;using System.Collections.Generic;using System.Linq;using System.Text; namespace GameDemo{ class Program { static void Main(string[] args) { int total=0;//计时 Console.WriteLine("开始游戏"); Console.WriteLine("准备好

[小游戏] 微信小游戏开发源码_教程_工具_资源最新集合

[小游戏资源] 微信小游戏开发资源目录 一.微信官方游戏教程 小游戏简易教程 小游戏API大全 小游戏开发工具 二.微信小游戏图标资源 Game-icons.net 三.微信小游戏图片资源 Super Game Asset GameDev Market envato market Game Art Partners KENNEY 四.微信小游戏音频资源 工具类 Audacity 9 款音频压缩软件推荐 7 款混音软件推荐 7 款降噪软件推荐 资源类 爱给音效库 freesound Soundim