最近把box2d研究了一遍,总体上差不多是了解了,但是在运行那个小球下落的demo时候发现移动速度与物理情况不一样,仔细研究了半天才发现原来有这么多细节概念要注意。这必须记录下来,对以后有同样问题的人肯定会有所启发。

 

物理模型:

    一个边长为1m的正方形盒子悬停在20m高的位置,然后自由落体。根据公式h=gt^2/2可知下落到0m高时应该是2s。

对应box2d的模型:

    一个形状为边长为1m的正四角型,不存在任何阻尼,放在20m高的空中,然后设置向下引力为9.8m/s^2。

 

注意一:单位换算 

    这个地方是新手最容易出错的。由于libgdx中的距离单位都是px,但box2d的距离单位都是m,所以你程序中所有要计算的值都要确保单位正确。 

    具体来说,比如我的显示器分辨率是1440px*900px,那我创建一个游戏界面为200px*200px的,如new JoglApplication(new DemoGame(), "Demo Demo", 200, 200, false);。这个很好理解,你的整个游戏界面就是这个大小,与显示器的分辨率是正比关系。

    然后,游戏中会有一个camera来看具体的界面内容,这里只考虑正投影摄像机(不会因为距离对界面放大或者缩小),那么定义其能看到的也是200px*200px的画面new OrthographicCamera(200, 200),这 样就真的是画面上的1px代表你显示器的1px了。camera大小的变化是你游戏中的1px与实际显示器1px是成反比的。我觉得这个与游戏界面设置一样最好理解了。

    那如果我希望10个像素代表1m(scale=10px/m),那计算出来的每个物体位移1m,你对应的actor都要移动10个像素。就是这么个换算关系。

    注1:box2d中的单位都是标准单位,比如长度就是m,重量就是kg。

    注2:box2d的物理计算与画面上的actor是分开的,需要把物理数据再通过同步来反应到actor上。但是在Box2DDebugRenderer可以直观的看这个物理世界。

 

注意二:最大速度限制

    这个时候物体移动起来有点意思了,当把世界引力加到很大,诡异的事情发生了,开始还是加速运动,突然到后面明显变成匀速运动了。查了一遍资料才发现,原来box2d把每帧位移限制在2m/frame,所以在60Hz的刷新率情况下物体最大移动速度就是120m/s了。悲剧的是这个限制是hard coding,这可能是让物体看起来移动比较连续吧。所以要把速度加上去,那就只能提高帧数了,比如world.step(1/100f, 3, 3);能提高最大速度就是200m/s,但这么修改会有一个严重的问题,就是让物体位移的时间刻度和实际时间不一致了,画面看上去会和人感知不一样。

    注:timestep是时间步长,这是动画里面一个重要概念,物理世界每次计算出来新的位置就必须要知道过了多久时间,这就让离散的时间点计算变成一个连续的动画。

 

注意三:初始化的影响

     以上工作都做好了,发现下落时间怎么统计都不对。弄了半天才发反应过来原来第一次render的时候就已经开始计算物体移动了,并且把这个deltaTime带入进去计算了。由于初始化速度会比较慢,程序远达不到60Hz的刷新度,所以我把那个时间作为第一次下落时间其实是已经晚了的。所以干脆把第一次render再让每个物体唤醒,这就得到期望的结果了。

 

 

附上测试代码:

  1. public class DemoGame implements ApplicationListener { 
  2.  
  3.     protected OrthographicCamera camera;  
  4.     protected Box2DDebugRenderer renderer; // 测试用绘制器  
  5.     private World world; 
  6.     private float scale = 10f; // 屏幕的缩放比例,10像素/米 
  7.     private long start = 0
  8.     private Body body; 
  9.     private boolean isFinished = true
  10.     private int count = 0
  11.     private float totalTime = 0
  12.  
  13.     @Override  
  14.     public void create() {  
  15.         Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 
  16.         camera = new OrthographicCamera(200/scale, 200/scale);// 这里为了展示物理世界,把视野也转换成m 
  17.         camera.position.set(100/scale, 100/scale, 0);  // 摄像机的postion是画面的中心点
  18.         renderer = new Box2DDebugRenderer();  
  19.          
  20.         world = new World(new Vector2(0, -9.8f), true); // 一般标准重力场  
  21.         BodyDef bd = new BodyDef(); //声明物体定义  
  22.         bd.position.set(1020);  
  23.         bd.type = BodyType.DynamicBody;  
  24.         body = world.createBody(bd); //通过world创建一个物体  
  25.         PolygonShape box = new PolygonShape(); 
  26.         box.setAsBox(0.5f, 0.5f);   // 注意单位是边长的一半 
  27.         FixtureDef fd = new FixtureDef(); 
  28.         fd.shape = box; 
  29.         fd.friction = 0
  30.         fd.restitution = 0
  31.         fd.density = 1
  32.         body.createFixture(fd); //将形状和密度赋给物体  
  33.         body.setLinearDamping(0f);  // 没有线性阻尼 
  34.         body.setAngularDamping(0f); // 没有旋转阻尼 
  35.         body.setAwake(false); 
  36.     } 
  37.  
  38.     @Override  
  39.     public void render() {  
  40.         world.step(Gdx.app.getGraphics().getDeltaTime(), 33);  
  41.         if(start == 0) { 
  42.             count = 1
  43.             start = System.currentTimeMillis(); 
  44.             System.out.println("start postion:" + body.getPosition().y); 
  45.             body.setAwake(true);    // 让渲染的第一帧再让物体运动,这样时间获得才准确, 
  46.                                     // 否则第一个deltaTime有初始化时间,会让整个计算不准 
  47.         } else { 
  48.             if(isFinished){ 
  49.                 System.out.println("动画渲染次数: " + count++); 
  50.                 System.out.println("物体移动速度: " + body.getLinearVelocity().y); 
  51.                 System.out.println("---------------------------------"); 
  52.                 totalTime += Gdx.app.getGraphics().getDeltaTime(); 
  53.             } 
  54.             if(body.getPosition().y <= 0 && isFinished) { 
  55.                 System.out.println("实际时间差:" + (System.currentTimeMillis() - start)/1000f); 
  56.                 System.out.println("图像增量统计时间差:" + totalTime); 
  57.                 System.out.println("end postion:" + body.getPosition().y); 
  58.                 isFinished = false
  59.                 world.destroyBody(body); 
  60.             } 
  61.         } 
  62.         GL10 gl = Gdx.app.getGraphics().getGL10();  
  63.         gl.glClear(GL10.GL_COLOR_BUFFER_BIT);  
  64.         camera.update();  
  65.         camera.apply(gl);  
  66.         renderer.render(world, camera.combined);  
  67.     } 
  68.      
  69.     @Override  
  70.     public void dispose() {  
  71.         renderer.dispose();  
  72.         world.dispose(); 
  73.          
  74.         renderer = null;  
  75.         world = null;  
  76.     } 
  77.      
  78.     @Override  
  79.     public void pause() {  
  80.     } 
  81.  
  82.     @Override  
  83.     public void resize(int width, int height) {  
  84.     } 
  85.  
  86.     @Override  
  87.     public void resume() {  
  88.     }