initial commit
This commit is contained in:
commit
e8c3f9862a
193 files changed
+29795
No files matched your search
@@ -0,0 +1,391 @@
|
||||
import { GameObject } from '../engine/GameObject.js';
|
||||
import { Vector2D } from '../engine/Vector2D.js';
|
||||
import { Bullet } from './Bullet.js';
|
||||
import { Zombie } from './Zombie.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,
|
||||
autoAim = false,
|
||||
} = {},
|
||||
) {
|
||||
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;
|
||||
this.autoAim = autoAim;
|
||||
}
|
||||
|
||||
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 (this.autoAim) {
|
||||
const nearest = this._nearestZombie();
|
||||
if (nearest) {
|
||||
const toEnemy = nearest.pos.subV(this.pos);
|
||||
if (toEnemy.lengthSqrt() > 1) {
|
||||
aim = toEnemy;
|
||||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
_nearestZombie() {
|
||||
let best = null;
|
||||
let bestDistSq = Infinity;
|
||||
for (const obj of this.container || []) {
|
||||
if (!(obj instanceof Zombie)) continue;
|
||||
const delta = obj.pos.subV(this.pos);
|
||||
const distSq = delta.lengthSqrt();
|
||||
if (distSq < bestDistSq) {
|
||||
bestDistSq = distSq;
|
||||
best = obj;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user