用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,我想说的是互联网越来越开放,技术交流也应该如此, 人生苦短,拥抱开源 :>