ifree's Blog - it's my way

用box2d制作完美绳子

前一段时间发现一个有意思的游戏叫 cut the rope ,凭借其简易的交互和巧妙的构思着实让我着迷了一把, 如果以一个游戏人的角度来分析这款游戏,最amazing的还是其流畅的物理场景,这类游戏往 往让玩家感受更多的真实性和趣味性. cut the rope是用cocos2d实现的,这里我用box2d来实现它的核心元素(rope),先看看demo:

(看不到请reload frame)

demo只是简单的实现了绳子,如果想看完整版的割绳子实现近期我会把实现的完整版放上来(完整版:http://mgp.qq.com/flash/76169/)

今天的主题是"make a perfect rope with box2d",so 如果大家感兴趣就跟着我这个 tutorial走吧.:)

 

box2d基础

为了不混淆你的代码,box2d的所有类型都以b2开头,整个box2d物理系统由b2world驱动,通过 b2Wrold的step方法来模拟时间经过.box2d的单位系统完全遵照现实世界,用米,秒,牛等单位, 所以使用代码时务必转换(有人因此吐槽box2d,为什么还要开发人员来转,原因是box2d根本 不需要关心你如何处理pixel,你如何显示资源,它就像我们无法触摸但真是存在的物理世界,= =).b2body则是物理系统中的物件,游戏中就是靠它们来模拟现实中的物理模型,结 合 b2Joint关节来连接 b2Body.

 

 

具体实现

好了,看了上面的介绍你应该对box有一些了解,现在上代码了,关键部分都有注释.

  //为了效率能高点推荐使用requestAnimationFrame
  window.requestAnimFrame = (function(){  //requestAnimFrame fallback
          return  window.requestAnimationFrame       || 
                  window.webkitRequestAnimationFrame || 
                  window.mozRequestAnimationFrame    || 
                  window.oRequestAnimationFrame      || 
                  window.msRequestAnimationFrame     || 
                  function(/* function */ callback, /* DOMElement */ element){
		    window.setTimeout(callback, 1000 / 60); }
})();

