mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-02 20:13:50 +08:00
feat: Add WorldRoute to enable swapping worlds from the RouterComponent (#3372)
This PR adds the `WorldRoute` which acts as a normal route, but instead of adding a new component (page), it swaps out the world that the camera (either the default camera or an explicitly passed in one) is watching.
This commit is contained in:
443
examples/lib/stories/router/router_world_example.dart
Normal file
443
examples/lib/stories/router/router_world_example.dart
Normal file
@ -0,0 +1,443 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/effects.dart';
|
||||
import 'package:flame/events.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/geometry.dart';
|
||||
import 'package:flame/rendering.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
class RouterWorldExample extends FlameGame {
|
||||
static const description = '''
|
||||
This example shows how to use the RouterComponent to navigate between
|
||||
different worlds and pages.
|
||||
''';
|
||||
|
||||
late final RouterComponent router;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
add(
|
||||
router = RouterComponent(
|
||||
routes: {
|
||||
'home': Route(StartPage.new),
|
||||
'level1': WorldRoute(Level1Page.new),
|
||||
'level2': WorldRoute(Level2Page.new, maintainState: false),
|
||||
'pause': PauseRoute(),
|
||||
},
|
||||
initialRoute: 'home',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StartPage extends Component with HasGameReference<RouterWorldExample> {
|
||||
StartPage() {
|
||||
addAll([
|
||||
_logo = TextComponent(
|
||||
text: 'Your Game',
|
||||
textRenderer: TextPaint(
|
||||
style: const TextStyle(
|
||||
fontSize: 64,
|
||||
color: Color(0xFFC8FFF5),
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
anchor: Anchor.center,
|
||||
),
|
||||
_button1 = RoundedButton(
|
||||
text: 'Level 1',
|
||||
action: () => game.router.pushNamed('level1'),
|
||||
color: const Color(0xffadde6c),
|
||||
borderColor: const Color(0xffedffab),
|
||||
),
|
||||
_button2 = RoundedButton(
|
||||
text: 'Level 2',
|
||||
action: () => game.router.pushNamed('level2'),
|
||||
color: const Color(0xffdebe6c),
|
||||
borderColor: const Color(0xfffff4c7),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
late final TextComponent _logo;
|
||||
late final RoundedButton _button1;
|
||||
late final RoundedButton _button2;
|
||||
|
||||
@override
|
||||
void onGameResize(Vector2 size) {
|
||||
super.onGameResize(size);
|
||||
_logo.position = Vector2(size.x / 2, size.y / 3);
|
||||
_button1.position = Vector2(size.x / 2, _logo.y + 80);
|
||||
_button2.position = Vector2(size.x / 2, _logo.y + 140);
|
||||
}
|
||||
}
|
||||
|
||||
class Background extends Component {
|
||||
Background(this.color);
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
canvas.drawColor(color, BlendMode.srcATop);
|
||||
}
|
||||
}
|
||||
|
||||
class RoundedButton extends PositionComponent with TapCallbacks {
|
||||
RoundedButton({
|
||||
required this.text,
|
||||
required this.action,
|
||||
required Color color,
|
||||
required Color borderColor,
|
||||
super.position,
|
||||
super.anchor = Anchor.center,
|
||||
}) : _textDrawable = TextPaint(
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
color: Color(0xFF000000),
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
).toTextPainter(text) {
|
||||
size = Vector2(150, 40);
|
||||
_textOffset = Offset(
|
||||
(size.x - _textDrawable.width) / 2,
|
||||
(size.y - _textDrawable.height) / 2,
|
||||
);
|
||||
_rrect = RRect.fromLTRBR(0, 0, size.x, size.y, Radius.circular(size.y / 2));
|
||||
_bgPaint = Paint()..color = color;
|
||||
_borderPaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2
|
||||
..color = borderColor;
|
||||
}
|
||||
|
||||
final String text;
|
||||
final void Function() action;
|
||||
final TextPainter _textDrawable;
|
||||
late final Offset _textOffset;
|
||||
late final RRect _rrect;
|
||||
late final Paint _borderPaint;
|
||||
late final Paint _bgPaint;
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
canvas.drawRRect(_rrect, _bgPaint);
|
||||
canvas.drawRRect(_rrect, _borderPaint);
|
||||
_textDrawable.paint(canvas, _textOffset);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTapDown(TapDownEvent event) {
|
||||
scale = Vector2.all(1.05);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTapUp(TapUpEvent event) {
|
||||
scale = Vector2.all(1.0);
|
||||
action();
|
||||
}
|
||||
|
||||
@override
|
||||
void onTapCancel(TapCancelEvent event) {
|
||||
scale = Vector2.all(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class SimpleButton extends PositionComponent with TapCallbacks {
|
||||
SimpleButton(this._iconPath, {super.position}) : super(size: Vector2.all(40));
|
||||
|
||||
final Paint _borderPaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..color = const Color(0x66ffffff);
|
||||
final Paint _iconPaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..color = const Color(0xffaaaaaa)
|
||||
..strokeWidth = 7;
|
||||
final Path _iconPath;
|
||||
|
||||
void action();
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(size.toRect(), const Radius.circular(8)),
|
||||
_borderPaint,
|
||||
);
|
||||
canvas.drawPath(_iconPath, _iconPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTapDown(TapDownEvent event) {
|
||||
_iconPaint.color = const Color(0xffffffff);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTapUp(TapUpEvent event) {
|
||||
_iconPaint.color = const Color(0xffaaaaaa);
|
||||
action();
|
||||
}
|
||||
|
||||
@override
|
||||
void onTapCancel(TapCancelEvent event) {
|
||||
_iconPaint.color = const Color(0xffaaaaaa);
|
||||
}
|
||||
}
|
||||
|
||||
class BackButton extends SimpleButton
|
||||
with HasGameReference<RouterWorldExample> {
|
||||
BackButton()
|
||||
: super(
|
||||
Path()
|
||||
..moveTo(22, 8)
|
||||
..lineTo(10, 20)
|
||||
..lineTo(22, 32)
|
||||
..moveTo(12, 20)
|
||||
..lineTo(34, 20),
|
||||
position: Vector2.all(10),
|
||||
);
|
||||
|
||||
@override
|
||||
void action() => game.router.pop();
|
||||
}
|
||||
|
||||
class PauseButton extends SimpleButton
|
||||
with HasGameReference<RouterWorldExample> {
|
||||
PauseButton()
|
||||
: super(
|
||||
Path()
|
||||
..moveTo(14, 10)
|
||||
..lineTo(14, 30)
|
||||
..moveTo(26, 10)
|
||||
..lineTo(26, 30),
|
||||
position: Vector2(60, 10),
|
||||
);
|
||||
|
||||
bool isPaused = false;
|
||||
|
||||
@override
|
||||
void action() {
|
||||
if (isPaused) {
|
||||
game.router.pop();
|
||||
} else {
|
||||
game.router.pushNamed('pause');
|
||||
}
|
||||
isPaused = !isPaused;
|
||||
}
|
||||
}
|
||||
|
||||
class Level1Page extends DecoratedWorld with HasGameReference {
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
addAll([
|
||||
Background(const Color(0xbb2a074f)),
|
||||
Planet(
|
||||
radius: 25,
|
||||
color: const Color(0xfffff188),
|
||||
children: [
|
||||
Orbit(
|
||||
radius: 110,
|
||||
revolutionPeriod: 6,
|
||||
planet: Planet(
|
||||
radius: 10,
|
||||
color: const Color(0xff54d7b1),
|
||||
children: [
|
||||
Orbit(
|
||||
radius: 25,
|
||||
revolutionPeriod: 5,
|
||||
planet: Planet(radius: 3, color: const Color(0xFFcccccc)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
final hudComponents = <Component>[];
|
||||
|
||||
@override
|
||||
void onMount() {
|
||||
hudComponents.addAll([
|
||||
BackButton(),
|
||||
PauseButton(),
|
||||
]);
|
||||
game.camera.viewport.addAll(hudComponents);
|
||||
}
|
||||
|
||||
@override
|
||||
void onRemove() {
|
||||
game.camera.viewport.removeAll(hudComponents);
|
||||
super.onRemove();
|
||||
}
|
||||
}
|
||||
|
||||
class Level2Page extends DecoratedWorld with HasGameReference {
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
addAll([
|
||||
Background(const Color(0xff052b44)),
|
||||
Planet(
|
||||
radius: 30,
|
||||
color: const Color(0xFFFFFFff),
|
||||
children: [
|
||||
Orbit(
|
||||
radius: 60,
|
||||
revolutionPeriod: 5,
|
||||
planet: Planet(radius: 10, color: const Color(0xffc9ce0d)),
|
||||
),
|
||||
Orbit(
|
||||
radius: 110,
|
||||
revolutionPeriod: 10,
|
||||
planet: Planet(
|
||||
radius: 14,
|
||||
color: const Color(0xfff32727),
|
||||
children: [
|
||||
Orbit(
|
||||
radius: 26,
|
||||
revolutionPeriod: 3,
|
||||
planet: Planet(radius: 5, color: const Color(0xffffdb00)),
|
||||
),
|
||||
Orbit(
|
||||
radius: 35,
|
||||
revolutionPeriod: 4,
|
||||
planet: Planet(radius: 3, color: const Color(0xffdc00ff)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
final hudComponents = <Component>[];
|
||||
|
||||
@override
|
||||
void onMount() {
|
||||
hudComponents.addAll([
|
||||
BackButton(),
|
||||
PauseButton(),
|
||||
]);
|
||||
game.camera.viewport.addAll(hudComponents);
|
||||
}
|
||||
|
||||
@override
|
||||
void onRemove() {
|
||||
game.camera.viewport.removeAll(hudComponents);
|
||||
super.onRemove();
|
||||
}
|
||||
}
|
||||
|
||||
class Planet extends PositionComponent {
|
||||
Planet({
|
||||
required this.radius,
|
||||
required this.color,
|
||||
super.position,
|
||||
super.children,
|
||||
}) : _paint = Paint()..color = color;
|
||||
|
||||
final double radius;
|
||||
final Color color;
|
||||
final Paint _paint;
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
canvas.drawCircle(Offset.zero, radius, _paint);
|
||||
}
|
||||
}
|
||||
|
||||
class Orbit extends PositionComponent {
|
||||
Orbit({
|
||||
required this.radius,
|
||||
required this.planet,
|
||||
required this.revolutionPeriod,
|
||||
double initialAngle = 0,
|
||||
}) : _paint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..color = const Color(0x888888aa),
|
||||
_angle = initialAngle {
|
||||
add(planet);
|
||||
}
|
||||
|
||||
final double radius;
|
||||
final double revolutionPeriod;
|
||||
final Planet planet;
|
||||
final Paint _paint;
|
||||
double _angle;
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
canvas.drawCircle(Offset.zero, radius, _paint);
|
||||
}
|
||||
|
||||
@override
|
||||
void update(double dt) {
|
||||
_angle += dt / revolutionPeriod * tau;
|
||||
planet.position = Vector2(radius, 0)..rotate(_angle);
|
||||
}
|
||||
}
|
||||
|
||||
class PauseRoute extends Route {
|
||||
PauseRoute() : super(PausePage.new, transparent: true);
|
||||
|
||||
@override
|
||||
void onPush(Route? previousRoute) {
|
||||
if (previousRoute is WorldRoute && previousRoute.world is DecoratedWorld) {
|
||||
(previousRoute.world! as DecoratedWorld).timeScale = 0;
|
||||
(previousRoute.world! as DecoratedWorld).decorator =
|
||||
PaintDecorator.grayscale(opacity: 0.5)..addBlur(3.0);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onPop(Route nextRoute) {
|
||||
if (nextRoute is WorldRoute && nextRoute.world is DecoratedWorld) {
|
||||
(nextRoute.world! as DecoratedWorld).timeScale = 1;
|
||||
(nextRoute.world! as DecoratedWorld).decorator = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PausePage extends Component
|
||||
with TapCallbacks, HasGameReference<RouterWorldExample> {
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
final game = findGame()!;
|
||||
addAll([
|
||||
TextComponent(
|
||||
text: 'PAUSED',
|
||||
position: game.canvasSize / 2,
|
||||
anchor: Anchor.center,
|
||||
children: [
|
||||
ScaleEffect.to(
|
||||
Vector2.all(1.1),
|
||||
EffectController(
|
||||
duration: 0.3,
|
||||
alternate: true,
|
||||
infinite: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
bool containsLocalPoint(Vector2 point) => true;
|
||||
|
||||
@override
|
||||
void onTapUp(TapUpEvent event) => game.router.pop();
|
||||
}
|
||||
|
||||
class DecoratedWorld extends World with HasTimeScale {
|
||||
PaintDecorator? decorator;
|
||||
|
||||
@override
|
||||
void renderFromCamera(Canvas canvas) {
|
||||
if (decorator == null) {
|
||||
super.renderFromCamera(canvas);
|
||||
} else {
|
||||
decorator!.applyChain(super.renderFromCamera, canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user