[LIBGDX学习]LibGDX代码详解(三)Box2D character control

本文介绍了使用LibGDX库进行Box2D游戏开发时,如何实现角色控制器的详细步骤。通过创建静态和动态物体、设置碰撞检测、处理玩家输入来控制角色移动和跳跃。同时,文章涵盖了世界、相机、形状和夹具的创建,以及摩擦力和速度限制的调整。

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

package com.mygdx.game;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
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.CircleShape;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.EdgeShape;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.physics.box2d.WorldManifold;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.TimeUtils;
import com.mygdx.game.utils.GdxTest;

public class Box2DCharacterControllerTest extends GdxTest implements ApplicationListener {

    final static float MAX_VELOCITY = 14f;
    boolean jump = false;
    World world;
    Body player;
    Fixture playerPhysicsFixture;// 夹具,将形状附加到物体上?
    Fixture playerSensorFixture;
    OrthographicCamera cam;
    Box2DDebugRenderer renderer;// 把Box2D的边缘线显示出来?

    Array<Platform> platforms = new Array<Platform>();
    Platform groundedPlatform = null;
    float stillTime = 0;
    long lastGroundTime = 0;
    SpriteBatch batch;
    BitmapFont font;
    float accum = 0;
    float TICK = 1 / 60f;//每秒60帧

    @Override
    public void create () {
        //world = new World(new Vector2(0, -40), true);
        world = new World(new Vector2(0, -10), true);
        renderer = new Box2DDebugRenderer();
        cam = new OrthographicCamera(28, 20);// 28米宽20米高
        createWorld();
        Gdx.input.setInputProcessor(this);
        batch = new SpriteBatch();
        font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"), false);
    }

    @Override
    public void dispose () {
        world.dispose();
        renderer.dispose();
        batch.dispose();
        font.dispose();
    }

    private void createWorld () {
        //float y1 = 1;// 创建平直路面
        float y1 = (float)Math.random() * 0.1f + 1;// 创建不规则路面
        float y2 = y1;
        for (int i = 0; i < 50; i++) {// 创建50单位长度的地面

            // 起点:-50米处,终点 -50+49*2+2 就是50米处 y1 y2 1米高的地方
            Body ground = createEdge(BodyType.StaticBody, (-50 + i * 2), (y1), (-50 + i * 2 + 2), (y2), 0);// createEdge 就是创建一条线?

            y1 = y2;
            //y2 = 1;
            y2=(float)Math.random() + 1;// 要么高要么低,在1米高处差上下最多1米
        }

        Body box = createBox(BodyType.StaticBody, 1, 1, 0);// 显示为绿色
        //Body box = createBox(BodyType.StaticBody, 5, 5, 0);
        box.setTransform(30, 3, 0);
        box = createBox(BodyType.StaticBody, 1.2f, 1.2f, 0);
        //box = createBox(BodyType.StaticBody, 5f, 5f, 0);
        box.setTransform(5, 2.4f, 0);
        player = createPlayer();
        player.setTransform(-40.0f, 4.0f, 0);
        player.setFixedRotation(true);// 这句斜杠掉player各种打转,然后不会跳了

        for (int i = 0; i < 20; i++) {// DynamicBody都是红色
            box = createBox(BodyType.DynamicBody, (float)Math.random(), (float)Math.random(), 3);
            box.setTransform((float)Math.random() * 10f - (float)Math.random() * 10f, (float)Math.random() * 10 + 6,
                    (float)(Math.random() * 2 * Math.PI));
        }

        for (int i = 0; i < 20; i++) {
            Body circle = createCircle(BodyType.DynamicBody, (float)Math.random() * 0.5f, 3);
            circle.setTransform((float)Math.random() * 10f - (float)Math.random() * 10f, (float)Math.random() * 10 + 6,
                    (float)(Math.random() * 2 * Math.PI));
        }

        platforms.add(new CirclePlatform(-24, -5, 10, (float)Math.PI / 4));// 大转盘
        platforms.add(new CirclePlatform(20, 3, 5, (float)Math.PI ));// 自己加一个小的
        platforms.add(new MovingPlatform(-2, 3, 2, 0.5f, 2, 0, (float)Math.PI / 10f, 4));
        platforms.add(new MovingPlatform(17, 2, 5, 0.5f, 2, 0, 0, 5));
        //platforms.add(new MovingPlatform(-7, 5, 2, 0.5f, -2, 2, 0, 8));
        platforms.add(new MovingPlatform(-7, 5, 2, 0.5f, -2, 1, 0, 16));
// platforms.add(new MovingPlatform(40, 3, 20, 0.5f, 0, 2, 5));
    }

