377 lines
9.9 KiB
JavaScript
377 lines
9.9 KiB
JavaScript
import { GameObject } from '../engine/GameObject.js';
|
|
import { Vector2D } from '../engine/Vector2D.js';
|
|
|
|
export class Zombie extends GameObject {
|
|
constructor(
|
|
x,
|
|
y,
|
|
target,
|
|
{
|
|
container,
|
|
viewport,
|
|
speed = 110,
|
|
size = 24,
|
|
color = [0.15, 0.65, 0.2, 1],
|
|
health = 1,
|
|
knockback = 260,
|
|
knockbackDamping = 10,
|
|
scoreValue = 1,
|
|
emergeTime = 0,
|
|
onDeath = null,
|
|
} = {},
|
|
) {
|
|
super(x, y);
|
|
this.target = target;
|
|
this.container = container;
|
|
this.viewport = viewport;
|
|
this.speed = speed;
|
|
this.size = size;
|
|
this.baseColor = [...color];
|
|
this.health = health;
|
|
this.maxHealth = health;
|
|
this.knockback = knockback;
|
|
this.knockbackDamping = knockbackDamping;
|
|
this.scoreValue = scoreValue;
|
|
this.onDeath = onDeath;
|
|
this._hitKnockback = new Vector2D(0, 0);
|
|
this._flash = 0;
|
|
this._time = 0;
|
|
this._broken = Math.random() * Math.PI * 2;
|
|
this._emergeTotal = emergeTime;
|
|
this._emerge = emergeTime;
|
|
this.width = size;
|
|
this.height = size;
|
|
}
|
|
|
|
die() {
|
|
if (this.onDeath) {
|
|
this.onDeath(this);
|
|
}
|
|
this.remove(this.container);
|
|
}
|
|
|
|
hit(impulse, damage = 1) {
|
|
this.health -= damage;
|
|
this._hitKnockback = impulse.mulS(this.knockback);
|
|
this._flash = 0.08;
|
|
return this.health <= 0;
|
|
}
|
|
|
|
update(dt) {
|
|
this._time += dt;
|
|
if (this._updateEmerge(dt)) return;
|
|
const delta = this.target.pos.subV(this.pos);
|
|
const distance = delta.length();
|
|
let chaseVelocity = new Vector2D(0, 0);
|
|
if (distance > 1) {
|
|
chaseVelocity = delta.divS(distance).mulS(this.speed);
|
|
}
|
|
|
|
this.velocity = chaseVelocity.addV(this._hitKnockback);
|
|
super.update(dt);
|
|
|
|
this._hitKnockback = this._hitKnockback.mulS(Math.max(0, 1 - dt * this.knockbackDamping));
|
|
this._flash = Math.max(0, this._flash - dt);
|
|
}
|
|
|
|
_updateEmerge(dt) {
|
|
if (this._emerge <= 0) return false;
|
|
this._emerge -= dt;
|
|
if (this._emerge <= 0) {
|
|
this._emerge = 0;
|
|
if (this._onEmergenceComplete) this._onEmergenceComplete();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
_emergeSize() {
|
|
if (this._emerge > 0 && this._emergeTotal > 0) {
|
|
return 0.3 + 0.7 * (1 - this._emerge / this._emergeTotal);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
_drawEmerge(renderer) {
|
|
if (this._emerge <= 0) return;
|
|
const gear = 1 - this._emerge / this._emergeTotal;
|
|
renderer.setBlendMode('additive');
|
|
const g = this.size * (3.4 - gear * 1.4);
|
|
renderer.drawTexture(
|
|
renderer.glowTexture,
|
|
this.pos.x - g / 2,
|
|
this.pos.y - g / 2,
|
|
g,
|
|
g,
|
|
[this.baseColor[0], this.baseColor[1], this.baseColor[2], 0.5 * (1 - gear)],
|
|
);
|
|
renderer.setBlendMode('normal');
|
|
}
|
|
|
|
draw(renderer) {
|
|
this.width = this.size;
|
|
this.height = this.size;
|
|
|
|
let color = this.baseColor;
|
|
if (this._flash > 0) {
|
|
color = [1, 1, 1, 1];
|
|
} else if (this.maxHealth > 1) {
|
|
const hpRatio = this.health / this.maxHealth;
|
|
color = [
|
|
this.baseColor[0] + (1 - this.baseColor[0]) * (1 - hpRatio),
|
|
this.baseColor[1] * hpRatio,
|
|
this.baseColor[2] * hpRatio,
|
|
1,
|
|
];
|
|
}
|
|
|
|
if (this.maxHealth > 1) {
|
|
this._drawJuggernaut(renderer, color);
|
|
return;
|
|
}
|
|
|
|
this._drawEmerge(renderer);
|
|
const s = this.size * this._emergeSize();
|
|
const lean = s <= 16;
|
|
const facing = Math.atan2(
|
|
this.target.pos.y - this.pos.y,
|
|
this.target.pos.x - this.pos.x,
|
|
);
|
|
const cosF = Math.cos(facing);
|
|
const sinF = Math.sin(facing);
|
|
const walk = Math.sin(this._time * (lean ? 14 : 10) + this._broken);
|
|
|
|
const shade = [color[0] * 0.6, color[1] * 0.6, color[2] * 0.6, 1];
|
|
const shadeDark = [color[0] * 0.38, color[1] * 0.38, color[2] * 0.38, 1];
|
|
const light = [
|
|
Math.min(1, color[0] + 0.25),
|
|
Math.min(1, color[1] + 0.25),
|
|
Math.min(1, color[2] + 0.25),
|
|
1,
|
|
];
|
|
|
|
const drawLimb = (ax, ay, bx, by, thickness, limbColor) => {
|
|
const len = Math.hypot(bx - ax, by - ay);
|
|
if (len < 1) return;
|
|
renderer.drawTexture(
|
|
renderer.whiteTexture,
|
|
(ax + bx) / 2 - len / 2,
|
|
(ay + by) / 2 - thickness / 2,
|
|
len,
|
|
thickness,
|
|
limbColor,
|
|
Math.atan2(by - ay, bx - ax),
|
|
);
|
|
};
|
|
|
|
if (!lean) {
|
|
renderer.drawTexture(
|
|
renderer.whiteTexture,
|
|
this.pos.x - s * 0.62,
|
|
this.pos.y - s * 0.5 + s * 0.06,
|
|
s * 1.24,
|
|
s * 0.92,
|
|
[0, 0, 0, 0.35],
|
|
);
|
|
}
|
|
|
|
const legLen = s * (lean ? 0.32 : 0.4);
|
|
const legSpread = s * 0.22;
|
|
const hipX = this.pos.x - cosF * s * 0.18;
|
|
const hipY = this.pos.y - sinF * s * 0.18;
|
|
for (let i = 0; i < 2; i += 1) {
|
|
const sign = i === 0 ? 1 : -1;
|
|
const step = sign * walk * s * (lean ? 0.3 : 0.18);
|
|
const footX = hipX + cosF * legLen - sinF * legSpread * sign + cosF * step;
|
|
const footY = hipY + sinF * legLen + cosF * legSpread * sign + sinF * step;
|
|
drawLimb(hipX, hipY, footX, footY, s * 0.15, shadeDark);
|
|
}
|
|
|
|
const torsoW = s * (lean ? 0.5 : 0.62);
|
|
const torsoH = s * (lean ? 0.46 : 0.55);
|
|
renderer.drawTexture(
|
|
renderer.whiteTexture,
|
|
this.pos.x - torsoW / 2,
|
|
this.pos.y - torsoH / 2,
|
|
torsoW,
|
|
torsoH,
|
|
color,
|
|
);
|
|
|
|
const armLen = s * 0.5;
|
|
const armSpread = s * 0.27;
|
|
for (let i = 0; i < 2; i += 1) {
|
|
const sign = i === 0 ? 1 : -1;
|
|
const shX = this.pos.x - sinF * armSpread * sign;
|
|
const shY = this.pos.y + cosF * armSpread * sign - s * 0.02;
|
|
const reach = 1 + walk * 0.1;
|
|
const handX = shX + cosF * armLen * reach;
|
|
const handY = shY + sinF * armLen * reach;
|
|
drawLimb(shX, shY, handX, handY, s * 0.17, shade);
|
|
}
|
|
|
|
const headSize = s * (lean ? 0.4 : 0.44);
|
|
const headX = this.pos.x;
|
|
const headY = this.pos.y - s * (lean ? 0.34 : 0.4) + walk * s * 0.03;
|
|
renderer.drawTexture(
|
|
renderer.whiteTexture,
|
|
headX - headSize / 2,
|
|
headY - headSize / 2,
|
|
headSize,
|
|
headSize,
|
|
light,
|
|
);
|
|
|
|
const eyeSize = headSize * 0.2;
|
|
const eyeSpread = headSize * 0.24;
|
|
const look = lean ? 0.1 : 0.04;
|
|
const eyeY = headY + sinF * headSize * look;
|
|
renderer.drawTexture(
|
|
renderer.whiteTexture,
|
|
headX - eyeSize / 2 - sinF * eyeSpread,
|
|
eyeY - eyeSize / 2 + cosF * eyeSpread,
|
|
eyeSize,
|
|
eyeSize,
|
|
[0.05, 0.05, 0.05, 1],
|
|
);
|
|
renderer.drawTexture(
|
|
renderer.whiteTexture,
|
|
headX - eyeSize / 2 + sinF * eyeSpread,
|
|
eyeY - eyeSize / 2 - cosF * eyeSpread,
|
|
eyeSize,
|
|
eyeSize,
|
|
[0.05, 0.05, 0.05, 1],
|
|
);
|
|
}
|
|
|
|
_drawJuggernaut(renderer, color) {
|
|
const s = this.size;
|
|
const facing = Math.atan2(
|
|
this.target.pos.y - this.pos.y,
|
|
this.target.pos.x - this.pos.x,
|
|
);
|
|
const cosF = Math.cos(facing);
|
|
const sinF = Math.sin(facing);
|
|
const walk = Math.sin(this._time * 5 + this._broken);
|
|
|
|
const shade = [color[0] * 0.6, color[1] * 0.6, color[2] * 0.6, 1];
|
|
const shadeDark = [color[0] * 0.38, color[1] * 0.38, color[2] * 0.38, 1];
|
|
const light = [
|
|
Math.min(1, color[0] + 0.25),
|
|
Math.min(1, color[1] + 0.25),
|
|
Math.min(1, color[2] + 0.25),
|
|
1,
|
|
];
|
|
const mix = (a, b, t) => a + (b - a) * t;
|
|
|
|
renderer.drawTexture(
|
|
renderer.whiteTexture,
|
|
this.pos.x - s * 0.78,
|
|
this.pos.y - s * 0.55 + s * 0.08,
|
|
s * 1.56,
|
|
s * 1.14,
|
|
[0, 0, 0, 0.35],
|
|
);
|
|
|
|
const footSize = s * 0.16;
|
|
const footBaseY = this.pos.y + s * 0.46;
|
|
for (let i = 0; i < 2; i += 1) {
|
|
const sign = i === 0 ? 1 : -1;
|
|
const stepX = -sinF * s * 0.2 * sign + cosF * walk * s * 0.06 * sign;
|
|
const stepY = cosF * s * 0.2 * sign + sinF * walk * s * 0.06 * sign;
|
|
renderer.drawTexture(
|
|
renderer.whiteTexture,
|
|
this.pos.x + stepX - footSize / 2,
|
|
footBaseY + stepY - footSize / 2,
|
|
footSize,
|
|
footSize,
|
|
shadeDark,
|
|
);
|
|
}
|
|
|
|
const shellW = s * 1.0;
|
|
const shellH = s * 0.95;
|
|
const bottomY = this.pos.y + s * 0.4;
|
|
const topY = this.pos.y - s * 0.5;
|
|
const layers = 6;
|
|
for (let i = 0; i < layers; i += 1) {
|
|
const u = i / (layers - 1);
|
|
const w = shellW * (1 - u * 0.66);
|
|
const h = (shellH / layers) * 1.4;
|
|
const cy = bottomY - u * (bottomY - topY) + walk * s * 0.02;
|
|
renderer.drawTexture(
|
|
renderer.whiteTexture,
|
|
this.pos.x - w / 2,
|
|
cy - h / 2,
|
|
w,
|
|
h,
|
|
[
|
|
mix(shadeDark[0], light[0], u),
|
|
mix(shadeDark[1], light[1], u),
|
|
mix(shadeDark[2], light[2], u),
|
|
1,
|
|
],
|
|
);
|
|
}
|
|
|
|
for (let i = 0; i < 3; i += 1) {
|
|
const rx = this.pos.x + (i - 1) * s * 0.16;
|
|
renderer.drawTexture(
|
|
renderer.whiteTexture,
|
|
rx - s * 0.045,
|
|
topY + s * 0.04,
|
|
s * 0.09,
|
|
shellH * 0.9,
|
|
shadeDark,
|
|
);
|
|
}
|
|
|
|
const rivet = s * 0.055;
|
|
renderer.drawTexture(
|
|
renderer.whiteTexture,
|
|
this.pos.x - s * 0.44,
|
|
bottomY - rivet * 0.4,
|
|
rivet,
|
|
rivet,
|
|
shadeDark,
|
|
);
|
|
renderer.drawTexture(
|
|
renderer.whiteTexture,
|
|
this.pos.x + s * 0.44 - rivet,
|
|
bottomY - rivet * 0.4,
|
|
rivet,
|
|
rivet,
|
|
shadeDark,
|
|
);
|
|
|
|
const headSize = s * 0.24;
|
|
const headX = this.pos.x + cosF * s * 0.52;
|
|
const headY = this.pos.y + sinF * s * 0.52;
|
|
renderer.drawTexture(
|
|
renderer.whiteTexture,
|
|
headX - headSize / 2,
|
|
headY - headSize / 2,
|
|
headSize,
|
|
headSize,
|
|
light,
|
|
);
|
|
|
|
const eyeSize = headSize * 0.22;
|
|
const eyeSpread = headSize * 0.3;
|
|
renderer.drawTexture(
|
|
renderer.whiteTexture,
|
|
headX - eyeSize / 2 - sinF * eyeSpread,
|
|
headY - eyeSize / 2 + cosF * eyeSpread,
|
|
eyeSize,
|
|
eyeSize,
|
|
[0.05, 0.05, 0.05, 1],
|
|
);
|
|
renderer.drawTexture(
|
|
renderer.whiteTexture,
|
|
headX - eyeSize / 2 + sinF * eyeSpread,
|
|
headY - eyeSize / 2 - cosF * eyeSpread,
|
|
eyeSize,
|
|
eyeSize,
|
|
[0.05, 0.05, 0.05, 1],
|
|
);
|
|
}
|
|
} |