From 6fec228efbc3534b7464e70e0791e0afdd94d4f6 Mon Sep 17 00:00:00 2001 From: Dmitri Morozov Date: Fri, 9 Feb 2024 14:44:02 +0100 Subject: Smart pursue for enemy ships --- .../snoopdesigns/endless/physics/PhysicalBody.java | 1 - .../endless/world/ObjectsRenderer.java | 2 +- .../endless/world/ship/SteerableEnemyShip.java | 183 +++++---------------- .../endless/world/steering/ChaseSteering.java | 108 ++++++++++++ .../endless/world/steering/Steering.java | 5 + .../endless/world/steering/SteeringApplicator.java | 9 + 6 files changed, 164 insertions(+), 144 deletions(-) create mode 100644 core/src/org/snoopdesigns/endless/world/steering/ChaseSteering.java create mode 100644 core/src/org/snoopdesigns/endless/world/steering/Steering.java create mode 100644 core/src/org/snoopdesigns/endless/world/steering/SteeringApplicator.java diff --git a/core/src/org/snoopdesigns/endless/physics/PhysicalBody.java b/core/src/org/snoopdesigns/endless/physics/PhysicalBody.java index 914d06a..31204a9 100644 --- a/core/src/org/snoopdesigns/endless/physics/PhysicalBody.java +++ b/core/src/org/snoopdesigns/endless/physics/PhysicalBody.java @@ -37,7 +37,6 @@ public abstract class PhysicalBody { public void limitVelocity() { if (getBody().getLinearVelocity().len() > getMaxVelocity()) { - System.out.println("Limit velocity" + getMaxVelocity()); getBody().setLinearVelocity(getBody().getLinearVelocity().limit(getMaxVelocity())); } } diff --git a/core/src/org/snoopdesigns/endless/world/ObjectsRenderer.java b/core/src/org/snoopdesigns/endless/world/ObjectsRenderer.java index 3afea26..3055532 100644 --- a/core/src/org/snoopdesigns/endless/world/ObjectsRenderer.java +++ b/core/src/org/snoopdesigns/endless/world/ObjectsRenderer.java @@ -20,7 +20,7 @@ public class ObjectsRenderer implements Renderer { batch = new SpriteBatch(); renderables.add(Context.getInstance().getPlayerShip()); - IntStream.range(0, 1).forEach(i -> + IntStream.range(0, 3).forEach(i -> renderables.add(new SteerableEnemyShip())); renderables.forEach(Renderable::create); diff --git a/core/src/org/snoopdesigns/endless/world/ship/SteerableEnemyShip.java b/core/src/org/snoopdesigns/endless/world/ship/SteerableEnemyShip.java index 6db087e..e8d429c 100644 --- a/core/src/org/snoopdesigns/endless/world/ship/SteerableEnemyShip.java +++ b/core/src/org/snoopdesigns/endless/world/ship/SteerableEnemyShip.java @@ -1,14 +1,6 @@ package org.snoopdesigns.endless.world.ship; -import java.util.List; - import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.ai.steer.SteeringAcceleration; -import com.badlogic.gdx.ai.steer.SteeringBehavior; -import com.badlogic.gdx.ai.steer.behaviors.Arrive; -import com.badlogic.gdx.ai.steer.behaviors.BlendedSteering; -import com.badlogic.gdx.ai.steer.behaviors.Face; -import com.badlogic.gdx.ai.utils.Location; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.Sprite; import com.badlogic.gdx.graphics.g2d.SpriteBatch; @@ -17,28 +9,18 @@ import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.physics.box2d.BodyDef.BodyType; import com.badlogic.gdx.physics.box2d.CircleShape; import com.badlogic.gdx.physics.box2d.FixtureDef; -import org.snoopdesigns.endless.context.Context; -import org.snoopdesigns.endless.physics.Box2DLocation; import org.snoopdesigns.endless.physics.SteerablePhysicalBody; import org.snoopdesigns.endless.world.Renderable; import org.snoopdesigns.endless.world.effects.EngineEffect; +import org.snoopdesigns.endless.world.steering.ChaseSteering; +import org.snoopdesigns.endless.world.steering.Steering; +import org.snoopdesigns.endless.world.steering.SteeringApplicator; public class SteerableEnemyShip extends SteerablePhysicalBody implements Renderable { private Sprite sprite; - private Sprite targetSprite; //target position - private Sprite farAttackPointSprite; //farAttackPoint - private Sprite faceSprite; //face position - private final SteeringAcceleration steeringOutput = new SteeringAcceleration<>(new Vector2()); - private BlendedSteering steeringCombination; - - private Location positionTarget; - private Location faceTarget; - private float targetDirection; - - private final Vector2 lastVelocity = new Vector2(0f, 0f); - private EngineEffect engineEffect; + private Steering steering; @Override public BodyType getBodyType() { @@ -54,21 +36,6 @@ public class SteerableEnemyShip extends SteerablePhysicalBody implements Rendera return fixtureDef; } - @Override - public float getMass() { - return 10000; - } - - @Override - public float getLinearDamping() { - return 1.5f; - } - - @Override - public float getMaxVelocity() { - return 80f; - } - @Override public Vector2 getInitialPosition() { return new Vector2(MathUtils.random(150), MathUtils.random(150)); @@ -77,6 +44,7 @@ public class SteerableEnemyShip extends SteerablePhysicalBody implements Rendera @Override public void create() { initBody(); + final Texture texture = new Texture(Gdx.files.internal("ship.png")); sprite = new Sprite(texture); final float expectedSizeInMeters = 15f; @@ -86,73 +54,40 @@ public class SteerableEnemyShip extends SteerablePhysicalBody implements Rendera sprite.setScale(scale.x, scale.y); sprite.setRotation(MathUtils.radDeg * getBody().getAngle()); - final Texture targetTexture = new Texture(Gdx.files.internal("target.png")); - targetSprite = new Sprite(targetTexture); - targetSprite.setScale(0.03f); - farAttackPointSprite = new Sprite(targetTexture); - farAttackPointSprite.setScale(0.02f); - faceSprite = new Sprite(new Texture(Gdx.files.internal("face.png"))); - faceSprite.setScale(0.02f); + engineEffect = new EngineEffect(this); + steering = new ChaseSteering(this, 120f, 90f, new SteeringApplicator() { - positionTarget = new Box2DLocation(); - faceTarget = new Box2DLocation(); + private boolean speedUp = false; - engineEffect = new EngineEffect(this); + @Override + public void applyAngularVelocity(float angular) { + getBody().setAngularVelocity(angular); + } - steeringCombination = new BlendedSteering<>(this); - getSteeringBehaviours().forEach(steeringBehaviour -> - steeringCombination.add(steeringBehaviour, 0.5f)); + @Override + public void applyLinearVelocity(Vector2 linear) { + speedUp(linear); + if (!speedUp) { + engineEffect.start(); + } + speedUp = true; + } - targetDirection = MathUtils.random(MathUtils.PI2); + @Override + public void stopLinearVelocity() { + engineEffect.stop(); + speedUp = false; + } + }); } @Override public void render(SpriteBatch batch) { - final float maxDistance = 100f; - final float minDistance = 70f; - final Vector2 targetDisplacement = new Vector2(maxDistance, 0f).rotateRad(targetDirection); - final Vector2 farAttackPoint = new Vector2( - Context.getInstance().getPlayerShip().getBody().getPosition().x + targetDisplacement.x, - Context.getInstance().getPlayerShip().getBody().getPosition().y + targetDisplacement.y); - final float distanceToPlayer = new Vector2( - getBody().getPosition().x - Context.getInstance().getPlayerShip().getBody().getPosition().x, - getBody().getPosition().y - Context.getInstance().getPlayerShip().getBody().getPosition().y) - .len(); - if (distanceToPlayer > maxDistance + 10f) { - // Rotate target rotation point only of player is far away - targetDirection += Gdx.graphics.getDeltaTime() / 2f; - } - final Vector2 displacement = targetDisplacement.limit(Math.max(0, distanceToPlayer - minDistance)); - final Vector2 targetPos = new Vector2( - Context.getInstance().getPlayerShip().getBody().getPosition().x + displacement.x, - Context.getInstance().getPlayerShip().getBody().getPosition().y + displacement.y); - - positionTarget.getPosition().set(targetPos); - //positionTarget.setOrientation(Context.getInstance().getPlayerShip().getBody().getAngle()); - faceTarget.getPosition().set(positionTarget.getPosition()); - - steeringCombination.calculateSteering(steeringOutput); - if (distanceToPlayer <= minDistance) { // do not move, if close to player, only rotate - steeringOutput.linear.setZero(); - } - applySteering(); + steering.calculate(); sprite.setCenter(getBody().getPosition().x, getBody().getPosition().y); sprite.setRotation(MathUtils.radDeg * getBody().getAngle()); sprite.draw(batch); - farAttackPointSprite.setCenter( - farAttackPoint.x, - farAttackPoint.y); - farAttackPointSprite.draw(batch); - targetSprite.setCenter( - positionTarget.getPosition().x, - positionTarget.getPosition().y); - targetSprite.draw(batch); - faceSprite.setCenter( - faceTarget.getPosition().x, - faceTarget.getPosition().y); - faceSprite.draw(batch); - engineEffect.render(batch); // TODO @@ -163,66 +98,30 @@ public class SteerableEnemyShip extends SteerablePhysicalBody implements Rendera public void dispose() { } - private List> getSteeringBehaviours() { - return List.of( - new Face<>(this, faceTarget), - new Arrive<>(this, positionTarget) - .setTimeToTarget(0.1f) - .setArrivalTolerance(10f) - .setDecelerationRadius(100f) - ); - } - - private void applySteering() { - // Update position and linear velocity. - if (steeringOutput.angular != 0) { - // this method internally scales the torque by deltaTime - getBody().setAngularVelocity(steeringOutput.angular); - } else { - // Update orientation and angular velocity - final Vector2 linearVelocity = getLinearVelocity(); - if (!linearVelocity.isZero(getZeroLinearSpeedThreshold())) { - float newOrientation = linearVelocity.angleRad(); - getBody().setAngularVelocity( - getMaxRotationAcceleration() * - angleDifference(newOrientation, getBody().getAngle())); - } - } - - if (!steeringOutput.linear.isZero()) { - if (steeringOutput.linear.len() >= lastVelocity.len() - 10) { - speedUp(steeringOutput.linear); - engineEffect.start(); - } else { - engineEffect.stop(); - } - lastVelocity.x = steeringOutput.linear.x; - lastVelocity.y = steeringOutput.linear.y; - } else { - engineEffect.stop(); - } - } - private void speedUp(final Vector2 force) { - /*getBody().applyForceToCenter( - force.x * 10000, - force.y * 10000, - true);*/ - final float forceToApply = getBody().getMass() * 200; // force 200x times more than self mass final Vector2 impulse = new Vector2(forceToApply, 0).rotateRad(getBody().getAngle()); getBody().applyForceToCenter(impulse, true); } - // TODO common - private float angleDifference(float angle1, float angle2) { - float diff = (angle1 - angle2) % (MathUtils.PI * 2); - return diff < -1 * MathUtils.PI ? diff + 2 * MathUtils.PI : diff; + @Override + public float getMass() { + return 10000; + } + + @Override + public float getLinearDamping() { + return 1.5f; + } + + @Override + public float getMaxVelocity() { + return getMaxSpeed(); } @Override public float getMaxSpeed() { - return 100f; + return 90f; } @Override diff --git a/core/src/org/snoopdesigns/endless/world/steering/ChaseSteering.java b/core/src/org/snoopdesigns/endless/world/steering/ChaseSteering.java new file mode 100644 index 0000000..3ac3ac7 --- /dev/null +++ b/core/src/org/snoopdesigns/endless/world/steering/ChaseSteering.java @@ -0,0 +1,108 @@ +package org.snoopdesigns.endless.world.steering; + +import java.util.List; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.ai.steer.SteeringAcceleration; +import com.badlogic.gdx.ai.steer.SteeringBehavior; +import com.badlogic.gdx.ai.steer.behaviors.Arrive; +import com.badlogic.gdx.ai.steer.behaviors.BlendedSteering; +import com.badlogic.gdx.ai.steer.behaviors.Face; +import com.badlogic.gdx.ai.utils.Location; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Vector2; +import org.snoopdesigns.endless.context.Context; +import org.snoopdesigns.endless.physics.Box2DLocation; +import org.snoopdesigns.endless.physics.SteerablePhysicalBody; + +public class ChaseSteering implements Steering { + + private final SteeringAcceleration steeringOutput = new SteeringAcceleration<>(new Vector2()); + private final BlendedSteering steeringCombination; + private final Location positionTarget; + private final Location faceTarget; + private final SteeringApplicator steeringApplicator; + private final SteerablePhysicalBody steerablePhysicalBody; + private final Vector2 lastVelocity = new Vector2(0f, 0f); + private final float maxDistance; + private final float minDistance; + private float targetDirection; + + public ChaseSteering(final SteerablePhysicalBody steerablePhysicalBody, + final float maxDistance, + final float minDistance, + final SteeringApplicator steeringApplicator) { + this.steerablePhysicalBody = steerablePhysicalBody; + this.maxDistance = maxDistance; + this.minDistance = minDistance; + this.steeringApplicator = steeringApplicator; + + positionTarget = new Box2DLocation(); + faceTarget = new Box2DLocation(); + + steeringCombination = new BlendedSteering<>(steerablePhysicalBody); + getSteeringBehaviours().forEach(steeringBehaviour -> + steeringCombination.add(steeringBehaviour, 0.5f)); + + targetDirection = MathUtils.random(MathUtils.PI2); + } + + @Override + public void calculate() { + final Vector2 targetDisplacement = new Vector2(maxDistance, 0f).rotateRad(targetDirection); + final float distanceToPlayer = new Vector2( + steerablePhysicalBody.getBody().getPosition().x - + Context.getInstance().getPlayerShip().getBody().getPosition().x, + steerablePhysicalBody.getBody().getPosition().y - + Context.getInstance().getPlayerShip().getBody().getPosition().y) + .len(); + if (distanceToPlayer > maxDistance + 10f) { + // Rotate target rotation point only of player is far away + targetDirection += Gdx.graphics.getDeltaTime() / 1f; + } + final Vector2 displacement = targetDisplacement.limit(Math.max(0, (distanceToPlayer - minDistance))); + final Vector2 targetPos = new Vector2( + Context.getInstance().getPlayerShip().getBody().getPosition().x + displacement.x, + Context.getInstance().getPlayerShip().getBody().getPosition().y + displacement.y); + + positionTarget.getPosition().set(targetPos); + faceTarget.getPosition().set(positionTarget.getPosition()); + + steeringCombination.calculateSteering(steeringOutput); + if (distanceToPlayer <= minDistance) { // do not move, if close to player, only rotate + steeringOutput.linear.setZero(); + } + + applySteering(); + } + + private void applySteering() { + // Update position and linear velocity. + if (steeringOutput.angular != 0) { + // this method internally scales the torque by deltaTime + steeringApplicator.applyAngularVelocity(steeringOutput.angular); + } + + if (!steeringOutput.linear.isZero()) { + if (steeringOutput.linear.len() >= lastVelocity.len() - 10) { + steeringApplicator.applyLinearVelocity(steeringOutput.linear); + } else { + steeringApplicator.stopLinearVelocity(); + } + lastVelocity.x = steeringOutput.linear.x; + lastVelocity.y = steeringOutput.linear.y; + } else { + steeringApplicator.stopLinearVelocity(); + } + } + + private List> getSteeringBehaviours() { + return List.of( + new Face<>(steerablePhysicalBody, faceTarget), + new Arrive<>(steerablePhysicalBody, positionTarget) + .setTimeToTarget(0.1f) + .setArrivalTolerance(10f) + .setDecelerationRadius(100f) + ); + } +} diff --git a/core/src/org/snoopdesigns/endless/world/steering/Steering.java b/core/src/org/snoopdesigns/endless/world/steering/Steering.java new file mode 100644 index 0000000..c13b575 --- /dev/null +++ b/core/src/org/snoopdesigns/endless/world/steering/Steering.java @@ -0,0 +1,5 @@ +package org.snoopdesigns.endless.world.steering; + +public interface Steering { + void calculate(); +} diff --git a/core/src/org/snoopdesigns/endless/world/steering/SteeringApplicator.java b/core/src/org/snoopdesigns/endless/world/steering/SteeringApplicator.java new file mode 100644 index 0000000..d683465 --- /dev/null +++ b/core/src/org/snoopdesigns/endless/world/steering/SteeringApplicator.java @@ -0,0 +1,9 @@ +package org.snoopdesigns.endless.world.steering; + +import com.badlogic.gdx.math.Vector2; + +public interface SteeringApplicator { + void applyAngularVelocity(float angular); + void applyLinearVelocity(Vector2 linear); + void stopLinearVelocity(); +} -- cgit v1.2.3