@dill-pixel/plugin-snap-physics
Version:
Snap Physics
178 lines (148 loc) • 4.48 kB
text/typescript
import { Application, Container } from 'dill-pixel';
import { Bounds, Circle, Container as PIXIContianer, Point, Rectangle, Sprite } from 'pixi.js';
import { ICollider } from './ICollider';
import { System } from './System';
import { EntityType, SnapBoundary } from './types';
export class Entity<T = any, A extends Application = Application> extends Container<A> implements ICollider {
view: PIXIContianer;
isActor: boolean = false;
isSolid: boolean = false;
isSensor: boolean = false;
debug: boolean = false;
debugColors = {
bounds: 0xff0000,
outerBounds: 0x00ff00,
};
type: EntityType = 'Solid';
isCircle: boolean = false;
isCollideable: boolean = true;
xRemainder: number = 0;
yRemainder: number = 0;
config: T;
protected subpixelX: number = 0;
protected subpixelY: number = 0;
protected remainder: Point = new Point(0, 0);
constructor(config?: Partial<T>) {
super({ autoUpdate: true });
this.config = config as T;
}
protected _cachedBounds: SnapBoundary | null = null;
get cachedBounds(): SnapBoundary {
if (!this._cachedBounds || this._dirtyBounds) {
const bounds = this.view.getBounds();
bounds.scale(1 / this.system.container.worldTransform.d);
if (this.isCircle) {
bounds.width = bounds.height = Math.max(bounds.width, bounds.height);
this._cachedBounds = new Circle(
bounds.x + bounds.width * 0.5,
bounds.y * bounds.width * 0.5,
bounds.width * 0.5,
);
} else {
this._cachedBounds = bounds;
}
}
return this._cachedBounds ?? (this.isCircle ? new Circle() : new Rectangle());
}
set cachedBounds(value: Bounds) {
this._cachedBounds = value;
}
protected _dirtyBounds: boolean = true;
get dirtyBounds() {
return this._dirtyBounds;
}
set dirtyBounds(value: boolean) {
this._dirtyBounds = value;
}
get boundingRect(): Rectangle {
const bb = this.getBoundingBox();
if (this.isCircle) {
return bb.getBounds();
}
return bb as Rectangle;
}
get top(): number {
return this.boundingRect.top;
}
get bottom(): number {
return this.boundingRect.bottom;
}
get left(): number {
return this.boundingRect.left;
}
get right(): number {
return this.boundingRect.right;
}
get system(): typeof System {
return System;
}
getCollideables<T extends Entity = Entity>(dx: number = 0, dy: number = 0): Set<T> {
void dx;
void dy;
return new Set<T>();
}
preFixedUpdate() {}
fixedUpdate(deltaTime?: number) {
void deltaTime;
}
postFixedUpdate() {}
getWorldBounds(): SnapBoundary {
const pos = this.system.container.toLocal(this.view.getGlobalPosition());
const bounds = this.cachedBounds;
bounds.x = pos.x;
bounds.y = pos.y;
if (this.view instanceof Sprite && this.view.anchor) {
if (!this.isCircle) {
bounds.x -= this.view.width * this.view.anchor.x;
bounds.y -= this.view.height * this.view.anchor.y;
}
}
return bounds;
}
getBoundingBox(): Rectangle | Circle {
const bounds = this.getWorldBounds();
return bounds instanceof Bounds ? bounds.rectangle : bounds;
}
getOuterBoundingBox(): Rectangle | Circle | null {
return null;
}
moveX(amount: number): void {
this.remainder.x += amount;
const move = this.remainder.x;
if (move !== 0) {
this.remainder.x -= move;
this.x += move;
}
}
moveY(amount: number): void {
this.remainder.y += amount;
const move = this.remainder.y;
if (move !== 0) {
this.remainder.y -= move;
this.y += move;
}
}
// Improved collision detection with subpixel precision
collidesWith(entity: Entity, dx: number = 0, dy: number = 0): boolean {
if (!entity) {
return false;
}
// Add subpixel remainders to the collision check
const totalDx = dx + this.remainder.x;
const totalDy = dy + this.remainder.y;
if (this.isCircle) {
if (entity.isCircle) {
return System.getCircleToCircleIntersection(entity, this, totalDx, totalDy);
} else {
return System.getRectToCircletIntersection(entity, this, totalDx, totalDy);
}
}
if (entity.isCircle) {
return System.getRectToCircletIntersection(this, entity, totalDx, totalDy);
}
return System.getRectangleIntersection(entity, this, totalDx, totalDy);
}
protected initialize() {
// noop
}
}