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.
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.