在Canvas元素上实现视差滚动
http://www.brighthub.com/internet/web-development/articles/40511.aspx
视差滚动是在2D应用中创造立体纵深感的一种技术。这篇文章就来看一看在我们刚刚创建的游戏框架基础上实现视差滚动有多容易。
视差滚动
有了游戏框架,就可以通过画布元素来做一些好玩的东西了。
视差滚动指的是屏幕上的几个图层发生相对位移的效果,换句话说,背景图层滚动得比它前面的那些图层要慢一些。这种创造视觉纵深感的技术在2D游戏中的应用极为普遍。
RepeatingGameObject.js
- /**
- 这个类可以重复显示纹理图像,支持纹理图像在x轴或y轴偏移
- @class
- */
- function RepeatingGameObject(){
- /** 最终图像占据的宽度
- @type Number
- */
- this.width = 0;
- /** 最终图像占据的高度
- @type Number
- */
- this.height = 0;
- /** 绘制时应用多少scrollX和scrollY
- @type Number
- */
- this.scrollFactor = 1;
- /**
- 初始化对象
- @return 对初始化对象的引用
- */
- this.startupRepeatingGameObject = function(image, x, y, z, width, height, scrollFactor){
- this.startupVisualGameObject(image, x, y, z);
- this.width = width;
- this.height = height;
- this.scrollFactor = scrollFactor;
- return this;
- }
- /**
- 清理对象
- */
- this.shutdownstartupRepeatingGameObject = function(){
- this.shutdownVisualGameObject();
- }
- /**
- 把当前元素绘制到后台缓冲
- @param dt 自上一帧绘制起经过的秒数
- @param context 绘制上下文
- @param xScroll x轴的全局滚动值
- @param yScroll y轴的全局滚动值
- */
- this.draw = function(dt, canvas, xScroll, yScroll){
- var areaDrawn = [0, 0];
- for (var y = 0; y < this.height; y += areaDrawn[1]){
- for (var x = 0; x < this.width; x += areaDrawn[0]){
- // 绘制下一张贴片左上角的点
- var newPosition = [this.x + x, this.y + y];
- // 剩余的绘制空间
- var newFillArea = [this.width - x, this.height - y];
- // 第一次必须从图像的中央开始绘制
- // 后续贴片从上方或左侧绘制
- var newScrollPosition = [0, 0];
- if (x==0) newScrollPosition[0] = xScroll * this.scrollFactor;
- if (y==0) newScrollPosition[1] = yScroll * this.scrollFactor;
- areaDrawn = this.drawRepeat(canvas, newPosition, newFillArea, newScrollPosition);
- }
- }
- }
- this.drawRepeat = function(canvas, newPosition, newFillArea, newScrollPosition){
- // 找到重复绘制纹理图像的起点(左上角)
- var xOffset = Math.abs(newScrollPosition[0]) % this.image.width;
- var yOffset = Math.abs(newScrollPosition[1]) % this.image.height;
- var left = newScrollPosition[0]<0?this.image.width-xOffset:xOffset;
- var top = newScrollPosition[1]<0?this.image.height-yOffset:yOffset;
- var width = newFillArea[0] < this.image.width-left?newFillArea[0]:this.image.width-left;
- var height = newFillArea[1] < this.image.height-top?newFillArea[1]:this.image.height-top;
- // 绘制图像
- canvas.drawImage(this.image, left, top, width, height, newPosition[0], newPosition[1], width, height);
- return [width, height];
- }
- }
- RepeatingGameObject.prototype = new VisualGameObject();
这个RepeatingGameObject类可以让图像在一个确定的区域内重复和滚动。此前,我们已经实现了绘制整幅图像。而 RepeatingGameObject的不同之处在于,它拿到一幅图像并用它来填充一块范围既定的区域(其尺寸与绘制的图像无关)。我们通过这个类每次 显示一幅大图像(如一座山的全景图)的一小部分,从而创建出一个背景。
也许你已经注意到了GameObjectManager的xScroll和yScroll属性,它们被传递给了GameObject的draw和 updata函数。这两个值定义的是摄像机沿x轴或y轴移动了多远。RepeatingGameObject使用这两个值来它们显示的纹理,以创造移动的 假象。
首先,定义RepeatingGameObject要绘制的区域。底层的GameObject类的x和y属性定义了左上角位置,而新的width和height属性定义的是绘制区域。
- /** 最终图像占据的宽度
- @type Number
- */
- this.width = 0;
- /** 最终图像占据的高度
- @type Number
- */
- this.height = 0;
- /** 绘制时应用多少scrollX和scrollY
- @type Number
- */
而scrollFactor属性用于改变RepeatingGameObject滚动的量,该变化是通过传递到draw函数的xScroll和yScroll来控制的。将scrollFactor设置为小于1的值,会导致滚动变慢,从而造就画面中的远景。
- /** 绘制时应用多少scrollX和scrollY
- @type Number
- */
- this.scrollFactor = 1;
最后两个draw和drawRepeat函数具体负责渲染贴片及偏移的纹理。
- /**
- 把当前元素绘制到后台缓冲
- @param dt 自上一帧绘制起经过的秒数
- @param context 绘制上下文
- @param xScroll x轴的全局滚动值
- @param yScroll y轴的全局滚动值
- */
- this.draw = function(dt, canvas, xScroll, yScroll){
- var areaDrawn = [0, 0];
- for (var y = 0; y < this.height; y += areaDrawn[1]){
- for (var x = 0; x < this.width; x += areaDrawn[0]){
- // 绘制下一张贴片左上角的点
- var newPosition = [this.x + x, this.y + y];
- // 剩余的绘制空间
- var newFillArea = [this.width - x, this.height - y];
- // 第一次必须从图像的中央开始绘制
- // 后续贴片从上方或左侧绘制
- var newScrollPosition = [0, 0];
- if (x==0) newScrollPosition[0] = xScroll * this.scrollFactor;
- if (y==0) newScrollPosition[1] = yScroll * this.scrollFactor;
- areaDrawn = this.drawRepeat(canvas, newPosition, newFillArea, newScrollPosition);
- }
- }
- }
- this.drawRepeat = function(canvas, newPosition, newFillArea, newScrollPosition){
- // 找到重复绘制纹理图像的起点(左上角)
- var xOffset = Math.abs(newScrollPosition[0]) % this.image.width;
- var yOffset = Math.abs(newScrollPosition[1]) % this.image.height;
- var left = newScrollPosition[0]<0?this.image.width-xOffset:xOffset;
- var top = newScrollPosition[1]<0?this.image.height-yOffset:yOffset;
- var width = newFillArea[0] < this.image.width-left?newFillArea[0]:this.image.width-left;
- var height = newFillArea[1] < this.image.height-top?newFillArea[1]:this.image.height-top;
- // 绘制图像
- canvas.drawImage(this.image, left, top, width, height, newPosition[0], newPosition[1], width, height);
- return [width, height];
- }
ApplicationManager.js
- /**
- ApplicationManager用于管理应用
- @class
- */
- function ApplicationManager(){
- /**
- 初始化对象
- @return A 对初始化对象的引用
- */
- this.startupApplicationManager = function(){
- this.startupGameObject();
- this.background3 = new RepeatingGameObject().startupRepeatingGameObject(g_back2, 0, 100, 3, 600, 320, 1);
- this.background2 = new RepeatingGameObject().startupRepeatingGameObject(g_back1, 0, 100, 2, 600, 320, 0.75);
- this.background = new RepeatingGameObject().startupRepeatingGameObject(g_back0, 0, 0, 1, 600, 320, 0.5);
- return this;
- }
- /**
- 更新当前对象
- @param dt 自上一帧绘制起经过的秒数
- @param context 绘制上下文
- @param xScroll x轴的全局滚动值
- @param yScroll y轴的全局滚动值
- */
- this.update = function(/**Number*/ dt, /**CanvasRenderingContext2D*/ context, /**Number*/ xScroll, /**Number*/ yScroll){
- g_GameObjectManager.xScroll += 50 * dt;
- }
- }
- ApplicationManager.prototype = new GameObject
在这里,我们通过ApplicationManager创建了三个RepeatingGameObject类的实例,每个实例分别显示为一个图层,使用z(深度)和scrollFactor值来创造RepeatingGameObject 渐远和渐慢的效果。
最终结果很不错。视差滚动为画布赋予了完美的纵深感,而整个效果只多编写了一个类就实现了。
看一看视差滚动的Demo吧。http://webdemos.sourceforge.net/jsplatformer4/jsplatformer4.html