[LIBGDX学习]LibGDX代码详解(四)Box2D

本文详细介绍了一个使用Box2D物理引擎实现的游戏测试场景。包括如何创建物理世界、添加各种形状的刚体、处理碰撞检测及响应等内容。通过具体代码展示了如何通过鼠标交互来移动物体。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

package com.mygdx.game;

import java.math.BigDecimal;
import java.util.ArrayList;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
import com.badlogic.gdx.physics.box2d.ChainShape;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.ContactImpulse;
import com.badlogic.gdx.physics.box2d.ContactListener;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.Manifold;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.QueryCallback;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.physics.box2d.WorldManifold;
import com.badlogic.gdx.physics.box2d.joints.MouseJoint;
import com.badlogic.gdx.physics.box2d.joints.MouseJointDef;
import com.badlogic.gdx.utils.TimeUtils;
import com.mygdx.game.utils.GdxTest;

public class Box2DTest extends GdxTest implements InputProcessor {
    /** the camera **/
    private com.badlogic.gdx.graphics.OrthographicCamera camera;

    /** the immediate mode renderer to output our debug drawings **/
    private ShapeRenderer renderer;

    /** box2d debug renderer **/
    private Box2DDebugRenderer debugRenderer;

    /** a spritebatch and a font for text rendering and a Texture to draw our boxes **/
    private SpriteBatch batch;
    private BitmapFont font;
    private TextureRegion textureRegion;

    // 测试一下大数
    BigDecimal bd;
    private ArrayList<Float> floatArray = new ArrayList<Float>();
    int floatCount;

    /** our box2D world **/
    private World world;

    /** our boxes **/
    private ArrayList<Body> boxes = new ArrayList<Body>();

    /** our ground box **/
    Body groundBody;

    /** our mouse joint **/
    private MouseJoint mouseJoint = null;// 意思是鼠标可以拖动?

    /** a hit body **/
    Body hitBody = null;

    @Override
    public void create () {
        // setup the camera. In Box2D we operate on a
        // meter scale, pixels won't do it. So we use
        // an orthographic camera with a viewport of
        // 48 meters in width and 32 meters in height.
        // We also position the camera so that it
        // looks at (0,16) (that's where the middle of the
        // screen will be located).
        //camera = new OrthographicCamera(48, 32);
        camera = new OrthographicCamera(192, 128);
        //camera.position.set(0, 16, 0);
        camera.position.set(0, 58, 0);

        // next we setup the immediate mode renderer
        // gdx引擎有一个ShapeRenderer类,他可以绘制一些最基本的图形,如矩形、线、圆形等,而不需要我们手动
        // 的设置一个个的opengl顶点和索引,有时候这些基本图形会有大用处。
        renderer = new ShapeRenderer();

        // next we create the box2d debug renderer
        debugRenderer = new Box2DDebugRenderer();

        // next we create a SpriteBatch and a font
        batch = new SpriteBatch();
        font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"), false);
        //font.setColor(Color.RED);
        font.setColor(Color.WHITE);
        //textureRegion = new TextureRegion(new Texture(Gdx.files.internal("data/badlogicsmall.jpg")));
        textureRegion = new TextureRegion(new Texture(Gdx.files.internal("data/badlogic2.jpg")));

        // next we create out physics world.
        createPhysicsWorld();

        // register ourselfs as an InputProcessor
        Gdx.input.setInputProcessor(this);
    }

