com.badlogic.gdx.ai.steer.behaviors.Wander 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.GdxAI;
import com.badlogic.gdx.ai.Timepiece;
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.utils.Location;
import org.mini2Dx.gdx.math.MathUtils;
import org.mini2Dx.gdx.math.Vector;
/** {@code Wander} behavior is designed to produce a steering acceleration that will give the impression of a random walk through
* the agent's environment. You'll often find it a useful ingredient when creating an agent's behavior.
*
* There is a circle in front of the owner (where front is determined by its current facing direction) on which the target is
* constrained. Each time the behavior is run, we move the target around the circle a little, by a random amount. Now there are 2
* ways to implement wander behavior:
*
* - The owner seeks the target, using the {@link Seek} behavior, and performs a {@link LookWhereYouAreGoing} behavior to
* correct its orientation.
* - The owner tries to face the target in each frame, using the {@link Face} behavior to align to the target, and applies full
* linear acceleration in the direction of its current orientation.
*
* In either case, the orientation of the owner is retained between calls (so smoothing the changes in orientation). The angles
* that the edges of the circle subtend to the owner determine how fast it will turn. If the target is on one of these extreme
* points, it will turn quickly. The target will twitch and jitter around the edge of the circle, but the owner's orientation will
* change smoothly.
*
* This implementation uses the second approach. However, if you manually align owner's orientation to its linear velocity on each
* time step, {@link Face} behavior should not be used (which is the default case). On the other hand, if the owner has
* independent facing you should explicitly call {@link #setFaceEnabled(boolean) setFaceEnabled(true)} before using Wander
* behavior.
*
* Note that this behavior internally calls the {@link Timepiece#getTime() GdxAI.getTimepiece().getTime()} method to get the
* current AI time and make the {@link #wanderRate} FPS independent. This means that
*
* - if you forget to {@link Timepiece#update(float) update the timepiece} the wander orientation won't change.
* - ideally the timepiece should be always updated before this steering behavior runs.
*
*
* This steering behavior can be used to produce a whole range of random motion, from very smooth undulating turns to wild
* Strictly Ballroom type whirls and pirouettes depending on the size of the circle, its distance from the agent, and the amount
* of random displacement each frame.
*
* @param Type of vector, either 2D or 3D, implementing the {@link Vector} interface
*
* @author davebaol */
public class Wander> extends Face {
/** The forward offset of the wander circle */
protected float wanderOffset;
/** The radius of the wander circle */
protected float wanderRadius;
/** The rate, expressed in radian per second, at which the wander orientation can change */
protected float wanderRate;
/** The last time the orientation of the wander target has been updated */
protected float lastTime;
/** The current orientation of the wander target */
protected float wanderOrientation;
/** The flag indicating whether to use {@link Face} behavior or not. This should be set to {@code true} when independent facing
* is used. */
protected boolean faceEnabled;
private T internalTargetPosition;
private T wanderCenter;
/** Creates a {@code Wander} behavior for the specified owner.
* @param owner the owner of this behavior. */
public Wander (Steerable owner) {
super(owner);
this.internalTargetPosition = newVector(owner);
this.wanderCenter = newVector(owner);
}
@Override
protected SteeringAcceleration calculateRealSteering (SteeringAcceleration steering) {
// Update the wander orientation
float now = GdxAI.getTimepiece().getTime();
if (lastTime > 0) {
float delta = now - lastTime;
wanderOrientation += MathUtils.randomTriangular(wanderRate * delta);
}
lastTime = now;
// Calculate the combined target orientation
float targetOrientation = wanderOrientation + owner.getOrientation();
// Calculate the center of the wander circle
wanderCenter.set(owner.getPosition()).mulAdd(owner.angleToVector(steering.linear, owner.getOrientation()), wanderOffset);
// Calculate the target location
// Notice that we're using steering.linear as temporary vector
internalTargetPosition.set(wanderCenter).mulAdd(owner.angleToVector(steering.linear, targetOrientation), wanderRadius);
float maxLinearAcceleration = getActualLimiter().getMaxLinearAcceleration();
if (faceEnabled) {
// Delegate to face
face(steering, internalTargetPosition);
// Set the linear acceleration to be at full
// acceleration in the direction of the orientation
owner.angleToVector(steering.linear, owner.getOrientation()).scl(maxLinearAcceleration);
} else {
// Seek the internal target position
steering.linear.set(internalTargetPosition).sub(owner.getPosition()).nor().scl(maxLinearAcceleration);
// No angular acceleration
steering.angular = 0;
}
return steering;
}
/** Returns the forward offset of the wander circle. */
public float getWanderOffset () {
return wanderOffset;
}
/** Sets the forward offset of the wander circle.
* @return this behavior for chaining. */
public Wander setWanderOffset (float wanderOffset) {
this.wanderOffset = wanderOffset;
return this;
}
/** Returns the radius of the wander circle. */
public float getWanderRadius () {
return wanderRadius;
}
/** Sets the radius of the wander circle.
* @return this behavior for chaining. */
public Wander setWanderRadius (float wanderRadius) {
this.wanderRadius = wanderRadius;
return this;
}
/** Returns the rate, expressed in radian per second, at which the wander orientation can change. */
public float getWanderRate () {
return wanderRate;
}
/** Sets the rate, expressed in radian per second, at which the wander orientation can change.
* @return this behavior for chaining. */
public Wander setWanderRate (float wanderRate) {
this.wanderRate = wanderRate;
return this;
}
/** Returns the current orientation of the wander target. */
public float getWanderOrientation () {
return wanderOrientation;
}
/** Sets the current orientation of the wander target.
* @return this behavior for chaining. */
public Wander setWanderOrientation (float wanderOrientation) {
this.wanderOrientation = wanderOrientation;
return this;
}
/** Returns the flag indicating whether to use {@link Face} behavior or not. */
public boolean isFaceEnabled () {
return faceEnabled;
}
/** Sets the flag indicating whether to use {@link Face} behavior or not. This should be set to {@code true} when independent
* facing is used.
* @return this behavior for chaining. */
public Wander setFaceEnabled (boolean faceEnabled) {
this.faceEnabled = faceEnabled;
return this;
}
/** Returns the current position of the wander target. This method is useful for debug purpose. */
public T getInternalTargetPosition () {
return internalTargetPosition;
}
/** Returns the current center of the wander circle. This method is useful for debug purpose. */
public T getWanderCenter () {
return wanderCenter;
}
//
// Setters overridden in order to fix the correct return type for chaining
//
@Override
public Wander setOwner (Steerable owner) {
this.owner = owner;
return this;
}
@Override
public Wander 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;
* additionally, if the flag {@code faceEnabled} is true, it must take care of the maximum angular speed and acceleration.
* @return this behavior for chaining. */
@Override
public Wander setLimiter (Limiter limiter) {
this.limiter = limiter;
return this;
}
/** Sets the target to align to. Notice that this method is inherited from {@link ReachOrientation}, but is completely useless
* for {@code Wander} because owner's orientation is determined by the internal target, which is moving on the wander circle.
* @return this behavior for chaining. */
@Override
public Wander setTarget (Location target) {
this.target = target;
return this;
}
@Override
public Wander setAlignTolerance (float alignTolerance) {
this.alignTolerance = alignTolerance;
return this;
}
@Override
public Wander setDecelerationRadius (float decelerationRadius) {
this.decelerationRadius = decelerationRadius;
return this;
}
@Override
public Wander setTimeToTarget (float timeToTarget) {
this.timeToTarget = timeToTarget;
return this;
}
}