(function(){
//命名空间的导入
    var   b2Vec2 = Box2D.Common.Math.b2Vec2
                ,       b2BodyDef = Box2D.Dynamics.b2BodyDef
                ,       b2Body = Box2D.Dynamics.b2Body
                ,       b2FixtureDef = Box2D.Dynamics.b2FixtureDef
                ,       b2Fixture = Box2D.Dynamics.b2Fixture
                ,       b2World = Box2D.Dynamics.b2World
                ,       b2MassData = Box2D.Collision.Shapes.b2MassData
                ,       b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
                ,       b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
                ,       b2DebugDraw = Box2D.Dynamics.b2DebugDraw
            ;
   //这里先创建一个box2d包装器,可以更方便的创建物体啥的而无须关心太多不必要的东西
    window.b2dWrapper=function(stage,scale){
	var gravity = new b2Vec2(0,10);
	var doSleep = true;
	this.world = new b2World( gravity, doSleep); 	
	this.debug=false;
	this.scale=scale;
	//setup debug draw
	var debugDraw = new b2DebugDraw();
	debugDraw.SetSprite(stage);
	debugDraw.SetDrawScale(scale);
	debugDraw.SetFillAlpha(0.3);
	debugDraw.SetLineThickness(1.0);
	debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
	this.world.SetDebugDraw(debugDraw);

	var updateListeners=[];	
	this.addUpdateListener=function(l){
	    updateListeners.push(l);
	}
        //shape工厂
	this.makeShape=function(param){
            //定义常用的参数,创建body时就不必传递太多不必要的参数
	    var defaults = { x: 3,
			     y: 3,
			     width: 3,
			     height: 3,
			     type:'box',
			     scale: 0, 
			     linearDamping: 0,
			     angularDamping: 0,
			     fixedRotation: false, 
			     allowSleep: true, 
			     isSleeping: false,
			     isSensor: false, 
			     isBullet: false, 
			     isDynamic: true,
			     skin: null, //...
			     scaleSkin: true,
			     density: 1.0,
			     friction: 0.5,
			     restitution: 0.2, 
			     angle: 0.0,
			     categoryBits:0x0001,
			     maskBits:0xFFFF,
			     groupIndex:0
			   };
			var p=Object.extend(defaults,param);
			if (p.width)
				p.width /= 2;
			if (p.height)
				p.height /= 2;
			
			var bodyDef = new b2BodyDef();
			var fixtureDef = new b2FixtureDef();
			var shape=null;
			var body;
			
			if (p.scale == 0)
				p.scale = this.scale;
			
			bodyDef.position.x = p.x / p.scale;
			bodyDef.position.y = p.y / p.scale;
			bodyDef.angle = p.angle;
			bodyDef.linearDamping = p.linearDamping;
			bodyDef.angularDamping = p.angularDamping;
			bodyDef.fixedRotation = p.fixedRotation;
			bodyDef.allowSleep = p.allowSleep;
			bodyDef.linearDamping = p.linearDamping;
			bodyDef.angularDamping = p.angularDamping;
			bodyDef.awake = !p.isSleeping;
			bodyDef.bullet = p.isBullet;
			bodyDef.type = p.isDynamic ? b2Body.b2_dynamicBody : b2Body.b2_staticBody;
			//filter data
			fixtureDef.filter.groupIndex = p.groupIndex;
			fixtureDef.filter.maskBits = p.maskBits;
			fixtureDef.filter.categoryBits = p.categoryBits;
			
			fixtureDef.friction = p.friction;
			fixtureDef.density = p.density;
			fixtureDef.restitution = p.restitution;
			fixtureDef.isSensor = p.isSensor;
			
			if (p.type == 'circle')//only support two type now
			{
				shape = new b2CircleShape(p.width / p.scale);
				p.height = p.width; //workaround....
			}
			else
			{
				shape = new b2PolygonShape();
				if (p.type == 'box')
				{
					shape.SetAsBox(p.width / p.scale, p.height / p.scale);
				}
				else if (type == 'SHAPE_POLYGON')
				{
					throw new Error("to be implemented.");
				}
			}
			
			fixtureDef.shape = shape;
			if (p.parent)
			{
				body = p.parent;
				shape.SetLocalPosition(bodyDef.position); //根据父对象设置相对位置
			}
			else
			{
				body = this.world.CreateBody(bodyDef);
			}
			
			//body.SetUserData(ud);//notice here
			body.CreateFixture(fixtureDef);
			return body;
    
	}
	var This=this;
	var update=function(){
	    
	    This.world.Step(1/60,10,10);
	    if(This.debug)
		This.world.DrawDebugData();
	    This.world.ClearForces();
	    if(updateListeners.length>0){
		updateListeners.forEach(function(itm){if(itm)itm();});
	    }

	    requestAnimFrame(update);
	}
	this.start=function(){
	    update();
	}
    }
})();  
//现在基本框架已经出来了,现在开始造绳子了!
b2dWrapper.prototype.createRope=function(bodyA,bodyB,params){
	//package import
	var b2DistanceJointDef= Box2D.Dynamics.Joints.b2DistanceJointDef,
	b2Vec2 = Box2D.Common.Math.b2Vec2,
	b2Joint= Box2D.Dynamics.Joints.b2Joint,
	b2RevoluteJointDef= Box2D.Dynamics.Joints.b2RevoluteJointDef,
    b2RevoluteJoint= Box2D.Dynamics.Joints.b2RevoluteJoint;
	var This=this;
	//创建旋转关节
	function revoluteJoint(bodyA,bodyB,anchorA,anchorB){
			var revoluteJointDef = new b2RevoluteJointDef();
			revoluteJointDef.localAnchorA.Set(anchorA.x,anchorA.y);
			revoluteJointDef.localAnchorB.Set(anchorB.x,anchorB.y);
			revoluteJointDef.bodyA=bodyA;
			revoluteJointDef.bodyB = bodyB;
			revoluteJointDef.collideConnected = true;
			revoluteJointDef.userData = 'chainParts';
			//revoluteJointDef.enableLimit = true;
			//revoluteJointDef.referenceAngle = -30 * Math.PI / 180;
			return This.world.CreateJoint(revoluteJointDef);
	}
        //连接两个物体
	function connectChain(bodyA,bodyB,chainLen/*CHAIN_LEN*/,isHead) 
	{
		if(!chainLen)
			chainLen=5;
		return revoluteJoint(bodyA, bodyB, new b2Vec2( isHead?0:chainLen / 2 , 0 ), new b2Vec2( -chainLen / 2 , 0));
	}
	var startP=bodyA.GetPosition().Copy(),endP=bodyB.GetPosition().Copy();
	var detY=endP.y-startP.y,detX=endP.x-startP.x;
	var angle_rad=Math.atan2(detY,detX);//获取头尾两点的角度
	var len=Math.sqrt(detX*detX*900+detY*detY*900);//总长
	var subLen=len/10;//每个节点长 
	var offset=new
	b2Vec2(Math.cos(angle_rad)*subLen,Math.sin(angle_rad)*subLen);//获取每个
	绳子节点相对于前一节点的偏移
	for(var i=0;i<10;i++){
		var box=this.makeShape(//创建绳子节点
			{
				type:'box',
				isDynamic:true,
				width:subLen,
				height:2,
				angle:angle_rad,
				isSensor:true,
				x:bodyA.GetPosition().x*30+offset.x,
				y:bodyA.GetPosition().y*30+offset.y
			}	
		);		
		var j=connectChain(bodyA,box,subLen/30,i==0);
		bodyA=box;
	}
	connectChain(bodyA,bodyB,subLen/30);

}


 

 

后记

没用多少时间的实现了这个小demo,我想说的是互联网越来越开放,技术交流也应该如此, 人生苦短,拥抱开源 :>