    private void createPhysicsWorld () {
        // we instantiate a new World with a proper gravity vector
        // and tell it to sleep when possible.
        //world = new World(new Vector2(0, -10), true);
        world = new World(new Vector2(0, -100), true);

        float[] vertices = {-0.07421887f, -0.16276085f,
                -0.12109375f, -0.22786504f,
                -0.157552f, -0.7122401f,
                0.04296875f, -0.7122401f,
                0.110677004f, -0.6419276f,
                0.13151026f, -0.49869835f,
                0.08984375f, -0.3190109f};

        PolygonShape shape = new PolygonShape();// 这个是用来干啥的?
        shape.set(vertices);

        // next we create a static ground platform. This platform
        // is not moveable and will not react to any influences from
        // outside. It will however influence other bodies. First we
        // create a PolygonShape that holds the form of the platform.
        // it will be 100 meters wide and 2 meters high, centered
        // around the origin
        PolygonShape groundPoly = new PolygonShape();
        //groundPoly.setAsBox(50, 1);
        groundPoly.setAsBox(500, 1);

        // next we create the body for the ground platform. It's
        // simply a static body.
        BodyDef groundBodyDef = new BodyDef();
        groundBodyDef.type = BodyType.StaticBody;
        groundBody = world.createBody(groundBodyDef);

        // finally we add a fixture to the body using the polygon
        // defined above. Note that we have to dispose PolygonShapes
        // and CircleShapes once they are no longer used. This is the
        // only time you have to care explicitly for memory management.
        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = groundPoly;
        fixtureDef.filter.groupIndex = 0;
        groundBody.createFixture(fixtureDef);
        groundPoly.dispose();// 不要忘记

        // We also create a simple ChainShape we put above our
        // ground polygon for extra funkyness.
        ChainShape chainShape = new ChainShape();
        // 顺序:左上角,左下角,右下角,右上角
        chainShape.createLoop(new Vector2[] {new Vector2(-10, 10), new Vector2(-10, 5), new Vector2(10, 5), new Vector2(10, 11),});
        //chainShape.createLoop(new Vector2[] {new Vector2(-10, 10), new Vector2(-10, 5), new Vector2(10, 5), new Vector2(10, 17),});
        BodyDef chainBodyDef = new BodyDef();
        chainBodyDef.type = BodyType.StaticBody;
        Body chainBody = world.createBody(chainBodyDef);
        chainBody.createFixture(chainShape, 0);
        chainShape.dispose();// 不要忘记

        createBoxes();

        // You can savely ignore the rest of this method :)
        // 留着以后慢慢分析
/*        world.setContactListener(new ContactListener() {
            @Override
            public void beginContact (Contact contact) {
 System.out.println("begin contact");
            }

            @Override
            public void endContact (Contact contact) {
 System.out.println("end contact");
            }

            @Override
            public void preSolve (Contact contact, Manifold oldManifold) {
 Manifold.ManifoldType type = oldManifold.getType();
 Vector2 localPoint = oldManifold.getLocalPoint();
 Vector2 localNormal = oldManifold.getLocalNormal();
 int pointCount = oldManifold.getPointCount();
 Manifold.ManifoldPoint[] points = oldManifold.getPoints();
 System.out.println("pre solve, " + type +
 ", point: " + localPoint +
 ", local normal: " + localNormal +
 ", #points: " + pointCount +
 ", [" + points[0] + ", " + points[1] + "]");
            }

            @Override
            public void postSolve (Contact contact, ContactImpulse impulse) {
 float[] ni = impulse.getNormalImpulses();
 float[] ti = impulse.getTangentImpulses();
 System.out.println("post solve, normal impulses: " + ni[0] + ", " + ni[1] + ", tangent impulses: " + ti[0] + ", " + ti[1]);
            }
        });*/
    }

    private void createBoxes () {
        // next we create 50 boxes at random locations above the ground
        // body. First we create a nice polygon representing a box 2 meters
        // wide and high.
        PolygonShape boxPoly = new PolygonShape();
        //boxPoly.setAsBox(1, 1);
        boxPoly.setAsBox(2, 2);
        int MAXBOX = 100;
        // next we create the 50 box bodies using the PolygonShape we just
        // defined. This process is similar to the one we used for the ground
        // body. Note that we reuse the polygon for each body fixture.
        for (int i = 0; i < MAXBOX; i++) {
            // Create the BodyDef, set a random position above the
            // ground and create a new body
            BodyDef boxBodyDef = new BodyDef();
            boxBodyDef.type = BodyType.DynamicBody;
            boxBodyDef.position.x = -24 + (float)(Math.random() * 48);// Math.random() * 48 意思是48以内?
            boxBodyDef.position.y = 10 + (float)(Math.random() * 100);

            // 自己加的
            // /boxBodyDef.fixedRotation=true;

            Body boxBody = world.createBody(boxBodyDef);

            boxBody.createFixture(boxPoly, 1);

            // add the box to our list of boxes
            boxes.add(boxBody);
        }

        // we are done, all that's left is disposing the boxPoly
        boxPoly.dispose();
    }

