com.badlogic.gdx.ai.steer.behaviors.Pursue Maven / Gradle / Ivy
/*******************************************************************************
* Copyright 2014 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogic.gdx.ai.steer.behaviors;
import com.badlogic.gdx.ai.steer.Limiter;
import com.badlogic.gdx.ai.steer.Steerable;
import com.badlogic.gdx.ai.steer.SteeringAcceleration;
import com.badlogic.gdx.ai.steer.SteeringBehavior;
import com.badlogic.gdx.math.Vector;
/** {@code Pursue} behavior produces a force that steers the agent towards the evader (the target). Actually it predicts where an
* agent will be in time @{code t} and seeks towards that point to intercept it. We did this naturally playing tag as children,
* which is why the most difficult tag players to catch were those who kept switching direction, foiling our predictions.
*
* This implementation performs the prediction by assuming the target will continue moving with the same velocity it currently
* has. This is a reasonable assumption over short distances, and even over longer distances it doesn't appear too stupid. The
* algorithm works out the distance between character and target and works out how long it would take to get there, at maximum
* speed. It uses this time interval as its prediction lookahead. It calculates the position of the target if it continues to move
* with its current velocity. This new position is then used as the target of a standard seek behavior.
*
* If the character is moving slowly, or the target is a long way away, the prediction time could be very large. The target is
* less likely to follow the same path forever, so we'd like to set a limit on how far ahead we aim. The algorithm has a
* {@code maxPredictionTime} for this reason. If the prediction time is beyond this, then the maximum time is used.
*
* @param Type of vector, either 2D or 3D, implementing the {@link Vector} interface
*
* @author davebaol */
public class Pursue> extends SteeringBehavior {
/** The target */
protected Steerable target;
/** The maximum prediction time */
protected float maxPredictionTime;
/** Creates a {@code Pursue} behavior for the specified owner and target. Maximum prediction time defaults to 1 second.
* @param owner the owner of this behavior.
* @param target the target of this behavior. */
public Pursue (Steerable owner, Steerable target) {
this(owner, target, 1);
}
/** Creates a {@code Pursue} behavior for the specified owner and target.
* @param owner the owner of this behavior
* @param target the target of this behavior
* @param maxPredictionTime the max time used to predict the target's position assuming it continues to move with its current
* velocity. */
public Pursue (Steerable owner, Steerable target, float maxPredictionTime) {
super(owner);
this.target = target;
this.maxPredictionTime = maxPredictionTime;
}
/** Returns the actual linear acceleration to be applied. This method is overridden by the {@link Evade} behavior to invert the
* maximum linear acceleration in order to evade the target. */
protected float getActualMaxLinearAcceleration () {
return getActualLimiter().getMaxLinearAcceleration();
}
@Override
protected SteeringAcceleration calculateRealSteering (SteeringAcceleration steering) {
T targetPosition = target.getPosition();
// Get the square distance to the evader (the target)
float squareDistance = steering.linear.set(targetPosition).sub(owner.getPosition()).len2();
// Work out our current square speed
float squareSpeed = owner.getLinearVelocity().len2();
float predictionTime = maxPredictionTime;
if (squareSpeed > 0) {
// Calculate prediction time if speed is not too small to give a reasonable value
float squarePredictionTime = squareDistance / squareSpeed;
if (squarePredictionTime < maxPredictionTime * maxPredictionTime)
predictionTime = (float)Math.sqrt(squarePredictionTime);
}
// Calculate and seek/flee the predicted position of the target
steering.linear.set(targetPosition).mulAdd(target.getLinearVelocity(), predictionTime).sub(owner.getPosition()).nor()
.scl(getActualMaxLinearAcceleration());
// No angular acceleration
steering.angular = 0;
// Output steering acceleration
return steering;
}
/** Returns the target. */
public Steerable getTarget () {
return target;
}
/** Sets the target.
* @return this behavior for chaining. */
public Pursue setTarget (Steerable target) {
this.target = target;
return this;
}
/** Returns the maximum prediction time. */
public float getMaxPredictionTime () {
return maxPredictionTime;
}
/** Sets the maximum prediction time.
* @return this behavior for chaining. */
public Pursue setMaxPredictionTime (float maxPredictionTime) {
this.maxPredictionTime = maxPredictionTime;
return this;
}
//
// Setters overridden in order to fix the correct return type for chaining
//
@Override
public Pursue setOwner (Steerable owner) {
this.owner = owner;
return this;
}
@Override
public Pursue setEnabled (boolean enabled) {
this.enabled = enabled;
return this;
}
/** Sets the limiter of this steering behavior. The given limiter must at least take care of the maximum linear acceleration.
* @return this behavior for chaining. */
@Override
public Pursue setLimiter (Limiter limiter) {
this.limiter = limiter;
return this;
}
}