HTML5游戏开发进阶 3 :物理引擎基础

    Box2D最初是有Erin Catto用C++编写的。很多流行的物理仿真类游戏都使用了该引擎,包含《愤怒的小鸟》,该引擎已经被转化为几种不同语言的版本,包括Java,ActionScript,C#和JavaScript。JS版本又被称为Box2dWeb。

    http://code.google.com/p/box2dweb

3.1 Box2D基础

     Box2D使用一些基本对象来定义和模拟游戏世界,其中最重要的有如下几个:

  • world:世界,Box2D的主对象,包含世界中的所有物体,对游戏中的物理现象进行模拟
  • body:刚性的物体,可能由一个或多个形状组成,这些形状通过载具添加到物体上。
  • shape:二维形状,如圆或多边形这些Box2D中用到的基本形状。
  • fixture:载具,用来向物体上添加形状以监测碰撞。载具还包括一些非形状数据,如摩擦系数、碰撞系数和碰撞阀值。
  • joint:接合点,用来以不同的方式将两个物体连接在一起。比如,一个旋转接合点使两个物体共享一个点,它们可以自由地绕着该点旋转。

      在游戏中使用Box2D时,首先要定义游戏的world对象,然后使用载具添加物体和形状。接着,逐个操作world对象中的body对象,让Box2D确定它们的位置和状态。最后,将所有body对象绘制出来。最复杂的计算(即确定物体的位置和状态)由Box2D world对象完成。

      引入Box2D:box2d.html

<!DOCTYPE html>
<html>
	<head>
		<meta http-equiv="Content-type" content="text/html; charset=utf-8">
		<title>Box2d Test</title>
		<script src="Box2dWeb-2.1.a.3.min.js" type="text/javascript" charset="utf-8"></script>		
		<script src="box2d.js" type="text/javascript" charset="utf-8"></script>
	</head>
	<body οnlοad="init();">
		<canvas id="canvas" width="640" height="480" style="border:1px solid black;">Your browser does not support HTML5 Canvas</canvas>
	</body>
</html>
     box2d.js

//为了方便,将常用的对象定义为快捷变量
var b2Vec2 = Box2D.Common.Math.b2Vec2;
var b2BodyDef = Box2D.Dynamics.b2BodyDef;
var b2Body = Box2D.Dynamics.b2Body;
var b2FixtureDef = Box2D.Dynamics.b2FixtureDef;
var b2Fixture = Box2D.Dynamics.b2Fixture;
var b2World = Box2D.Dynamics.b2World;
var b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;
var b2CircleShape = Box2D.Collision.Shapes.b2CircleShape;
var b2DebugDraw = Box2D.Dynamics.b2DebugDraw;
var b2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef;

     定义World变量:重力加速度

//创建b2World对象
var world;
var scale = 30; //在canvas上的30像素表示Box2d世界中的1米
function init() {
	// 创建Box2D world对象,该对象将完成大部分物理计算
	var gravity = new b2Vec2(0, 9.8); //重力加速度9.8m/s^2
	var allowSleep = true;  //允许静止的物体进入休眠状态,休眠物体不参与物理仿真计算
	world = new b2World(gravity, allowSleep);
}

     添加第一物体: 地面  createFloor()
     在Box2D中创建任何物体都需经过以下步骤:

  1. 使用b2BodyDef声明一个body的预定义对象。该对象包含了物体的细节,如物体的位置(x,y)和物体的类型(静态和动态)。静态物体不会受重力加速度和其他物体的碰撞影响。
  2. 使用b2FixtureDef声明一个fixture的预定义对象。载具用来给物体指定形状。该对象也包含了一些其他信息,如指定形状的密度、摩擦系数和弹性恢复系数。
  3. 设置载具指定的形状。这里使用Box2D中两种类型的形状:多边形(b2polygonshape)和圆(b2circleshape)。
  4. 将物体的预定义对象传入createBody()方法中,返回一个body对象。
  5. 将载具的预定义对象传入createFixture()方法中,为物体指定形状。

      绘制世界:调试绘图模式 setupDebugDraw()

      使用DrawDebugData()在给定的canvas上绘制出世界

      动画:步骤

  • 告诉Box2D为很短时间进行一次模拟。使用world.Step()方法完成这一步
  • 将所有的物体重新绘制在新的位置。使用world.DrawDebugData()或这些物体自身的绘制函数
  • 使用world.ClearForces()方法清除作用在物体上的力。