    @Override
    public void render () {
        // first we update the world. For simplicity
        // we use the delta time provided by the Graphics
        // instance. Normally you'll want to fix the time
        // step.
        long start = TimeUtils.nanoTime();
        world.step(Gdx.graphics.getDeltaTime(), 8, 3);// 自己看box2d pdf
        float updateTime = (TimeUtils.nanoTime() - start) / 1000000000.0f;
        bd = new BigDecimal(updateTime);
        String ii = bd.toPlainString();
        //System.out.println(ii);// 这应该是以秒为单位的实际耗时?
        floatArray.add(updateTime);
        floatCount+=1;
        float sum=0;
        int MAXCOUNT = 100;
        if(floatCount==MAXCOUNT){
            floatCount=0;
            for(int i=0;i<MAXCOUNT;i++) {
                sum+=floatArray.get(i);
            }
            float average = sum/MAXCOUNT;
            bd = new BigDecimal(average);
            ii = bd.toPlainString();
            //System.out.println("Avrage time between step? "+ii);
            floatArray.clear();
        }


        // next we clear the color buffer and set the camera
        // matrices
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        camera.update();

        // next we render the ground body
        renderBox(groundBody, 500, 1);

        // next we render each box via the SpriteBatch.
        // for this we have to set the projection matrix of the
        // spritebatch to the camera's combined matrix. This will
        // make the spritebatch work in world coordinates
        batch.getProjectionMatrix().set(camera.combined);// 注释掉这一句,图片会变得非常小,显示在屏幕左下角
        batch.begin();
        for (int i = 0; i < boxes.size(); i++) {
            Body box = boxes.get(i);
            Vector2 position = box.getPosition(); // that's the box's center position
            float angle = MathUtils.radiansToDegrees * box.getAngle(); // the rotation angle around the center
            batch.draw(textureRegion,
                    //position.x - 1, position.y - 1, // the bottom left corner of the box, unrotated
                    position.x -2, position.y -2, // 自定义
                    //1f, 1f, // the rotation center relative to the bottom left corner of the box
                    2f, 2f, // 箱子变大了这里也要改
                    //2, 2, // the width and height of the box
                    4, 4, // 自己设定宽高
                    1, 1, // the scale on the x- and y-axis
                    angle); // the rotation angle

            // 改一下上面的代码就知道这句的作用了
/*            batch.draw(textureRegion, position.x, position.y, // the bottom left corner of the box, unrotated
                    1f, 1f, // the rotation center relative to the bottom left corner of the box
                    2, 2, // the width and height of the box
                    1, 1, // the scale on the x- and y-axis
                    angle-45); // the rotation angle*/
        }
        batch.end();

        // next we use the debug renderer. Note that we
        // simply apply the camera again and then call
        // the renderer. the camera.apply() call is actually
        // not needed as the opengl matrices are already set
        // by the spritebatch which in turn uses the camera matrices :)
        debugRenderer.render(world, camera.combined);// 这句注释掉就看不见Box2D的debug框了

        // finally we render all contact points
        renderer.setProjectionMatrix(camera.combined);
        //renderer.begin(ShapeType.Point);//这里如果渲染圆就会报错
        renderer.begin(ShapeType.Filled);//实心圆
        //renderer.begin(ShapeType.Line);//这里变成一堆空心圆
        renderer.setColor(1, 1, 0, 1);
        for (int i = 0; i < world.getContactCount(); i++) {
            Contact contact = world.getContactList().get(i);
            // we only render the contact if it actually touches
            if (contact.isTouching()) {
                // get the world manifold from which we get the
                // contact points. A manifold can have 0, 1 or 2
                // contact points.
                WorldManifold manifold = contact.getWorldManifold();
                int numContactPoints = manifold.getNumberOfContactPoints();
                for (int j = 0; j < numContactPoints; j++) {
                    Vector2 point = manifold.getPoints()[j];
                    //renderer.point(point.x, point.y, 0);
                    renderer.circle(point.x,point.y,0.3f);
                }
            }
            // 实验一下没有前面的if contact is touching
            // 怎么没感觉有什么区别
/*          WorldManifold manifold = contact.getWorldManifold();
            int numContactPoints = manifold.getNumberOfContactPoints();
            for (int j = 0; j < numContactPoints; j++) {
                Vector2 point = manifold.getPoints()[j];
                //renderer.point(point.x, point.y, 0);
                renderer.circle(point.x, point.y, 0.3f);
            }*/
        }
        renderer.end();

        // finally we render the time it took to update the world
        // for this we have to set the projection matrix again, so
        // we work in pixel coordinates
        batch.getProjectionMatrix().setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
        batch.begin();
        font.draw(batch, "fps: " + Gdx.graphics.getFramesPerSecond() + " update time: " + updateTime, 0, 20);
        batch.end();
    }

    Matrix4 transform = new Matrix4();

