如标题,在没遇到这个问题之前通常是用转轴把刚体固定在某一点,然后利用扭矩来实现刚体旋转,但使用转轴的问题在于,如果一个动态刚体掉落过程中撞到这个利用转轴转动的刚体时,这个刚体会发生微小的转动,虽然这属于正常的物理特性,但对于某些游戏来说对体验的影响还是不小的。Google了N多资料,试过N多种方法,最终给Box2d的源文件加入了个自定义方法,但总算是把问题解决了。

实现步骤

Box2D/Dynamics/b2Body.as中加入如下函数,静态刚体依靠该函数旋转。

public function rotateAroundExternalPoint(ox:Number, oy:Number, angle:Number) : void
{
    var f:b2Fixture;
    if (m_world.IsLocked() == true)
        return;
    
    // begin
    var newPos:b2Vec2 = new b2Vec2();
    newPos.x = m_xf.position.x - ox;
    newPos.y = m_xf.position.y - oy;
    
    var s:Number = Math.sin(angle);
    var c:Number = Math.cos(angle);
    
    var na:Number;
    var nb:Number;
    var nc:Number;
    var nd:Number;
    var nx:Number;
    var ny:Number;
    
    nx = newPos.x * c + newPos.y * -s;
    ny = newPos.x * s + newPos.y * c;
    newPos.x = nx;
    newPos.y = ny;
    
    na = m_xf.R.col1.x * c + m_xf.R.col1.y * -s;
    nb = m_xf.R.col1.x * s + m_xf.R.col1.y * c;
    nc = m_xf.R.col2.x * c + m_xf.R.col2.y * -s;
    nd = m_xf.R.col2.x * s + m_xf.R.col2.y * c;
    m_xf.R.col1.x = na;
    m_xf.R.col1.y = nb;
    m_xf.R.col2.x = nc;
    m_xf.R.col2.y = nd;
    
    newPos.x += ox;
    newPos.y += oy;
    
    m_sweep.c.SetV(newPos);
    m_sweep.c0.SetV(newPos);
    m_xf.position.SetV(newPos);
    
    m_sweep.a += angle;
    m_sweep.a0 = m_sweep.a;
    
    // end
    
    var broadPhase:IBroadPhase = m_world.m_contactManager.m_broadPhase;
    for (f = m_fixtureList; f; f = f.m_next)
        f.Synchronize(broadPhase, m_xf, m_xf);
    m_world.m_contactManager.FindNewContacts();
}

实例源码