    Body createBox (BodyType type, float width, float height, float density) {
        BodyDef def = new BodyDef();
        def.type = type;
        Body box = world.createBody(def);

        PolygonShape poly = new PolygonShape();
        poly.setAsBox(width, height);
        box.createFixture(poly, density);
        poly.dispose();// 不要忘记

        return box;
    }

    private Body createEdge (BodyType type, float x1, float y1, float x2, float y2, float density) {
        BodyDef def = new BodyDef();
        def.type = type;
        Body box = world.createBody(def);

        EdgeShape poly = new EdgeShape();// 创建一条线?
        poly.set(new Vector2(0, 0), new Vector2(x2 - x1, y2 - y1));// 长度从x1y1到x2y2那么长
        box.createFixture(poly, density);// density为零就是不动的物体如地面
        box.setTransform(x1, y1, 0);
        poly.dispose();

        return box;
    }

    Body createCircle (BodyType type, float radius, float density) {
        BodyDef def = new BodyDef();
        def.type = type;
        Body box = world.createBody(def);

        CircleShape poly = new CircleShape();
        poly.setRadius(radius);
        box.createFixture(poly, density);
        poly.dispose();

        return box;
    }

    private Body createPlayer () {
        BodyDef def = new BodyDef();
        def.type = BodyType.DynamicBody;
        Body box = world.createBody(def);

        PolygonShape poly = new PolygonShape();
        poly.setAsBox(0.45f, 1.4f);
        playerPhysicsFixture = box.createFixture(poly, 1);
        poly.dispose();

        CircleShape circle = new CircleShape();
        circle.setRadius(0.45f);
        circle.setPosition(new Vector2(0, -1.4f));
        playerSensorFixture = box.createFixture(circle, 0);
        circle.dispose();

        box.setBullet(true);// 碰撞检测更精确?

        return box;
    }

    @Override
    public void resume () {

    }

