Building a Complete Mini Game Project
Throughout this course you learned how Flutter web and Flame can be used to build browser games from the ground up. You learned about game loops sprites animations controls collision systems optimization deployment multiplayer concepts and publishing.
In this final chapter you will combine everything together into a complete endless runner project.
Endless runner games are one of the best beginner game genres because they are simple fun fast paced and teach many important game development concepts.
In an endless runner the player keeps moving automatically while avoiding obstacles collecting points and surviving as long as possible.
This project will teach you how to structure a real game project from beginning to end.
You will learn how to create the project setup build the player system create obstacles handle collisions add scoring create game over systems optimize performance and finally export the game for the web.
This chapter focuses on building a small but complete game that feels like a real browser project instead of disconnected examples.
The goal is not creating a huge commercial game. The goal is understanding how all systems connect together inside one project.
By the end of this chapter you will understand how to create your own browser games using Flutter and Flame with confidence.
Planning the Endless Runner Game
Before writing code developers should always plan the project carefully. Planning helps organize systems and reduces confusion during development.
Our endless runner game will contain a player character obstacles scoring collision detection jumping controls and a game over screen.
The player automatically runs forward while obstacles move toward the player from the right side of the screen.
The player survives by jumping over obstacles.
Every avoided obstacle increases the score.
When the player collides with an obstacle the game ends.
Endless runners work well because the mechanics are easy to understand but still addictive and replayable.
During planning developers should also organize folders and assets.
A clean structure makes projects easier to maintain.
We will use separate files for the game class player component and obstacle component.
Asset planning is also important.
The game needs player sprites obstacle sprites and a background image.
Developers should keep assets lightweight because browser performance matters greatly.
We also need to think about screen sizes.
The game should work on desktop and mobile devices.
Simple controls improve accessibility for beginners.
We will support keyboard jumping and touch jumping.
Another important planning step is game states.
The game has three main states.
Start state running state and game over state.
Planning game states early makes menu systems easier later.
Good planning helps developers avoid rewriting large systems during development.
assets/
images/
player.png
obstacle.png
background.png
Organizing assets helps keep projects clean and manageable.
lib/
main.dart
game/
runner_game.dart
player.dart
obstacle.dart
Separating files improves readability and maintenance.
Creating the Game Project and Setup
The first development step is creating the Flutter project and installing Flame.
Flutter provides the application structure while Flame provides the game engine systems.
Developers usually create projects using terminal commands.
After creating the project Flame should be added inside pubspec.yaml.
Assets must also be registered correctly so Flutter can load images and sounds.
The main.dart file becomes the entry point of the game.
Instead of showing normal Flutter widgets we launch a Flame game using GameWidget.
The main game class controls updates rendering collisions scoring and object spawning.
Flame games update continuously through the game loop.
The update method runs many times every second which allows movement and animation systems to work smoothly.
Asset loading should happen during onLoad.
Loading assets early prevents missing textures and gameplay interruptions.
Developers should also test the project frequently during development.
Small tests help catch problems early before systems become too complicated.
Browser support is another important setup step.
Flutter web projects should be tested inside Chrome regularly.
Early setup quality helps avoid many future technical problems.
flutter create endless_runner
This command creates the Flutter project.
cd endless_runner
flutter pub add flame
This installs the Flame game engine package.
flutter run -d chrome
This launches the game in Chrome.
name: endless_runner
flutter:
assets:
- assets/images/
Assets must be registered inside pubspec.yaml.
import 'package:flutter/material.dart';
import 'package:flame/game.dart';
import 'game/runner_game.dart';
void main() {
runApp(
GameWidget(
game: RunnerGame(),
),
);
}
This launches the Flame game inside Flutter.
Creating the Player and Movement System
The player is the most important part of the game because every gameplay interaction depends on player movement and controls.
In our endless runner the player remains near the left side of the screen while obstacles move toward them.
The main action is jumping.
The player presses spacebar clicks the mouse or taps the screen to jump over obstacles.
Jumping systems usually use gravity and velocity.
When the player jumps a negative vertical velocity pushes the character upward.
Gravity then pulls the player back downward naturally.
Collision with the ground stops falling.
Smooth jumping is very important because endless runners depend heavily on responsive controls.
Delayed controls make games frustrating immediately.
Flame components help organize the player logic cleanly.
The player component stores position velocity and jump behavior.
Developers should also prevent double jumping unless intentionally designing that mechanic.
Input systems must support multiple platforms because browser games run on phones and desktops.
Animation systems improve player feedback.
Even simple running animations make gameplay feel much more alive.
Collision hitboxes are also attached to the player so the game can detect obstacle impacts correctly.
A well designed player system creates the foundation for enjoyable gameplay.
import 'package:flame/components.dart';
class Player extends SpriteComponent {
double velocityY = 0;
double gravity = 900;
bool isOnGround = true;
Player() : super(size: Vector2(80, 80));
@override
Future<void> onLoad() async {
sprite = await Sprite.load('player.png');
position = Vector2(100, 300);
}
@override
void update(double dt) {
super.update(dt);
velocityY += gravity * dt;
position.y += velocityY * dt;
if (position.y >= 300) {
position.y = 300;
velocityY = 0;
isOnGround = true;
}
}
void jump() {
if (isOnGround) {
velocityY = -500;
isOnGround = false;
}
}
}
This player system supports gravity and jumping.
Creating Obstacles Collision and Scoring
Obstacles create challenge inside endless runners.
Without obstacles the player would simply jump forever without difficulty.
Obstacles move continuously from the right side of the screen toward the player.
When obstacles move off screen they should be removed to save memory and performance.
The game should spawn new obstacles repeatedly at timed intervals.
Random spacing helps gameplay feel less predictable.
Collision detection determines whether the player touched an obstacle.
Flame provides collision systems using hitboxes.
If the player collides with an obstacle the game enters the game over state.
Score systems increase player motivation.
Every avoided obstacle can increase the score by one point.
Endless runners become more exciting when players compete against personal high scores.
Difficulty progression also improves engagement.
Obstacles can gradually move faster over time which increases challenge naturally.
Developers should carefully balance obstacle spacing because impossible jumps frustrate players unfairly.
Visual clarity is important too.
Players should easily recognize dangerous objects quickly.
Clean collision systems scoring and obstacle spawning transform simple movement into a complete gameplay loop.
import 'package:flame/components.dart';
class Obstacle extends SpriteComponent {
double speed = 300;
Obstacle() : super(size: Vector2(60, 60));
@override
Future<void> onLoad() async {
sprite = await Sprite.load('obstacle.png');
position = Vector2(900, 320);
}
@override
void update(double dt) {
super.update(dt);
position.x -= speed * dt;
if (position.x < -100) {
removeFromParent();
}
}
}
Obstacles move left toward the player continuously.
if (player.toRect().overlaps(obstacle.toRect())) {
gameOver = true;
}
This checks collision between player and obstacle.
score += 1;
The score increases after surviving obstacles.
Adding UI Game Over and Optimization
A complete game needs menus score displays and game over systems.
User interface systems help players understand the game state clearly.
The score should remain visible during gameplay.
Players should also see instructions explaining controls before gameplay begins.
The game over screen should display the final score and provide a restart button.
Restart systems are important because endless runners encourage repeated attempts.
Flame can work together with Flutter overlays for menus and interface systems.
Optimization is also critical before release.
Developers should remove unnecessary updates and large assets.
Browser games must remain responsive on weaker devices.
FPS drops can make jumping feel inaccurate.
Developers should preload images before gameplay starts.
Sound effects improve feedback greatly.
Simple jump and collision sounds make gameplay feel more satisfying.
Background music can also increase excitement but audio files should remain compressed for faster loading.
After optimization the project should be tested across different browsers and devices.
Final polish separates prototypes from production ready games.
overlays.add('GameOver');
This displays the game over overlay.
Text(
'Score: $score',
style: const TextStyle(
fontSize: 32,
color: Colors.white,
),
)
This displays the player score.
flutter build web
This creates the final optimized web build.
Full Endless Runner Game Code
The following example combines the major systems together into one simple endless runner project.
This code demonstrates player movement obstacle spawning collision handling scoring and keyboard controls.
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
void main() {
runApp(
GameWidget(
game: RunnerGame(),
),
);
}
class RunnerGame extends FlameGame
with KeyboardEvents, TapDetector {
late Player player;
List<Obstacle> obstacles = [];
double obstacleTimer = 0;
int score = 0;
bool gameOver = false;
@override
Future<void> onLoad() async {
player = Player();
add(player);
}
@override
void update(double dt) {
super.update(dt);
if (gameOver) {
return;
}
obstacleTimer += dt;
if (obstacleTimer > 2) {
obstacleTimer = 0;
final obstacle = Obstacle();
obstacles.add(obstacle);
add(obstacle);
}
for (final obstacle in obstacles) {
if (player.toRect().overlaps(obstacle.toRect())) {
gameOver = true;
}
if (!obstacle.passed && obstacle.position.x < player.position.x) {
obstacle.passed = true;
score++;
}
}
}
@override
KeyEventResult onKeyEvent(
KeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
if (event is KeyDownEvent) {
if (event.logicalKey == LogicalKeyboardKey.space) {
player.jump();
}
}
return KeyEventResult.handled;
}
@override
void onTap() {
player.jump();
}
}
class Player extends SpriteComponent {
double velocityY = 0;
double gravity = 900;
bool isOnGround = true;
Player() : super(size: Vector2(80, 80));
@override
Future<void> onLoad() async {
sprite = await Sprite.load('player.png');
position = Vector2(100, 300);
}
@override
void update(double dt) {
super.update(dt);
velocityY += gravity * dt;
position.y += velocityY * dt;
if (position.y >= 300) {
position.y = 300;
velocityY = 0;
isOnGround = true;
}
}
void jump() {
if (isOnGround) {
velocityY = -500;
isOnGround = false;
}
}
}
class Obstacle extends SpriteComponent {
double speed = 300;
bool passed = false;
Obstacle() : super(size: Vector2(60, 60));
@override
Future<void> onLoad() async {
sprite = await Sprite.load('obstacle.png');
position = Vector2(900, 320);
}
@override
void update(double dt) {
super.update(dt);
position.x -= speed * dt;
if (position.x < -100) {
removeFromParent();
}
}
}
This complete example creates a playable endless runner using Flutter and Flame.
Final Commands and Deployment
After completing development developers should create the final production build and upload the game online.
Release builds are optimized and much faster than debug builds.
Hosting services such as Firebase Hosting Netlify and GitHub Pages can publish Flutter web games publicly.
Developers should always test production builds after deployment because browser behavior sometimes changes between local testing and live hosting.
SEO metadata thumbnails and responsive layouts should also be verified before release.
Publishing a complete game project is a major achievement because it combines programming design optimization and creativity together.
Every game project teaches valuable lessons and improves development skills.
The best way to continue improving is building more games experimenting with new mechanics and learning from player feedback.
flutter clean
flutter pub get
flutter build web
firebase deploy
These commands clean build and deploy the game project online.
Conclusion
Building a complete mini game project helps developers understand how real browser games are structured from beginning to end.
This endless runner combined player movement obstacle spawning collision systems scoring menus optimization and deployment into one connected project.
Flutter and Flame provide powerful tools for building browser games with smooth controls responsive design and cross platform support.
The most important part of learning game development is practice.
Every completed project improves problem solving creativity optimization skills and programming confidence.
Developers can continue expanding this endless runner by adding animations multiplayer systems achievements leaderboards enemy types and mobile support.
Completing this course means you now understand the full workflow of Flutter web game development from creating projects to publishing production ready browser games online.