Files
zombie-game/resources/js/game/Player.js
T
2026-09-07 23:02:47 +02:00

364 lines
9.8 KiB
JavaScript

import { GameObject } from '../engine/GameObject.js';
import { Vector2D } from '../engine/Vector2D.js';
import { Bullet } from './Bullet.js';
export class Player extends GameObject {
constructor(
{
input,
container,
viewport,
moveSpeed = 420,
fireRate = 0.2,
recoilSpeed = 100,
recoilDamping = 15,
bulletSpeed = 800,
bulletSize = 8,
bulletCount = 1,
bulletSpread = 0.09,
damage = 1,
maxUpgradeLevel = 3,
health = 3,
particles = null,
sounds = null,
size = 24,
} = {},
) {
super(viewport.width / 2, viewport.height / 2);
this.input = input;
this.container = container;
this.viewport = viewport;
this.moveSpeed = moveSpeed;
this.fireRate = fireRate;
this.bulletSpeed = bulletSpeed;
this.bulletSize = bulletSize;
this.bulletCount = bulletCount;
this.bulletSpread = bulletSpread;
this.damage = damage;
this.maxUpgradeLevel = maxUpgradeLevel;
this.upgrades = { rapid: 0, damage: 0, multishot: 0, speed: 0 };
this.recoilSpeed = recoilSpeed;
this.recoilDamping = recoilDamping;
this.health = health;
this.maxHealth = health;
this.particles = particles;
this.sounds = sounds;
this.size = size;
this._cooldown = 0;
this._aimDirection = new Vector2D(1, 0);
this._recoil = new Vector2D(0, 0);
this._damageCooldown = 0;
this._time = 0;
}
update(dt) {
const { width, height } = this.viewport;
this._time += dt;
const movement = new Vector2D(0, 0);
const { keys } = this.input;
if (keys.has('w') || keys.has('arrowup')) movement.y -= 1;
if (keys.has('s') || keys.has('arrowdown')) movement.y += 1;
if (keys.has('a') || keys.has('arrowleft')) movement.x -= 1;
if (keys.has('d') || keys.has('arrowright')) movement.x += 1;
let moveVelocity = new Vector2D(0, 0);
if (movement.lengthSqrt() > 0) {
moveVelocity = movement.normalize().mulS(this.moveSpeed);
}
this.velocity = this._recoil.lengthSqrt() > 1
? moveVelocity.addV(this._recoil)
: moveVelocity;
super.update(dt);
const maxX = Math.max(this.width / 2, 0);
const maxY = Math.max(this.height / 2, 0);
this.pos.x = Math.min(Math.max(this.pos.x, maxX), width - maxX);
this.pos.y = Math.min(Math.max(this.pos.y, maxY), height - maxY);
let aim = this.input.mouse.subV(this.pos);
if (aim.lengthSqrt() > 1) {
this._aimDirection = aim.divS(aim.length());
}
this._recoil = this._recoil.mulS(Math.max(0, 1 - dt * this.recoilDamping));
this._damageCooldown = Math.max(0, this._damageCooldown - dt);
this._cooldown -= dt;
if (this._cooldown <= 0) {
this._cooldown = this.fireRate;
this.shoot();
}
}
shoot() {
const aimAngle = Math.atan2(this._aimDirection.y, this._aimDirection.x);
this._recoil = this._aimDirection.mulS(-this.recoilSpeed);
this.sounds?.shoot();
this.particles?.burst(this.pos.x, this.pos.y, {
count: 6,
speed: 120,
direction: aimAngle,
spread: 0.7,
life: 0.25,
size: 4,
color: [1, 0.85, 0.3, 1],
gravity: 0,
});
this.particles?.burst(this.pos.x, this.pos.y, {
count: 4,
speed: 260,
direction: aimAngle,
spread: 0.25,
life: 0.14,
size: 5,
color: [1, 1, 1, 1],
gravity: 0,
});
this.particles?.burst(this.pos.x, this.pos.y, {
count: 5,
speed: 70,
spread: Math.PI * 2,
life: 0.4,
size: 4,
color: [0.6, 0.6, 0.6, 1],
gravity: 0,
});
const offset = ((this.bulletCount - 1) / 2) * this.bulletSpread;
for (let i = 0; i < this.bulletCount; i += 1) {
const angle = aimAngle - offset + i * this.bulletSpread;
const direction = new Vector2D(Math.cos(angle), Math.sin(angle));
const bullet = new Bullet(this.pos.x, this.pos.y, direction, {
container: this.container,
viewport: this.viewport,
speed: this.bulletSpeed,
size: this.bulletSize,
damage: this.damage,
});
this.container?.push(bullet);
}
}
applyUpgrade(type) {
if (!(type in this.upgrades)) return false;
if (this.upgrades[type] >= this.maxUpgradeLevel) return false;
this.upgrades[type] += 1;
switch (type) {
case 'rapid':
this.fireRate = Math.max(0.06, this.fireRate * 0.8);
break;
case 'damage':
this.damage += 1;
break;
case 'multishot':
this.bulletCount = Math.min(7, this.bulletCount + 1);
break;
case 'speed':
this.bulletSpeed = Math.min(1800, this.bulletSpeed * 1.15);
break;
default:
break;
}
return true;
}
takeDamage() {
if (this._damageCooldown > 0) return this.health;
this._damageCooldown = 0.5;
this.health -= 1;
this.sounds?.damage();
return this.health;
}
draw(renderer) {
const s = this.size;
this.width = s;
this.height = s;
let alpha = 1;
if (this._damageCooldown > 0) {
alpha = Math.sin(this._time * 30) > 0 ? 1 : 0.25;
}
const moving = this.velocity.lengthSqrt() > 10;
const facing = Math.atan2(this._aimDirection.y, this._aimDirection.x);
const dirX = Math.cos(facing);
const dirY = Math.sin(facing);
const perpX = -dirY;
const perpY = dirX;
const bob = Math.sin(this._time * (moving ? 11 : 2)) * s * (moving ? 0.04 : 0.015);
const lean = facing * 0.35;
const pull = Math.min(1, this._recoil.lengthSqrt() / Math.max(1, this.recoilSpeed));
const kickX = -this._recoil.x * 0.04;
const kickY = -this._recoil.y * 0.04;
const ox = this.pos.x + kickX;
const oy = this.pos.y + kickY + bob;
const armor = [0.17, 0.21, 0.3, alpha];
const armorLight = [0.3, 0.36, 0.48, alpha];
const armorDark = [0.09, 0.11, 0.16, alpha];
const accent = [0.34, 0.96, 1, alpha];
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),
);
};
renderer.drawTexture(
renderer.whiteTexture,
this.pos.x - s * 0.85,
this.pos.y - s * 0.3 + s * 0.14,
s * 1.7,
s * 0.56,
[0, 0, 0, 0.35],
);
const capeX = ox - dirX * s * (0.42 + 0.05 * Math.sin(this._time * (moving ? 6 : 2)));
const capeY = oy - dirY * s * (0.42 + 0.05 * Math.sin(this._time * (moving ? 6 : 2))) + s * 0.05;
renderer.drawTexture(
renderer.whiteTexture,
capeX - s * 0.26,
capeY - s * 0.26,
s * 0.52,
s * 0.52,
armorDark,
facing,
);
const gripX = ox + dirX * s * (0.22 - 0.14 * pull);
const gripY = oy + dirY * s * (0.22 - 0.14 * pull);
drawLimb(ox - dirX * s * 0.08, oy, gripX - dirX * s * 0.14, gripY, s * 0.11, armorDark);
for (let i = 0; i < 2; i += 1) {
const sign = i === 0 ? 1 : -1;
const step = Math.sin(this._time * 13 + i * Math.PI) * s * (moving ? 0.1 : 0.02);
const hx = ox + perpX * s * 0.14 * sign;
const hy = oy + perpY * s * 0.14 * sign;
renderer.drawTexture(
renderer.whiteTexture,
hx + dirX * step - s * 0.07,
hy + s * 0.14 - s * 0.09,
s * 0.14,
s * 0.18,
i === 0 ? armorDark : armor,
);
}
renderer.drawTexture(
renderer.whiteTexture,
ox - s * 0.25,
oy - s * 0.31,
s * 0.5,
s * 0.62,
armor,
lean * 0.4,
);
renderer.drawTexture(
renderer.whiteTexture,
ox + dirX * s * 0.03 - s * 0.18,
oy - s * 0.25,
s * 0.36,
s * 0.4,
armorLight,
lean * 0.4,
);
renderer.drawTexture(
renderer.whiteTexture,
ox - s * 0.03,
oy - s * 0.08,
s * 0.06,
s * 0.06,
accent,
);
renderer.drawTexture(
renderer.whiteTexture,
ox - s * 0.25,
oy + s * 0.1 - s * 0.03,
s * 0.5,
s * 0.06,
armorDark,
lean * 0.4,
);
for (let i = 0; i < 2; i += 1) {
const sign = i === 0 ? 1 : -1;
renderer.drawTexture(
renderer.whiteTexture,
ox + perpX * s * 0.3 * sign - s * 0.11,
oy + perpY * s * 0.3 * sign - s * 0.2 - s * 0.11,
s * 0.22,
s * 0.22,
armorDark,
);
}
const hx = ox + dirX * s * 0.06;
const hy = oy - s * 0.44;
renderer.drawTexture(
renderer.whiteTexture,
hx - s * 0.18,
hy - s * 0.17,
s * 0.36,
s * 0.34,
armor,
lean * 0.4,
);
const visorLen = s * 0.26;
const visorX = hx + dirX * s * 0.09 - visorLen / 2;
const visorY = hy + dirY * s * 0.09 - s * 0.045;
renderer.drawTexture(
renderer.whiteTexture,
visorX - s * 0.04,
visorY - s * 0.04,
visorLen + s * 0.08,
s * 0.17,
[accent[0], accent[1], accent[2], 0.22 * alpha],
facing,
);
renderer.drawTexture(
renderer.whiteTexture,
visorX,
visorY,
visorLen,
s * 0.09,
accent,
facing,
);
const reach = s * (0.5 - 0.18 * pull);
const gunX = ox + dirX * (s * 0.18 + reach * 0.4);
const gunY = oy + dirY * (s * 0.18 + reach * 0.4);
const muzzleX = gunX + dirX * (reach * 0.6 + s * 0.4);
const muzzleY = gunY + dirY * (reach * 0.6 + s * 0.4);
drawLimb(gunX, gunY, muzzleX, muzzleY, s * 0.12, [0.06, 0.08, 0.12, alpha]);
renderer.drawTexture(
renderer.whiteTexture,
muzzleX - s * 0.04,
muzzleY - s * 0.04,
s * 0.08,
s * 0.08,
accent,
);
renderer.drawTexture(
renderer.whiteTexture,
gunX - dirX * s * 0.08 - s * 0.09,
gunY - dirY * s * 0.08 - s * 0.09,
s * 0.18,
s * 0.18,
armorDark,
facing,
);
}
}