3 min read

Getting Started with Your Game Using Flutter's Endless Runner Template

Getting Started with Your Game Using Flutter's Endless Runner Template
Photo by Jukan Tateisi / Unsplash

Motivation

If you want to create a flutter game other than an endless runner but want to utilize Flutter's endless runner template features like FlameGame base template, collision detection, spawning, etc.; feel free to read on.

Environment

  • IDE: Android Studio Hedgehog (2023.1.1)
  • OS: MacOS 14.3.1 (Sonoma)
  • Platform: Android

Preparation

  1. It's best to atleast get familiar with Flutter's Casual Game Toolkit.
  2. Download the template from GitHub: Flutter Games.
  3. Copy or move ~/games-main/templates/endless_runner/ outside the ~/games-main/ folder.
  4. Get familiar with the template by going through its readme file. It's also advisable to try building, running, and playing the game to make sure your setup is working.
ℹ️
If you only want to work on a specific platfrom, you can specify that when running flutter create. For example, I only want to focus on android for now so I used this command:

flutter create . --platforms android

Renaming Your Project

  1. I used Flutter's rename CLI tool to update my project's name and bundle ID but it doesn't seem to work fully. You can confirm this by checking, and if necessary updating your package names in:
  • MainActivity.java
  • AndroidManifest.xml
  • build.gradle
  • ~/<project_name>/android/app/src/main/java/your/package/name/

More info here.

  1. Optional: For clarity, you can also rename the file names and class names in ~/<project_name>/lib/flame_game/. In my case, I added the prefixes endless_runner and EndlessRunner in each file name and class name respectively.

  2. Rebuild and run your project to confirm everything is working.

Creating Your Own Classes

First, create a new folder in ~/<project_name>/lib/. For this example, let's name it my_flame_game.

Create the following scripts inside my_flame_game. The following code are the minimum of what I have to copy from the template in order to get me started.

my_world.dart

class MyWorld extends World with TapCallbacks, HasGameReference {
  MyWorld({
    required this.level,
    required this.playerProgress,
    Random? pRandom
  }) : random = pRandom ?? Random();

  final GameLevel level;
  final PlayerProgress playerProgress;
  final Random random;
}

my_game.dart

class MyGame extends FlameGame<MyWorld> with HasCollisionDetection {
  MyGame({
    required this.level,
    required PlayerProgress playerProgress,
    required this.audioController
  }) : super (
    world: MyWorld(level: level, playerProgress: playerProgress),
    camera: CameraComponent.withFixedResolution(width: 1600, height: 720)
  );

  final GameLevel level;
  final AudioController audioController;
}

my_game_screen.dart

class MyGameScreen extends StatelessWidget {
  const MyGameScreen({required this.level, super.key});

  final GameLevel level;

  @override
  Widget build(BuildContext context) {
    final audioController = context.read<AudioController>();
    return Scaffold(
      body: GameWidget<MyGame>(
        key: const Key('play session'),
        game: MyGame(
          level: level,
          playerProgress: context.read<PlayerProgress>(),
          audioController: audioController
        )
      ),
    );
  }
}

With these classes, I can update router.dart to open my game screen instead of the template one.

final router = GoRouter(
  routes: [
    GoRoute(
      // ...
      routes: [
        GoRoute(
          // ...
          routes: [
            GoRoute(
              path: 'session/:level',
              pageBuilder: (context, state) {
                // ...
                return buildPageTransition<void>(
                  // ...
                  // child: EndlessRunnerGameScreen(level: level),
                  child: MyGameScreen(level: level),
                );
              },
            ),
          ],
        ),
        // ...
      ],
    ),
  ],
);

Now when you test your changes, a black screen should show up after selecting a level.

ℹ️
I'm keeping the files in the flame_game folder as reference while working on my project. I just prefer to have everything as much as possible in one project to minimize switching between multiple windows.

Optional Changes

The following changes are specific to my game but you might also find them useful:

Skipping Level Selection

After making these changes in router.dart, pressing play should display your own game screen.

final router = GoRouter(
  routes: [
    GoRoute(
      // ...
      routes: [
        GoRoute(
          path: 'play',
          pageBuilder: (context, state) => buildPageTransition<void>(
            key: const ValueKey('play'),
            // color: context.watch<Palette>().backgroundLevelSelection.color,
            color: context.watch<Palette>().backgroundPlaySession.color,
            // child: const LevelSelectionScreen(
            //   key: Key('level selection'),
            // ),
            child: MyGameScreen(level: gameLevels[0]),
          ),
          // routes: [
          //   GoRoute(
          //     path: 'session/:level',
          //     pageBuilder: (context, state) {
          //       final levelNumber = int.parse(state.pathParameters['level']!);
          //       final level = gameLevels[levelNumber - 1];
          //       return buildPageTransition<void>(
          //         key: const ValueKey('level'),
          //         color: context.watch<Palette>().backgroundPlaySession.color,
          //         // child: EndlessRunnerGameScreen(level: level),
          //         child: MyGameScreen(level: level),
          //       );
          //     },
          //   ),
          ],
        ),
        // ...
      ],
    ),
  ],
);

Switching to Portrait Orientation

In main.dart:

void main() async {
  // ...
  // await Flame.device.setLandscape();
  await Flame.device.setPortrait();
  // ...
}

Then in my_game.dart, swap the camera's resolution width and height.

// camera: CameraComponent.withFixedResolution(width: 720, height: 1600)
camera: CameraComponent.withFixedResolution(width: 1600, height: 720)

Next Steps

As said in Flutter's endless runner template,

Focus on making your core gameplay fun first.

Now we can proceed making our own core game by updating the three classes that we just created. We can start adding our own components, visual effects, and custom menus using the template as reference; just as what we did with the initial classes in this tutorial.

Happy coding!


🐵
For more content, please subscribe!