sandsim/js/entities/entity.js
2025-04-05 17:26:25 +02:00

166 lines
4.8 KiB
JavaScript

// Base entity system
const ENTITY_TYPES = {
RABBIT: 'rabbit'
};
// Store all entities
const entities = [];
// Base Entity class
class Entity {
constructor(type, x, y, options = {}) {
this.type = type;
this.x = x;
this.y = y;
this.vx = 0;
this.vy = 0;
this.width = options.width || 10;
this.height = options.height || 10;
this.rotation = 0;
this.sprite = null;
this.flipped = false;
this.isStatic = false;
this.lastUpdate = performance.now();
this.id = Entity.nextId++;
}
static nextId = 1;
update() {
// Override in subclasses
return false;
}
render(ctx, offsetX, offsetY) {
// Default rendering - override in subclasses
const screenX = (this.x - offsetX) * PIXEL_SIZE;
const screenY = (this.y - offsetY) * PIXEL_SIZE;
ctx.save();
ctx.translate(screenX, screenY);
ctx.rotate(this.rotation);
if (this.sprite && this.sprite.complete) {
const width = this.width * PIXEL_SIZE;
const height = this.height * PIXEL_SIZE;
if (this.flipped) {
ctx.scale(-1, 1);
ctx.drawImage(this.sprite, -width/2, -height/2, width, height);
} else {
ctx.drawImage(this.sprite, -width/2, -height/2, width, height);
}
} else {
// Fallback if sprite not loaded
ctx.fillStyle = '#FF00FF';
ctx.fillRect(
-this.width/2 * PIXEL_SIZE,
-this.height/2 * PIXEL_SIZE,
this.width * PIXEL_SIZE,
this.height * PIXEL_SIZE
);
}
ctx.restore();
}
checkCollisions(newX, newY) {
const result = {
collision: false,
horizontal: false,
vertical: false,
ground: false
};
// Check points around the entity
const halfWidth = this.width / 2;
const halfHeight = this.height / 2;
// Check bottom points for ground collision - check multiple points along the bottom
const numBottomPoints = 5;
let groundCollision = false;
for (let i = 0; i < numBottomPoints; i++) {
const ratio = i / (numBottomPoints - 1);
const bottomX = newX - halfWidth + (2 * halfWidth * ratio);
const bottomY = newY + halfHeight;
if (this.isPixelSolid(bottomX, bottomY)) {
groundCollision = true;
break;
}
}
if (groundCollision) {
result.collision = true;
result.vertical = true;
result.ground = true;
}
// Check side points for horizontal collision
const leftMiddle = { x: newX - halfWidth, y: newY };
const rightMiddle = { x: newX + halfWidth, y: newY };
if (this.isPixelSolid(leftMiddle.x, leftMiddle.y)) {
result.collision = true;
result.horizontal = true;
}
if (this.isPixelSolid(rightMiddle.x, rightMiddle.y)) {
result.collision = true;
result.horizontal = true;
}
// Check top for ceiling collision
const topMiddle = { x: newX, y: newY - halfHeight };
if (this.isPixelSolid(topMiddle.x, topMiddle.y)) {
result.collision = true;
result.vertical = true;
}
return result;
}
isPixelSolid(x, y) {
// Use ceiling for y coordinate to better detect ground below
const pixel = getPixel(Math.floor(x), Math.ceil(y));
return pixel !== EMPTY &&
pixel !== WATER &&
pixel !== FIRE &&
pixel !== SQUARE &&
pixel !== CIRCLE &&
pixel !== TRIANGLE;
}
}
// Function to create and register an entity
function createEntity(type, x, y, options = {}) {
let entity;
switch(type) {
case ENTITY_TYPES.RABBIT:
entity = new Rabbit(x, y, options);
break;
default:
console.error(`Unknown entity type: ${type}`);
return null;
}
entities.push(entity);
return entity;
}
// Update all entities
function updateEntities() {
for (let i = entities.length - 1; i >= 0; i--) {
entities[i].update();
}
}
// Render all entities
function renderEntities(ctx, offsetX, offsetY) {
for (const entity of entities) {
entity.render(ctx, offsetX, offsetY);
}
}