You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Excalibur has a solid typesafe/autocompleted Event/Listener setup, but it's really hard to add your own custom events without creating wrappers or fooling TS.
Proposal
I propose using generic types on EX classes like Actor, Entity and Engine to handle event types, making it a lot easier to have autocompletion and type safety with custom events as well.
The following code example shows my suggested code changes to handle this with minimal code changes and max backwards compatibility:
import{ActorasExActor,EventEmitter,EventKey,InitializeEvent,Engine,Handler,typeEntityEvents,CollisionEndEvent,EventMap,}from"excalibur";// Not importable through "excalibur", while EntityEvents is, so could possibly be a bug.// import type {ActorEvents} from "excalibur/build/dist/Actor";// Imported ActorEvents act wonky in Jetbrains IDEs, probably because of weird inputtypeActorEvents={initialize: InitializeEvent,collisionend: CollisionEndEvent,};// I had a hard time figuring out the correct names for everything, so suggestions are welcome for this type and its variables.typeAppendedEventMap<TOriginalextendsEventMap,TExtraextendsEventMap|undefined>=TExtraextendsEventMap ? TOriginal&TExtra : TOriginal;// You can also add this to Engine and other classes using EventEmitterclassEntity</** TKnownComponents extends Component = unknown, **/TCustomEventsextendsEventMap|undefined=undefined,// TEvents isn't the nicest way to solve this, but using `AppendedEventMap<EntityEvents, TCustomEvents>`// everywhere makes the rest of the code harder to read.TEventsextendsEventMap=AppendedEventMap<EntityEvents,TCustomEvents>>/** implements OnInitialize, OnPreUpdate, OnPostUpdate **/{publicevents=newEventEmitter<TEvents>();// No longer 3 different ways to handle events to confuse IDEspublicon<TEventNameextendsEventKey<TEvents>>(eventName: TEventName,handler: Handler<TEvents[TEventName]>){this.events.on(eventName,handler);}publicoff<TEventNameextendsEventKey<TEvents>>(eventName: TEventName,handler: Handler<TEvents[TEventName]>){this.events.off(eventName,handler);}publicemit<TEventNameextendsEventKey<TEvents>>(eventName: TEventName,event: TEvents[TEventName]): void{this.events.emit(eventName,event);}}classActor<TCustomEventsextendsEventMap|undefined=undefined,TEventsextendsEventMap=AppendedEventMap<ActorEvents,TCustomEvents>>extendsEntity<TEvents>/** implements Eventable, PointerEvents, CanInitialize, CanUpdate, CanBeKilled **/{// No need to override this.events anymore}// Custom event typeinterfaceDamageTakenEvent{amount: number}typeCustomEvents={"damage-taken": DamageTakenEvent,"random-number": number,}classPlayerextendsActor<CustomEvents>{}// `new Actor<CustomEvents>()` is also validconstactorWithCustomEvents=newPlayer();// Valid custom eventsactorWithCustomEvents.on('damage-taken',(event)=>console.log('Damage taken!',event));actorWithCustomEvents.emit('damage-taken',{amount: 1});actorWithCustomEvents.emit('random-number',Math.random());// @ts-expect-error Example of invalid code because of missing dataactorWithCustomEvents.on('damage-taken');// @ts-expect-error Example of invalid code because of invalid dataactorWithCustomEvents.on('damage-taken',1);// Valid system eventactorWithCustomEvents.on('initialize',(event: InitializeEvent)=>console.log('initialize:',event));actorWithCustomEvents.emit('initialize',newInitializeEvent(newEngine(),newExActor()));// @ts-expect-error Example of invalid System Event codeactorWithCustomEvents.on('collisionend',(event: InitializeEvent)=>console.log('initialize:',event));// This change will also make it easy to hook into the already existing system events because of GameEvent.// (This is the only not 100% valid code example, because of the Actor/Entity class created for this example)actorWithCustomEvents.on('collisionend',(event: CollisionEndEvent<Player>)=>{event.other.emit('damage-taken',(event: DamageTakenEvent)=>{console.log(event);});});
The text was updated successfully, but these errors were encountered:
Context
Excalibur has a solid typesafe/autocompleted Event/Listener setup, but it's really hard to add your own custom events without creating wrappers or fooling TS.
Proposal
I propose using generic types on EX classes like Actor, Entity and Engine to handle event types, making it a lot easier to have autocompletion and type safety with custom events as well.
The following code example shows my suggested code changes to handle this with minimal code changes and max backwards compatibility:
The text was updated successfully, but these errors were encountered: