(x, y)
and having speed (a, b)
. Motion is calculated using the Euler method : position = position + velocity
velocity = normalize(target - position) * max_velocity
desired_velocity = normalize(target - position) * max_velocity steering = desired_velocity - velocity
steering = truncate (steering, max_force) steering = steering / mass velocity = truncate (velocity + steering , max_speed) position = position + velocity
desired_velocity = normalize(target - position) * max_velocity steering = desired_velocity - velocity
desired_velocity
is the shortest path between the character and the target. It is obtained by subtracting the position of the target from the position of the character. The result is a force vector passing from the character to the target .desired_velocity
vector desired_velocity
calculated by subtracting the character 's position from the target's position, which gives us a vector directed from the target to the character . desired_velocity = normalize(position - target) * max_velocity steering = desired_velocity - velocity
desired_velocity
represents the shortest escape route that a character can use to get away from the goal. The controlling force causes the character to leave the current route, pushing it in the direction of the vector of the desired speed. flee_desired_velocity = -seek_desired_velocity
// (velocity + steering) , velocity = truncate(velocity + steering, max_speed) position = position + velocity function truncate(vector:Vector3D, max:Number) :void { var i :Number; i = max / vector.length; i = i < 1.0 ? i : 1.0; vector.scaleBy(i); }
// desired_velocity = target - position distance = length(desired_velocity) // , // if (distance < slowingRadius) { // desired_velocity = normalize(desired_velocity) * max_velocity * (distance / slowingRadius) } else { // desired_velocity = normalize(desired_velocity) * max_velocity } // steering = desired_velocity - velocity
slowingRadius
, then the character is far from the target and his speed should remain equal to max_velocity
.slowingRadius
, then the character entered the area of ​​slowing down and his speed should decrease.distance / slowingRadius
varies from 1
(when distance
is slowingRadius
) to 0
(when distance
almost zero). A linear change causes the speed to decrease smoothly: steering = desired_velocity - velocity velocity = truncate (velocity + steering , max_speed) position = position + velocity
-velocity
. Therefore, when this control force is added to the speed, we get a zero, which causes the character to stop.-velocity
, not allowing the character to move while this force is applied. The original velocity vector of the character does not change and continues to work, but is reset by the addition of the control force. // private function wander() :Vector3D { var now :Number = (new Date()).getTime(); if (now >= nextDecision) { // "" } // , // ( seek) return seek(target); } // // , : public function update() :void { steering = wander() steering = truncate (steering, max_force) steering = steering / mass velocity = truncate (velocity + steering , max_speed) position = position + velocity }
// CIRCLE_DISTANCE - // , - . // : var circleCenter :Vector3D; circleCenter = velocity.clone(); circleCenter.normalize(); circleCenter.scaleBy(CIRCLE_DISTANCE);
circleCenter
vector is a clone (copy) of the velocity vector, that is, it points in the same direction. It is normalized and multiplied by the scalar value (in our case, CIRCLE_DISTANCE
), which will give us the following vector: var displacement :Vector3D; displacement = new Vector3D(0, -1); displacement.scaleBy(CIRCLE_RADIUS); // // // setAngle(displacement, wanderAngle); // // wanderAngle, // // . wanderAngle += (Math.random() * ANGLE_CHANGE) - (ANGLE_CHANGE * .5);
wanderAngle
is a scalar value that determines the amount of “slope” of the bias force; after using it, a random value is added to it, so that in the next frame of the game it will be different. This creates the necessary randomness in motion.Hint: do not forget that mathematical vectors do not have a position in space, they only have a direction and a magnitude (length). Therefore, they can be located anywhere.
var wanderForce :Vector3D; wanderForce = circleCenter.add(displacement);
private function wander() :Vector3D { // var circleCenter :Vector3D; circleCenter = velocity.clone(); circleCenter.normalize(); circleCenter.scaleBy(CIRCLE_DISTANCE); // // var displacement :Vector3D; displacement = new Vector3D(0, -1); displacement.scaleBy(CIRCLE_RADIUS); // // // setAngle(displacement, wanderAngle); // // wanderAngle, // // . wanderAngle += Math.random() * ANGLE_CHANGE - ANGLE_CHANGE * .5; // // var wanderForce :Vector3D; wanderForce = circleCenter.add(displacement); return wanderForce; } public function setAngle(vector :Vector3D, value:Number):void { var len :Number = vector.length; vector.x = Math.cos(value) * len; vector.y = Math.sin(value) * len; }
steering = wander() steering = truncate (steering, max_force) steering = steering / mass velocity = truncate (velocity + steering , max_speed) position = position + velocity
position = position + velocity
T
game updates. Suppose a character moves in a straight line and the position we want to predict is located after three updates ( T=3
). Then the character’s future position will be: position = position + velocity * T
T
If the value is too large, the pursuer will chase the ghost. If T
too close to zero, then the pursuer does not really pursue, but simply follows the target (no prediction).Boid
. Then in the following pseudocode, the basic idea of ​​pursuit behavior is implemented: public function pursuit(t :Boid) :Vector3D { T :int = 3; futurePosition :Vector3D = t.position + t.velocity * T; return seek(futurePosition); }
public function update() :void { steering = pursuit(target) steering = truncate (steering, max_force) steering = steering / mass velocity = truncate (velocity + steering , max_speed) position = position + velocity }
T=30
.T
, : . , , , T
«».T
: T = distanceBetweenTargetAndPursuer / MAX_VELOCITY
T
calculated based on the distance between the two characters and the maximum speed that the target can reach. Simply put, a new one T
means “how many updates does a target need to move from the current position to the pursuer position” .T
, that is, the pursuer will tend to a point far ahead of the goal. The shorter the distance, the smaller it will be T
, that is, it will tend to a point that is very close to the target. The new code for this implementation will look like this: public function pursuit(t :Boid) :Vector3D { var distance :Vector3D = t.position - position; var T :int = distance.length / MAX_VELOCITY; futurePosition :Vector3D = t.position + t.velocity * T; return seek(futurePosition); }
T
. public function evade(t :Boid) :Vector3D { var distance :Vector3D = t.position - position; var updatesAhead :int = distance.length / MAX_VELOCITY; futurePosition :Vector3D = t.position + t.velocity * updatesAhead; return flee(futurePosition); }
steering = seek(); // steering = truncate (steering, max_force) steering = steering / mass velocity = truncate (velocity + steering , max_speed) position = position + velocity
steering = nothing(); // , " " steering = steering + seek(); steering = steering + flee(); (...) steering = truncate (steering, max_force) steering = steering / mass velocity = truncate (velocity + steering , max_speed) position = position + velocity
seek()
and flee()
. With each call of such methods, the manager updates its internal properties to create a vector of controlling force.Point
Vector2D
. .IBoid
, , steering behaviors, IBoid
. : public interface IBoid { function getVelocity() :Vector3D; function getMaxVelocity() :Number; function getPosition() :Vector3D; function getMass() :Number; }
public class SteeringManager { public var steering :Vector3D; public var host :IBoid; // public function SteeringManager(host :IBoid) { this.host = host; this.steering = new Vector3D(0, 0); } // API ( ) public function seek(target :Vector3D, slowingRadius :Number = 20) :void {} public function flee(target :Vector3D) :void {} public function wander() :void {} public function evade(target :IBoid) :void {} public function pursuit(target :IBoid) :void {} // . // public function update() :void {} // . public function reset() :void {} // API private function doSeek(target :Vector3D, slowingRadius :Number = 0) :Vector3D {} private function doFlee(target :Vector3D) :Vector3D {} private function doWander() :Vector3D {} private function doEvade(target :IBoid) :Vector3D {} private function doPursuit(target :IBoid) :Vector3D {} }
public function seek(target :Vector3D, slowingRadius :Number = 20) :void {} private function doSeek(target :Vector3D, slowingRadius :Number = 0) :Vector3D {}
seek()
will be called to instruct the manager to apply this particular behavior. This method has no return value, and its parameters are associated with the behavior itself, for example, a point in space. Inside the private method will be called doSeek()
, and its return value, i.e. the calculated control power of this particular behavior will be added to the steering
manager property . // . // slowingRadius ( Arrival). public function seek(target :Vector3D, slowingRadius :Number = 20) :void { steering.incrementBy(doSeek(target, slowingRadius)); } // Seek ( Arrival) private function doSeek(target :Vector3D, slowingRadius :Number = 0) :Vector3D { var force :Vector3D; var distance :Number; desired = target.subtract(host.getPosition()); distance = desired.length; desired.normalize(); if (distance <= slowingRadius) { desired.scaleBy(host.getMaxVelocity() * distance/slowingRadius); } else { desired.scaleBy(host.getMaxVelocity()); } force = desired.subtract(host.getVelocity()); return force; }
pursuit()
would look like this: public function pursuit(target :IBoid) :void { steering.incrementBy(doPursuit(target)); } private function doPursuit(target :IBoid) :Vector3D { distance = target.getPosition().subtract(host.getPosition()); var updatesNeeded :Number = distance.length / host.getMaxVelocity(); var tv :Vector3D = target.getVelocity().clone(); tv.scaleBy(updatesNeeded); targetFuturePosition = target.getPosition().clone().add(tv); return doSeek(targetFuturePosition); }
behavior()
and doBehavior()
so that it can be added to the motion manager.steering
manager. Consequently, all control forces accumulate in this property.update()
motion manager method : public function update():void { var velocity :Vector3D = host.getVelocity(); var position :Vector3D = host.getPosition(); truncate(steering, MAX_FORCE); steering.scaleBy(1 / host.getMass()); velocity.incrementBy(steering); truncate(velocity, host.getMaxVelocity()); position.incrementBy(velocity); }
Prey
that should move using steering behavior, but so far it has neither a control code nor a motion manager. Its structure will look like this: public class Prey { public var position :Vector3D; public var velocity :Vector3D; public var mass :Number; public function Prey(posX :Number, posY :Number, totalMass :Number) { position = new Vector3D(posX, posY); velocity = new Vector3D(-1, -2); mass = totalMass; x = position.x; y = position.y; } public function update():void { velocity.normalize(); velocity.scaleBy(MAX_VELOCITY); velocity.scaleBy(1 / mass); truncate(velocity, MAX_VELOCITY); position = position.add(velocity); x = position.x; y = position.y; } }
IBoid
: public class Prey implements IBoid { public var position :Vector3D; public var velocity :Vector3D; public var mass :Number; public var steering :SteeringManager; public function Prey(posX :Number, posY :Number, totalMass :Number) { position = new Vector3D(posX, posY); velocity = new Vector3D(-1, -2); mass = totalMass; steering = new SteeringManager(this); x = position.x; y = position.y; } public function update():void { velocity.normalize(); velocity.scaleBy(MAX_VELOCITY); velocity.scaleBy(1 / mass); truncate(velocity, MAX_VELOCITY); position = position.add(velocity); x = position.x; y = position.y; } // , IBoid. public function getVelocity() :Vector3D { return velocity; } public function getMaxVelocity() :Number { return 3; } public function getPosition() :Vector3D { return position; } public function getMass() :Number { return mass; } }
update()
needs to be changed accordingly so that the manager can also be updated: public function update():void { // , prey ... steering.wander(); // , prey. // , // "". steering.update(); // , // "". x = position.x; y = position.y; }
update()
, .update()
Prey, (seek) (evade) ( ): public function update():void { var destination :Vector3D = getDestination(); // , var hunter :IBoid = getHunter(); // , // (!) steering.seek(destination); steering.evade(hunter); // , prey. // , // "". steering.update(); // , // "". x = position.x; y = position.y; }
update()
the Hunter class method : public function update():void { if (resting && stamina++ >= MAX_STAMINA) { resting = false; } if (prey != null && !resting) { steering.pursuit(prey); stamina -= 2; if (stamina <= 0) { prey = null; resting = true; } } else { steering.wander(); prey = getClosestPrey(position); } steering.update(); x = position.x; y = position.y; }
update()
Prey: public function update():void { var distance :Number = Vector3D.distance(position, Game.mouse); hunter = getHunterWithinRange(position); if (hunter != null) { steering.evade(hunter); } if (distance <= 300 && hunter == null) { steering.seek(Game.mouse, 30); } else if(hunter == null){ steering.wander(); } steering.update(); x = position.x; y = position.y; }
Source: https://habr.com/ru/post/358366/
All Articles