import { Entity, System } from "@cosmic/core"; import { Transform, Collider } from "@cosmic/kit/components"; export class CollisionSystem extends System { public readonly requiredComponents = new Set([Transform.name, Collider.name]); public update(entities: Entity[], _deltaTime: number) { // Reset collision flag for all first for (const entity of entities) { const transform = entity.getComponent(Transform)!; transform.isCollidingWithEntity = false; } // Check each pair once for (let i = 0; i < entities.length; i++) { const entityA = entities[i]; const aTransform = entityA.getComponent(Transform)!; const aCollider = entityA.getComponent(Collider)!; for (let j = i + 1; j < entities.length; j++) { const entityB = entities[j]; const bTransform = entityB.getComponent(Transform)!; const bCollider = entityB.getComponent(Collider)!; if (this.areColliding(aTransform, bTransform)) { aTransform.isCollidingWithEntity = true; bTransform.isCollidingWithEntity = true; this.resolveOverlap(aTransform, bTransform, aCollider, bCollider); } } } } protected areColliding(a: Transform, b: Transform): boolean { // AABB vs AABB return ( a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y ); } protected resolveOverlap(a: Transform, b: Transform, colA: Collider, colB: Collider) { // Centers const centerAx = a.x + a.width / 2; const centerAy = a.y + a.height / 2; const centerBx = b.x + b.width / 2; const centerBy = b.y + b.height / 2; const dx = centerAx - centerBx; const dy = centerAy - centerBy; const combinedHalfWidths = (a.width + b.width) / 2; const combinedHalfHeights = (a.height + b.height) / 2; const overlapX = combinedHalfWidths - Math.abs(dx); const overlapY = combinedHalfHeights - Math.abs(dy); if (overlapX <= 0 || overlapY <= 0) return; // just in case const aStatic = colA.isStatic; const bStatic = colB.isStatic; // Decide which axis to resolve along (least penetration) if (overlapX < overlapY) { // Horizontal if (dx > 0) { // A is to the right of B this.pushApartX(a, b, overlapX, +1, aStatic, bStatic); } else { // A is to the left of B this.pushApartX(a, b, overlapX, -1, aStatic, bStatic); } } else { // Vertical if (dy > 0) { // A is below B this.pushApartY(a, b, overlapY, +1, aStatic, bStatic); } else { // A is above B this.pushApartY(a, b, overlapY, -1, aStatic, bStatic); } } } private pushApartX( a: Transform, b: Transform, overlap: number, direction: 1 | -1, // +1: A right of B, -1: A left of B aStatic: boolean, bStatic: boolean, ) { if (aStatic && bStatic) return; if (aStatic && !bStatic) { // Only move B b.x -= direction * overlap; } else if (!aStatic && bStatic) { // Only move A a.x += direction * overlap; } else { // Both dynamic → split const half = overlap / 2; a.x += direction * half; b.x -= direction * half; } } private pushApartY( a: Transform, b: Transform, overlap: number, direction: 1 | -1, // +1: A below B, -1: A above B aStatic: boolean, bStatic: boolean, ) { if (aStatic && bStatic) return; if (aStatic && !bStatic) { b.y -= direction * overlap; } else if (!aStatic && bStatic) { a.y += direction * overlap; } else { const half = overlap / 2; a.y += direction * half; b.y -= direction * half; } } }