import { config } from '../config';
/**
* Entity Collision
* @class
* @category Game
*/
export class Collision
{
/**
* Determine whether or not two entities intersect on the canvas.
*
* This method is a basic collision check that determines if two entities,
* defined by their rectangular properties (x, y, width, height), intersect.
* The logic is straightforward: if any side of one entity is positioned to the
* left, right, above, or below the other, they do not intersect. Otherwise,
* they overlap, triggering a collision.
*
* @param {Object} r1 - first entity
* @param {number} r1.x - first entity x coordinate
* @param {number} r1.y - first entity y coordinate
* @param {number} r1.width - first entity width
* @param {number} r1.height - first entity height
* @param {Object} r2 - second entity
* @param {number} r2.x - second entity x coordinate
* @param {number} r2.y - second entity y coordinate
* @param {number} r2.width - second entity width
* @param {number} r2.height - second entity height
*
* @returns {boolean}
*/
static intersection (r1, r2) {
return ! (r1.x + r1.width < r2.x
|| r1.y + r1.height < r2.y
|| r1.x > r2.x + r2.width
|| r1.y > r2.y + r2.height
);
}
/**
* Determine distance between two vectors or two entities with vectors.
*
* This method calculates the Euclidean distance between two points.
* It's versatile, accepting either entity objects or numerical values
* as parameters. When `vectors` is set to `true`, the method considers
* the parameters as vector components, determining the distance between
* the points they represent.
*
* @param {Object|number} e1 - the first entity or vector coordinates
* @param {Object|number} e2 - the second entity or vector coordinates
* @param {boolean} useVectors - whether or not we're passing entities or vectors
*
* @returns {number}
*/
static distance (e1, e2, useVectors = true) {
if (useVectors) {
return Math.sqrt(e1*e1 + e2*e2);
}
const vectorX = e1.x - e2.x;
const vectorY = e1.y - e2.y;
return Math.sqrt(vectorX*vectorX + vectorY*vectorY);
}
/**
* Caclulate collision beween entity (arc) and wall (box).
*
* This method is a specialized function designed for detecting collisions between
* an arc-shaped entity (potentially representing the player) and a rectangular
* box-shaped entity (potentially representing a wall) on the canvas.
*
* @param {Object} params
* @param {number} params.arcX - the arc entity's x-coordinate
* @param {number} params.arcY - the arc entity's y-coordinate
* @param {number} params.rectX - the box entity's x-coordinate
* @param {number} params.rectY - the box entity's y-coordinate
* @param {number} params.size - the size of the box entity
* @param {number} params.radius - the radius of the arc entity
*
* @returns {boolean}
*/
static arcBoxCollision ({ arcX, arcY, rectX, rectY, size, radius }) {
const distX = Math.abs(arcX - rectX - size / 2);
const distY = Math.abs(arcY - rectY - size / 2);
// No overlap
if (distX > (size / 2 + radius) || distY > (size / 2 + radius)) {
return false;
}
// collision along x or y axis
if (distX <= (size / 2) || distY <= (size / 2)) {
return true;
}
// circular collision
const dX = distX - size / 2;
const dY = distY - size / 2;
return (dX * dX + dY * dY <= (radius * radius));
}
/**
* Detect and handle collision between entities and walls.
*
* @see https://github.com/sentrychris/docs/full/collision/entity-to-walls.md
*
* @param {Object} entity - a game entity
* @param {array} walls - the walls
*
* @returns {Object}
*/
static entityToWalls (entity, walls) {
const result = {
x: 0,
y: 0
};
for (let i = 0; i < walls.length; i++) {
const wall = walls[i];
if (Collision.arcBoxCollision({
arcX: entity.x,
arcY: entity.y,
rectX: wall.x,
rectY: wall.y,
size: config.cell.size,
radius: config.cell.radius,
})) {
const wallCenterX = wall.x + config.cell.size / 2;
const wallCenterY = wall.y + config.cell.size / 2;
let vectorX = entity.x - wallCenterX;
let vectorY = entity.y - wallCenterY;
const distance = Collision.distance(vectorX, vectorY);
if (distance > 0) {
vectorX /= distance;
vectorY /= distance;
result.x += vectorX;
result.y += vectorY;
}
}
}
return result;
}
/**
* Player-to-entity collision.
*
* @see https://github.com/sentrychris/docs/full/collision/entity-to-player.md
*
* @param {Object} entity - the entity
* @param {Game} game - the managed game instance
* @param {Object} callbackOptions
* @param {string|null} callbackOptions.on - maps to the entity type to know which entity to execute on
* @param {number|null} callbackOptions.onDistance - the distance between the player and the entity before the callback executes
* @param {function|null} callbackOptions.onCallback - the callback to execute when the player and entity intersect
*
* @returns {void}
*/
static entityToPlayer (entity, game, { on = null, onDistance = null, onCallback = null }) {
let vectorX = game.player.x - entity.x;
let vectorY = game.player.y - entity.y;
if (game.player.dead) {
vectorX = entity.lastVectorX;
vectorY = entity.lastVectorY;
} else {
entity.lastVectorX = vectorX;
entity.lastVectorY = vectorY;
}
const distance = Collision.distance(vectorX, vectorY);
if (onCallback && on === entity.type && distance <= onDistance) {
onCallback();
}
if (distance > 0 && entity.type === 'enemy') {
vectorX /= distance;
vectorY /= distance;
if (distance < 800) {
entity.angle = Math.atan2(vectorY, vectorX) - 90 * Math.PI / 180;
entity.x += vectorX * entity.speed;
entity.y += vectorY * entity.speed;
const collisionVector = Collision.entityToWalls(entity, game.walls);
entity.x += collisionVector.x * entity.speed;
entity.y += collisionVector.y * entity.speed;
entity.incrementer += entity.speed;
entity.position = Math.sin(entity.incrementer * Math.PI / 180);
if (distance < 100) {
game.player.takeDamage(entity);
}
}
}
}
}
Source