使用JavaScript和Canvas开发游戏(五)

在Canvas元素上实现视差滚动

http://www.brighthub.com/internet/web-development/articles/40511.aspx

视差滚动是在2D应用中创造立体纵深感的一种技术。这篇文章就来看一看在我们刚刚创建的游戏框架基础上实现视差滚动有多容易。

视差滚动

有了游戏框架,就可以通过画布元素来做一些好玩的东西了。

视差滚动指的是屏幕上的几个图层发生相对位移的效果,换句话说,背景图层滚动得比它前面的那些图层要慢一些。这种创造视觉纵深感的技术在2D游戏中的应用极为普遍。

RepeatingGameObject.js

01 /**
02     这个类可以重复显示纹理图像,支持纹理图像在x轴或y轴偏移
03     @class
04 */
05 function RepeatingGameObject()
06 {
07     /** 最终图像占据的宽度
08     @type Number
09     */
10     this.width = 0;
11     /** 最终图像占据的高度
12         @type Number
13     */
14     this.height = 0;
15     /** 绘制时应用多少scrollX和scrollY
16     @type Number
17     */
18     this.scrollFactor = 1;
19  
20     /**
21         初始化对象
22         @return 对初始化对象的引用
23     */
24     this.startupRepeatingGameObject = function(image, x, y, z, width, height, scrollFactor)
25     {
26         this.startupVisualGameObject(image, x, y, z);
27         this.width = width;
28         this.height = height;
29         this.scrollFactor = scrollFactor;
30         return this;
31     }
32  
33     /**
34         清理对象
35     */
36     this.shutdownstartupRepeatingGameObject = function()
37     {
38         this.shutdownVisualGameObject();
39     }
40  
41     /**
42         把当前元素绘制到后台缓冲
43         @param dt 自上一帧绘制起经过的秒数
44         @param context 绘制上下文
45         @param xScroll x轴的全局滚动值
46         @param yScroll y轴的全局滚动值
47  
48     */
49     this.draw = function(dt, canvas, xScroll, yScroll)
50     {
51         var areaDrawn = [0, 0];
52  
53         for (var y = 0; y < this.height; y += areaDrawn[1])
54         {
55             for (var x = 0; x < this.width; x += areaDrawn[0])
56             {
57                 // 绘制下一张贴片左上角的点
58         var newPosition = [this.x + x, this.y + y];
59         // 剩余的绘制空间
60                 var newFillArea = [this.width - x, this.height - y];
61         // 第一次必须从图像的中央开始绘制
62         // 后续贴片从上方或左侧绘制
63                 var newScrollPosition = [0, 0];
64                 if (x==0) newScrollPosition[0] = xScroll * this.scrollFactor;
65                 if (y==0) newScrollPosition[1] = yScroll * this.scrollFactor;
66                 areaDrawn = this.drawRepeat(canvas, newPosition, newFillArea, newScrollPosition);
67             }
68         }
69     }
70  
71     this.drawRepeat = function(canvas, newPosition, newFillArea, newScrollPosition)
72     {
73         // 找到重复绘制纹理图像的起点(左上角)
74         var xOffset = Math.abs(newScrollPosition[0]) % this.image.width;
75         var yOffset = Math.abs(newScrollPosition[1]) % this.image.height;
76         var left = newScrollPosition[0]<0?this.image.width-xOffset:xOffset;
77         var top = newScrollPosition[1]<0?this.image.height-yOffset:yOffset;
78         var width = newFillArea[0] < this.image.width-left?newFillArea[0]:this.image.width-left;
79         var height = newFillArea[1] < this.image.height-top?newFillArea[1]:this.image.height-top;
80  
81         // 绘制图像
82         canvas.drawImage(this.image, left, top, width, height, newPosition[0], newPosition[1], width, height);
83  
84         return [width, height];
85     }
86 }
87 RepeatingGameObject.prototype = new VisualGameObject();

这个RepeatingGameObject类可以让图像在一个确定的区域内重复和滚动。此前,我们已经实现了绘制整幅图像。而RepeatingGameObject的不同之处在于,它拿到一幅图像并用它来填充一块范围既定的区域(其尺寸与绘制的图像无关)。我们通过这个类每次显示一幅大图像(如一座山的全景图)的一小部分,从而创建出一个背景。

也许你已经注意到了GameObjectManager的xScroll和yScroll属性,它们被传递给了GameObject的draw和updata函数。这两个值定义的是摄像机沿x轴或y轴移动了多远。RepeatingGameObject使用这两个值来它们显示的纹理,以创造移动的假象。

首先,定义RepeatingGameObject要绘制的区域。底层的GameObject类的x和y属性定义了左上角位置,而新的width和height属性定义的是绘制区域。

