HTML5物理游戏开发 - 越野山地自行车(一)建立各式各样的地形

本文主要是介绍HTML5物理游戏开发 - 越野山地自行车(一)建立各式各样的地形,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在上一篇文章中,我们研究了一下Box2dWeb的锁链效果,当我研究出来以后,便突发奇想地想用可以这一效果制作一个越野自行车小游戏,让一个小自行车在各种地形之间来回颠簸。出于兴趣便对此研究了一番。今天就先来看看越野自行车里的地形是如何实现的。


一,准备工作

首先你需要下载lufylegend和box2dweb 这两个引擎。

box2dweb可以到这里下载:
http://code.google.com/p/box2dweb/downloads/list

lufylegend可以到这里:
http://lufylegend.com/lufylegend

关于lufylegend怎么用,可以到这里看看API文档:
http://lufylegend.com/lufylegend/api

Box2dWeb怎么用?其实我也不太清楚,这次主要用lufylegend封装的API,所以掌握lufylegend对box2dweb的操作就可以了。

在此之后,我们创建一个项目,就叫box2dBicycle吧,然后在里面分别建立data,lib,main文件夹和index.html。如下:

box2dBicycle

|---data

|---lib

|---main

|---index.html

接下来,我们来介绍一下这些文件夹是用来装什么的。data文件夹是用来装游戏中的关卡数据的,我准备把游戏中的关卡用json装起来,这些json就放在这个文件夹里。lib文件夹是装引擎用的,也就是box2dweb和lufylegend.js。main就是放我们源代码文件的。好了,介绍完了准备工作,我们来看看具体的代码。


二,初始游戏

既然是html5游戏,那么一定要有html代码,这些代码就在index.html中放着好了,代码如下:

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>box2d demo</title><script type="text/javascript" src="./lib/Box2dWeb-2.1.a.3.min.js"></script><script type="text/javascript" src="./lib/lufylegend-1.8.7.min.js"></script><script type="text/javascript">init(50,"mylegend",800,450,gameInit);var world;var JS_FILE_PATH = "./main/";var LEVEL_FILE_PATH = "./data/"var loadData = [{path:JS_FILE_PATH+"Main.js",type:"js"},{path:JS_FILE_PATH+"Road.js",type:"js"},{path:JS_FILE_PATH+"BridgeGround.js",type:"js"},{path:JS_FILE_PATH+"SmoothGround.js",type:"js"},{path:JS_FILE_PATH+"HillGround.js",type:"js"},{path:JS_FILE_PATH+"SlopeGround.js",type:"js"},{path:LEVEL_FILE_PATH+"level01.js",type:"js"}];function gameInit(){LStage.setDebug(true);LStage.box2d = new LBox2d();if(LStage.canTouch == true){document.body.style.margin = "0px";document.body.style.padding = "0px";LStage.stageScale = LStageScaleMode.SHOW_ALL;LSystem.screen(LStage.FULL_SCREEN);}LLoadManage.load(loadData,null,function(){world = new Main();addChild(world);world.init();});}</script>
</head>
<body><div id="mylegend"></div>
</body>
</html>

中间有一大段是js代码,这些代码就是游戏中的数据加载和lufylegend初始化,游戏全屏设置等。

接下来逐句解释一下这个js里的代码。首先是

init(50,"mylegend",800,450,gameInit);
也许你在纳闷这个init函数是啥,让我来告诉大家好了,这个函数是初始化lufylegend,也就是说是lufylegend中定义的。我这么写一行就说明要在id为"mylegend"的div中创建一个宽800,高450的canvas,游戏界面刷新次数为50ms一次,当界面初始完成后,调用gameInit函数。当然,具体用法还是看lufylegend.js api文档吧。

接着是一些变量:

var world;
var JS_FILE_PATH = "./main/";
var LEVEL_FILE_PATH = "./data/"
var loadData = [{path:JS_FILE_PATH+"Main.js",type:"js"},{path:JS_FILE_PATH+"Road.js",type:"js"},{path:JS_FILE_PATH+"BridgeGround.js",type:"js"},{path:JS_FILE_PATH+"SmoothGround.js",type:"js"},{path:JS_FILE_PATH+"HillGround.js",type:"js"},{path:JS_FILE_PATH+"SlopeGround.js",type:"js"},{path:LEVEL_FILE_PATH+"level01.js",type:"js"}
];