//为了方便,将常用的对象定义为快捷变量
var b2Vec2 = Box2D.Common.Math.b2Vec2;
var b2BodyDef = Box2D.Dynamics.b2BodyDef;
var b2Body = Box2D.Dynamics.b2Body;
var b2FixtureDef = Box2D.Dynamics.b2FixtureDef;
var b2Fixture = Box2D.Dynamics.b2Fixture;
var b2World = Box2D.Dynamics.b2World;
var b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;
var b2CircleShape = Box2D.Collision.Shapes.b2CircleShape;
var b2DebugDraw = Box2D.Dynamics.b2DebugDraw;
var b2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef;

//创建b2World对象
var world;
var scale = 30; //在canvas上的30像素表示Box2d世界中的1米
function init() {
	// 创建Box2D world对象,该对象将完成大部分物理计算
	var gravity = new b2Vec2(0, 9.8); //重力加速度9.8m/s^2
	var allowSleep = true;  //允许静止的物体进入休眠状态,休眠物体不参与物理仿真计算
	world = new b2World(gravity, allowSleep);

	createFloor();
	setupDebugDraw();
	animate();
}
//创建地面
function createFloor() {
	//body的预定义对象,包含创建body刚体需要用到的所有数据
	var bodyDef = new b2BodyDef;
	bodyDef.type = b2Body.b2_staticBody; //静态 地面不受重力或其他物体碰撞影响
	bodyDef.position.x = 640/2/scale;  // 位置 x=320px, y=450px
	bodyDef.position.y = 450/scale;     
	// fixture用来向body添加shape以实现碰撞检测
	// fixture的预定义对象,用来建立fixture
	var fixtureDef = new b2FixtureDef;
	fixtureDef.density = 1.0;   //形状的密度、摩擦系数、弹性恢复系数等属性
	fixtureDef.friction = 0.5;
	fixtureDef.restitution = 0.2;
	fixtureDef.shape = new b2PolygonShape;  //多边形
	fixtureDef.shape.SetAsBox(320/scale, 10/scale); //640宽,20高

	var body = world.CreateBody(bodyDef);
	var fixture = body.CreateFixture(fixtureDef);
}
//设置调试绘图
var context;
function setupDebugDraw() {
	context = document.getElementById("canvas").getContext('2d');
	var debugDraw = new b2DebugDraw();
	//使用canvas绘图环境来绘制调试画面
	debugDraw.SetSprite(context);
	//设置绘图比例
	debugDraw.SetDrawScale(scale);
	//填充的透明度为0.3
	debugDraw.SetFillAlpha(0.3);
	//线条的宽度为1
	debugDraw.SetLineThickness(1.0);
	//绘制所有的shape和joint
	debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
	//设置调制绘图模式
	world.SetDebugDraw(debugDraw);
}
//设立Box2D动画循环
var timeStep = 1/60;
//按照Box2D手册建议的迭代数,速度是8,位置是3
var velocityIterations = 8;
var positionIterations = 3;
function animate() {
	world.Step(timeStep, velocityIterations, positionIterations);
	world.ClearForces();
	world.DrawDebugData();
	setTimeout(animate, timeStep);
}

3.2 更多的Box2D元素

     Box2D允许向世界中添加不同种类的元素,包括:

  • 简单形状的物体,如矩形、圆、多边形。
  • 复杂的由多个形状组成的物体。
  • 接合点,如连接多个物体的旋转接合点
  • 接触监听器,允许我们处理碰撞事件。

     创建矩形物体:createRectangularBody()

     创建圆形物体:createCircularBody()

     创建多边形:createSimplePolygonBody()

     创建多种形状的复杂物体:createComplexBody()
     连接物体的接合点:接合点用来将物体与物体之间连接起来。包括滑轮、齿轮、杆、转动关节和焊接点。

     createRevoluteJoint()函数