package
{
    import Box2D.Collision.*;
    import Box2D.Collision.Shapes.*;
    import Box2D.Common.Math.*;
    import Box2D.Dynamics.*;
    
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    
    [SWF(width="400", height="400", backgroundColor="#EEEEEE", frameRate="30")]
    public class B2DTest extends Sprite
    {
        
        public var m_world:b2World;
        public var m_velocityIterations:int = 10;
        public var m_positionIterations:int = 10;
        public var m_timeStep:Number = 1.0/30.0;

        private var _body:b2Body;
        
        private var _leftKeyDown:Boolean = false;
        private var _rightKeyDown:Boolean = false;
        
        public function B2DTest()
        {
            // Add event for main loop
            addEventListener(Event.ENTER_FRAME, Update, false, 0, true);
            stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
            stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
            
            
            // Define the gravity vector
            var gravity:b2Vec2 = new b2Vec2(0.0, 10.0);
            
            // Allow bodies to sleep
            var doSleep:Boolean = false;
            
            // Construct a world object
            m_world = new b2World( gravity, doSleep);
            
            // set debug draw
            var debugDraw:b2DebugDraw = new b2DebugDraw();
            debugDraw.SetSprite(this);
            debugDraw.SetDrawScale(30.0);
            debugDraw.SetFillAlpha(0.3);
            debugDraw.SetLineThickness(1.0);
            debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
            m_world.SetDebugDraw(debugDraw);
            m_world.DrawDebugData();
            
            // Vars used to create bodies
            var body:b2Body;
            var bodyDef:b2BodyDef;
            var boxShape:b2PolygonShape;
            var circleShape:b2CircleShape;
            
            // Add ground body
            bodyDef = new b2BodyDef();
            bodyDef.position.Set(6, 8);
            bodyDef.type = b2Body.b2_staticBody;
            boxShape = new b2PolygonShape();
            boxShape.SetAsOrientedBox(9, 0.3, new b2Vec2(0, 0));
            var fixtureDef:b2FixtureDef = new b2FixtureDef();
            fixtureDef.shape = boxShape;
            fixtureDef.friction = 0.3;
            fixtureDef.density = 0; // static bodies require zero density
            // Add sprite to body userData
            bodyDef.userData = new Sprite();
            bodyDef.userData.graphics.beginFill(0x00FF00);
            bodyDef.userData.graphics.drawRect(-270, -9, 540, 18);
            bodyDef.userData.graphics.endFill();
            _body = m_world.CreateBody(bodyDef);
            _body.CreateFixture(fixtureDef);
            
            // Add some objects
            for (var i:int = 1; i < 10; i++){
                bodyDef = new b2BodyDef();
                bodyDef.position.x = Math.random() * 15 + 5;
                bodyDef.position.y = Math.random() * 5;
                bodyDef.type = b2Body.b2_dynamicBody;
                var rX:Number = Math.random() + 0.5;
                var rY:Number = Math.random() + 0.5;
                // Box
                if (Math.random() < 0.5){
                    boxShape = new b2PolygonShape();
                    boxShape.SetAsBox(rX, rY);
                    fixtureDef.shape = boxShape;
                    fixtureDef.density = 1.0;
                    fixtureDef.friction = 0.5;
                    fixtureDef.restitution = 0.2;
                    body = m_world.CreateBody(bodyDef);
                    body.CreateFixture(fixtureDef);
                } 
                // Circle
                else {
                    circleShape = new b2CircleShape(rX);
                    fixtureDef.shape = circleShape;
                    fixtureDef.density = 1.0;
                    fixtureDef.friction = 0.5;
                    fixtureDef.restitution = 0.2;
                    body = m_world.CreateBody(bodyDef);
                    body.CreateFixture(fixtureDef);
                }
            }
            
        }
        
        public function Update(e:Event):void
        {
            var joint:b2Vec2 = new b2Vec2(6, 6);
            if (_rightKeyDown)
                _body.rotateAroundExternalPoint(joint.x, joint.y, -0.05);
            
            if (_leftKeyDown)
                _body.rotateAroundExternalPoint(joint.x, joint.y, 0.05);

            
            m_world.Step(m_timeStep, m_velocityIterations, m_positionIterations);
            
            m_world.DrawDebugData();
            
            // Go through body list and update sprite positions/rotations
            for (var bb:b2Body = m_world.GetBodyList(); bb; bb = bb.GetNext()){
                if (bb.GetUserData() is Sprite){
                    var sprite:Sprite = bb.GetUserData() as Sprite;
                    sprite.x = bb.GetPosition().x * 30;
                    sprite.y = bb.GetPosition().y * 30;
                    sprite.rotation = bb.GetAngle() * (180/Math.PI);
                }
            }
            
        }
        
        private function onKeyDown(e:KeyboardEvent):void
        {
            if (e.keyCode == 37)
                _leftKeyDown = true;
            
            if (e.keyCode == 39)
                _rightKeyDown = true;
        }
        
        private function onKeyUp(e:KeyboardEvent):void
        {
            if (e.keyCode == 37)
                _leftKeyDown = false;
            
            if (e.keyCode == 39)
                _rightKeyDown = false;
        }
    }
}

因为贴图默认的原点是在左上角,而box2d刚体默认原点是在中心,所以要想办法在draw的时候将原点放到贴图中心,实现方法很简单,这里就不多说了。

实例(方向键左右控制静态刚体旋转)