    @Override
    public void render () {
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        cam.position.set(player.getPosition().x, player.getPosition().y, 0);
        cam.update();
        renderer.render(world, cam.combined);

        Vector2 vel = player.getLinearVelocity();
        Vector2 pos = player.getPosition();
        boolean grounded = isPlayerGrounded(Gdx.graphics.getDeltaTime());
        if (grounded) {
            lastGroundTime = TimeUtils.nanoTime();
        } else {
            //if (TimeUtils.nanoTime() - lastGroundTime < 100000000) {
            if (TimeUtils.nanoTime() - lastGroundTime < 100000000) {// 让grounded有一个延时
                //Gdx.app.log("Jacob","nanotime: "+TimeUtils.nanoTime());
                //Gdx.app.log("Jacob","lastgroundtime: "+lastGroundTime);
                grounded = true;// 这里是什么意思?意思是只要是在100000000这个时间范围内都还是算落地了?
            }
        }

        // cap max velocity on x
        if (Math.abs(vel.x) > MAX_VELOCITY) {
            vel.x = Math.signum(vel.x) * MAX_VELOCITY;// signum 返回参数的正负号
            player.setLinearVelocity(vel.x, vel.y);
        }

        // calculate stilltime & damp
        if (!Gdx.input.isKeyPressed(Keys.A) && !Gdx.input.isKeyPressed(Keys.D)) {
            stillTime += Gdx.graphics.getDeltaTime();
            //player.setLinearVelocity(vel.x * 0.9f, vel.y);// 逐渐减速?
            //player.setLinearVelocity(vel.x * 1.1f, vel.y);
            player.setLinearVelocity(vel.x * 0.01f, vel.y);// 逐渐减速?
        } else {
            stillTime = 0;
        }

        // disable friction while jumping
        if (!grounded) {
            playerPhysicsFixture.setFriction(0f);
            playerSensorFixture.setFriction(0f);
        } else {
            if (!Gdx.input.isKeyPressed(Keys.A) && !Gdx.input.isKeyPressed(Keys.D) && stillTime > 0.2) {
                playerPhysicsFixture.setFriction(1000f);
                playerSensorFixture.setFriction(1000f);
            } else {
                playerPhysicsFixture.setFriction(0.2f);
                playerSensorFixture.setFriction(0.2f);
//                playerPhysicsFixture.setFriction(1000f);
//                playerSensorFixture.setFriction(1000f);
            }

            // dampen sudden changes in x/y of a MovingPlatform a little bit, otherwise
            // character hops :)
            // 如果没有下面代码,移动平台如果突然往下移动player就会跳起来
            if (groundedPlatform != null && groundedPlatform instanceof MovingPlatform
                    && ((MovingPlatform)groundedPlatform).dist == 0) {// .dist==0意味着是平台转弯的瞬间施加冲力
                player.applyLinearImpulse(0, -24, pos.x, pos.y, true);
                // player.applyLinearImpulse(0, 24, pos.x, pos.y, true);
            }
        }

        // since Box2D 2.2 we need to reset the friction of any existing contacts
        // 没有下面的代码,在移动和旋转平台上都会停在一个位置不动。摩擦力永远是最大所以就不动?
        Array<Contact> contacts = world.getContactList();
        for (int i = 0; i < world.getContactCount(); i++) {
            Contact contact = contacts.get(i);
            contact.resetFriction();
        }

        // apply left impulse, but only if max velocity is not reached yet
        if (Gdx.input.isKeyPressed(Keys.A) && vel.x > -MAX_VELOCITY) {
            player.applyLinearImpulse(-2f, 0, pos.x, pos.y, true);
        }

        // apply right impulse, but only if max velocity is not reached yet
/*        if (Gdx.input.isKeyPressed(Keys.D) && vel.x < MAX_VELOCITY) {
            player.applyLinearImpulse(2f, 0, pos.x, pos.y, true);
        }*/
        if (Gdx.input.isKeyPressed(Keys.D)) {
            player.applyLinearImpulse(2f, 0, pos.x, pos.y, true);
        }// 好像没什么改变,和上面的相比。可能最大速度会变快一点?

        // 自己加一个下冲力玩玩
        if (Gdx.input.isKeyPressed(Keys.S)) {
            player.applyLinearImpulse(0, -40f, pos.x, pos.y, true);
        }

        // jump, but only when grounded
        if (jump) {
            jump = false;
            if (grounded) {
                player.setLinearVelocity(vel.x, 0);
                System.out.println("jump before: " + player.getLinearVelocity());
                player.setTransform(pos.x, pos.y + 0.01f, 0);
                player.applyLinearImpulse(0, 40, pos.x, pos.y, true);
                System.out.println("jump, " + player.getLinearVelocity());
            }

            // 试试多段跳
/*            player.setLinearVelocity(vel.x, 0);
            System.out.println("jump before: " + player.getLinearVelocity());
            //player.setTransform(pos.x, pos.y + 0.01f, 0);// 这里是什么意思?
            //player.setTransform(pos.x, pos.y + 5f, 0);
            player.applyLinearImpulse(0, 40, pos.x, pos.y, true);
            System.out.println("jump, " + player.getLinearVelocity());*/
        }

        // update platforms
/*        for (int i = 0; i < platforms.size; i++) {
            Platform platform = platforms.get(i);
            platform.update(Math.max(1 / 30.0f, Gdx.graphics.getDeltaTime()));
        }*/

        // le step...
        /**
         * timeStep,时步。通常建议设置为1/60,这样的话你的fps最好也设置到60。但是我发现更好的办法就是把timeStep设置为fps的倒数即可。如果你的fps是30,那么就设置为1/30。
         * velocityIterations,速度迭代
         * positionIterations,位置迭代
         * 这两个值通常建议设置为10,更低的迭代值会让你牺牲一些准确性,相反的为你的程序提升一部分性能。
         */
        world.step(Gdx.graphics.getDeltaTime(), 4, 4);
// accum += Gdx.graphics.getDeltaTime();
// while(accum > TICK) {
// accum -= TICK;
// world.step(TICK, 4, 4);
// }
        player.setAwake(true);

        cam.project(point.set(pos.x, pos.y, 0));//把米转换成相对于屏幕的位置
        batch.begin();
        font.draw(batch, "friction: " + playerPhysicsFixture.getFriction() + "\ngrounded: " + grounded, point.x + 20, point.y);

        // 我自己加的,显示位置
        font.draw(batch, "position x: "+pos.x+"\nposition y: "+pos.y, point.x +20, point.y - 35);
        if(grounded){

        }

        cam.project(point.set(-50.2f, 2.2f, 0));

        // 自己加的,显示坐标
        font.draw(batch, "x", point.x, point.y);
        for(int i=-50;i<=50;i++){
            cam.project(point.set(i, 2, 0));
            font.draw(batch, "" + i, point.x, point.y);
        }
        batch.end();
    }