3.3 追踪碰撞与破坏

     在调查一个物体是否被损坏前,需要将该物体与“生命值”或“健康值”联系起来。

     创建具有自定义属性的物体:createSpecialBody()

     接触监听器:b2ContactListener对象,有四个事件

  • BeginContact():两个物体开始接触时被调用
  • EndContact():两个物体结束接触时被调用
  • PostSolve():求解器完成后调用,进行碰撞检测时很有用
  • PreSolve():在求解器求解前调用

     实现: listenForContact()函数

     摧毁物体:

3.4 绘制角色 

//为了方便,将常用的对象定义为快捷变量
var b2Vec2 = Box2D.Common.Math.b2Vec2;
var b2BodyDef = Box2D.Dynamics.b2BodyDef;
var b2Body = Box2D.Dynamics.b2Body;
var b2FixtureDef = Box2D.Dynamics.b2FixtureDef;
var b2Fixture = Box2D.Dynamics.b2Fixture;
var b2World = Box2D.Dynamics.b2World;
var b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;
var b2CircleShape = Box2D.Collision.Shapes.b2CircleShape;
var b2DebugDraw = Box2D.Dynamics.b2DebugDraw;
var b2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef;

//创建b2World对象
var world;
var scale = 30; //在canvas上的30像素表示Box2d世界中的1米
function init() {
	// 创建Box2D world对象,该对象将完成大部分物理计算
	var gravity = new b2Vec2(0, 9.8); //重力加速度9.8m/s^2
	var allowSleep = true;  //允许静止的物体进入休眠状态,休眠物体不参与物理仿真计算
	world = new b2World(gravity, allowSleep);

	createFloor();
	//创建一些具有简单形状的物体
	createRectangularBody();
	createCircularBody();
	createSimplePolygonBody();
	createComplexBody();
	createRevoluteJoint();

	createSpecialBody();
	listenForContact();

	setupDebugDraw();
	animate();
}
//创建地面
function createFloor() {
	//body的预定义对象,包含创建body刚体需要用到的所有数据
	var bodyDef = new b2BodyDef;
	bodyDef.type = b2Body.b2_staticBody; //静态 地面不受重力或其他物体碰撞影响
	bodyDef.position.x = 640/2/scale;  // 位置 x=320px, y=450px
	bodyDef.position.y = 450/scale;     
	// fixture用来向body添加shape以实现碰撞检测
	// fixture的预定义对象,用来建立fixture
	var fixtureDef = new b2FixtureDef;
	fixtureDef.density = 1.0;   //形状的密度、摩擦系数、弹性恢复系数等属性
	fixtureDef.friction = 0.5;
	fixtureDef.restitution = 0.2;
	fixtureDef.shape = new b2PolygonShape;  //多边形
	fixtureDef.shape.SetAsBox(320/scale, 10/scale); //640宽,20高

	var body = world.CreateBody(bodyDef);
	var fixture = body.CreateFixture(fixtureDef);
}
//设置调试绘图
var context;
function setupDebugDraw() {
	context = document.getElementById("canvas").getContext('2d');
	var debugDraw = new b2DebugDraw();
	//使用canvas绘图环境来绘制调试画面
	debugDraw.SetSprite(context);
	//设置绘图比例
	debugDraw.SetDrawScale(scale);
	//填充的透明度为0.3
	debugDraw.SetFillAlpha(0.3);
	//线条的宽度为1
	debugDraw.SetLineThickness(1.0);
	//绘制所有的shape和joint
	debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
	//设置调制绘图模式
	world.SetDebugDraw(debugDraw);
}
//设立Box2D动画循环
var timeStep = 1/60;
//按照Box2D手册建议的迭代数,速度是8,位置是3
var velocityIterations = 8;
var positionIterations = 3;
function animate() {
	world.Step(timeStep, velocityIterations, positionIterations);
	world.ClearForces();
	world.DrawDebugData();
	setTimeout(animate, timeStep);

    //自定义绘制
    if (specialBody) {
    	drawSpecialBody();
    }

	//摧毁耗尽生命值的物体
	if (specialBody && specialBody.GetUserData().life <= 0){
		//world.DestroyBody(specialBody);
		//specialBody = undefined;
		console.log("destroyed");
	}
	setTimeout(animate, timeStep);
}
//创建矩形物体
function createRectangularBody(){
	var bodyDef = new b2BodyDef;
	bodyDef.type = b2Body.b2_dynamicBody;
	bodyDef.position.x = 40/scale;
	bodyDef.position.y = 100/scale;
	var fixtureDef = new b2FixtureDef;
	fixtureDef.density = 1.0;
	fixtureDef.friction = 0.5;
	fixtureDef.restitution = 0.3;
	fixtureDef.shape = new b2PolygonShape;  //多边形
	fixtureDef.shape.SetAsBox(30/scale, 50/scale); //60宽,100高

	var body = world.CreateBody(bodyDef);
	var fixture = body.CreateFixture(fixtureDef);
}
//创建一个圆形
function createCircularBody(){
	var bodyDef = new b2BodyDef;
	bodyDef.type = b2Body.b2_dynamicBody;
	bodyDef.position.x = 130/scale;
	bodyDef.position.y = 100/scale;
	var fixtureDef = new b2FixtureDef;
	fixtureDef.density = 1.0;
	fixtureDef.friction = 0.5;
	fixtureDef.restitution = 0.7;
	fixtureDef.shape = new b2CircleShape(30/scale);  //

	var body = world.CreateBody(bodyDef);
	var fixture = body.CreateFixture(fixtureDef);
}
//创建多边形物体
function createSimplePolygonBody(){
	var bodyDef = new b2BodyDef;
	bodyDef.type = b2Body.b2_dynamicBody;
	bodyDef.position.x = 230/scale;
	bodyDef.position.y = 50/scale;

	var fixtureDef = new b2FixtureDef;
	fixtureDef.density = 1.0;
	fixtureDef.friction = 0.5;
	fixtureDef.restitution = 0.2;
	fixtureDef.shape = new b2PolygonShape;  
	//按顺时针方向创建一个b2Vec2顶点数组
	var points = [
	   new b2Vec2(0,0),
	   new b2Vec2(40/scale,50/scale),
	   new b2Vec2(50/scale,100/scale),
	   new b2Vec2(-50/scale,100/scale),
	   new b2Vec2(-40/scale,50/scale),
	];
	fixtureDef.shape.SetAsArray(points, points.length);
	var body = world.CreateBody(bodyDef);
	var fixture = body.CreateFixture(fixtureDef);
}
// 创建多种形状的复杂物体,由两个形状组成的物体
function createComplexBody(){
	var bodyDef = new b2BodyDef;
	bodyDef.type = b2Body.b2_dynamicBody;
	bodyDef.position.x = 350/scale;
	bodyDef.position.y = 50/scale;
	var body = world.CreateBody(bodyDef);

    //创建第一个载具并为物体添加圆形状
	var fixtureDef = new b2FixtureDef;
	fixtureDef.density = 1.0;
	fixtureDef.friction = 0.5;
	fixtureDef.restitution = 0.7;
	fixtureDef.shape = new b2CircleShape(40/scale);  //
    body.CreateFixture(fixtureDef);

    //创建第二个
	fixtureDef.shape = new b2PolygonShape;  
	//按顺时针方向创建一个b2Vec2顶点数组
	var points = [
	   new b2Vec2(0,0),
	   new b2Vec2(40/scale,50/scale),
	   new b2Vec2(50/scale,100/scale),
	   new b2Vec2(-50/scale,100/scale),
	   new b2Vec2(-40/scale,50/scale),
	];
	fixtureDef.shape.SetAsArray(points, points.length);
	var fixture = body.CreateFixture(fixtureDef);
}
//创建转动关节
function createRevoluteJoint(){
	//定义第一个物体
	var bodyDef1 = new b2BodyDef;
	bodyDef1.type = b2Body.b2_dynamicBody;
	bodyDef1.position.x = 480/scale;
	bodyDef1.position.y = 50/scale;
	var body1 = world.CreateBody(bodyDef1);

	var fixtureDef1 = new b2FixtureDef;
	fixtureDef1.density = 1.0;
	fixtureDef1.friction = 0.5;
	fixtureDef1.restitution = 0.5;
	fixtureDef1.shape = new b2PolygonShape;  //多边形
	fixtureDef1.shape.SetAsBox(50/scale, 10/scale); //60宽,100高

	body1.CreateFixture(fixtureDef1);

    //定义第二个物体
	var bodyDef2 = new b2BodyDef;
	bodyDef2.type = b2Body.b2_dynamicBody;
	bodyDef2.position.x = 470/scale;
	bodyDef2.position.y = 50/scale;
	var body2 = world.CreateBody(bodyDef2);

	var fixtureDef2 = new b2FixtureDef;
	fixtureDef2.density = 1.0;
	fixtureDef2.friction = 0.5;
	fixtureDef2.restitution = 0.5;
	fixtureDef2.shape = new b2PolygonShape;  //多边形
	var points = [
	   new b2Vec2(0,0),
	   new b2Vec2(40/scale,50/scale),
	   new b2Vec2(50/scale,100/scale),
	   new b2Vec2(-50/scale,100/scale),
	   new b2Vec2(-40/scale,50/scale),
	];
	fixtureDef2.shape.SetAsArray(points, points.length);

	body2.CreateFixture(fixtureDef2);

	//创建接合点连接body1和body2
	var jointDef = new b2RevoluteJointDef;
	var jointCenter = new b2Vec2(470/scale, 50/scale);

	jointDef.Initialize(body1, body2, jointCenter);
	world.CreateJoint(jointDef);	
}
//创建具有自定义属性的物体
var specialBody;
function createSpecialBody(){
	var bodyDef = new b2BodyDef;
	bodyDef.type = b2Body.b2_dynamicBody;
	bodyDef.position.x = 450/scale;
	bodyDef.position.y = 0/scale;

	specialBody = world.CreateBody(bodyDef);
	specialBody.SetUserData({name:"special", life:250});

	var fixtureDef = new b2FixtureDef;
	fixtureDef.density = 1.0;
	fixtureDef.friction = 0.5;
	fixtureDef.restitution = 0.5;
	fixtureDef.shape = new b2CircleShape(30/scale);  //

	var fixture = specialBody.CreateFixture(fixtureDef);
}
//实现接触监听器
function listenForContact(){
	var listener = new Box2D.Dynamics.b2ContactListener;
	//参数为接触和冲击力(法向和切向冲击力)
	listener.PostSolve = function(contact, impulse){
		var body1 = contact.GetFixtureA().GetBody();
		var body2 = contact.GetFixtureB().GetBody();
		//如果接触的两个物体都具有生命值,则减少其生命值
		if (body1 == specialBody || body2 == specialBody){
			var impulseAlongNormal = impulse.normalImpulses[0];
			//生命值减少
			specialBody.GetUserData().life -= impulseAlongNormal;
			console.log("impulse:", impulseAlongNormal, 
				"life:", specialBody.GetUserData().life);
		}
	};
	world.SetContactListener(listener);
}
//绘制自己的角色
function drawSpecialBody(){
	//获取body的位置和角度
	var position = specialBody.GetPosition();
	var angle = specialBody.GetAngle();
	//移动并旋转物体
	context.translate(position.x*scale, position.y*scale);
	context.rotate(angle);
	//绘制实心的圆面
	context.fillStyle = "rgb(200, 150, 250);";
	context.beginPath();
	context.arc(0,0,30,0.2*Math.PI,false);
	context.fill();
	//绘制两个矩形的眼睛
	context.fillStyle = "rgb(255, 255, 255);";
	context.fillRect(-15, -15, 10, 5);
	context.fillRect(5, -15, 10, 5);
    //绘制向上或向下的圆弧,根据生命值决定是否微笑
    context.strokeStyle = "rgb(255, 255, 255);";
    context.beginPath();
    if (specialBody.GetUserData().life > 100){
    	context.arc(0,0,10,Math.PI,2*Math.PI,true);
    } else {
    	context.arc(0,10,10,Math.PI,2*Math.PI,false);
    }
    context.stroke();
    //移动并旋转坐标轴至最初的位置和角度
    context.rotate(-angle);
    context.translate(-position.x*scale, -position.y*scale);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值