Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/excaliburjs/Excalibur. 🎮 Your friendly TypeScript 2D game engine for the web 🗡️ excaliburjs.com
excalibur excaliburjs game-development game-engine game-framework gamedev games html5-canvas typescript
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169import type { Engine } from '../engine';import type { Entity, World, Query } from '../entity-component-system';import { System, TransformComponent, SystemType, SystemPriority } from '../entity-component-system';import { GraphicsComponent } from '../graphics/graphics-component';import type { Scene } from '../scene';import { PointerComponent } from './pointer-component';import type { PointerEventReceiver } from './pointer-event-receiver';import { CoordPlane } from '../math/coord-plane';import { SparseHashGrid } from '../collision/detection/sparse-hash-grid';import { PointerEventsToObjectDispatcher } from './pointer-events-to-object-dispatcher';
/** * The PointerSystem is responsible for dispatching pointer events to entities * that need them. * * The PointerSystem can be optionally configured by the {@apilink PointerComponent}, by default Entities use * the {@apilink Collider}'s shape for pointer events. */export class PointerSystem extends System { static override priority = SystemPriority.Higher;
public readonly systemType = SystemType.Update;
private _engine!: Engine; private _receivers!: PointerEventReceiver[]; private _engineReceiver!: PointerEventReceiver; private _graphicsHashGrid = new SparseHashGrid<GraphicsComponent>({ size: 100 }); private _graphics: GraphicsComponent[] = []; private _entityToPointer = new Map<Entity, PointerComponent>(); private _pointerEventDispatcher = new PointerEventsToObjectDispatcher();
query: Query<typeof TransformComponent | typeof PointerComponent>;
constructor(public world: World) { super(); this.query = this.world.query([TransformComponent, PointerComponent]);
this.query.entityAdded$.subscribe((e) => { const tx = e.get(TransformComponent); const pointer = e.get(PointerComponent); this._pointerEventDispatcher.addObject( e, (pos) => { // If pointer bounds defined if (pointer && pointer.localBounds) { const pointerBounds = pointer.localBounds.transform(tx.get().matrix); return pointerBounds.contains(tx.coordPlane === CoordPlane.World ? pos.worldPos : pos.screenPos); } return false; }, () => e.isActive ); this._entityToPointer.set(e, pointer); const maybeGfx = e.get(GraphicsComponent); if (maybeGfx) { this._graphics.push(maybeGfx); this._graphicsHashGrid.track(maybeGfx); } this._sortedTransforms.push(tx); this._sortedEntities.push(tx.owner!); tx.zIndexChanged$.subscribe(this._zIndexUpdate); this._zHasChanged = true; });
this.query.entityRemoved$.subscribe((e) => { this._pointerEventDispatcher.removeObject(e); const tx = e.get(TransformComponent); this._entityToPointer.delete(e); const maybeGfx = e.get(GraphicsComponent); if (maybeGfx) { const index = this._graphics.indexOf(maybeGfx); if (index > -1) { this._graphics.splice(index, 1); } this._graphicsHashGrid.untrack(maybeGfx); } tx.zIndexChanged$.unsubscribe(this._zIndexUpdate); const index = this._sortedTransforms.indexOf(tx); if (index > -1) { this._sortedTransforms.splice(index, 1); this._sortedEntities.splice(index, 1); } }); }
/** * Optionally override component configuration for all entities */ public overrideUseColliderShape = false; /** * Optionally override component configuration for all entities */ public overrideUseGraphicsBounds = false;
private _scene!: Scene<unknown>;
public override initialize(world: World, scene: Scene): void { this._engine = scene.engine; this._scene = scene; }
private _sortedTransforms: TransformComponent[] = []; private _sortedEntities: Entity[] = [];
private _zHasChanged = false; private _zIndexUpdate = () => { this._zHasChanged = true; };
public override preupdate(): void { if (this._scene.camera.hasChanged()) { // if the camera has changed we want to force a transform update so pointers can be correctly calc'd this._scene.camera.updateTransform(this._scene.camera.pos); }
// event receiver might change per frame this._receivers = [this._engine.input.pointers, this._scene.input.pointers]; this._engineReceiver = this._engine.input.pointers; if (this._zHasChanged) { this._sortedTransforms.sort((a, b) => { return b.z - a.z; }); this._sortedEntities = this._sortedTransforms.map((t) => t.owner!); this._zHasChanged = false; } }
public update(): void { // Update graphics this._graphicsHashGrid.update(this._graphics);
// Locate all the pointer/entity mappings for (const [pointerId, pos] of this._engineReceiver.currentFramePointerCoords.entries()) { const colliders = this._scene.physics.query(pos.worldPos); for (let i = 0; i < colliders.length; i++) { const collider = colliders[i]; const maybePointer = this._entityToPointer.get(collider.owner!); if (maybePointer && (maybePointer.useColliderShape || this.overrideUseColliderShape)) { if (collider.contains(pos.worldPos)) { this._pointerEventDispatcher.addPointerToObject(collider.owner!, pointerId); } } }
const graphics = this._graphicsHashGrid.query(pos.worldPos); for (let i = 0; i < graphics.length; i++) { const graphic = graphics[i]; const maybePointer = this._entityToPointer.get(graphic.owner!); if (maybePointer && (maybePointer.useGraphicsBounds || this.overrideUseGraphicsBounds)) { this._pointerEventDispatcher.addPointerToObject(graphic.owner!, pointerId); } } }
this._pointerEventDispatcher.processPointerToObject(this._engineReceiver, this._sortedEntities);
// Dispatch pointer events on entities this._pointerEventDispatcher.dispatchEvents(this._engineReceiver, this._sortedEntities);
// Dispatch pointer events on top level pointers this._receivers.forEach((r) => r.update());
// Clear last frame's events this._pointerEventDispatcher.clear();
this._receivers.forEach((r) => r.clear()); }}