2
\$\begingroup\$

I'm making an infinite runner type game and I want one of my enemies (when it's created) to swoop down in a sort of U motion where it'll hit the player if they don't react quick enough once the enemy (bat) leaves the screen it'll be destroyed and created again once certain requirements are met . The path will be the same each time so I don't need anything to do with AI. Unfortunately I'm only in grade 10 so calculus makes no sense to me and neither does the pathfinding libgdx documentation. I either need something similar to this (But written for libgdx) or some way to get a box2d body to move in an exact way.

My game is structured like this:

in Bat.java:

public class Bat extends Sprite {

private World world;


public Bat(GameScreen screen) {

    this.world = screen.getWorld();
}

In GameScreen.Java

public class GameScreen implements Screen {

private Bat bat = new Bat(this);

public World getWorld(){
    return world;
}
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

If you don't need the bat to be a Box2D Body then an easy way to achieve this is just to use a Spline, BSpline or CatmullRomSpline and let that be the path of the bat.

If you need the bat to be a Box2D Body then I would still use one of the splines to define the path, then use a kinetic Box2D Body and set the linear velocity to be the delta between the bat's current position and the target position.

For example, in the animation below the small square is the player, the tiny circle is the bat, the six-sided shape is the target position for the bat and the path is the bendy thing.

enter image description here

As the player gets close to the bat's X position the bat starts following the path.

Full working example code:

package com.bornander.playground3d;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.CatmullRomSpline;
import com.badlogic.gdx.math.Vector2;
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.FixtureDef;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.World;



public class Playground3DGame extends ApplicationAdapter {
    private OrthographicCamera camera;
    private ShapeRenderer shapeRenderer;
    private World world;
    private Box2DDebugRenderer debugRenderer;


    private Body player;
    private Body bat;
    private boolean batIsHunting = false;
    private float batTime = 0.0f;

    private CatmullRomSpline<Vector2> batPath;
    private Vector2 batTargetPosition = new Vector2();

    @Override
    public void create() {
        float aspectRatio = (float)Gdx.graphics.getHeight() / (float)Gdx.graphics.getWidth();
        float width = 60;
        float height = width * aspectRatio;
        camera = new OrthographicCamera(width, height);


        world = new World(new Vector2(0,-20), false);
        debugRenderer = new Box2DDebugRenderer();

        shapeRenderer = new ShapeRenderer();

        // Floor
        BodyDef floorDefinition = new BodyDef();
        floorDefinition.type = BodyType.StaticBody;

        PolygonShape floorShape = new PolygonShape();
        floorShape.setAsBox(20, 2, Vector2.Zero, 0f);

        FixtureDef floorFixture = new FixtureDef();
        floorFixture.shape = floorShape;
        floorFixture.friction = 0.5f;

        Body floor = world.createBody(floorDefinition);
        floor.createFixture(floorFixture);


        // Player
        BodyDef playerDefinition = new BodyDef();
        playerDefinition.type = BodyType.DynamicBody;

        PolygonShape playerShape = new PolygonShape();
        playerShape.setAsBox(1, 1, new Vector2(0, 8), 0f);

        FixtureDef playerFixture = new FixtureDef();
        playerFixture.shape = playerShape;
        playerFixture.friction = 0.5f;
        playerFixture.density = 1.0f;

        player = world.createBody(playerDefinition);
        player.createFixture(playerFixture);

        // Bat
        BodyDef batDefinition = new BodyDef();
        batDefinition.type = BodyType.KinematicBody;

        CircleShape batShape = new CircleShape();
        batShape.setRadius(0.5f);
        batShape.setPosition(new Vector2(0, 0));


        FixtureDef batFixture = new FixtureDef();
        batFixture.shape = batShape;
        batFixture.friction = 0.5f;
        batFixture.density = 1.0f;

        bat = world.createBody(batDefinition);
        bat.setTransform(16, 10, 0);
        bat.createFixture(batFixture);          

        batPath = new CatmullRomSpline<Vector2>(new Vector2[] {
                new Vector2(16, 10),
                new Vector2(10, 2),
                new Vector2(0, 10)
        }, true);
    }

    @Override
    public void render() {
        // Control the player using input
        if (Gdx.input.isKeyPressed(Keys.LEFT))
            player.applyForceToCenter(-200, 0, true);
        if (Gdx.input.isKeyPressed(Keys.RIGHT))
            player.applyForceToCenter(200, 0, true);
        if (Gdx.input.isKeyJustPressed(Keys.SPACE) && Math.abs(player.getLinearVelocity().y) < 0.1f)
            player.applyForceToCenter(0, 4000, true);

        // Control the bat
        if (!batIsHunting && (player.getPosition().x > 8 && player.getPosition().x < 9)) {
            batIsHunting = true;
        }

        if (batIsHunting) {
            batTime += Gdx.graphics.getDeltaTime();
            final float maxHuntTime = 4.0f; // Hunt will last for 4 seconds, get a fraction value of this between 0 and 1. 
            float f = batTime / maxHuntTime;
            if (f <= 1.0f) {
                Vector2 bodyPosition = bat.getWorldCenter();
                batPath.valueAt(batTargetPosition, f);
                Vector2 positionDelta = (new Vector2(batTargetPosition)).sub(bodyPosition);

                bat.setLinearVelocity(positionDelta.scl(10));
            }
            else {
                batTime = 0;
                batIsHunting = false;
                bat.setLinearVelocity(0, 0);
            }
        }

        camera.update();
        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
        world.step(Gdx.graphics.getDeltaTime(), 8, 4);
        debugRenderer.render(world, camera.combined);


        // Debug draw the path of the bat
        int k = 100; 
        Vector2[] points = new Vector2[k];
        for(int i = 0; i < k; ++i)
        {
            points[i] = new Vector2();
            batPath.valueAt(points[i], ((float)i)/((float)k-1));
        }           

        shapeRenderer.setProjectionMatrix(camera.combined);
        shapeRenderer.begin(ShapeType.Line);
        for(int i = 0; i < k-1; ++i)
        {
            shapeRenderer.line(points[i], points[i+1]);
        }

        shapeRenderer.circle(batTargetPosition.x, batTargetPosition.y, 1.0f);
        shapeRenderer.end();            
    }
}

The advantage with this approach is that it's easy to define complex paths using splines.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .