Files
Munsterlander 63704c40ee docs: Added fix for building on Windows and resolved other build errors (#2395)
<!--
The title of your PR on the line above should start with a [Conventional
Commit] prefix
(`fix:`, `feat:`, `docs:`, `test:`, `chore:`, `refactor:`, `perf:`,
`build:`, `ci:`,
`style:`, `revert:`). This title will later become an entry in the
[CHANGELOG], so please
make sure that it summarizes the PR adequately.
-->

# Description
<!--
Provide a description of what this PR is doing.
If you're modifying existing behavior, describe the existing behavior,
how this PR is changing it,
and what motivated the change. If this is a breaking change, specify
explicitly which APIs were
changed.
-->

@eukleshnin identified issues with building the documents locally on a
Windows workstation. Namely, the following was occurring:

```
reading sources... [  2%] flame/layout/align_component
Exception occurred:

FileNotFoundError: [WinError 2]
```
This was determined to be a combination of several things.

1. In `dart_domain.py`, the `subprocess.run` calling dartdoc_json did
not have the shell parameter set to true. This solves the error about
the file not being found. This then generates errors that subsequent
references to the `temp_file` did not exist.
2. This was due to the default setting with Python `tempfile` where when
it determines the temp file has been closed, it deletes it; however, it
was still needed, so by setting the `delete=False` parameter, the file
would still remain.
3. Unfortunately, because it still remains, it needs to be deleted once
it is no longer needed. Trying to use `finally:` with the `try` block
failed to produce the results desired, so the temp file name was
registered with the class so it can be deleted in the original calling
function if it exists. This proved successful.
4. Although not critical, the same temp file uses a suffix of `json` so
it was creating files `xxxxjson`. By adding the "." in the suffix, it
creates valid file names now. This doesn't actually fix anything, it
just seemed wrong, so I fixed it to be valid files if ever needed down
the road.

Now that the docs built, there were several warnings that could be
resolved:
1. `Overlays.md` was not referenced in the TOC tree.
2. Since overlays were removed in
https://github.com/flame-engine/flame/pull/2384, the Platformer tutorial
had a link to the old path and it needed to be updated.

Finally and open for discussion, during this process of debugging, I
upgraded all packages to the most current to see what impacts there
were. The following is the old and new potential `requirements.txt`:

```
----Old
linkify-it-py==2.0.0
myst-parser==0.18.1
Pygments==2.12.0
Sphinx==5.0.2
sphinxcontrib-mermaid==0.8.1
sphinx-autobuild==2021.3.14
jinja2==3.1.2

----- New
linkify-it-py==2.0.0
myst-parser==1.0.0
Pygments==2.14.0
Sphinx==6.1.3
sphinxcontrib-mermaid==0.8.1
sphinx-autobuild==2021.3.14
Jinja2==3.1.2
```

The only byproduct of this upgrade was a deprecated package warning for
`attrs_image` in `conf.py` which was updated to `attrs_inline`. I made
that change initially for this PR, but backed it out as I didn't know if
there was a desire to update the `requirements.txt` and felt some
discussion may be warranted.

Regardless, with everything upgraded or left as is, the other fixes
resolve the issues on Windows.

## Checklist
<!--
Before you create this PR confirm that it meets all requirements listed
below by checking the
relevant checkboxes with `[x]`. If some checkbox is not applicable, mark
it as `[-]`.
-->

- [X] I have followed the [Contributor Guide] when preparing my PR.
- [ ] I have updated/added tests for ALL new/updated/fixed
functionality.
- [X] I have updated/added relevant documentation in `docs` and added
dartdoc comments with `///`.
- [ ] I have updated/added relevant examples in `examples` or `docs`.


## Breaking Change?
<!--
Would your PR require Flame users to update their apps following your
change?

If yes, then the title of the PR should include "!" (for example,
`feat!:`, `fix!:`). See
[Conventional Commit] for details. Also, for a breaking PR uncomment and
fill in the "Migration
instructions" section below.

### Migration instructions

If the PR is breaking, uncomment this header and add instructions for
how to migrate from the
currently released version to the new proposed way.
-->

- [ ] Yes, this PR is a breaking change.
- [X] No, this PR is not a breaking change.


## Related Issues
<!--
Indicate which issues this PR resolves, if any. For example:

Closes #1234
!-->

<!-- Links -->
[Contributor Guide]:
https://github.com/flame-engine/flame/blob/main/CONTRIBUTING.md
[Conventional Commit]: https://conventionalcommits.org
[CHANGELOG]:
https://github.com/flame-engine/flame/blob/main/CHANGELOG.md

---------

Co-authored-by: Lukas Klingsbo <me@lukas.fyi>
2023-03-12 20:54:48 +00:00

8.3 KiB

7. Adding Menus

To add menus to the game, we will leverage Flame's built-in overlay system.

Main Menu

In the lib/overlays folder, create main_menu.dart and add the following code:

import 'package:flutter/material.dart';

import '../ember_quest.dart';

class MainMenu extends StatelessWidget {
  // Reference to parent game.
  final EmberQuestGame game;

  const MainMenu({super.key, required this.game});

  @override
  Widget build(BuildContext context) {
    const blackTextColor = Color.fromRGBO(0, 0, 0, 1.0);
    const whiteTextColor = Color.fromRGBO(255, 255, 255, 1.0);

    return Material(
      color: Colors.transparent,
      child: Center(
        child: Container(
          padding: const EdgeInsets.all(10.0),
          height: 250,
          width: 300,
          decoration: const BoxDecoration(
            color: blackTextColor,
            borderRadius: const BorderRadius.all(
              Radius.circular(20),
            ),
          ),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text(
                'Ember Quest',
                style: TextStyle(
                  color: whiteTextColor,
                  fontSize: 24,
                ),
              ),
              const SizedBox(height: 40),
              SizedBox(
                width: 200,
                height: 75,
                child: ElevatedButton(
                  onPressed: () {
                    game.overlays.remove('MainMenu');
                  },
                  style: ElevatedButton.styleFrom(
                    backgroundColor: whiteTextColor,
                  ),
                  child: const Text(
                    'Play',
                    style: TextStyle(
                      fontSize: 40.0,
                      color: blackTextColor,
                    ),
                  ),
                ),
              ),
              const SizedBox(height: 20),
              const Text(
                'Use WASD or Arrow Keys for movement.  Space bar to jump.
                 Collect as many stars as you can and avoid enemies!',
                textAlign: TextAlign.center,
                style: TextStyle(
                  color: whiteTextColor,
                  fontSize: 14,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

This is a pretty self-explanatory file that just uses standard Flutter widgets to display information and provide a Play button. The only Flame-related line is game.overlays.remove('MainMenu'); which simply removes the overlay so the user can play the game. It should be noted that the user can technically move Ember while this is displayed, but trapping the input is outside the scope of this tutorial as there are multiple ways this can be accomplished.

Game Over Menu

Next, create a file called lib/overlays/game_over.dart and add the following code:

import 'package:flutter/material.dart';

import '../ember_quest.dart';

class GameOver extends StatelessWidget {
  // Reference to parent game.
  final EmberQuestGame game;
  const GameOver({super.key, required this.game});

  @override
  Widget build(BuildContext context) {
    const blackTextColor = Color.fromRGBO(0, 0, 0, 1.0);
    const whiteTextColor = Color.fromRGBO(255, 255, 255, 1.0);

    return Material(
      color: Colors.transparent,
      child: Center(
        child: Container(
          padding: const EdgeInsets.all(10.0),
          height: 200,
          width: 300,
          decoration: const BoxDecoration(
            color: blackTextColor,
            borderRadius: const BorderRadius.all(
              Radius.circular(20),
            ),
          ),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text(
                'Game Over',
                style: TextStyle(
                  color: whiteTextColor,
                  fontSize: 24,
                ),
              ),
              const SizedBox(height: 40),
              SizedBox(
                width: 200,
                height: 75,
                child: ElevatedButton(
                  onPressed: () {
                    game.reset();
                    game.overlays.remove('GameOver');
                  },
                  style: ElevatedButton.styleFrom(
                    backgroundColor: whiteTextColor,
                  ),
                  child: const Text(
                    'Play Again',
                    style: TextStyle(
                      fontSize: 28.0,
                      color: blackTextColor,
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

As with the Main Menu, this is all standard Flutter widgets except for the call to remove the overlay and also the call to game.reset() which we will create now.

Open lib/ember_quest.dart and add / update the following code:

@override
Future<void> onLoad() async {
    await images.loadAll([
        'block.png',
        'ember.png',
        'ground.png',
        'heart_half.png',
        'heart.png',
        'star.png',
        'water_enemy.png',
    ]);
    initializeGame(true);
}

void initializeGame(bool loadHud) {
    // Assume that size.x < 3200
    final segmentsToLoad = (size.x / 640).ceil();
    segmentsToLoad.clamp(0, segments.length);

    for (var i = 0; i <= segmentsToLoad; i++) {
      loadGameSegments(i, (640 * i).toDouble());
    }

    _ember = EmberPlayer(
      position: Vector2(128, canvasSize.y - 128),
    );
    add(_ember);
    if (loadHud) {
      add(Hud());
    }
  }

void reset() {
    starsCollected = 0;
    health = 3;
    initializeGame(false);
}

You may notice that we have added a parameter to the initializeGame method which allows us to bypass adding the HUD to the game. This is because in the coming section, when Ember's health drops to 0, we will wipe the game, but we do not need to remove the HUD, as we just simply need to reset the values using reset().

Displaying the Menus

To display the menus, add the following code to lib/main.dart:

void main() {
  runApp(
    GameWidget<EmberQuestGame>.controlled(
      gameFactory: EmberQuestGame.new,
      overlayBuilderMap: {
        'MainMenu': (_, game) => MainMenu(game: game),
        'GameOver': (_, game) => GameOver(game: game),
      },
      initialActiveOverlays: const ['MainMenu'],
    ),
  );
}

If the menus did not auto-import, add the following:

import 'overlays/game_over.dart';
import 'overlays/main_menu.dart';

If you run the game now, you should be greeted with the Main Menu overlay. Pressing play will remove it and allow you to start playing the game.

Health Check for Game Over

Our last step to finish Ember Quest is to add a game-over mechanism. This is fairly simple but requires us to place similar code in all of our components. So let's get started!

In lib/actors/ember.dart, in the update method, add the following:

// If ember fell in pit, then game over.
if (position.y > game.size.y + size.y) {
    game.health = 0;
}

if (game.health <= 0) {
    removeFromParent();
}

In lib/actors/water_enemy.dart, in the update method update the following code:

if (position.x < -size.x || game.health <= 0) {
    removeFromParent();
}

In lib/objects/ground_block.dart, in the update method update the following code:

if (game.health <= 0) {
    removeFromParent();
}

In lib/objects/platform_block.dart, in the update method update the following code:

if (position.x < -size.x || game.health <= 0) {
    removeFromParent();
}

In lib/objects/star.dart, in the update method update the following code:

if (position.x < -size.x || game.health <= 0) {
    removeFromParent();
}

Finally, in lib/ember_quest.dart, add the following update method:

@override
void update(double dt) {
    if (health <= 0) {
        overlays.add('GameOver');
    }
    super.update(dt);
}

Congratulations

You made it! You have a working Ember Quest. Press the button below to see what the resulting code looks like or to play it live.

:sources: ../tutorials/platformer/app
:show: popup code