    private boolean isPlayerGrounded (float deltaTime) {
        groundedPlatform = null;
        Array<Contact> contactList = world.getContactList();
        for (int i = 0; i < contactList.size; i++) {
            Contact contact = contactList.get(i);
            if (contact.isTouching()
                    && (contact.getFixtureA() == playerSensorFixture || contact.getFixtureB() == playerSensorFixture)) {

                Vector2 pos = player.getPosition();
                WorldManifold manifold = contact.getWorldManifold();
                boolean below = true;
                for (int j = 0; j < manifold.getNumberOfContactPoints(); j++) {
                    /**
                     * “&=”是JavaScript赋值运算符,意思是将左边变量与右操作数的值按位与。如,a&=b,相当于a=a&b。等号“=”就是赋值用的。
                     * 而&是JavaScript的位运算符,是按位与的意思,就是当两个操作数的相应位都为1时,该位的结果为1,否则为0。例如,5&6等于4,
                     * 因为0101&0110(注意都是用二进制表示的)的运算结果是0100。
                     *
                     * below =1 判断=1 新的below就是1 在下方?
                     * below =0 新的below就是0
                     * 判断=0 新的below就是0
                     */
                    below &= (manifold.getPoints()[j].y < pos.y - 1.5f);
                    //below &= (manifold.getPoints()[j].y < pos.y-2f);// player落地的时候的接触点y数值大于pos.y,这个时候永远不会落地,因此永远跳不起来。
                }

                if (below) {
                    if (contact.getFixtureA().getUserData() != null && contact.getFixtureA().getUserData().equals("p")) {
                        groundedPlatform = (Platform)contact.getFixtureA().getBody().getUserData();
                    }

                    if (contact.getFixtureB().getUserData() != null && contact.getFixtureB().getUserData().equals("p")) {
                        groundedPlatform = (Platform)contact.getFixtureB().getBody().getUserData();
                    }
                    return true;
                }

                return false;
            }
        }
        return false;
    }

    @Override
    public boolean keyDown (int keycode) {
        if (keycode == Keys.W) jump = true;
        return false;
    }

    @Override
    public boolean keyUp (int keycode) {
        if (keycode == Keys.W) jump = false;
        return false;
    }

    Vector2 last = null;
    Vector3 point = new Vector3();

    @Override
    public boolean touchDown (int x, int y, int pointerId, int button) {
        cam.unproject(point.set(x, y, 0));// 将屏幕坐标转换成世界坐标?

        if (button == Input.Buttons.LEFT) {
            if (last == null) {
                last = new Vector2(point.x, point.y);
            } else {
                createEdge(BodyType.StaticBody, last.x, last.y, point.x, point.y, 0);// 为何手机上的touchdown也是鼠标左键?
                last.set(point.x, point.y);
            }
        } else {
            last = null;
        }

        return false;
    }

    // 相当于一个接口
    abstract class Platform {
        abstract void update (float deltatime);
    }

    class CirclePlatform extends Platform {
        Body platform;

        public CirclePlatform (int x, int y, float radius, float da) {
            // A kinematic body is an hybrid body which is not affected by forces and collisions like a static body but can moved with a linear velocity like a dynamic body.
            platform = createCircle(BodyType.KinematicBody, radius, 1);
            platform.setTransform(x, y, 0);
            platform.getFixtureList().get(0).setUserData("p");
            platform.setAngularVelocity(da);
            platform.setUserData(this);
        }

        @Override
        void update (float deltatime) {
        }
    }

    class MovingPlatform extends Platform {
        Body platform;
        Vector2 pos = new Vector2();
        Vector2 dir = new Vector2();
        float dist = 0;
        float maxDist = 0;

        /**
         *  da: angle?
         *  dx: move x?
         *  dy: move y? 数值越大移动速度越快,dx,dy数值不同还改变角度
         *  maxDist: max distance to move
         */
        public MovingPlatform (float x, float y, float width, float height, float dx, float dy, float da, float maxDist) {
            platform = createBox(BodyType.KinematicBody, width, height, 1);// density 不知道代表什么,还需要看box2d的教程才知道。
            pos.x = x;
            pos.y = y;
            dir.x = dx;
            dir.y = dy;
            this.maxDist = maxDist;
            platform.setTransform(pos, 0);
            platform.getFixtureList().get(0).setUserData("p");
            //platform.getFixtureList().get(0).setUserData("q");
            platform.setAngularVelocity(da);
            platform.setUserData(this);
        }

        public void update (float deltaTime) {
            dist += dir.len() * deltaTime;
            if (dist > maxDist) {
                dir.scl(-1);
                dist = 0;
            }

            platform.setLinearVelocity(dir);// 以dxdy为基础的速度方向
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值