这些变量各有所用,打头的那个world是用于保存整个界面对象用的,现在暂时没有赋值,但在后面大家会看到他是一个Main类的实例,Main类是啥?在后面慢慢讲吧。然后JS_FILE_PATH和LEVEL_FILE_PATH是用来保存公共路径用的。loadData是装有需要加载资源的数组,这个数组里的内容写法有点特殊,不过是个lufylegend的固定写法,所以必须遵循库件规范。其中,每一条就是一个资源,可以是图片,可以是js文件,txt文件,mp3文件……在这里我用的是加载js,为什么不加载图片呢?因为没有用到图片呗……加载js文件时,需要设置path属性(也就是js文件路径),type属性(用于识别加载方式,加载js则填写为"js",txt就是"txt")。当然加载图片时的写法就不一样了,可以参考我以前写的文章,里面有加载图片时的资源列表的写法。

好了,这些变量解释完了,就来看gameInit部分了:

function gameInit(){LStage.setDebug(true);LStage.box2d = new LBox2d();if(LStage.canTouch == true){document.body.style.margin = "0px";document.body.style.padding = "0px";LStage.stageScale = LStageScaleMode.SHOW_ALL;LSystem.screen(LStage.FULL_SCREEN);}LLoadManage.load(loadData,null,function(){world = new Main();addChild(world);world.init();});
}
首先,因为我们的游戏中暂时没有图片,所以为了看到物理刚体我们就要设置debug模式,也就是通过LStage.setDebug(true)来实现。然后初始lufylegend中的box2d,所以用到了LStage.box2d = new LBox2d()。再来看看接下来的这段代码:

if(LStage.canTouch == true){document.body.style.margin = "0px";document.body.style.padding = "0px";LStage.stageScale = LStageScaleMode.SHOW_ALL;LSystem.screen(LStage.FULL_SCREEN);
}
这些代码有啥用呢?还是让我来告诉你吧,首先开头的if是为了限制平台用的,LStage这个静态类有一个canTouch属性,用来判断是否是移动端。如果是(true),那么就进入if中的代码,if中的代码是为了设置全屏用的,前两行是把body元素位置归0,然后通过LSystem.screen(LStage.FULL_SCREEN)来设置全屏,改LStage.stageScale属性是为了设置全屏模式,除了LStageScaleMode.SHOW_ALL之外,还有其他的几种,具体的还是看API文档吧。

最后到了加载资源这一步骤:

LLoadManage.load(loadData,null,function(){world = new Main();addChild(world);world.init();
});

LLoadManage这个类功能挺强大的,主要是可以将多个数据一起加载到游戏中(这里所说的“多个数据”就是上面我们写的那个名叫loadData的数组,也就是传给LLoadManage.load的第一个参数)。详细用法请移步API文档。这里只介绍最后一个参数,这个参数是一个function,这个function是在所有资源被加载完成之后调用的。所以说我们游戏一切的开始都应该是在这个函数中调用的,要不然加载的图片和js文件就使用不上了呢。当然在这里这个函数里的内容就是实例一个Main类出来,然后用addChild这个lufylegend中的方法把他加入到界面中,并调用init这个成员函数来初始化游戏,这个Main类会在下文作详细解释。关于addChild(obj),这个函数是用来添加显示对象到底层用的,也有对应的spriteObj.addChild(obj),也就是把obj添加到spriteObj里,这个spriteObj必须是一个LSprite,具体的解释还是看API文档吧,讲得绝对比我详细。

ok,游戏代码中的index.html讲完了。接下来切换主题,蹦到很基础的lufylegend添加刚体讲解。


三,基础讲解


1,什么是刚体

说实话,这个刚体是什么我也不是很清楚,我们不妨把它当成一个现实生活中的物体?刚体其实还有一个比较详细的解释,这个解释来自《HTML5 Canvas游戏开发实战》一书:刚体表示十分坚硬的物质,它上面任意两点的位置都是完全不变的,它就像“钻石”那样“坚硬”。


2,在lufylegend中,如何创建刚体

在lufylegend中,可以通过box2dweb创建圆形,方形,凸多边形(凹多边形也可以,但是box2dweb中对凹多边形碰撞处理有一些问题,所以不推荐使用)这几种刚体。

