change: GraphicsGroup no longer centers with anchors (#2966)
`useAnchor: false` The graphics members are positioned from the top left
```typescript
const graphicGroup = new ex.GraphicsGroup({
useAnchor: false,
members: [
{
graphic: heartImage.toSprite(),
offset: ex.vec(0, 0),
},
{
graphic: heartImage.toSprite(),
offset: ex.vec(0, 16),
},
{
graphic: heartImage.toSprite(),
offset: ex.vec(16, 16),
},
{
graphic: heartImage.toSprite(),
offset: ex.vec(16, 0),
},
],
});
```

`useAnchor: true` The graphics members' total combined bounds are centered by the actor's anchor (.5, .5) by default
```typescript
const graphicGroup = new ex.GraphicsGroup({
useAnchor: true,
members: [
{
graphic: heartImage.toSprite(),
offset: ex.vec(0, 0),
},
{
graphic: heartImage.toSprite(),
offset: ex.vec(0, 16),
},
{
graphic: heartImage.toSprite(),
offset: ex.vec(16, 16),
},
{
graphic: heartImage.toSprite(),
offset: ex.vec(16, 0),
},
],
});
```

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: Implement SpriteSheet getSprite options (#2901)
This PR implements new `ex.SpriteSheet.getSprite(..., options)`
You can now influence sprites directly out of the sprite sheet
```typescript
const sprite = ss.getSprite(0, 0, {
flipHorizontal: true,
flipVertical: true,
width: 200,
height: 201,
opacity: .5,
scale: ex.vec(2, 2),
origin: ex.vec(0, 1),
tint: ex.Color.Red,
rotation: 4
});
```
Also the animation helper `ex.Animation.fromSpriteSheetCoordinates()` you can now pass any valid `ex.GraphicOptions` to influence the sprite per frame
```typescript
const anim = ex.Animation.fromSpriteSheetCoordinates({
spriteSheet: ss,
frameCoordinates: [
{x: 0, y: 0, duration: 100, options: { flipHorizontal: true }},
{x: 1, y: 0, duration: 100, options: { flipVertical: true }},
{x: 2, y: 0, duration: 100},
{x: 3, y: 0, duration: 100}
],
strategy: ex.AnimationStrategy.Freeze
});
```
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,
});
```

change: GraphicsGroup no longer centers with anchors (#2966)
`useAnchor: false` The graphics members are positioned from the top left
```typescript
const graphicGroup = new ex.GraphicsGroup({
useAnchor: false,
members: [
{
graphic: heartImage.toSprite(),
offset: ex.vec(0, 0),
},
{
graphic: heartImage.toSprite(),
offset: ex.vec(0, 16),
},
{
graphic: heartImage.toSprite(),
offset: ex.vec(16, 16),
},
{
graphic: heartImage.toSprite(),
offset: ex.vec(16, 0),
},
],
});
```

`useAnchor: true` The graphics members' total combined bounds are centered by the actor's anchor (.5, .5) by default
```typescript
const graphicGroup = new ex.GraphicsGroup({
useAnchor: true,
members: [
{
graphic: heartImage.toSprite(),
offset: ex.vec(0, 0),
},
{
graphic: heartImage.toSprite(),
offset: ex.vec(0, 16),
},
{
graphic: heartImage.toSprite(),
offset: ex.vec(16, 16),
},
{
graphic: heartImage.toSprite(),
offset: ex.vec(16, 0),
},
],
});
```

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()`