xxxxxxxxxx
265
class Evader extends Particle {
static AutoEvade = true;
static AllowWander = false;
static AllowMouseThrust = true;
static STATES = {
fear: 0,
cry: 1,
relieve: 2,
cool: 3,
};
constructor(x, y) {
super(x, y, {
radius: 24,
label: "evader",
frictionAir: 0.06,
maxSpeed: 11,
maxForce: 0.15,
});
this.reliveTime = 50; // frames
this.relieveCounter = 30;
this.lastEvadeSteer = 0;
// The desired velocity when wandering around aimlessly.
this.wanderVelocity = p5.Vector.Null;
this.wanderTimeMax = 80;
this.wanderTimeMin = 20;
this.wanderTime = 0;
this.steer = 1;
this.steerChangeTime = 0;
this.displaceRange = 0.2;
this.wanderTheta = PI / 2;
this.wanderMult = this.maxSpeed * 8;
this.wanderRadius = 35;
}
thrust() {
if (!Evader.AllowMouseThrust) return;
const acc = new p5.Vector(mouseX, mouseY).sub(this);
acc.setMag(acc.mag() * 0.0075);
this.applyForce(acc);
}
_getClosest(intersects) {
let [closest, min] = [null, Infinity];
for (const p of intersects) {
const dist = p.dist(this);
if (dist < min) {
min = dist;
closest = p;
}
}
return closest;
}
_checkForBoundary(vector, mult = 24) {
const current = vector
.copy()
.setMag(this.maxSpeed * mult)
.add(this);
if (!Boundary.isIntersecting(this, current)) {
this.lastEvadeSteer = 0;
return vector;
}
let rotationAmount = 0;
const steerLeft = vector.copy().setMag(this.maxSpeed * mult);
const steerRight = vector.copy().setMag(this.maxSpeed * mult);
while (rotationAmount < Math.PI * 2) {
rotationAmount += Math.PI / 12;
steerLeft.setHeading(vector.heading() + rotationAmount);
steerRight.setHeading(vector.heading() - rotationAmount);
const leftIntersects = [];
const leftPrediction = p5.Vector.add(this, steerLeft);
const isLeftBlocked = Boundary.isIntersecting(
this,
leftPrediction,
false,
leftIntersects,
true
);
const rightIntersects = [];
const rightPrediction = p5.Vector.add(this, steerRight);
const isRightBlocked = Boundary.isIntersecting(
this,
rightPrediction,
false,
rightIntersects,
true
);
if (!isRightBlocked && !isLeftBlocked) {
if (this.lastEvadeSteer > 0) {
this.lastEvadeSteer++;
return steerLeft;
} else if (this.lastEvaderSteer < 0) {
this.lastEvadeSteer--;
return steerRight;
} else {
this.lastEvadeSteer = Math.floor(Math.random() * 2) === 1 ? 0 : -1;
return this.lastEvadeSteer >= 0 ? steerLeft : steerRight;
}
}
if (!isRightBlocked) {
this.lastEvadeSteer--;
return steerRight;
} else if (!isLeftBlocked) {
this.lastEvadeSteer++;
return steerLeft;
}
if (Particle.Debug) {
const leftClosest = this._getClosest(leftIntersects);
const rightClosest = this._getClosest(rightIntersects);
push();
stroke("grey");
strokeWeight(1.5);
line(this.x, this.y, rightClosest.x, rightClosest.y);
line(this.x, this.y, leftClosest.x, leftClosest.y);
noStroke();
fill("green");
circle(leftClosest.x, leftClosest.y, 7);
circle(rightClosest.x, rightClosest.y, 7);
pop();
}
}
this.lastEvadeSteer = Math.floor(Math.random() * 2) === 1 ? 0 : -1;
return this.lastEvadeSteer >= 0 ? steerLeft : steerRight;
}
_evade(seekers) {
seekers = seekers.filter((s) => s.isState("attack"));
let desired = p5.Vector.Null;
for (const seeker of seekers) {
const vecToSeeker = p5.Vector.sub(seeker, this);
desired.add(vecToSeeker.mult(-1));
}
desired = this._checkForBoundary(desired);
desired.setMag(this.maxSpeed);
if (!Seeker.Pause) this.applySteer(desired);
if (Particle.Debug) {
const vec = desired.setMag(CANVAS_WIDTH / 22);
push();
stroke("green");
strokeWeight(2);
translate(this.x, this.y);
line(0, 0, vec.x, vec.y);
translate(vec.x, vec.y);
rotate(desired.heading() - Math.PI / 2);
line(0, 0, -5, -5);
line(0, 0, +5, -5);
pop();
}
}
_wander()
{
// Change to a completley new direction when the state has just changed to wander.
if (this.prevState !== Evader.STATES.cool) {
this.steer = Math.floor(Math.random() * 2) === 0 ? 1 : -1;
const angle = Math.random() * (Math.PI * 2) * this.steer;
this.wanderVelocity = p5.Vector.fromAngle(angle, this.maxSpeed / 2);
}
const wanderPoint = this.velocity.copy().setMag(this.wanderMult).add(this);
fill(255, 0, 0);
noStroke();
circle(wanderPoint.x, wanderPoint.y, 8);
noFill();
stroke(255);
circle(wanderPoint.x, wanderPoint.y, this.wanderRadius * 2);
line(this.x, this.y, wanderPoint.x, wanderPoint.y);
const theta = this.wanderTheta + this.velocity.heading();
const x = this.wanderRadius * Math.cos(theta);
const y = this.wanderRadius * Math.sin(theta);
wanderPoint.add(x, y);
fill(0, 255, 0);
noStroke();
circle(wanderPoint.x, wanderPoint.y, 16);
stroke(255);
line(this.x, this.y, wanderPoint.x, wanderPoint.y);
this.wanderTheta += random(-this.displaceRange, this.displaceRange);
let wanderSteer = wanderPoint.sub(this);
wanderSteer.setMag(this.maxForce / 4);
let desired = wanderSteer.add(this.velocity);
desired = this._checkForBoundary(desired, 24);
desired.setMag(this.maxSpeed / 4);
super.applySteer(desired);
}
_decideState(seeker) {
const wanderCount = seeker.filter((s) => s.isState("wander")).length;
const attackCount = seeker.length - wanderCount;
this.prevState = this.state;
if (wanderCount === 0) this.setState("cry");
else if (attackCount > 0) this.setState("fear");
else if (attackCount === 0 && this.isState(["fear", "cry"])) {
this.setState("relieve");
this.relieveCounter = this.reliveTime;
} else if (this.isState("relieve") && this.relieveCounter > 0) {
this.relieveCounter--;
} else {
this.setState("cool");
}
}
evade(seekers) {
this._decideState(seekers);
if (Evader.AutoEvade && this.isState(["fear", "cry"])) this._evade(seekers);
else if (this.isState("cool") && Evader.AllowWander) this._wander();
}
draw() {
const [r, r2] = [this.radius, this.radius * 2.1];
switch (this.state) {
case Evader.STATES.fear:
image(IMAGES.fear, -r, -r, r2, r2);
break;
case Evader.STATES.cry:
image(IMAGES.cry, -r, -r, r2, r2);
break;
case Evader.STATES.relieve:
image(IMAGES.relieve, -r, -r, r2, r2);
break;
case Evader.STATES.cool:
default:
image(IMAGES.cool, -r, -r, r2, r2);
break;
}
}
}