Files
tankwar_proj/js/entities/Bullet.js
2026-05-16 09:59:54 +08:00

132 lines
3.1 KiB
JavaScript

/**
* Bullet.js
* Bullet entity that travels in a straight line and interacts with terrain/tanks.
*/
const {
BULLET_SPEED,
BULLET_SIZE,
DIRECTION,
DIR_VECTORS,
MAP_OFFSET_X,
MAP_OFFSET_Y,
MAP_WIDTH,
MAP_HEIGHT,
} = require('../base/GameGlobal');
class Bullet {
constructor() {
this.x = 0;
this.y = 0;
this.direction = DIRECTION.UP;
this.speed = BULLET_SPEED;
this.size = BULLET_SIZE;
this.halfSize = BULLET_SIZE / 2;
this.alive = false;
this.canBreakSteel = false;
/** @type {'player'|'enemy'} */
this.owner = 'player';
/** @type {object|null} Reference to the tank that fired this bullet */
this.ownerTank = null;
}
/**
* Initialize/reset the bullet for reuse from object pool.
* @param {object} config
* @param {number} config.x
* @param {number} config.y
* @param {number} config.direction
* @param {string} config.owner - 'player' or 'enemy'
* @param {boolean} [config.canBreakSteel]
* @param {object} [config.ownerTank]
*/
init(config) {
this.x = config.x;
this.y = config.y;
this.direction = config.direction;
this.owner = config.owner || 'player';
this.canBreakSteel = config.canBreakSteel || false;
this.ownerTank = config.ownerTank || null;
this.alive = true;
}
/**
* Update bullet position.
* @param {number} dt - Delta time in seconds.
*/
update(dt) {
if (!this.alive) return;
const vec = DIR_VECTORS[this.direction];
const moveAmount = this.speed * dt * 60;
this.x += vec.dx * moveAmount;
this.y += vec.dy * moveAmount;
// Check map boundaries
if (
this.x < MAP_OFFSET_X ||
this.y < MAP_OFFSET_Y ||
this.x > MAP_OFFSET_X + MAP_WIDTH ||
this.y > MAP_OFFSET_Y + MAP_HEIGHT
) {
this.destroy();
}
}
/**
* Render the bullet.
* In team battle mode, bullet color is determined by the _isAlly flag
* set by the scene: ally = bright yellow, enemy = red.
* In single-player / PVE mode, owner-based colors are used.
* @param {CanvasRenderingContext2D} ctx
*/
render(ctx) {
if (!this.alive) return;
let fillColor;
if (this._isAlly !== undefined) {
// Team mode: use team-aware coloring
fillColor = this._isAlly ? '#FFFF00' : '#FF4444';
} else {
// Single-player / PVE fallback
fillColor = this.owner === 'player' ? '#FFFF00' : '#FF6600';
}
ctx.fillStyle = fillColor;
ctx.fillRect(
this.x - this.halfSize,
this.y - this.halfSize,
this.size,
this.size
);
}
/**
* Get bounding box.
* @returns {{x: number, y: number, w: number, h: number}}
*/
getBounds() {
return {
x: this.x - this.halfSize,
y: this.y - this.halfSize,
w: this.size,
h: this.size,
};
}
/**
* Destroy the bullet (mark for recycling).
*/
destroy() {
this.alive = false;
// Decrement owner's active bullet count
if (this.ownerTank && typeof this.ownerTank.activeBullets === 'number') {
this.ownerTank.activeBullets = Math.max(0, this.ownerTank.activeBullets - 1);
}
}
}
module.exports = Bullet;