Android应用libGDX引擎系列(三)-Android FrameWork 基于libGDX实现高性能动画特效(Box2D/物理碰撞 篇)

by AlexQ (email alexq_andr@163.com

工程托管在此:GitHub

image
image
image

工程托管在此:GitHub

前情提要

之前写的那篇Android FrameWork 基于libGDX实现高性能动画特效(烟花/粒子特效篇)最后提到了Box2D,很久之前我写过一个基于jbox2d库开发的一个碰撞特效,由于jbox2d性能所限制,同时出现20颗左右的物体碰撞时,就会出现卡顿掉帧的现象,便萌生了想用libGDX中提供的Box2D来实现一个更高效的版本,很高兴很快就完成了,我们今天就来继续分享一下这部分内容。(基于jbox2d版本demo工程地址)**

Gif较大,可能需要较长时间加载,或者直接查看视频效果

图片名称
图片名称

导语

libGDX是一个小有名气的跨平台游戏开发引擎,Box2D则是大名鼎鼎模拟刚体运动特性的库,文章中我们会介绍如下几点:

  1. 关于libGDX以及如何引入到App;
  2. 学习Box2D
  3. App FrameWork层使用libGDX-Box2D实现特效的那些细节;
  4. 推荐

回顾:libGDX入门学习以及如何引入到App

阅读此篇文章之前可能您需要对以下两个知识点做一些了解和铺垫,


学习Box2D

为了不分散我们这篇文章的主旨,关于Box2D入门学习内容,我特意编写在这篇文章中请移步到这里查看Box2D 入门简要

当您初步掌握了Box2D中基本概念后会很容易的掌握下面的内容。


App FrameWork层使用libGDX-Box2D实现特效的那些细节

  1. 工程组织结构
  2. 性能表现卓越
  3. 利用libGDX中提供的Box2D实现特效
  4. 添加重力感应逻辑

1.工程组织结构三个部分

  • libs中再增加libgdx提供的gdx-box2d.so以及gdx-box2d.jar包


  • 资源文件增加碰撞物体显示图片

  • 核心代码文件,用来在app中使用libgdx-box2d实现带有重力感应的碰撞效果

2.性能卓越

以下的对比很好的说明了左侧基于jbox2d的实现与右侧基于libgdx-box2d实现的两个版本之间的巨大性能差异。
运算方面一个是在java层进行的,另外一个则是在底层C上进行的,绘制方面则一个是用android通常的ondraw绘制,另一个用opengles绘制的。
如果对比还不够明显的话,你可以尝试将星星的上线调整到100左右,在你的真机上运行一下试试效果。基于jbox2d版本demo工程地址


3.利用libGDX中提供的Box2D实现特效

  1. 注意坐标系
    首先有个重要的问题要说明,Box2D的坐标系原点是在你创建的Fragment的中心点,而libGDX的绘制坐标系是Fragment的左下,如果你发现你的debug视图是好的,而一旦按照Box2D返回的坐标贴图,造成显示位置偏差那就是没有注意坐标系关系造成的
工程中Tools目录下的Transform,就是为了转换坐标系用的,提供双向的转换:

libGDX -> Box2D 以及 Box2D -> libGDX

使用方法就是贴图绘制前,将你从box2d中拿到的坐标做一次转化之后再使用。
```
Vector2 transformVect = Transform.mtp(box2d.x, box2d.y, new Vector2(w, h), PXTM);
```
  1. “盖房子”,添加左、右、底三面墙
    建立用来限制小星星碰撞范围的围墙,三面围墙原理相同,只是位置不同罢了,可以通过打开“Open DebugDraw”,看到绿色的细线就是围墙,这里我们用staticBody这种刚体类型来表现他最合适不过。我们列举地面的代码:

    private void addground(){
        BodyDef groundBodyDef =new BodyDef();
        groundBodyDef.position.set(new Vector2(0, -camera.viewportHeight/2));
        Body groundBody = world.createBody(groundBodyDef);
        PolygonShape groundBox = new PolygonShape();
        groundBox.setAsBox(camera.viewportWidth, 1.0f / PXTM);
        groundBody.createFixture(groundBox, 0.5f);
        groundBox.dispose();
    }
    

    这里面有2个点值得注意:

    • 首先是设置位置的时候,我们将地面的Y坐标设置为-camera.viewportHeight/2,是因为,我们之前提到了,Box2D的坐标系是中心点,所以,Y向下为负值,总高度的一半向下,自然就是最下面的边缘了。

    • 其次注意到PXTM这个常量了嘛,它的作用是单位换算,什么单位呢,Box2D用的距离单位都是米(m),而我们屏幕的单位是像素,这两者有个转化比例就是PXTM = 30(1米等于30像素);这个是除去坐标系转化之外,影响绘制位置的重要因素,请切记。

  1. render方法中的三件事情

    预览代码:

     @Override
    public void render() {
        Gdx.gl.glClearColor(0, 0, 0, 0);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        synchronized (Box2dEffectView.class) {
            if (m_ballBodys.size() == 0)
                return;
            float deltatm = Gdx.app.getGraphics().getDeltaTime();
            world.step(deltatm, 6, 2);
            _ballUpdatasLogic(deltatm);
            if (m_isDebugRenderer)
                m_debugRenderer.render(world, camera.combined);
        }
    }
    
    private void _ballUpdatasLogic(float deltatm){
        for (int i=0; i<m_ballBodys.size(); i++){
            Body bd = m_ballBodys.get(i);
            BallInfo ballInfo = (BallInfo)bd.getUserData();
            float alphascale = 1f;
            float curruntm = ballInfo.getRuntimes();
            if (curruntm >= Box2dConstant.MaxBallLiferSP){
                alphascale = hidesmallBody(i);
            }
            //判断是否已经到了生命尽头
            if (curruntm >= Box2dConstant.MaxBallLifer){
                destoryBody(i);
                i--;
                continue;
            }
            //更新时间
            ballInfo.setRuntimes(curruntm + deltatm);
            Vector2 transformVect = Transform.mtp(bd.getPosition().x + 1f, bd.getPosition().y + 1f, new Vector2(2f, 2f), PXTM);
            //绘制
            m_spriteBatch.begin();
            m_spriteBatch.setColor(new Color(1,1,1,alphascale));
            m_spriteBatch.draw(TESTTEXTURE, transformVect.x+(1f-alphascale)*30, transformVect.y+(1f-alphascale)*30, alphascale*60f, alphascale*60f);
            m_spriteBatch.end();
        }
    }
    
* 模拟碰撞

    调用  world.step(deltatm, 6, 2);启动Box2D模拟运算。
    
在_ballUpdatasLogic函数里面遍历所有小星星的循环
* 控制物体生命周期

    响应业务逻辑,判断如果小星星展示时间超过一个我们定义阀值,启动摧毁这颗小星星代码逻辑;

    
* 绘制
    像我们在前一篇粒子特效中和libGDX入门中介绍的一样,最终我们要将Box2D模拟的数据结果绘制出来,通过调用m_spriteBatch.draw方法,将小星星的图片按照Box2D运算结果绘制到屏幕上。其中我们为了更好的视觉效果,在小星星的消失过程中设置了缩小以及渐隐,通过设置透明度以及调整贴图来达到,并不需要改变Box2D运算过程。

4.添加重力感应逻辑

如果您仔细观察最开始那个事例gif图片的话,在左上角有一个实时在动的像是水平仪的东西(带有指针的小圆圈),对,他就是个水平仪。我们为整个碰撞特效添加了重力感应的效果,当你左右晃动手机时,会发现小星星也跟着左右摆动起来,很有意思很互动,水平仪表现了手机摆动的情况。

首先为了能够直观的演示重力感应,我在工程的testcode目录里面实现了一个levelgaugeView,也就是你看到的这个小东西,帮助你看到自己的陀螺仪水平方向上的变化,这个demo表现了,我们将手机先是水平的,之后向右倾斜,在之后向左倾斜,最后恢复水平状态。


public Box2dSenserLogic(final World world, Context context) {
        m_world = world;
        //获得重力感应硬件控制器
        sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
        sensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        //添加重力感应侦听,并实现其方法,
        sel = new SensorEventListener() {
            public void onSensorChanged(SensorEvent se) {
                float x = se.values[SensorManager.DATA_X];
                float y = se.values[SensorManager.DATA_Y];
                float xSenser, ySenser;
                if (m_isPortrait) {
                    xSenser = -5.0f * x;
                    ySenser = (y >= -1) ? -30 : -y * 5.0f;
                    if (y<-1){
                        xSenser = -1 * xSenser;
                        ySenser = -1 * ySenser;
                    }
                }
                else{
                    xSenser = 5.0f * y;
                    ySenser = (x >= -1) ? -30 : -x * 5.0f;
                    if (x<-1) {
                        xSenser = -1 * xSenser;
                        ySenser = -1 * ySenser;
                    }
                }
                Vector2 vec2 = m_isPortrait ? new Vector2(xSenser, ySenser) : new Vector2(xSenser, ySenser);
                if (world != null)
                    world.setGravity(vec2);
            }
            public void onAccuracyChanged(Sensor arg0, int arg1) {
            }
        };
    }

我们考虑了如果你开启屏幕方向变化的因素,横竖屏幕我们分开处理。其中能够达到效果的核心代码就是:“world.setGravity(vec2);”,根据陀螺仪传递的数据,我们设置好相应的新的重力方向,就能够达到们看到的效果了。别忘了退出时,关闭重力感应接收哦。

以上便是用libgdx-Box2d实现碰撞特效的全部内容,如果您之前了解了libGDX入门内容+libGDX如何引入到app中+Box2D入门知识,掌握起来还是很简单的


推荐

1.推荐书目


作者: 陈文登
出版社: 科学出版社
出版年: 2015-3-16
页数: 328
定价: 78
装帧: 平装
ISBN: 9787030434340

2.推荐网址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容