Skip to content

Commit

Permalink
fix: CrossFade for firefox + docs
Browse files Browse the repository at this point in the history
update test
  • Loading branch information
eonarheim committed Jan 16, 2024
1 parent b7a5819 commit ef5bb7a
Show file tree
Hide file tree
Showing 6 changed files with 404 additions and 43 deletions.
237 changes: 237 additions & 0 deletions site/docs/02-fundamentals/05-transitions.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
---
title: Scene Transitions 🧪
slug: /transitions
section: Fundamentals
---

import TransitionExample from '!!raw-loader!./examples/scene-transitions.ts';
import CrossFadeTransitionExample from '!!raw-loader!./examples/scene-crossfade.ts';

```twoslash include ex
/// <reference path="../src/engine/excalibur.d.ts" />
class MyScene extends ex.Scene {}
class MyOtherScene extends ex.Scene {}
```

:::warning

This is currently an alpha feature, and will be released in the next version! API might change/fluctuate until it lands in a supported version.

:::

🧪 `npm install [email protected]` or greater

Many times in your game you'll want to smoothly go from one scene to the next, or provide a custom effect when transitioning!

## Using Pre-Definited Scene Transitions

It is generally recommended that you define you scenes up front, when you do you have the opportunity to also specify the in/out transitions for a scene.

* `in` transitions play when the target scene has started, and will play the effect until the `duration` in milliseconds is complete
* `out` transitions play before the current scene is deactivated, the transition must complete before deactivation.

If no `in/out` transition is specified for a scene it will hard cut from one to the other.

```ts twoslash
// @include: ex
// ---cut---
const game = new ex.Engine({
scenes: {
scene1: {
scene: MyScene,
transitions: {
in: new ex.FadeInOut({duration: 500, direction: 'in', color: ex.Color.Black}),
out: new ex.FadeInOut({duration: 500, direction: 'out', color: ex.Color.Black})
}
},
scene2: {
scene: MyOtherScene,
transitions: {
in: new ex.FadeInOut({duration: 500, direction: 'in', color: ex.Color.Black}),
out: new ex.FadeInOut({duration: 500, direction: 'out', color: ex.Color.Black})
}
}
}
});


game.goto('scene1');

```

or using the add scene api

```ts twoslash
// @include: ex
// ---cut---
const game = new ex.Engine();

game.add('scene1', {
scene: MyScene,
transitions: {
in: new ex.FadeInOut({duration: 500, direction: 'in', color: ex.Color.Black}),
out: new ex.FadeInOut({duration: 500, direction: 'out', color: ex.Color.Black})
}
});

```

Click canvas to transition!

<GameCodeBlock live
title="Transition"
code={TransitionExample}
/>

## Transition Options
Transitions have a few tricks up their sleeves, you can control duration, direction, easing function, whether to hide any loaders, or block user input during the transition

```ts twoslash
// @include: ex
// ---cut---
const transition = new ex.Transition({
/**
* Transition duration in milliseconds
*/
duration: 1000,

/**
* Optionally hides the loader during the transition
*
* If either the out or in transition have this set to true, then the loader will be hidden.
*
* Default false
*/
hideLoader: false,

/**
* Optionally blocks user input during a transition
*
* Default false
*/
blockInput: false,

/**
* Optionally specify a easing function, by default linear
*/
easing: ex.EasingFunctions.Linear,
/**
* Optionally specify a transition direction, by default 'out'
*
* * For 'in' direction transitions start at 1 and complete is at 0
* * For 'out' direction transitions start at 0 and complete is at 1
*/
direction: 'out',
})
```

## Overriding Transitions

There are 2 ways to override pre-defined transitions

* `goto('myscene', { destinationIn: ..., sourceOut: ... })` takes the highest precedence and will override any transition
* Extending [[Scene.onTransition]] you can provide dynamic transitions depending on your scene's state

```typescript
class MyCustomScene extends ex.Scene {
onTransition(direction: "in" | "out") {
return new ex.FadeInOut({
direction,
color: ex.Color.Violet,
duration: 2000
});
}
}
```

## FadeInOut

This transition does exactly as it sounds, you can specific a duration in milliseconds and it will fade in the specified `direction`. [[FadeInOut]] uses the color [[Color.Black]] by default.

* The `direction: 'in'` direction means the transition will start fully opaque (non-transparent), then transition to fully transparent.
* The `direction: 'out'` direction means the transition will start fully transparent, then transition to fully opaque (non-transparent).

## CrossFade

:::warning