创建圆形刚体

var cLayer = new LSprite();
cLayer.x = 50 + Math.random()*700;
cLayer.y = 50;
addChild(cLayer);
//给LSprite加入圆形刚体
cLayer.addBodyCircle(50,50,50,1,0.5,0.4,0.5);
addBodyCircle(radius, cx, cy, type, density, friction, restitution)

■参数:
radius:半径
cx:圆心坐标x
cy:圆心坐标y
type:是否动态(1或0)
density:密度
friction:摩擦
restitution:弹性


创建方形刚体

cLayer = new LSprite();
cLayer.x = 50 + Math.random()*700;
cLayer.y = 50;
addChild(cLayer);
//给LSprite加入方形刚体
cLayer.addBodyPolygon(bitmap.getWidth(),bitmap.getHeight(),1,5,.4,.2);
addBodyPolygon(width, height, type, density, friction, restitution)

■参数:
width:矩形宽
height:矩形高
type:是否动态(1或0)
density:密度
friction:摩擦
restitution:弹性


创建多边形刚体

cLayer = new LSprite();
cLayer.x = 50 + Math.random()*700;
cLayer.y = 50;
addChild(cLayer);
//设置多边形顶点数组
var shapeArray = [[[0,54],[27,0],[54,54]]
];
//给LSprite添加多边形刚体
cLayer.addBodyVertices(shapeArray,27,27,1,.5,.4,.5);

addBodyVertices(v, cx, cy, type, density, friction, restitution)

■参数:
v:顶点数组
cx:中心坐标x
cy:中心坐标y
type:是否动态(1或0)
density:密度
friction:摩擦
restitution:弹性


ok,有了上面的介绍就可以进入下一个研究环节了。


四,建立各式各样的地形


1,Main类详解

首先来看看类的构造器。

function Main(){var s = this;base(s,LSprite,[]);/**设置场景大小*/s.sceneWidth = 8500;s.sceneHeight = LStage.height+1000;
}
打头的那句var s = this;可能就会懵住许多同学,疑问不在于var的使用,而在于为什么要这么写。其实在这里不这么写,把s换成this应该也可以,但是出于习惯,我还是加上了,原因在于如果以后在构造器里面出现了多个对象的使用,那么用this的指向就会出错。比如说我想在构造器里面加一个调用事件的函数,然后在这个调用的函数里面想访问类的一个属性,那么在这个事件监听函数中用this,指向的应该事件监听函数,而取不到类这个对象。这时候,用s把this代换掉,在事件监听函数中用s就可以来访问类了。第一行代码解释了这么久,相信熟悉js中class的朋友都看得不耐烦了吧。ok,咱们继续看下一行代码。

类里面第二行代码是base函数的运用。这个函数也是lufylegend里的全局函数,用于继承某个类。这里我选择继承了LSprite类。说实话,一般在lufylegend中用到继承,通常都是用的继承LSprite。继承了LSprite之后,我们就可以用到LSprite的一系列函数了,包括添加刚体的函数。

接下来的几行代码就是添加用于控制场景大小用的属性。这里显得它们很酱油,不过在后面还是用到了的。这两个属性顾名思义+注释,所以,不解释……(猛然看到LStage.height这个属性,这个是取canvas大小的属性,当然,还有LStage.width)

很简单的构造器代码就这样地解释完毕了。刚才我们说到Main有个init函数,看看吧?

Main.prototype.init = function(){var s = this;/**加入边框*/s.addBorder();/**加入路面*/s.addRoad();/**加入自行车*/s.addBicycle();/**加入循环事件*/s.addEventListener(LEvent.ENTER_FRAME,s.loop);
};
在这个函数里面,全部都是函数的调用。前三个是我自己拓展的,最后一个addEventListener是lufylegend中LSprite拥有的函数。(按理说应该是LInteractiveObject里才有addEventListener函数,但是LSprite继承自这个类,所以我才说到LSprite拥有这个函数)。这里的addEventListener和原始js中的不同,不过都是加事件用的,这个是仿照ActionScript设计的,参数依此是:[ 事件名称, 事件触发函数 ]。在这里触发的是loop这个成员函数,这个函数在现在没有任何作用,不过,过一阵子后,就必须要在里面做一些处理。

接下来我们来到addBorder这个函数:

