base2020/src/ecs/Components.ts

152 lines
4.2 KiB
TypeScript

import { Layer, SpriteSheet } from "../applet/Render";
import { Component, copyDense, copySparse, EntityState, StateForSchema, Store } from "./Data";
/**
* Nominal type used to remind users to enforce some level
* of quantization on numbers kept in game state. This reduces
* physics precision in theory, but lets us feel more comfortable
* about determinism for rollback physics.
*/
export type FixedPoint = number & {_type: "fixed point"};
export function Floor(x: number): FixedPoint {
return Math.floor(x) as FixedPoint;
}
export class Box {
constructor(
public x: FixedPoint, public y: FixedPoint,
public w: FixedPoint, public h: FixedPoint
) { };
};
/**
* Return source moved towards target by speed, without going past.
*/
export function Approach(source: number, target: number, speed: number): number {
const delta = target - source;
if (Math.abs(delta) <= speed) {
return target;
} else {
return source + Math.sign(delta) * speed;
}
}
/**
* pairs of vertex coordinates in ccw winding order
*/
export interface Polygon {
points: FixedPoint[];
}
export class PolygonComponent extends Component<PolygonComponent> {
points: FixedPoint[];
constructor(from: Partial<PolygonComponent>) {
super(from);
this.points = from.points?.slice() ?? []
};
clone() {
return new PolygonComponent(this);
}
}
export class Location extends Component<Location> {
X: FixedPoint;
Y: FixedPoint;
Angle: number;
VX: number;
VY: number;
VAngle: number;
constructor(from: Partial<Location>) {
super(from);
this.X = from.X ?? (0 as FixedPoint);
this.Y = from.Y ?? (0 as FixedPoint);
this.Angle = from.Angle ?? 0;
this.VX = from.VX ?? 0;
this.VY = from.VY ?? 0;
this.VAngle = from.VAngle ?? 0;
};
clone() {
return new Location(this);
}
}
export class CollisionClass extends Component<CollisionClass> {
public name: string;
constructor(from: Partial<CollisionClass>) {
super(from);
this.name = from.name ?? "unknown";
};
clone() {
return new CollisionClass(this);
}
}
export class RenderBounds extends Component<RenderBounds> {
public color: string;
public layer: number;
constructor(from: Partial<RenderBounds>) {
super(from);
this.color = from.color ?? "#f00";
this.layer = from.layer ?? 1;
};
clone() {
return new RenderBounds(this);
}
};
export class RenderSprite extends Component<RenderSprite> {
// TODO: make this an id/handle for serializability
public sheet: SpriteSheet;
public layer: number;
public index: number;
public offsetX: number;
public offsetY: number;
constructor(from: Partial<RenderSprite> & { sheet: SpriteSheet }) {
super(from);
this.sheet = from.sheet;
this.layer = from.layer ?? 1;
this.index = from.index ?? 0;
this.offsetX = from.offsetX ?? 0;
this.offsetY = from.offsetY ?? 0;
};
clone() {
return new RenderSprite(this);
}
};
export interface ComponentSchema {
location: Location;
bounds: PolygonComponent;
renderBounds: RenderBounds;
renderSprite: RenderSprite;
collisionSourceClass: CollisionClass;
collisionTargetClass: CollisionClass;
}
export class Data implements StateForSchema<ComponentSchema> {
entity: EntityState[];
location: Store<Location>;
bounds: Store<PolygonComponent>;
renderBounds: Store<RenderBounds>;
renderSprite: Store<RenderSprite>;
collisionSourceClass: Store<CollisionClass>;
collisionTargetClass: Store<CollisionClass>;
layers: Layer[] = [new Layer(0), new Layer(1)];
constructor(from: Partial<Data>) {
this.entity = copyDense(from.entity);
this.location = copyDense(from.location);
this.bounds = copyDense(from.bounds);
this.renderBounds = copySparse(from.renderBounds);
this.renderSprite = copySparse(from.renderSprite);
this.collisionSourceClass = copySparse(from.collisionSourceClass);
this.collisionTargetClass = copySparse(from.collisionTargetClass);
}
clone() {
return new Data(this);
}
}