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 }

  首先还是和以往一样,对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 };

  先来看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 }

  只要计时器没有超过blinkDelay就不绘制新的图片,这样图片就会停留在上一次绘制的状态,恐龙此时是睁着眼睛的。当时间超过了blinkDelay,即执行眨眼的时间到了,这时会绘制this.currentFrame这一帧。如果这一帧是0(闭眼),由于之前设置了this.timer >= this.msPerFrame时会切换帧,当时间再次超过blinkDelay时,这时就会绘制帧1(睁眼),我们看到的效果就是眼睛闭上只有一瞬然后立刻睁开了。 如果当前帧是1(睁眼),重新设置blinkDelay,于是在deltaTime没有超过重新设置blinkDelay的情况下,都不会绘制新图片(始终保持在帧1(睁眼)),这样我们看到的效果就是睁眼的时间稍长。

下面是运行后的效果:

时间: 2024-10-10 20:55:27

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

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

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

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

在上一篇<Chrome自带恐龙小游戏的源码研究(七)>中研究了恐龙与障碍物的碰撞检测,这一篇主要研究组成游戏的其它要素. 游戏分数记录 如图所示,分数及最高分记录显示在游戏界面的右上角,每达到100分就会出现闪烁特效,游戏第一次gameover时显示历史最高分.分数记录器由DistanceMeter构造函数实现,以下是它的全部代码: 1 DistanceMeter.dimensions = { 2 WIDTH: 10, //每个字符的宽度 3 HEIGHT: 13, //每个字符的高 4 DE

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

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

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

mqtt协议-broker之moqutte源码研究五之UNSUBSCRIBE与DISCONN报文处理

本文讲解moquette对UNSUBSCRIBE和DISCONNECT的处理 先说UNSUBSCRIBE,代码比较简单 public void processUnsubscribe(Channel channel, MqttUnsubscribeMessage msg) { List<String> topics = msg.payload().topics(); String clientID = NettyUtils.clientID(channel); LOG.info("Pr

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("准备好