01 /** 最终图像占据的宽度
02 @type Number
03 */
04 this.width = 0;
05 /** 最终图像占据的高度
06     @type Number
07 */
08 this.height = 0;
09 /** 绘制时应用多少scrollX和scrollY
10 @type Number
11 */

而scrollFactor属性用于改变RepeatingGameObject滚动的量,该变化是通过传递到draw函数的xScroll和yScroll来控制的。将scrollFactor设置为小于1的值,会导致滚动变慢,从而造就画面中的远景。

1 /** 绘制时应用多少scrollX和scrollY
2 @type Number
3 */
4 this.scrollFactor = 1;

最后两个draw和drawRepeat函数具体负责渲染贴片及偏移的纹理。

01 /**
02     把当前元素绘制到后台缓冲
03     @param dt 自上一帧绘制起经过的秒数
04     @param context 绘制上下文
05     @param xScroll x轴的全局滚动值
06     @param yScroll y轴的全局滚动值
07  
08 */
09 this.draw = function(dt, canvas, xScroll, yScroll)
10 {
11     var areaDrawn = [0, 0];
12  
13     for (var y = 0; y < this.height; y += areaDrawn[1])
14     {
15         for (var x = 0; x < this.width; x += areaDrawn[0])
16         {
17             // 绘制下一张贴片左上角的点
18     var newPosition = [this.x + x, this.y + y];
19     // 剩余的绘制空间
20             var newFillArea = [this.width - x, this.height - y];
21     // 第一次必须从图像的中央开始绘制
22     // 后续贴片从上方或左侧绘制
23             var newScrollPosition = [0, 0];
24             if (x==0) newScrollPosition[0] = xScroll * this.scrollFactor;
25             if (y==0) newScrollPosition[1] = yScroll * this.scrollFactor;
26             areaDrawn = this.drawRepeat(canvas, newPosition, newFillArea, newScrollPosition);
27         }
28     }
29 }
30  
31 this.drawRepeat = function(canvas, newPosition, newFillArea, newScrollPosition)
32 {
33     // 找到重复绘制纹理图像的起点(左上角)
34     var xOffset = Math.abs(newScrollPosition[0]) % this.image.width;
35     var yOffset = Math.abs(newScrollPosition[1]) % this.image.height;
36     var left = newScrollPosition[0]<0?this.image.width-xOffset:xOffset;
37     var top = newScrollPosition[1]<0?this.image.height-yOffset:yOffset;
38     var width = newFillArea[0] < this.image.width-left?newFillArea[0]:this.image.width-left;
39     var height = newFillArea[1] < this.image.height-top?newFillArea[1]:this.image.height-top;
40  
41     // 绘制图像
42     canvas.drawImage(this.image, left, top, width, height, newPosition[0], newPosition[1], width, height);
43  
44     return [width, height];
45 }  

ApplicationManager.js

01 /**
02     ApplicationManager用于管理应用
03     @class
04 */
05 function ApplicationManager()
06 {
07     /**
08         初始化对象
09         @return A 对初始化对象的引用
10     */
11     this.startupApplicationManager = function()
12     {
13         this.startupGameObject();
14     this.background3 = new RepeatingGameObject().startupRepeatingGameObject(g_back2, 0, 100, 3, 600, 320, 1);
15         this.background2 = new RepeatingGameObject().startupRepeatingGameObject(g_back1, 0, 100, 2, 600, 320, 0.75);
16         this.background = new RepeatingGameObject().startupRepeatingGameObject(g_back0, 0, 0, 1, 600, 320, 0.5);
17         return this;
18     }
19  
20     /**
21         更新当前对象
22         @param dt 自上一帧绘制起经过的秒数
23         @param context 绘制上下文
24         @param xScroll x轴的全局滚动值
25         @param yScroll y轴的全局滚动值
26     */
27     this.update = function(/**Number*/ dt, /**CanvasRenderingContext2D*/ context, /**Number*/xScroll, /**Number*/ yScroll)
28     {
29         g_GameObjectManager.xScroll += 50 * dt;
30     }
31 }
32 ApplicationManager.prototype = new GameObject

在这里,我们通过ApplicationManager创建了三个RepeatingGameObject类的实例,每个实例分别显示为一个图层,使用z(深度)和scrollFactor值来创造RepeatingGameObject 渐远和渐慢的效果。

最终结果很不错。视差滚动为画布赋予了完美的纵深感,而整个效果只多编写了一个类就实现了。

看一看视差滚动的Demo吧。http://webdemos.sourceforge.net/jsplatformer4/jsplatformer4.html


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值