Main.prototype.addBorder = function(){var s = this;/**创建边框*///设置边框尺寸var borderSize = 10;//顶部边框var topBorder = new LSprite();topBorder.x = s.sceneWidth/2;topBorder.y = 5;topBorder.addBodyPolygon(s.sceneWidth,borderSize,0);s.addChild(topBorder);//右部边框var rightBorder = new LSprite();rightBorder.x = s.sceneWidth-5;rightBorder.y = s.sceneHeight/2;rightBorder.addBodyPolygon(borderSize,s.sceneHeight,0);s.addChild(rightBorder);//底部边框var bottomBorder = new LSprite();bottomBorder.x = s.sceneWidth/2;bottomBorder.y = s.sceneHeight-5;bottomBorder.addBodyPolygon(s.sceneWidth,borderSize,0);s.addChild(bottomBorder);//左部边框var leftBorder = new LSprite();leftBorder.x = 5;leftBorder.y = s.sceneHeight/2;leftBorder.addBodyPolygon(borderSize,s.sceneHeight,0);s.addChild(leftBorder);
};
从这里开始,我们就已经开始用到lufylegend加物理box2d刚体了。在上面的基础讲解中,我们都已经了解了这些,所以再加上注释,这些代码就不难看懂了。不过也许有朋友会问,加入这些边框是干什么用的?解释起来很简单,如果不加边框,当你加入动态的刚体时,刚体会落到显示范围之外,所以为了防止这一点发生,我们给这个世界加一个限制,把刚体“关”在里面。
然后是addRoad这个函数:

Main.prototype.addRoad = function(){var s = this;/**创建路面*/var roadObj = new Road(0,450);s.addChild(roadObj);
};
在这里面我们又用到了Road类,这个类一样是以后讲解,先把Main讲完再说吧。

然后是addBicycle这个函数

Main.prototype.addBicycle = function(){var s = this;//创建自行车对象s.bicycleObj = new LSprite();s.bicycleObj.x = 50;s.bicycleObj.y = 385;s.bicycleObj.addBodyCircle(30,30,30,1);s.bicycleObj.setBodyMouseJoint(true);s.addChild(s.bicycleObj);
};
由于暂时没有写关于自行车方面的代码,所以就先搞个圆形刚体给大家玩玩吧,当然本章重点不在自行车,在地形呢……上面的代码有一行没有介绍过,那就是setBodyMouseJoint这个函数。这个函数是让刚体可以进行鼠标拖拽用的。因为没有加入按键控制,所以就先来个鼠标操控。

最后到了loop函数。这个函数是在讲addEventListener时提到过的,忘记了的朋友不妨回忆一下那段代码。可以看到,我给的事件名称是LEvent.ENTER_FRAME,这个是哪里定义的呢?其实你知道了也没用,我是不会告诉你这个LEvent.ENTER_FRAME的值只是一个字符传而已。这个LEvent.ENTER_FRAME是lufylegend中的事件轴事件,时间轴事件是个啥?这个虽然问得很好,但是我在很多文章中都讲过,所以这里不讲……想了解更多的朋友不妨移步到《HTML5游戏引擎Lufylegend.js深入浅出》-【基础篇】-引擎介绍&原理 里面有详细的解释呢。回归正题,看看loop里的代码吧:

Main.prototype.loop = function(event){var s = event.target;var bo = s.bicycleObj;/**设置场景位置*/s.x = LStage.width*0.5 - (bo.x + bo.getWidth()*0.5);s.y = LStage.height*0.5 - (bo.y + bo.getHeight()*0.5);/**处理位置*/if(s.x > 0){s.x = 0;}else if(s.x < LStage.width - s.sceneWidth){s.x = LStage.width - s.sceneWidth;}if(s.y > 0){s.y = 0;}else if(s.y < LStage.height - s.sceneHeight){s.y = LStage.height - s.sceneHeight;}//计算刚体坐标LStage.box2d.synchronous();
};
首先我们通过时间轴事件触发时传给监听函数的event参数的target成员获取到自身这个对象(Main对象),然后,接下来的处理就是为了达到跟随镜头的效果。我们知道,在这种同类型的游戏中,场景通常都很大,如果不把镜头跟随自行车,那么,自行车开跑了,那就会直接跑到屏幕外去了,看不着了,所以在这里要处理一下。中间部分代码很简单,就是设置一些坐标罢了。只有最后一行LStage.box2d.synchronous()可能使大家有点懵。这个是lufylegend中用来重新计算刚体坐标用的。我们知道,把刚体加入到界面上后,刚体会进行物理运动,所以我们把刚体所在的LSprite改变了,刚体位置是会不变的,所以我们要把它们的位置重新计算一下好了。

