Sarah
Sarah

Reputation: 23

Navigate to a flutter widget from a flame game without losing the game state

My flame game concept is that a player moves, and when the player collides with a star object, a pop up flutter window contains a question shall appear, and when the user closes it, the game state and player position will be restored.How can I do so?

I tried Navigator.push but it doesn't work, it says that no context available :(

Also I tried a different way, which is the overlay, but the Star class cannot access the overlays property, I wrote a method in the main game class "PlayerWithBG" but calling it throws an exception...

This is the code inside Star class

import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/sprite.dart';
import 'package:flutter/material.dart';
import 'package:khalooq/Game/PlayerWithBG.dart';
import '../Questions/firstLevelQuestions.dart';
import 'helpers/directions.dart';

class Star extends SpriteAnimationComponent
    with HasGameRef, CollisionCallbacks {
  Star(Vector2 position)
      : super(size: Vector2.all(50), position: position, anchor: Anchor.center);

  final gameObject = PlayerWithBG();

  late final SpriteAnimation _rotateAnimation;
  final double _animationSpeed = .3;
  Direction direction = Direction.right;

  @override
  Future<void> onLoad() async {
    super.onLoad();
    await _loadAnimations().then((_) => {animation = _rotateAnimation});
    add(CircleHitbox());
  }

  Future<void> _loadAnimations() async {
    final spriteSheet = SpriteSheet.fromColumnsAndRows(
        image: await gameRef.images.load('stars1.png'), columns: 10, rows: 1);

    _rotateAnimation = spriteSheet.createAnimation(
        row: 0, stepTime: _animationSpeed, from: 0, to: 4);
  }

  @override
  void onCollision(Set<Vector2> intersectionPoints, PositionComponent other) {
    super.onCollision(intersectionPoints, other);
    // gameObject.showQuestion();
    // calling the showQuestion() method throws an exception in the method
    /*
     * Exception has occurred.
      _AssertionError ('package:flame/src/game/overlay_manager.dart': Failed assertion: line 51 pos 7: '_builders.containsKey(name)': Trying to add an unknown overlay "Question")
     */

    removeFromParent();
    //This is what I want to do--------------------------------------------------
    // Navigator.push(
    //     context, MaterialPageRoute(builder: (context) => firstLevelQuestion()));
  }
}

And this is the code inside the main game class "PlayerWithBG"

import 'dart:ui';

import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flutter/services.dart';
import 'Player.dart';
import 'GameBG.dart';
import 'package:flutter/material.dart';

import 'Star.dart';
import 'helpers/directions.dart';

class PlayerWithBG extends FlameGame
    with KeyboardEvents, HasCollisionDetection, TapDetector {
  Player _player = Player();
  GameBG _gameBG = GameBG();

  @override
  Future<void> onLoad() async {
    super.onLoad();
    await add(_gameBG);
    double bgWidth = _gameBG.size.x;
    double bghight = _gameBG.size.y;
    await add(_player);
    _player.position = Vector2(bgWidth * 0.05, bghight * 0.95);
    camera.followComponent(_player,
        worldBounds: Rect.fromLTRB(0, 0, _gameBG.size.x, _gameBG.size.y));
    //--------------------------------------------------------------------------------------
    //Stars are the elements that should open the question when the player collides with them
    add(Star(Vector2(bgWidth * 0.10, bghight * 0.95)));
    add(Star(Vector2(bgWidth * 0.30, bghight * 0.95)));
  }

  void showQuestion() {
    if (overlays.isActive('Question')) {
      overlays.remove('Question');
      resumeEngine();
    } else {
      overlays.add('Question');
      pauseEngine();
    }
  }

  onArrowKeyChanged(Direction direction) {
    _player.direction = direction;
  }
}

And here where I call the game widget with a temp overlay container

          GameWidget(
            game: game..paused = false,
            //here is the overlayer to render the question--------------------
            overlayBuilderMap: {
              'Question': (BuildContext context, PlayerWithBG game) {
                return Center(
                  child: Container(
                    width: 100,
                    height: 100,
                    color: Colors.orange,
                    child: Center(child: Text('This is a question')),
                  ),
                );
              },
            },
            //-----------------------------------------------------------------
          ),

Upvotes: 2

Views: 1549

Answers (1)

spydon
spydon

Reputation: 11512

You can do this in a few different ways:

1.Use the overlays system

Just like you tried, but to access the overlays you can use the fact that you have the HasGameRef mixin on your component, which gives your access to the gameRef variable and thus gameRef.overlays.add/remove.

You can read more about that here.

2. Use the RouterComponent

This technique is slightly more advanced, but it is definitely worth exploring if you will have a lot of different screens and scenes.

Example:

final router = RouterComponent(
  routes: {
    'ok-dialog': OverlayRoute(
      (context, game) {
        return Center(
          child: DecoratedContainer(...),
        );
      },
    ),  // OverlayRoute
    'confirm-dialog': OverlayRoute.existing(),
  },
);

router.pushNamed('ok-dialog);

You can read a lot more about that here.

3. Use the normal Flutter navigation

The last, but least ergonomic way, is to use Flutter's built-in navigation (or another navigation package). You can get the context in the same way as you accessed the overlays, when you have the HasGameRef mixin:

gameRef.buildContext;

Depending on how you do the navigation you might have to use the GameWidget.controlled constructor when you create your GameWidget.

Upvotes: 2

Related Questions