From fa12ccc7390ccf764e5adf121e547fd5beccd213 Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Sun, 28 Dec 2025 07:15:37 -0600 Subject: [PATCH 1/3] wip --- src/engine/Collision/ColliderComponent.ts | 39 +++++--- src/engine/Collision/Colliders/Shape.ts | 105 ++++++++++++++++++++++ src/engine/TileMap/IsometricMap.ts | 2 +- 3 files changed, 131 insertions(+), 15 deletions(-) diff --git a/src/engine/Collision/ColliderComponent.ts b/src/engine/Collision/ColliderComponent.ts index 47358650a0..8abb4c44ad 100644 --- a/src/engine/Collision/ColliderComponent.ts +++ b/src/engine/Collision/ColliderComponent.ts @@ -29,23 +29,17 @@ export class ColliderComponent extends Component { constructor(collider?: Collider) { super(); - this.set(collider); + this.use(collider); } private _collider: Collider; - /** - * Get the current collider geometry - */ - public get(): Collider | undefined { - return this._collider; - } /** - * Set the collider geometry + * Use specific collider geometry * @param collider * @returns the collider you set */ - public set(collider: T): T { + public use(collider: T): T { this.clear(); if (collider) { this._collider = collider; @@ -57,6 +51,23 @@ export class ColliderComponent extends Component { return collider; } + /** + * Get the current collider geometry + */ + public get(): Collider | undefined { + return this._collider; + } + + /** + * Set the collider geometry + * @param collider + * @returns the collider you set + * @deprecated Use .use(collider) instead + */ + public set(collider: T): T { + return this.use(collider); + } + private _collidersToRemove: Collider[] = []; /** * Remove collider geometry from collider component @@ -209,7 +220,7 @@ export class ColliderComponent extends Component { */ useBoxCollider(width: number, height: number, anchor: Vector = Vector.Half, center: Vector = Vector.Zero): PolygonCollider { const collider = Shape.Box(width, height, anchor, center); - return this.set(collider); + return this.use(collider); } /** @@ -223,7 +234,7 @@ export class ColliderComponent extends Component { */ usePolygonCollider(points: Vector[], center: Vector = Vector.Zero): PolygonCollider { const poly = Shape.Polygon(points, center); - return this.set(poly); + return this.use(poly); } /** @@ -233,7 +244,7 @@ export class ColliderComponent extends Component { */ useCircleCollider(radius: number, center: Vector = Vector.Zero): CircleCollider { const collider = Shape.Circle(radius, center); - return this.set(collider); + return this.use(collider); } /** @@ -244,7 +255,7 @@ export class ColliderComponent extends Component { */ useEdgeCollider(begin: Vector, end: Vector): EdgeCollider { const collider = Shape.Edge(begin, end); - return this.set(collider); + return this.use(collider); } /** @@ -252,6 +263,6 @@ export class ColliderComponent extends Component { * @param colliders */ useCompositeCollider(colliders: Collider[]): CompositeCollider { - return this.set(new CompositeCollider(colliders)); + return this.use(new CompositeCollider(colliders)); } } diff --git a/src/engine/Collision/Colliders/Shape.ts b/src/engine/Collision/Colliders/Shape.ts index f64393ab34..11ff388380 100644 --- a/src/engine/Collision/Colliders/Shape.ts +++ b/src/engine/Collision/Colliders/Shape.ts @@ -6,8 +6,108 @@ import { vec, Vector } from '../../Math/vector'; import { CompositeCollider } from './CompositeCollider'; import { Logger } from '../..'; +export class Colliders { + /** + * Creates a box collider, under the hood defines a {@apilink PolygonCollider} collider + * @param width Width of the box + * @param height Height of the box + * @param anchor Anchor of the box (default (.5, .5)) which positions the box relative to the center of the collider's position + * @param offset Optional offset relative to the collider in local coordinates + */ + static Box(width: number, height: number, anchor: Vector = Vector.Half, offset: Vector = Vector.Zero): PolygonCollider { + return new PolygonCollider({ + points: new BoundingBox(-width * anchor.x, -height * anchor.y, width - width * anchor.x, height - height * anchor.y) + .getPoints() + .slice(), + offset: offset + }); + } + + /** + * Creates a new {@apilink PolygonCollider | `arbitrary polygon`} collider + * + * PolygonColliders are useful for creating convex polygon shapes + * @param points Points specified in counter clockwise + * @param offset Optional offset relative to the collider in local coordinates + */ + static Polygon(points: Vector[], offset: Vector = Vector.Zero, suppressConvexWarning = false): PolygonCollider { + return new PolygonCollider({ + points: points, + offset: offset, + suppressConvexWarning + }); + } + + /** + * Creates a new {@apilink CircleCollider | `circle`} collider + * + * Circle colliders are useful for balls, or to make collisions more forgiving on sharp edges + * @param radius Radius of the circle collider + * @param offset Optional offset relative to the collider in local coordinates + */ + static Circle(radius: number, offset: Vector = Vector.Zero): CircleCollider { + return new CircleCollider({ + radius: radius, + offset: offset + }); + } + + /** + * Creates a new {@apilink EdgeCollider | `edge`} collider + * + * Edge colliders are useful for floors, walls, and other barriers + * @param begin Beginning of the edge in local coordinates to the collider + * @param end Ending of the edge in local coordinates to the collider + */ + static Edge(begin: Vector, end: Vector): EdgeCollider { + return new EdgeCollider({ + begin: begin, + end: end + }); + } + + /** + * Creates a new capsule shaped {@apilink CompositeCollider} using 2 circles and a box + * + * Capsule colliders are useful for platformers with incline or jagged floors to have a smooth + * player experience. + * @param width + * @param height + * @param offset Optional offset + */ + static Capsule(width: number, height: number, offset = Vector.Zero): CompositeCollider { + const logger = Logger.getInstance(); + if (width === height) { + logger.warn('A capsule collider with equal width and height is a circle, consider using a ex.Colliders.Circle or ex.CircleCollider'); + } + + const vertical = height >= width; + + if (vertical) { + // height > width, if equal maybe use a circle + const capsule = new CompositeCollider([ + Colliders.Circle(width / 2, vec(0, -height / 2 + width / 2).add(offset)), + Colliders.Box(width, height - width, Vector.Half, offset), + Colliders.Circle(width / 2, vec(0, height / 2 - width / 2).add(offset)) + ]); + capsule.compositeStrategy = 'together'; + return capsule; + } else { + // width > height, if equal maybe use a circle + const capsule = new CompositeCollider([ + Colliders.Circle(height / 2, vec(-width / 2 + height / 2, 0).add(offset)), + Colliders.Box(width - height, height, Vector.Half, offset), + Colliders.Circle(height / 2, vec(width / 2 - height / 2, 0).add(offset)) + ]); + capsule.compositeStrategy = 'together'; + return capsule; + } + } +} + /** * Excalibur helper for defining colliders quickly + * @deprecated use Colliders.* */ export class Shape { /** @@ -16,6 +116,7 @@ export class Shape { * @param height Height of the box * @param anchor Anchor of the box (default (.5, .5)) which positions the box relative to the center of the collider's position * @param offset Optional offset relative to the collider in local coordinates + * @deprecated use Colliders.Box */ static Box(width: number, height: number, anchor: Vector = Vector.Half, offset: Vector = Vector.Zero): PolygonCollider { return new PolygonCollider({ @@ -32,6 +133,7 @@ export class Shape { * PolygonColliders are useful for creating convex polygon shapes * @param points Points specified in counter clockwise * @param offset Optional offset relative to the collider in local coordinates + * @deprecated Use Colliders.Polygon */ static Polygon(points: Vector[], offset: Vector = Vector.Zero, suppressConvexWarning = false): PolygonCollider { return new PolygonCollider({ @@ -47,6 +149,7 @@ export class Shape { * Circle colliders are useful for balls, or to make collisions more forgiving on sharp edges * @param radius Radius of the circle collider * @param offset Optional offset relative to the collider in local coordinates + * @deprecated Use Colliders.Circle */ static Circle(radius: number, offset: Vector = Vector.Zero): CircleCollider { return new CircleCollider({ @@ -61,6 +164,7 @@ export class Shape { * Edge colliders are useful for floors, walls, and other barriers * @param begin Beginning of the edge in local coordinates to the collider * @param end Ending of the edge in local coordinates to the collider + * @deprecated Use Colliders.Edge */ static Edge(begin: Vector, end: Vector): EdgeCollider { return new EdgeCollider({ @@ -77,6 +181,7 @@ export class Shape { * @param width * @param height * @param offset Optional offset + * @deprecated Use Colliders.Capsule */ static Capsule(width: number, height: number, offset = Vector.Zero): CompositeCollider { const logger = Logger.getInstance(); diff --git a/src/engine/TileMap/IsometricMap.ts b/src/engine/TileMap/IsometricMap.ts index 3fdc83e71c..8862ee0374 100644 --- a/src/engine/TileMap/IsometricMap.ts +++ b/src/engine/TileMap/IsometricMap.ts @@ -362,7 +362,7 @@ export class IsometricMap extends Entity implements HasNestedPointerEvents { this.collider = this.get(ColliderComponent); if (this.collider) { - this.collider.set((this._composite = new CompositeCollider([]))); + this.collider.use((this._composite = new CompositeCollider([]))); } this.pointer = this.get(PointerComponent); From 8dda2840dbce662fc708d504cb7f8db8ebaaf537 Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Mon, 5 Jan 2026 10:43:21 -0600 Subject: [PATCH 2/3] add changelog --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a54fb5ce5..d4298755fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,19 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Breaking Changes - Behavior change - TileMap now uses 'separate' as the `compositeStrategy` as a better default. Commonly TileMap is used to build levels, so this default aligns with the common use. +- Removed old legacy `ex.EasingFunctions` in favor of the [0, 1] common easing functions +- Removed EaseTo/EaseBy/actor.actions.(easeTo|easeBy) Action in favor of MoveTo/MoveBy/actor.actions.(moveTo|moveBy) Action with easing support +- Removed `body.sleeping` in favor of `isSleeping` +- Removed `entity.active` in favor of `isActive` +- Removed `actor.getGlobalPos()` in favor of `actor.globalPos` getter/setter +- Removed `actor.getGlobalRotation()` in favor of `actor.globalRotation` getter/setter +- Removed `actor.getGlobalScale()` in favor of `actor.globalScale` getter/setter +- Removed `BoundingBox.draw` in favor of `BoundingBox.debug` +- Removed typo in `engine.isFullScreen` now `engine.isFullscreen` +- Removed unused `tag-query.ts` +- Removed `world.queryTags`, `world.query(...)` can query tags +- Removed `Animation.durationPerFrameMs` in favor of `Animation.durationPerFrame` +- Removed `Vector.size` in favor of `Vector.magnitude` ### Deprecated From 90b60b4ceaaef9e34c178820d6662dd21de00d1d Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Mon, 5 Jan 2026 11:17:14 -0600 Subject: [PATCH 3/3] update Args suffix to Options --- src/engine/actor.ts | 10 ++++++++-- src/engine/label.ts | 4 ++-- src/engine/particles/gpu-particle-emitter.ts | 4 ++-- src/engine/particles/particles.ts | 7 ++++++- src/engine/screen-element.ts | 6 +++--- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/engine/actor.ts b/src/engine/actor.ts index 25ac2792b9..b19c5bad95 100644 --- a/src/engine/actor.ts +++ b/src/engine/actor.ts @@ -56,7 +56,7 @@ export function isActor(x: any): x is Actor { /** * Actor constructor options */ -export type ActorArgs = ColliderArgs & { +export type ActorOptions = ColliderArgs & { /** * Optionally set the name of the actor, default is 'anonymous' */ @@ -193,6 +193,12 @@ type ColliderArgs = height?: undefined; }; +/** + * Actor constructor options + * @deprecated use ActorOptions + */ +export type ActorArgs = ActorOptions; + export interface ActorEvents extends EntityEvents { collisionstart: CollisionStartEvent; collisionend: CollisionEndEvent; @@ -565,7 +571,7 @@ export class Actor extends Entity implements Eventable, PointerEvents, CanInitia * * @param config */ - constructor(config?: ActorArgs) { + constructor(config?: ActorOptions) { super(); const { diff --git a/src/engine/label.ts b/src/engine/label.ts index 299cde0952..0608412abe 100644 --- a/src/engine/label.ts +++ b/src/engine/label.ts @@ -6,7 +6,7 @@ import type { SpriteFont } from './graphics'; import { GraphicsComponent } from './graphics'; import { Font } from './graphics/font'; import { Actor } from './actor'; -import type { ActorArgs } from './actor'; +import type { ActorOptions } from './actor'; /** * Option for creating a label @@ -113,7 +113,7 @@ export class Label extends Actor { * Build a new label * @param options */ - constructor(options?: LabelOptions & ActorArgs) { + constructor(options?: LabelOptions & ActorOptions) { super(options); const { text, pos, x, y, spriteFont, font, color, maxWidth } = { text: '', ...options }; diff --git a/src/engine/particles/gpu-particle-emitter.ts b/src/engine/particles/gpu-particle-emitter.ts index f93951b3bb..8a27f91032 100644 --- a/src/engine/particles/gpu-particle-emitter.ts +++ b/src/engine/particles/gpu-particle-emitter.ts @@ -1,7 +1,7 @@ import type { Engine } from '../engine'; import { Actor } from '../actor'; import { EmitterType } from './emitter-type'; -import type { ParticleEmitterArgs } from './particles'; +import type { ParticleEmitterOptions } from './particles'; import { ParticleTransform } from './particles'; import type { GpuParticleConfig } from './gpu-particle-renderer'; import { GpuParticleRenderer } from './gpu-particle-renderer'; @@ -53,7 +53,7 @@ export class GpuParticleEmitter extends Actor { this.transform.z = z; } - constructor(config: ParticleEmitterArgs & { maxParticles?: number; particle?: GpuParticleConfig }) { + constructor(config: ParticleEmitterOptions & { maxParticles?: number; particle?: GpuParticleConfig }) { super({ name: `GpuParticleEmitter`, width: config.width, height: config.height }); // somewhat goofy way of doing width/height this.addComponent(this.graphics, true); (this.graphics.onPostDraw as any) = this.draw.bind(this); diff --git a/src/engine/particles/particles.ts b/src/engine/particles/particles.ts index 83f9471384..9304e51156 100644 --- a/src/engine/particles/particles.ts +++ b/src/engine/particles/particles.ts @@ -297,7 +297,7 @@ export enum ParticleTransform { Local = 'local' } -export interface ParticleEmitterArgs { +export interface ParticleEmitterOptions { particle?: ParticleConfig; x?: number; y?: number; @@ -325,3 +325,8 @@ export interface ParticleEmitterArgs { radius?: number; random?: Random; } + +/** + * @deprecated use ParticleEmitterOptions + */ +export type ParticleEmitterArgs = ParticleEmitterOptions; diff --git a/src/engine/screen-element.ts b/src/engine/screen-element.ts index e5c02ef1af..5a32f95c2e 100644 --- a/src/engine/screen-element.ts +++ b/src/engine/screen-element.ts @@ -1,6 +1,6 @@ import { Vector, vec } from './math/vector'; import type { Engine } from './engine'; -import type { ActorArgs } from './actor'; +import type { ActorOptions } from './actor'; import { Actor } from './actor'; import { TransformComponent } from './entity-component-system/components/transform-component'; import { CollisionType } from './collision/collision-type'; @@ -21,9 +21,9 @@ export class ScreenElement extends Actor { protected _engine: Engine; constructor(); - constructor(config?: ActorArgs); + constructor(config?: ActorOptions); - constructor(config?: ActorArgs) { + constructor(config?: ActorOptions) { super({ ...config }); this.get(TransformComponent).coordPlane = CoordPlane.Screen; this.anchor = config?.anchor ?? vec(0, 0);