[[CrossFade]] can only be used on the `in`` transition for a scene, this is because it needs to [[Engine.screenshot|screenshot]] the previous scene in order to cross fade it.

:::


You can specific a duration in milliseconds and it will fade in the specified `direction`. [[CrossFade]] takes a screen shot of the previous scene and blends that into the current scene.

* The `direction: 'in'` direction means the transition will start fully opaque (non-transparent), then transition to fully transparent.
* The `direction: 'out'` direction means the transition will start fully transparent, then transition to fully opaque (non-transparent).

Click canvas to transition!

<GameCodeBlock live
title="CrossFade Transition"
code={CrossFadeTransitionExample}
/>

## Starting Scene Transition

Sometimes you want a special start transition for the beginning of your game after loading. You may want to match the color, do something special, etc.

This can be done on the [[Engine.start]] by providing a start transition

```typescript
game.start('scene1',
{
inTransition: startTransition
});

```

## Custom built Transitions

Transitions are really an [[Entity]] with a [[TransformComponent]] and [[GraphicsComponent]] that take up the entire screen and draw on top of everything by default `z = Infinity`.

To build your own custom transition, extend [[Transition]] and implement the stubbed methods

For example this is CrossFade's implementation

```typescript
export class CrossFade extends Transition {
engine: Engine;
image: HTMLImageElement;
screenCover: Sprite;
constructor(options: TransitionOptions & CrossFadeOptions) {
super(options);
this.name = `CrossFade#${this.id}`;
}

override async onPreviousSceneDeactivate(scene: Scene<unknown>) {
this.image = await scene.engine.screenshot(true);
}

override onInitialize(engine: Engine): void {
this.engine = engine;
const bounds = engine.screen.getWorldBounds();
this.transform.pos = vec(bounds.left, bounds.top);
this.screenCover = ImageSource.fromHtmlImageElement(this.image).toSprite();
this.graphics.add(this.screenCover);
this.transform.scale = vec(1 / engine.screen.pixelRatio, 1 / engine.screen.pixelRatio);
this.graphics.opacity = this.progress;
}

override onStart(_progress: number): void {
this.graphics.opacity = this.progress;
}

override onReset() {
this.graphics.opacity = this.progress;
}

override onEnd(progress: number): void {
this.graphics.opacity = progress;
}

override onUpdate(progress: number): void {
this.graphics.opacity = progress;
}
}
```
46 changes: 46 additions & 0 deletions site/docs/02-fundamentals/examples/scene-crossfade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@


class MyScene extends ex.Scene {
public onInitialize(): void {
this.add(
new ex.Actor({
pos: ex.vec(200, 200),
color: ex.Color.Red,
width: 100,
height: 200
}))
}
}


class MyOtherScene extends ex.Scene {
public onInitialize(): void {
this.add(
new ex.Actor({
pos: ex.vec(200, 200),
color: ex.Color.Blue,
width: 200,
height: 100
}))
}
}

game.add('scene1', {
scene: MyScene,
transitions: {
in: new ex.CrossFade({duration: 1500, blockInput: true }),
}
});

game.add('scene2', {
scene: MyOtherScene,
transitions: {
in: new ex.CrossFade({duration: 1500, blockInput: true }),
}
});

game.input.pointers.primary.on('down', () => {
game.currentSceneName === 'scene2' ? game.goto('scene1') : game.goto('scene2');
});

game.start('scene2');
48 changes: 48 additions & 0 deletions site/docs/02-fundamentals/examples/scene-transitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@


class MyScene extends ex.Scene {
public onInitialize(): void {
this.add(
new ex.Actor({
pos: ex.vec(200, 200),
color: ex.Color.Red,
width: 100,
height: 200
}))
}
}


class MyOtherScene extends ex.Scene {
public onInitialize(): void {
this.add(
new ex.Actor({
pos: ex.vec(200, 200),
color: ex.Color.Blue,
width: 200,
height: 100
}))
}
}

game.add('scene1', {
scene: MyScene,
transitions: {
in: new ex.FadeInOut({duration: 500, direction: 'in', color: ex.Color.Black}),
out: new ex.FadeInOut({duration: 500, direction: 'out', color: ex.Color.Black})
}
});

game.add('scene2', {
scene: MyOtherScene,
transitions: {
in: new ex.FadeInOut({duration: 500, direction: 'in', color: ex.Color.Black}),
out: new ex.FadeInOut({duration: 500, direction: 'out', color: ex.Color.Black})
}
});

game.input.pointers.primary.on('down', () => {
game.currentSceneName === 'scene2' ? game.goto('scene1') : game.goto('scene2');
});

game.start('scene2');
5 changes: 4 additions & 1 deletion src/engine/Director/CrossFade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ export class CrossFade extends Transition {
image: HTMLImageElement;
screenCover: Sprite;
constructor(options: TransitionOptions & CrossFadeOptions) {
super(options);
super({direction: 'in', ...options}); // default the correct direction
this.name = `CrossFade#${this.id}`;
}

override async onPreviousSceneDeactivate(scene: Scene<unknown>) {
this.image = await scene.engine.screenshot(true);
// Firefox is particularly slow
// needed in case the image isn't ready yet
await this.image.decode();
}

override onInitialize(engine: Engine): void {
Expand Down
Loading

0 comments on commit ef5bb7a

Please sign in to comment.