Scene-Dash v2

Scene-Dash v2: the combat sample

Scene-Dash is an ECS-driven gameplay architecture for flutter_scene. It gives Flutter games a structured runtime for gameplay state, system orchestration, custom and frame-driven schedules, resources/DI, events and observers, state machines, entity lifecycles, and headless testing.

This is the pure-Dart core: the ECS runtime and TestGame, no Flutter dependency. The scene binding, widgets and SceneGame.boot shown below live in scene_dash_v2, which re-exports this package.

  • Entities and components sparse sets, queries, deferred structural changes, lifecycle hooks
  • Systems plain functions, ordered by sets and run conditions
  • Schedules startup, update, fixed step, custom run conditions, plus custom schedules you run yourself
  • Resources shared services and state in the world, injected into systems
  • Events and observers decoupled communication between systems
  • States and machines game states with enter/exit behavior and scoped entities and state machines
  • Routines a reusable sequencer for gameplay with an ordered flow: wave directors, objectives, encounters, tutorials
  • Input buttons, axes, and buffered presses as resources; your app writes, systems read
  • Time frame and fixed clocks, timers, schedule-aware dt
  • Diagnostics entity names, debug descriptions, system timings
  • Headless testing run systems, schedules, and whole features with no rendering

World-reactive widgets

A widget selects one value out of the world and rebuilds only when that value changes:

EntityBuilder<Health, double>(
  entity: player,
  select: (h) => h.current,                 // compared once per frame
  builder: (context, hp) => HealthBar(hp),  // runs only when it changed
  absent: const RespawnCountdown(),         // entity dead / component gone
)

Same frame tick, same select-and-compare:

WorldBuilder<int>(select: (w) => w.query<Health>(require: const [Enemy]).count(),
    builder: (ctx, n) => Text('$n enemies'))     // any world-derived value

GameStateBuilder<GameStatus>(builder: (ctx, s) => switch (s) { ... })
                                                 // a subtree per game state

WorldEventListener<EnemyKilled>(onEvent: (ctx, e) => shakeScore(ctx),
    child: const ScorePanel())                   // world events into UI

The rest of the widget layer: .matching resolves the entity through the world, .pulse drives transient feedback, every: throttles a heavy select, GameScope reaches the game from any context.

A complete game in one file

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_scene/scene.dart';
import 'package:scene_dash_v2/scene_dash_v2.dart';
import 'package:vector_math/vector_math.dart' show Vector3;

Future<void> main() async {
  final game = await SceneGame.boot(features: [installCubes]);

  runApp(
    GameScope(                       // provides the game to the subtree
      game: game,
      child: MaterialApp(
        home: Scaffold(
          body: SceneView(           // flutter_scene widget; not wrapped
            game.scene,
            cameraBuilder: _camera,
            onTick: game.onTick,     // forwards frame ticks to the game
          ),
        ),
      ),
    ),
  );
}

Camera _camera(Duration elapsed) =>
    PerspectiveCamera(position: Vector3(0, 3, -6), target: Vector3.zero());

void installCubes(GameBuilder game) {          // a feature: a plain function
  game
    ..addSystem(Schedules.startup, spawnCube, writes: {Orbit, SceneTransform})
    ..addSystem(Schedules.update, orbitCubes, writes: {Orbit, SceneTransform});
}

void spawnCube(World world) => world.spawn(cubeBundle());

void orbitCubes(World world) {                 // a system: a plain function
  world.query2<Orbit, SceneTransform>().each((entity, orbit, transform) {
    orbit.phase += orbit.speed * world.dt;     // dt is schedule-aware
    transform
      ..x = orbit.radius * cos(orbit.phase)
      ..z = orbit.radius * sin(orbit.phase);
  });
}

final class Orbit {                            // a component: a plain class
  final double radius;
  final double speed;
  double phase;
  Orbit({required this.radius, required this.speed, this.phase = 0});
}

List<Object> cubeBundle() => [       // a bundle: a function → the spawn list
  Orbit(radius: 2, speed: 1),
  SceneTransform.zero(),
  NodeRef(Node(mesh: Mesh(CuboidGeometry(Vector3.all(0.8)), UnlitMaterial()))),
];

Hot reload applies edits to system bodies; there is no build step.

Quick start

flutter channel master          # flutter_scene needs Flutter GPU
flutter pub get                 # resolve the workspace (repo root)
cd examples/combat_sample
flutter run --enable-flutter-gpu

Reference

docs/reference.md

docs/concept.md for the architecture, docs/integration.md for the flutter_scene bridge.

Packages and examples

Path Purpose
packages/scene_dash_v2_core Pure-Dart ECS runtime, authoring surface, headless TestGame.
packages/scene_dash_v2 flutter_scene integration: SceneGame.boot, mounting, transform sync, physics bridge, debug draw, widget layer. Re-exports core, so one import covers both.
packages/scene_dash_inspector Optional debug overlay: live entities, resources, system timings, event channels. Read-only, polled at 4 Hz.
examples/scene_game Complete game: Rapier physics, one feature per folder.
examples/headless_example The core without Flutter.
examples/scene_benchmark On-device render benchmark: static vs mount-only vs ECS vs instanced.
examples/combat_sample Combat slice: KayKit knight against waves of barbarians, lock-on, buyable skills, giants, Rapier ragdolls, authored .fmat materials. Gameplay pinned headless.
benchmarks Query, structural, and record-overhead benchmarks.
examples/scene_game/lib/features/   # feature based structure
├── player/                         #   else you keep beside it (hud/, fx/,
├── projectiles/                    #   common/, main.dart) is your call
├── rocks/
├── collectables/
├── rules/
├── world/
└── decor/

examples/scene_game/lib/features/player/     # every feature, same shape
├── player.dart      # the Feature function: installs this feature's systems
├── data/            # components, bundles, config, resources
├── systems/         # one file per concern, not one file per feature
└── animation/       # optional; clip selection lives with its feature

Libraries

advanced
Internal ECS APIs.
scene_dash_v2_core
Core ECS APIs.