Flame is a 2D game engine built on top of Flutter. It runs anywhere Flutter runs — including the browser via flutter build web, which outputs a static bundle with a JS loader and your Dart code compiled to JavaScript (using CanvasKit for rendering).
Flutter has a --wasm flag for dart2wasm output, but as of Flutter 3.41 the skwasm assets it loads from Google's CDN are missing for some engine hashes, which breaks the build at runtime. Stick with the default CanvasKit output below until that stabilizes.
Setup
Install Flutter (3.22+). pubspec.yaml only needs flame — dart:js_interop is part of the SDK:
dependencies:
flutter:
sdk: flutter
flame: ^1.18.0
Build for web
flutter pub get
flutter build web --release --no-web-resources-cdn
The output lands in build/web/ with index.html, flutter_bootstrap.js, your compiled app, and a self-contained canvaskit/ directory (~30 MB). Point upload_dir at build/web/.
--no-web-resources-cdn tells Flutter to bundle CanvasKit with the build instead of fetching it from gstatic.com at runtime. The default CDN fetch is faster to ship but has had gaps on some Flutter engine hashes (canvaskit.js / skwasm.js 404s), so self-hosting is the safer default for production.
SDK integration
Flutter for web compiles Dart to JavaScript. Use dart:js_interop and the @JS annotation to bind straight to the Wavedash globals — no dynamic getProperty/callMethod needed:
Calling Wavedash.init() is required. Your game stays hidden behind the Wavedash loading screen until you do. Call it once your game is ready to play.
import 'dart:js_interop';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
@JS('Wavedash.init')
external void _wavedashInit();
@JS('Wavedash.updateLoadProgressZeroToOne')
external void _wavedashUpdateProgress(double p);
void wavedashInit() {
_wavedashUpdateProgress(1.0);
_wavedashInit();
}
class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
// ... load assets, build world ...
wavedashInit();
}
}
void main() {
runApp(GameWidget(game: MyGame()));
}
Call updateLoadProgressZeroToOne(...) with intermediate values while onLoad is fetching assets. init() automatically signals load completion, so call it last.
HTML shell
Flutter's generated build/web/index.html already bootstraps the engine. Wavedash injects window.Wavedash before Flutter mounts, so no extra wiring is needed in the shell.
Flutter manages its own service worker for asset caching. Disable it to avoid clashing with Wavedash's local dev service worker:
<script>
window.flutterConfiguration = {
serviceWorkerSettings: { serviceWorkerVersion: null }
};
</script>
wavedash.toml
game_id = "YOUR_GAME_ID_HERE"
upload_dir = "./build/web"
Other SDK features
Add an @JS binding for each SDK method you need:
@JS('Wavedash.uploadLeaderboardScore')
external void _submitScore(String leaderboardId, int score, bool autoAuth);
@JS('Wavedash.setAchievement')
external void _setAchievement(String id, bool storeNow);
See the SDK reference for the full API.