feat: Excalibur Scene Transitions & Loaders (#2790)
This PR implements an API inspired by https://github.com/mattjennings/excalibur-router/ with permission from Matt.
The idea behind this refactoring is to add transitions between scenes and scene specific loaders!
* Add or remove scenes by constructor
* Add loaders by constructor
* New `DefaultLoader` type that allows for easier custom loader creation
* New `Transition` type for building custom transitions
* New scene lifecycle to allow scene specific resource loading
* `onTransition(direction: "in" | "out") {...}`
* `onPreLoad(loader: DefaultLoader) {...}`
* New async goto API that allows overriding loaders/transitions between scenes
* Scenes now can have `async onInitialize` and `async onActivate`!
* New scenes director API that allows upfront definition of scenes/transitions/loaders
View [this file](https://github.com/excaliburjs/Excalibur/blob/feat/async-init-ex-router/sandbox/tests/router/index.ts) for a current example of what the API looks like
Defining scenes upfront
```typescript
const game = new ex.Engine({
scenes: {
scene1: {
scene: scene1,
transitions: {
out: new ex.FadeInOut({duration: 1000, direction: 'out', color: ex.Color.Black}),
in: new ex.FadeInOut({duration: 1000, direction: 'in'})
}
},
scene2: {
scene: scene2,
loader: ex.DefaultLoader, // Constructor only option!
transitions: {
out: new ex.FadeInOut({duration: 1000, direction: 'out'}),
in: new ex.FadeInOut({duration: 1000, direction: 'in', color: ex.Color.Black })
}
},
scene3: ex.Scene // Constructor only option!
}
})
game.start('scene1',
{
inTransition: new ex.FadeInOut({duration: 500, direction: 'in', color: ex.Color.ExcaliburBlue})
loader: boot,
});
```

feat: Add missing Sound features `.seek()`, `.duration`, & `getTotalPlaybackDuration()` (#2340)
This PR adds the ability to `ex.Sound.seek()` to a specific position in the sound, it also adds the `ex.Sound.getTotalPlaybackDuration()` so you may know how long in seconds the sound file is. Lastly `ex.Sound.duration` if set will limit the duration of the clip from where played();
In order to accomplish this, `ex.Sound()` was refactored using a small `ex.StateMachine` implementation
Example
```typescript
const machine = ex.StateMachine.create({
start: 'STOPPED',
states: {
PLAYING: {
onEnter: () => {
console.log("playing");
},
transitions: ['STOPPED', 'PAUSED']
},
STOPPED: {
onEnter: () => {
console.log("stopped");
},
transitions: ['PLAYING']
},
PAUSED: {
onEnter: () => {
console.log("paused")
},
transitions: ['PLAYING', 'STOPPED']
}
}
});
```
## Changes:
- Adds `ex.Sound.seek()`
- Adds `ex.Sound.duration`
- Adds `ex.Sound.getTotalPlaybackDuration()`
- Adds new `ex.StateMachine()`