好了,Main讲完了,该讲Road类了。


2,Road类

这个类顾名思义,就是用来创建各种地形的。先看构造器中的代码:

function Road(sx,sy){var s = this;base(s,LSprite,[]);//设置起始位置s.sx = sx;s.sy = sy;//设置新对象出现位置s.newObjPosX = s.sx;s.newObjPosY = s.sy;/**设置路面数据*/s.roadData = level01//初始化s.init();
}
同样是一些属性的设置,很简单。值得注意的是,roadData这个属性是赋值level01,这个前面所说的地形数据文件,也就是在data文件夹下的文件。我们先创建一个简单的地形,所以在level01.js中加入如下代码:

var level01 = [{type:Road.TYPE.Ground,groundWidth:200,groundHeight:50,terrain:Road.TERRAIN.Smooth},{type:Road.TYPE.Bridge,plankWidth:50,plankHeight:25,plankAmount:10},{type:Road.TYPE.Ground,groundWidth:600,groundHeight:50,terrain:Road.TERRAIN.Smooth}
];
这个很明显是一个数组套json的数据格式,其中数组中每一条都是一个地形区,比如说Road.TYPE.Ground就是代表地面,然后地面又分地形,比如说Road.TERRAIN.Smooth代表平地。每一种不同的地形就会有不同的数据名称,比如说平地中有groundWidth和groundHeight,而吊桥(Road.TYPE.Bridge)中有plankWidth,plankHeight,plankAmount。至于其他的地形,我们以后再说吧。

回到Road类中,我们在上面的level01.js中看到了Road.XXX.xxx,这是在哪里定义的呢?看这里吧:

Road.TYPE = {//锁链桥Bridge:"bridge",//地面Ground:"ground",//空隙Spacing:"spacing"
};
Road.TERRAIN = {//平地Smooth:"smooth",//斜坡Slope:"slope",//山坡Hill:"hill"
};
我们暂时先定义这么多,以后有其他的地形再拓展吧。在Road类的构造器中调用过init这个函数,这个函数的代码如下:

Road.prototype.init = function(){var s = this;for(var key in s.roadData){var item = s.roadData[key];switch(item.type){/**路地*/case Road.TYPE.Ground:s.addGround(item);break;/**锁桥*/case Road.TYPE.Bridge:s.addBridge(item);break;/**空隙*/case Road.TYPE.Spacing:s.addSpacing(item);break;}}
};
在这个函数中,我们用到for和switch来加入不同的地形。接下来是addGround和addBridge以及addSpacing里的代码:

Road.prototype.addSpacing = function(data){var s = this;//设置新对象出现位置s.newObjPosX += data.spacingWidth;s.newObjPosY += data.spacingHeight;
};
Road.prototype.addBridge = function(data){var s = this;//加入锁链桥var bridgeGroundObj = new BridgeGround(s.newObjPosX,s.newObjPosY,data.plankAmount,data.plankWidth,data.plankHeight);world.addChild(bridgeGroundObj);//设置新对象出现位置s.newObjPosX += bridgeGroundObj.getGroundWidth();
};
Road.prototype.addGround = function(data){var s = this;switch(data.terrain){case Road.TERRAIN.Smooth://加入平地路面var smoothGroundObj = new SmoothGround(s.newObjPosX,s.newObjPosY,data.groundWidth,data.groundHeight);world.addChild(smoothGroundObj);//设置新对象出现位置s.newObjPosX += smoothGroundObj.getGroundWidth();break;case Road.TERRAIN.Hill://加入山坡路面var hillGroundObj = new HillGround(s.newObjPosX,s.newObjPosY,data.slopeAmount,data.hillWidth,data.hillHeight,data.groundWidth,data.groundHeight);world.addChild(hillGroundObj);//设置新对象出现位置s.newObjPosX += hillGroundObj.getGroundWidth();break;case Road.TERRAIN.Slope://加入斜坡路面var slopeGroundObj = new SlopeGround(s.newObjPosX,s.newObjPosY,data.groundWidth,data.groundHeight,data.angle);world.addChild(slopeGroundObj);//设置新对象出现位置s.newObjPosX += slopeGroundObj.getGroundWidth();s.newObjPosY += slopeGroundObj.getGroundHeight();break;}
};
这看上去是一大串很长的代码,其实结构相似,主要是需要注意newObjPosX和newObjPosY这个属性的变换。当这个属性是用于确定下一个物体出现时的位置。在addSpacing时,没有其他的什么物体要加入,所以直接把newObjPosX和newObjPosY改变了就能达到效果。在addBridge中,出现这个类:BridgeGround。这个类就是在上一篇文章中所说的铁锁桥类。具体实现过程就看上一篇文章吧: 【HTML5物理小Demo】用Box2dWeb实现锁链+弹簧效果

介绍完addSpacing和addBridge,我们来到了addGround函数,这个函数和上面两个有点区别,因为地面会分很多种地形,所以我们首先要用switch来区分到底添加哪个地形。我打算分成平地路面、斜坡路面、山坡路面。在平地路面中,我们用到了SmoothGround类,在斜坡路面用到了SlopeGround类,山坡路面用的是HillGround。接下来我们依此来看看这些类中的代码,先是SmoothGround:

function SmoothGround(sx,sy,w,h){var s = this;base(s,LSprite,[]);//保存路面尺寸s.groundW = w;s.groundH = h;//设置位置s.x = sx+w/2;s.y = sy;//初始化s.init();
}
SmoothGround.prototype.init = function(){var s = this;//加入刚体s.addBodyPolygon(s.groundW,s.groundH,0);
};
SmoothGround.prototype.getGroundWidth = function(){return this.groundW;
};
SmoothGround.prototype.getGroundHeight = function(){return this.groundH;
};

这个类是最简单的,所以就不加以分析了。接下来轮到SlopeGround类了:

function SlopeGround(sx,sy,w,h,angle){var s = this;base(s,LSprite,[]);//保存路面尺寸s.groundW = w;s.groundH = h;//保存坡度s.angle = angle;//设置位置s.x = sx+s.getGroundWidth()/2-(Math.sin(s.angle*Math.PI/180)*s.groundH*0.5);s.y = sy+s.getGroundHeight()/2;//初始化s.init();
}
SlopeGround.prototype.init = function(){var s = this;//加入刚体s.addBodyPolygon(s.groundW,s.groundH,0);//旋转刚体s.setRotate(s.angle*(Math.PI/180));
};
SlopeGround.prototype.getGroundWidth = function(){return (Math.cos(this.angle*Math.PI/180)*this.groundW);
};
SlopeGround.prototype.getGroundHeight = function(){return (Math.sin(this.angle*Math.PI/180)*this.groundW);
};
这个类和上一个类很类似,但是唯一不同的是我们在这个类中设置了刚体旋转,并且在取尺寸用到的函数getGroundHeight、getGroundWidth都用了sin和cos进行处理。设置刚体旋转用的是setRotate,参数是旋转的弧度(角度a转成弧度公式:a*Math.PI/180)。除了这几点之外,其他就和第一个讲的SmoothGround类没有什么区别了呢。

最后是HillGround,这个类很特殊,需要详细一点讲:

function HillGround(x,y,a,hw,hh,gw,gh){var s = this;base(s,LSprite,[]);//保存山坡尺寸s.hillW = hw;s.hillH = hh;//保存山坡平地部分尺寸s.groundW = gw;s.groundH = gh;//保存山坡数量s.hillAmount = a;//设置初始位置s.sx = x;s.sy = y;//初始化s.init();
}
HillGround.prototype.init = function(){var s = this;//加入山坡下方的平地var ground = new LSprite();ground.x = s.sx+s.getGroundWidth()/2;ground.y = s.sy;ground.addBodyPolygon(s.getGroundWidth(),s.groundH,0);world.addChild(ground);//设置山坡顶点初始位置var toX = 0;var toY = 0;//循环添加山坡for(var i=0; i<s.hillAmount; i++){//设置山坡顶点数组var shapeArray = new Array();shapeArray.push(new Array());shapeArray[0].push([toX,toY]);shapeArray[0].push([toX+=s.hillW,toY-=s.hillH],[toX+=s.hillW,toY+=s.hillH]);var hill = new LSprite();hill.x = s.sx+s.groundW;hill.y = s.sy-s.hillH/2-s.groundH/2;//绘画多边形刚体hill.addBodyVertices(shapeArray,0,s.hillH/2,0);//加入显示列表world.addChild(hill);}
};
HillGround.prototype.getGroundWidth = function(){return this.groundW*2+this.hillAmount*2*this.hillW;
};
HillGround.prototype.getGroundHeight = function(){return this.groundH+this.hillH;
};
这个类最特别的是init函数部分。在这个函数中,我们为了添加小山丘,所以要画一个三角形刚体,实现三角形刚体可以用画多边形刚体用的函数addBodyVertices(),这个函数在上面的基础讲解部分提到过,参数需要一个顶点数组,所以我们在init中计算了每个顶点的坐标,然后把它们加入到顶点数组中。至于为什么要用for,那是因为考虑到会有连续的小山丘添加的情况。

好了,到了这一步算是成功了一大半。

最后拓展level01.js:

var level01 = [{type:Road.TYPE.Ground,groundWidth:200,groundHeight:50,terrain:Road.TERRAIN.Smooth},{type:Road.TYPE.Bridge,plankWidth:50,plankHeight:25,plankAmount:10},{type:Road.TYPE.Ground,groundWidth:600,groundHeight:50,terrain:Road.TERRAIN.Smooth},{type:Road.TYPE.Ground,groundWidth:400,groundHeight:50,angle:20,terrain:Road.TERRAIN.Slope},{type:Road.TYPE.Ground,groundWidth:400,groundHeight:50,angle:30,terrain:Road.TERRAIN.Slope},{type:Road.TYPE.Ground,groundWidth:200,groundHeight:50,terrain:Road.TERRAIN.Smooth},{type:Road.TYPE.Ground,hillWidth:230,hillHeight:50,groundWidth:40,groundHeight:50,slopeAmount:2,terrain:Road.TERRAIN.Hill},{type:Road.TYPE.Ground,groundWidth:300,groundHeight:50,terrain:Road.TERRAIN.Smooth},{type:Road.TYPE.Ground,groundWidth:400,groundHeight:50,angle:-30,terrain:Road.TERRAIN.Slope},{type:Road.TYPE.Ground,groundWidth:200,groundHeight:50,terrain:Road.TERRAIN.Smooth},{type:Road.TYPE.Bridge,plankWidth:50,plankHeight:25,plankAmount:20},{type:Road.TYPE.Ground,groundWidth:600,groundHeight:50,terrain:Road.TERRAIN.Smooth},{type:Road.TYPE.Spacing,spacingWidth:200,spacingHeight:150,},{type:Road.TYPE.Ground,groundWidth:600,groundHeight:50,terrain:Road.TERRAIN.Smooth},{type:Road.TYPE.Ground,hillWidth:430,hillHeight:150,groundWidth:40,groundHeight:50,slopeAmount:1,terrain:Road.TERRAIN.Hill},{type:Road.TYPE.Ground,groundWidth:800,groundHeight:50,angle:-30,terrain:Road.TERRAIN.Slope},{type:Road.TYPE.Ground,groundWidth:600,groundHeight:50,terrain:Road.TERRAIN.Smooth}
];
运行效果:




测试地址:http://www.cnblogs.com/yorhom/articles/box2dweb_bike1.html
打开测试地址后,用鼠标拖动圆球,使镜头跟随小球移动,看看我们创建的地形怎么样吧。


源代码下载:

http://files.cnblogs.com/yorhom/box2dBicycle%281%29.rar


本章就先到这里了。如果文章有任何疏漏之处,欢迎指正。当然,有不懂之处也欢迎各位在本文下方留言,我会尽力回复大家的。下一章。我们来研究一下如何用box2dweb+lufylegend创建一个自行车,并让这辆自行车能受到我们的控制,敬请期待~

----------------------------------------------------------------

欢迎大家转载我的文章。

转载请注明:转自Yorhom's Game Box

http://blog.csdn.net/yorhomwang

欢迎继续关注我的博客

这篇关于HTML5物理游戏开发 - 越野山地自行车(一)建立各式各样的地形的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/340672

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能