    private void renderBox (Body body, float halfWidth, float halfHeight) {
        // get the bodies center and angle in world coordinates
        Vector2 pos = body.getWorldCenter();
        float angle = body.getAngle();

        // set the translation and rotation matrix
        // 为什么有没有下面这两句没区别?
        transform.setToTranslation(pos.x, pos.y, 0);
        //transform.setToTranslation(pos.x*100, pos.y, 0);
        transform.rotate(0, 0, 1, (float)Math.toDegrees(angle));
        //transform.rotate(0, 45, 1, (float)Math.toDegrees(angle));

        // render the box
        renderer.begin(ShapeType.Filled);
        renderer.setTransformMatrix(transform);// 为什么这句有没有没区别?
        //renderer.setProjectionMatrix(transform);
        renderer.setColor(1, 1, 1, 1);
        renderer.rect(-halfWidth, -halfHeight, halfWidth * 2, halfHeight * 2);
        renderer.end();
    }

    /** we instantiate this vector and the callback here so we don't irritate the GC
     *
     * ReportFixture is the method that will get called whenever Box2D detects an intersection.
     *
     * 不太懂,意思是地面的碰撞不上报?
     * **/
    Vector3 testPoint = new Vector3();// 意思是这里创建一个后面就不用每碰一次都要重新创建一个了
    QueryCallback callback = new QueryCallback() {
        // reportFixture意思是在AABB框中找到fixture以后就上报
        @Override
        public boolean reportFixture (Fixture fixture) {// 这里意思是鼠标点到物体就执行下面的
            // if the hit fixture's body is the ground body
            // we ignore it
            if (fixture.getBody() == groundBody) {
                System.out.println("GROUND!!!");
                return true;
            };

            // if the hit point is inside the fixture of the body
            // we report it
            if (fixture.testPoint(testPoint.x, testPoint.y)) {
                hitBody = fixture.getBody();

                // 自己加一个实验一下
                //groundBody = fixture.getBody();

                System.out.println("HIT!!!");
                return false;
            } else
                return true;
        }
    };

    @Override
    public boolean touchDown (int x, int y, int pointer, int newParam) {
        // translate the mouse coordinates to world coordinates
        testPoint.set(x, y, 0);
        camera.unproject(testPoint);

        // ask the world which bodies are within the given
        // bounding box around the mouse pointer
        hitBody = null;

        // AABB就是碰撞的包围框?
        // 上面的callback用在这里
        // testPoint就是上面创建的那一个
        //world.QueryAABB(callback, testPoint.x - 0.1f, testPoint.y - 0.1f, testPoint.x + 0.1f, testPoint.y + 0.1f);
        world.QueryAABB(callback, testPoint.x - 3f, testPoint.y - 3f, testPoint.x + 3f, testPoint.y + 3f);

        // if we hit something we create a new mouse joint
        // and attach it to the hit body.
        if (hitBody != null) {
            MouseJointDef def = new MouseJointDef();
            def.bodyA = groundBody;
            def.bodyB = hitBody;// bodyA bodyB 注释任何一个点击鼠标就报错
            //def.collideConnected = true;
            def.collideConnected = false;// 自己改代码实验一下,没影响。。。
            def.target.set(testPoint.x, testPoint.y);// 没有这句就成表演魔术了,悬空取物
            //def.maxForce = 1000.0f * hitBody.getMass();// 不设定maxForce你就拉不动
            def.maxForce = 200000.0f * hitBody.getMass();

            mouseJoint = (MouseJoint)world.createJoint(def);
            hitBody.setAwake(true);// 感觉有没有这句没区别?
        } else {
/*            for (Body box : boxes)
                world.destroyBody(box);
            boxes.clear();
            createBoxes();*/
        }

        return false;
    }

    /** another temporary vector **/
    Vector2 target = new Vector2();

    @Override
    public boolean touchDragged (int x, int y, int pointer) {
        // if a mouse joint exists we simply update
        // the target of the joint based on the new
        // mouse coordinates
        if (mouseJoint != null) {
            //testPoint.set(x,y,0);//改成这句注释掉下面拖拽的时候会是神奇的结果
            camera.unproject(testPoint.set(x, y, 0));// 将testPoint的坐标转化为世界坐标
            mouseJoint.setTarget(target.set(testPoint.x, testPoint.y));
            //mouseJoint.setTarget(target.set(testPoint.x*10, testPoint.y));// 改一下就知道上一句是什么意思了
        }
        return false;
    }

    @Override
    public boolean touchUp (int x, int y, int pointer, int button) {
        // if a mouse joint exists we simply destroy it
        if (mouseJoint != null) {
            world.destroyJoint(mouseJoint);
            mouseJoint = null;
        }
        return false;
    }

    @Override
    public void dispose () {
        world.dispose();
        renderer.dispose();
        debugRenderer.dispose();
        font.dispose();
        textureRegion.getTexture().dispose();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值