mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-01 10:38:17 +08:00
refactor classes in game.dart to separate files
This commit is contained in:
4
doc/examples/particles/.gitignore
vendored
4
doc/examples/particles/.gitignore
vendored
@ -71,3 +71,7 @@
|
|||||||
!**/ios/**/default.pbxuser
|
!**/ios/**/default.pbxuser
|
||||||
!**/ios/**/default.perspectivev3
|
!**/ios/**/default.perspectivev3
|
||||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
|
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
macos
|
||||||
|
test
|
||||||
|
|||||||
@ -217,7 +217,7 @@ class MyGame extends BaseGame {
|
|||||||
return Particle.generate(
|
return Particle.generate(
|
||||||
count: 5,
|
count: 5,
|
||||||
generator: (i) => MovingParticle(
|
generator: (i) => MovingParticle(
|
||||||
curve: Interval(.2, .6, curve: Curves.easeInOutCubic),
|
curve: const Interval(.2, .6, curve: Curves.easeInOutCubic),
|
||||||
to: randomCellOffset() * .5,
|
to: randomCellOffset() * .5,
|
||||||
child: CircleParticle(
|
child: CircleParticle(
|
||||||
radius: 5 + rnd.nextDouble() * 5,
|
radius: 5 + rnd.nextDouble() * 5,
|
||||||
|
|||||||
2
doc/examples/sprites/.gitignore
vendored
2
doc/examples/sprites/.gitignore
vendored
@ -70,7 +70,5 @@
|
|||||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
|
|
||||||
macos
|
macos
|
||||||
|
|
||||||
test
|
test
|
||||||
|
|
||||||
.flutter-plugins-dependencies
|
.flutter-plugins-dependencies
|
||||||
|
|||||||
3
example/.gitignore
vendored
3
example/.gitignore
vendored
@ -71,3 +71,6 @@ build/
|
|||||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
|
|
||||||
.flutter-plugins-dependencies
|
.flutter-plugins-dependencies
|
||||||
|
|
||||||
|
macos
|
||||||
|
test
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import 'dart:ui';
|
|||||||
|
|
||||||
import 'package:flame/box2d/box2d_component.dart';
|
import 'package:flame/box2d/box2d_component.dart';
|
||||||
import 'package:flame/components/component.dart';
|
import 'package:flame/components/component.dart';
|
||||||
import 'package:flame/game.dart';
|
import 'package:flame/game/base_game.dart';
|
||||||
|
|
||||||
class Box2DGame extends BaseGame {
|
class Box2DGame extends BaseGame {
|
||||||
final Box2DComponent box;
|
final Box2DComponent box;
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import 'dart:ui';
|
|||||||
|
|
||||||
import 'package:flame/components/mixins/has_game_ref.dart';
|
import 'package:flame/components/mixins/has_game_ref.dart';
|
||||||
import 'package:flame/components/mixins/tapable.dart';
|
import 'package:flame/components/mixins/tapable.dart';
|
||||||
import 'package:flame/game.dart';
|
import 'package:flame/game/base_game.dart';
|
||||||
import 'package:ordered_set/comparing.dart';
|
import 'package:ordered_set/comparing.dart';
|
||||||
import 'package:ordered_set/ordered_set.dart';
|
import 'package:ordered_set/ordered_set.dart';
|
||||||
|
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
// Keeping compatible with earlier versions of Flame
|
// Keeping compatible with earlier versions of Flame
|
||||||
export './game/game.dart';
|
export './game/game.dart';
|
||||||
|
export './game/base_game.dart';
|
||||||
|
|||||||
194
lib/game/base_game.dart
Normal file
194
lib/game/base_game.dart
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flame/components/composed_component.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/widgets.dart' hide WidgetBuilder;
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:ordered_set/comparing.dart';
|
||||||
|
import 'package:ordered_set/ordered_set.dart';
|
||||||
|
|
||||||
|
import '../components/component.dart';
|
||||||
|
import '../components/mixins/has_game_ref.dart';
|
||||||
|
import '../components/mixins/tapable.dart';
|
||||||
|
import '../position.dart';
|
||||||
|
import '../gestures.dart';
|
||||||
|
import 'game.dart';
|
||||||
|
|
||||||
|
|
||||||
|
/// This is a more complete and opinionated implementation of Game.
|
||||||
|
///
|
||||||
|
/// It still needs to be subclasses to add your game logic, but the [update], [render] and [resize] methods have default implementations.
|
||||||
|
/// This is the recommended structure to use for most games.
|
||||||
|
/// It is based on the Component system.
|
||||||
|
abstract class BaseGame extends Game with TapDetector {
|
||||||
|
/// The list of components to be updated and rendered by the base game.
|
||||||
|
OrderedSet<Component> components =
|
||||||
|
OrderedSet(Comparing.on((c) => c.priority()));
|
||||||
|
|
||||||
|
/// Components added by the [addLater] method
|
||||||
|
final List<Component> _addLater = [];
|
||||||
|
|
||||||
|
/// Current screen size, updated every resize via the [resize] method hook
|
||||||
|
Size size;
|
||||||
|
|
||||||
|
/// Camera position; every non-HUD component is translated so that the camera position is the top-left corner of the screen.
|
||||||
|
Position camera = Position.empty();
|
||||||
|
|
||||||
|
/// List of deltas used in debug mode to calculate FPS
|
||||||
|
final List<double> _dts = [];
|
||||||
|
|
||||||
|
Iterable<Tapable> get _tapableComponents =>
|
||||||
|
components.where((c) => c is Tapable).cast();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTapCancel() {
|
||||||
|
_tapableComponents.forEach((c) => c.handleTapCancel());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTapDown(TapDownDetails details) {
|
||||||
|
_tapableComponents.forEach((c) => c.handleTapDown(details));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTapUp(TapUpDetails details) {
|
||||||
|
_tapableComponents.forEach((c) => c.handleTapUp(details));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This method is called for every component added, both via [add] and [addLater] methods.
|
||||||
|
///
|
||||||
|
/// You can use this to setup your mixins, pre-calculate stuff on every component, or anything you desire.
|
||||||
|
/// By default, this calls the first time resize for every component, so don't forget to call super.preAdd when overriding.
|
||||||
|
@mustCallSuper
|
||||||
|
void preAdd(Component c) {
|
||||||
|
if (debugMode() && c is PositionComponent) {
|
||||||
|
c.debugMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first time resize
|
||||||
|
if (size != null) {
|
||||||
|
c.resize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c is HasGameRef) {
|
||||||
|
(c as HasGameRef).gameRef = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c is ComposedComponent) {
|
||||||
|
c.components.forEach(preAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
c.onMount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new component to the components list.
|
||||||
|
///
|
||||||
|
/// Also calls [preAdd], witch in turn sets the current size on the component (because the resize hook won't be called until a new resize happens).
|
||||||
|
void add(Component c) {
|
||||||
|
preAdd(c);
|
||||||
|
components.add(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers a component to be added on the components on the next tick.
|
||||||
|
///
|
||||||
|
/// Use this to add components in places where a concurrent issue with the update method might happen.
|
||||||
|
/// Also calls [preAdd] for the component added, immediately.
|
||||||
|
void addLater(Component c) {
|
||||||
|
preAdd(c);
|
||||||
|
_addLater.add(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This implementation of render basically calls [renderComponent] for every component, making sure the canvas is reset for each one.
|
||||||
|
///
|
||||||
|
/// You can override it further to add more custom behaviour.
|
||||||
|
/// Beware of however you are rendering components if not using this; you must be careful to save and restore the canvas to avoid components messing up with each other.
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
canvas.save();
|
||||||
|
components.forEach((comp) => renderComponent(canvas, comp));
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This renders a single component obeying BaseGame rules.
|
||||||
|
///
|
||||||
|
/// It translates the camera unless hud, call the render method and restore the canvas.
|
||||||
|
/// This makes sure the canvas is not messed up by one component and all components render independently.
|
||||||
|
void renderComponent(Canvas canvas, Component c) {
|
||||||
|
if (!c.loaded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!c.isHud()) {
|
||||||
|
canvas.translate(-camera.x, -camera.y);
|
||||||
|
}
|
||||||
|
c.render(canvas);
|
||||||
|
canvas.restore();
|
||||||
|
canvas.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This implementation of update updates every component in the list.
|
||||||
|
///
|
||||||
|
/// It also actually adds the components that were added by the [addLater] method, and remove those that are marked for destruction via the [Component.destroy] method.
|
||||||
|
/// You can override it further to add more custom behaviour.
|
||||||
|
@override
|
||||||
|
void update(double t) {
|
||||||
|
components.addAll(_addLater);
|
||||||
|
_addLater.clear();
|
||||||
|
|
||||||
|
components.forEach((c) => c.update(t));
|
||||||
|
components.removeWhere((c) => c.destroy()).forEach((c) => c.onDestroy());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This implementation of resize passes the resize call along to every component in the list, enabling each one to make their decisions as how to handle the resize.
|
||||||
|
///
|
||||||
|
/// It also updates the [size] field of the class to be used by later added components and other methods.
|
||||||
|
/// You can override it further to add more custom behaviour, but you should seriously consider calling the super implementation as well.
|
||||||
|
@override
|
||||||
|
@mustCallSuper
|
||||||
|
void resize(Size size) {
|
||||||
|
this.size = size;
|
||||||
|
components.forEach((c) => c.resize(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this [Game] is in debug mode or not.
|
||||||
|
///
|
||||||
|
/// Returns `false` by default. Override to use the debug mode.
|
||||||
|
/// In debug mode, the [recordDt] method actually records every `dt` for statistics.
|
||||||
|
/// Then, you can use the [fps] method to check the game FPS.
|
||||||
|
/// You can also use this value to enable other debug behaviors for your game, like bounding box rendering, for instance.
|
||||||
|
bool debugMode() => false;
|
||||||
|
|
||||||
|
/// This is a hook that comes from the RenderBox to allow recording of render times and statistics.
|
||||||
|
@override
|
||||||
|
void recordDt(double dt) {
|
||||||
|
if (debugMode()) {
|
||||||
|
_dts.add(dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the average FPS for the last [average] measures.
|
||||||
|
///
|
||||||
|
/// The values are only saved if in debug mode (override [debugMode] to use this).
|
||||||
|
/// Selects the last [average] dts, averages then, and returns the inverse value.
|
||||||
|
/// So it's technically updates per second, but the relation between updates and renders is 1:1.
|
||||||
|
/// Returns 0 if empty.
|
||||||
|
double fps([int average = 1]) {
|
||||||
|
final List<double> dts = _dts.sublist(math.max(0, _dts.length - average));
|
||||||
|
if (dts.isEmpty) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
final double dtSum = dts.reduce((s, t) => s + t);
|
||||||
|
final double averageDt = dtSum / average;
|
||||||
|
return 1 / averageDt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current time in seconds with microseconds precision.
|
||||||
|
///
|
||||||
|
/// This is compatible with the `dt` value used in the [update] method.
|
||||||
|
double currentTime() {
|
||||||
|
return DateTime.now().microsecondsSinceEpoch.toDouble() /
|
||||||
|
Duration.microsecondsPerSecond;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
38
lib/game/embedded_game_widget.dart
Normal file
38
lib/game/embedded_game_widget.dart
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/widgets.dart' hide WidgetBuilder;
|
||||||
|
|
||||||
|
import '../position.dart';
|
||||||
|
|
||||||
|
import 'game_render_box.dart';
|
||||||
|
import 'game.dart';
|
||||||
|
|
||||||
|
/// This a widget to embed a game inside the Widget tree. You can use it in pair with [SimpleGame] or any other more complex [Game], as desired.
|
||||||
|
///
|
||||||
|
/// It handles for you positioning, size constraints and other factors that arise when your game is embedded within the component tree.
|
||||||
|
/// Provided it with a [Game] instance for your game and the optional size of the widget.
|
||||||
|
/// Creating this without a fixed size might mess up how other components are rendered with relation to this one in the tree.
|
||||||
|
/// You can bind Gesture Recognizers immediately around this to add controls to your widgets, with easy coordinate conversions.
|
||||||
|
class EmbeddedGameWidget extends LeafRenderObjectWidget {
|
||||||
|
final Game game;
|
||||||
|
final Position size;
|
||||||
|
|
||||||
|
EmbeddedGameWidget(this.game, {this.size});
|
||||||
|
|
||||||
|
@override
|
||||||
|
RenderBox createRenderObject(BuildContext context) {
|
||||||
|
return RenderConstrainedBox(
|
||||||
|
child: GameRenderBox(context, game),
|
||||||
|
additionalConstraints:
|
||||||
|
BoxConstraints.expand(width: size?.x, height: size?.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateRenderObject(
|
||||||
|
BuildContext context, RenderConstrainedBox renderBox) {
|
||||||
|
renderBox
|
||||||
|
..child = GameRenderBox(context, game)
|
||||||
|
..additionalConstraints =
|
||||||
|
BoxConstraints.expand(width: size?.x, height: size?.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,25 +1,14 @@
|
|||||||
import 'dart:math' as math;
|
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flame/components/composed_component.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/widgets.dart' hide WidgetBuilder;
|
import 'package:flutter/widgets.dart' hide WidgetBuilder;
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:ordered_set/comparing.dart';
|
|
||||||
import 'package:ordered_set/ordered_set.dart';
|
|
||||||
|
|
||||||
import '../components/component.dart';
|
|
||||||
import '../components/mixins/has_game_ref.dart';
|
|
||||||
import '../components/mixins/tapable.dart';
|
|
||||||
import '../position.dart';
|
|
||||||
import '../gestures.dart';
|
|
||||||
import '../keyboard.dart';
|
import '../keyboard.dart';
|
||||||
|
|
||||||
import 'game_loop.dart';
|
|
||||||
import 'widget_builder.dart';
|
import 'widget_builder.dart';
|
||||||
|
|
||||||
/// Represents a generic game.
|
/// Represents a generic game.
|
||||||
@ -55,7 +44,7 @@ abstract class Game {
|
|||||||
void lifecycleStateChange(AppLifecycleState state) {}
|
void lifecycleStateChange(AppLifecycleState state) {}
|
||||||
|
|
||||||
/// Used for debugging
|
/// Used for debugging
|
||||||
void _recordDt(double dt) {}
|
void recordDt(double dt) {}
|
||||||
|
|
||||||
/// Returns the game widget. Put this in your structure to start rendering and updating the game.
|
/// Returns the game widget. Put this in your structure to start rendering and updating the game.
|
||||||
/// You can add it directly to the runApp method or inside your widget structure (if you use vanilla screens and widgets).
|
/// You can add it directly to the runApp method or inside your widget structure (if you use vanilla screens and widgets).
|
||||||
@ -95,13 +84,13 @@ abstract class Game {
|
|||||||
bool runOnCreation = true;
|
bool runOnCreation = true;
|
||||||
|
|
||||||
/// Pauses the engine game loop execution
|
/// Pauses the engine game loop execution
|
||||||
void pauseEngine() => _pauseEngineFn?.call();
|
void pauseEngine() => pauseEngineFn?.call();
|
||||||
|
|
||||||
/// Resumes the engine game loop execution
|
/// Resumes the engine game loop execution
|
||||||
void resumeEngine() => _resumeEngineFn?.call();
|
void resumeEngine() => resumeEngineFn?.call();
|
||||||
|
|
||||||
VoidCallback _pauseEngineFn;
|
VoidCallback pauseEngineFn;
|
||||||
VoidCallback _resumeEngineFn;
|
VoidCallback resumeEngineFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
class OverlayWidget {
|
class OverlayWidget {
|
||||||
@ -127,289 +116,4 @@ mixin HasWidgetsOverlay on Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is a more complete and opinionated implementation of Game.
|
|
||||||
///
|
|
||||||
/// It still needs to be subclasses to add your game logic, but the [update], [render] and [resize] methods have default implementations.
|
|
||||||
/// This is the recommended structure to use for most games.
|
|
||||||
/// It is based on the Component system.
|
|
||||||
abstract class BaseGame extends Game with TapDetector {
|
|
||||||
/// The list of components to be updated and rendered by the base game.
|
|
||||||
OrderedSet<Component> components =
|
|
||||||
OrderedSet(Comparing.on((c) => c.priority()));
|
|
||||||
|
|
||||||
/// Components added by the [addLater] method
|
|
||||||
final List<Component> _addLater = [];
|
|
||||||
|
|
||||||
/// Current screen size, updated every resize via the [resize] method hook
|
|
||||||
Size size;
|
|
||||||
|
|
||||||
/// Camera position; every non-HUD component is translated so that the camera position is the top-left corner of the screen.
|
|
||||||
Position camera = Position.empty();
|
|
||||||
|
|
||||||
/// List of deltas used in debug mode to calculate FPS
|
|
||||||
final List<double> _dts = [];
|
|
||||||
|
|
||||||
Iterable<Tapable> get _tapableComponents =>
|
|
||||||
components.where((c) => c is Tapable).cast();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onTapCancel() {
|
|
||||||
_tapableComponents.forEach((c) => c.handleTapCancel());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onTapDown(TapDownDetails details) {
|
|
||||||
_tapableComponents.forEach((c) => c.handleTapDown(details));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onTapUp(TapUpDetails details) {
|
|
||||||
_tapableComponents.forEach((c) => c.handleTapUp(details));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This method is called for every component added, both via [add] and [addLater] methods.
|
|
||||||
///
|
|
||||||
/// You can use this to setup your mixins, pre-calculate stuff on every component, or anything you desire.
|
|
||||||
/// By default, this calls the first time resize for every component, so don't forget to call super.preAdd when overriding.
|
|
||||||
@mustCallSuper
|
|
||||||
void preAdd(Component c) {
|
|
||||||
if (debugMode() && c is PositionComponent) {
|
|
||||||
c.debugMode = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// first time resize
|
|
||||||
if (size != null) {
|
|
||||||
c.resize(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c is HasGameRef) {
|
|
||||||
(c as HasGameRef).gameRef = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c is ComposedComponent) {
|
|
||||||
c.components.forEach(preAdd);
|
|
||||||
}
|
|
||||||
|
|
||||||
c.onMount();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a new component to the components list.
|
|
||||||
///
|
|
||||||
/// Also calls [preAdd], witch in turn sets the current size on the component (because the resize hook won't be called until a new resize happens).
|
|
||||||
void add(Component c) {
|
|
||||||
preAdd(c);
|
|
||||||
components.add(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Registers a component to be added on the components on the next tick.
|
|
||||||
///
|
|
||||||
/// Use this to add components in places where a concurrent issue with the update method might happen.
|
|
||||||
/// Also calls [preAdd] for the component added, immediately.
|
|
||||||
void addLater(Component c) {
|
|
||||||
preAdd(c);
|
|
||||||
_addLater.add(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This implementation of render basically calls [renderComponent] for every component, making sure the canvas is reset for each one.
|
|
||||||
///
|
|
||||||
/// You can override it further to add more custom behaviour.
|
|
||||||
/// Beware of however you are rendering components if not using this; you must be careful to save and restore the canvas to avoid components messing up with each other.
|
|
||||||
@override
|
|
||||||
void render(Canvas canvas) {
|
|
||||||
canvas.save();
|
|
||||||
components.forEach((comp) => renderComponent(canvas, comp));
|
|
||||||
canvas.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This renders a single component obeying BaseGame rules.
|
|
||||||
///
|
|
||||||
/// It translates the camera unless hud, call the render method and restore the canvas.
|
|
||||||
/// This makes sure the canvas is not messed up by one component and all components render independently.
|
|
||||||
void renderComponent(Canvas canvas, Component c) {
|
|
||||||
if (!c.loaded()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!c.isHud()) {
|
|
||||||
canvas.translate(-camera.x, -camera.y);
|
|
||||||
}
|
|
||||||
c.render(canvas);
|
|
||||||
canvas.restore();
|
|
||||||
canvas.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This implementation of update updates every component in the list.
|
|
||||||
///
|
|
||||||
/// It also actually adds the components that were added by the [addLater] method, and remove those that are marked for destruction via the [Component.destroy] method.
|
|
||||||
/// You can override it further to add more custom behaviour.
|
|
||||||
@override
|
|
||||||
void update(double t) {
|
|
||||||
components.addAll(_addLater);
|
|
||||||
_addLater.clear();
|
|
||||||
|
|
||||||
components.forEach((c) => c.update(t));
|
|
||||||
components.removeWhere((c) => c.destroy()).forEach((c) => c.onDestroy());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This implementation of resize passes the resize call along to every component in the list, enabling each one to make their decisions as how to handle the resize.
|
|
||||||
///
|
|
||||||
/// It also updates the [size] field of the class to be used by later added components and other methods.
|
|
||||||
/// You can override it further to add more custom behaviour, but you should seriously consider calling the super implementation as well.
|
|
||||||
@override
|
|
||||||
@mustCallSuper
|
|
||||||
void resize(Size size) {
|
|
||||||
this.size = size;
|
|
||||||
components.forEach((c) => c.resize(size));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns whether this [Game] is in debug mode or not.
|
|
||||||
///
|
|
||||||
/// Returns `false` by default. Override to use the debug mode.
|
|
||||||
/// In debug mode, the [_recordDt] method actually records every `dt` for statistics.
|
|
||||||
/// Then, you can use the [fps] method to check the game FPS.
|
|
||||||
/// You can also use this value to enable other debug behaviors for your game, like bounding box rendering, for instance.
|
|
||||||
bool debugMode() => false;
|
|
||||||
|
|
||||||
/// This is a hook that comes from the RenderBox to allow recording of render times and statistics.
|
|
||||||
@override
|
|
||||||
void _recordDt(double dt) {
|
|
||||||
if (debugMode()) {
|
|
||||||
_dts.add(dt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the average FPS for the last [average] measures.
|
|
||||||
///
|
|
||||||
/// The values are only saved if in debug mode (override [debugMode] to use this).
|
|
||||||
/// Selects the last [average] dts, averages then, and returns the inverse value.
|
|
||||||
/// So it's technically updates per second, but the relation between updates and renders is 1:1.
|
|
||||||
/// Returns 0 if empty.
|
|
||||||
double fps([int average = 1]) {
|
|
||||||
final List<double> dts = _dts.sublist(math.max(0, _dts.length - average));
|
|
||||||
if (dts.isEmpty) {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
final double dtSum = dts.reduce((s, t) => s + t);
|
|
||||||
final double averageDt = dtSum / average;
|
|
||||||
return 1 / averageDt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the current time in seconds with microseconds precision.
|
|
||||||
///
|
|
||||||
/// This is compatible with the `dt` value used in the [update] method.
|
|
||||||
double currentTime() {
|
|
||||||
return DateTime.now().microsecondsSinceEpoch.toDouble() /
|
|
||||||
Duration.microsecondsPerSecond;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is a helper implementation of a [BaseGame] designed to allow to easily create a game with a single component.
|
|
||||||
///
|
|
||||||
/// This is useful to add sprites, animations and other Flame components "directly" to your non-game Flutter widget tree, when combined with [EmbeddedGameWidget].
|
|
||||||
class SimpleGame extends BaseGame {
|
|
||||||
SimpleGame(Component c) {
|
|
||||||
add(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This a widget to embed a game inside the Widget tree. You can use it in pair with [SimpleGame] or any other more complex [Game], as desired.
|
|
||||||
///
|
|
||||||
/// It handles for you positioning, size constraints and other factors that arise when your game is embedded within the component tree.
|
|
||||||
/// Provided it with a [Game] instance for your game and the optional size of the widget.
|
|
||||||
/// Creating this without a fixed size might mess up how other components are rendered with relation to this one in the tree.
|
|
||||||
/// You can bind Gesture Recognizers immediately around this to add controls to your widgets, with easy coordinate conversions.
|
|
||||||
class EmbeddedGameWidget extends LeafRenderObjectWidget {
|
|
||||||
final Game game;
|
|
||||||
final Position size;
|
|
||||||
|
|
||||||
EmbeddedGameWidget(this.game, {this.size});
|
|
||||||
|
|
||||||
@override
|
|
||||||
RenderBox createRenderObject(BuildContext context) {
|
|
||||||
return RenderConstrainedBox(
|
|
||||||
child: GameRenderBox(context, game),
|
|
||||||
additionalConstraints:
|
|
||||||
BoxConstraints.expand(width: size?.x, height: size?.y));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void updateRenderObject(
|
|
||||||
BuildContext context, RenderConstrainedBox renderBox) {
|
|
||||||
renderBox
|
|
||||||
..child = GameRenderBox(context, game)
|
|
||||||
..additionalConstraints =
|
|
||||||
BoxConstraints.expand(width: size?.x, height: size?.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class GameRenderBox extends RenderBox with WidgetsBindingObserver {
|
|
||||||
BuildContext context;
|
|
||||||
Game game;
|
|
||||||
GameLoop gameLoop;
|
|
||||||
|
|
||||||
GameRenderBox(this.context, this.game) {
|
|
||||||
gameLoop = GameLoop(gameLoopCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get sizedByParent => true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void performResize() {
|
|
||||||
super.performResize();
|
|
||||||
game.resize(constraints.biggest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void attach(PipelineOwner owner) {
|
|
||||||
super.attach(owner);
|
|
||||||
game.onAttach();
|
|
||||||
|
|
||||||
game._pauseEngineFn = gameLoop.pause;
|
|
||||||
game._resumeEngineFn = gameLoop.resume;
|
|
||||||
|
|
||||||
if (game.runOnCreation) {
|
|
||||||
gameLoop.scheduleTick();
|
|
||||||
}
|
|
||||||
|
|
||||||
_bindLifecycleListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void detach() {
|
|
||||||
super.detach();
|
|
||||||
game.onDetach();
|
|
||||||
gameLoop.unscheduleTick();
|
|
||||||
_unbindLifecycleListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
void gameLoopCallback(double dt) {
|
|
||||||
if (!attached) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
game._recordDt(dt);
|
|
||||||
game.update(dt);
|
|
||||||
markNeedsPaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(PaintingContext context, Offset offset) {
|
|
||||||
context.canvas.save();
|
|
||||||
context.canvas.translate(
|
|
||||||
game.builder.offset.dx + offset.dx, game.builder.offset.dy + offset.dy);
|
|
||||||
game.render(context.canvas);
|
|
||||||
context.canvas.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _bindLifecycleListener() {
|
|
||||||
WidgetsBinding.instance.addObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _unbindLifecycleListener() {
|
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
||||||
game.lifecycleStateChange(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
82
lib/game/game_render_box.dart
Normal file
82
lib/game/game_render_box.dart
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:flutter/widgets.dart' hide WidgetBuilder;
|
||||||
|
|
||||||
|
import 'game_loop.dart';
|
||||||
|
import 'game.dart';
|
||||||
|
|
||||||
|
class GameRenderBox extends RenderBox with WidgetsBindingObserver {
|
||||||
|
BuildContext context;
|
||||||
|
Game game;
|
||||||
|
GameLoop gameLoop;
|
||||||
|
|
||||||
|
GameRenderBox(this.context, this.game) {
|
||||||
|
gameLoop = GameLoop(gameLoopCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get sizedByParent => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void performResize() {
|
||||||
|
super.performResize();
|
||||||
|
game.resize(constraints.biggest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void attach(PipelineOwner owner) {
|
||||||
|
super.attach(owner);
|
||||||
|
game.onAttach();
|
||||||
|
|
||||||
|
game.pauseEngineFn = gameLoop.pause;
|
||||||
|
game.resumeEngineFn = gameLoop.resume;
|
||||||
|
|
||||||
|
if (game.runOnCreation) {
|
||||||
|
gameLoop.scheduleTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
_bindLifecycleListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void detach() {
|
||||||
|
super.detach();
|
||||||
|
game.onDetach();
|
||||||
|
gameLoop.unscheduleTick();
|
||||||
|
_unbindLifecycleListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
void gameLoopCallback(double dt) {
|
||||||
|
if (!attached) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
game.recordDt(dt);
|
||||||
|
game.update(dt);
|
||||||
|
markNeedsPaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(PaintingContext context, Offset offset) {
|
||||||
|
context.canvas.save();
|
||||||
|
context.canvas.translate(
|
||||||
|
game.builder.offset.dx + offset.dx, game.builder.offset.dy + offset.dy);
|
||||||
|
game.render(context.canvas);
|
||||||
|
context.canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _bindLifecycleListener() {
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _unbindLifecycleListener() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
game.lifecycleStateChange(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
lib/game/simple_game.dart
Normal file
12
lib/game/simple_game.dart
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import 'package:flame/components/component.dart';
|
||||||
|
|
||||||
|
import 'base_game.dart';
|
||||||
|
|
||||||
|
/// This is a helper implementation of a [BaseGame] designed to allow to easily create a game with a single component.
|
||||||
|
///
|
||||||
|
/// This is useful to add sprites, animations and other Flame components "directly" to your non-game Flutter widget tree, when combined with [EmbeddedGameWidget].
|
||||||
|
class SimpleGame extends BaseGame {
|
||||||
|
SimpleGame(Component c) {
|
||||||
|
add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import '../gestures.dart';
|
import '../gestures.dart';
|
||||||
|
import 'embedded_game_widget.dart';
|
||||||
import 'game.dart';
|
import 'game.dart';
|
||||||
|
|
||||||
class WidgetBuilder {
|
class WidgetBuilder {
|
||||||
@ -9,70 +10,53 @@ class WidgetBuilder {
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
// Taps
|
// Taps
|
||||||
onTap: game is TapDetector ? () => (game as TapDetector).onTap() : null,
|
onTap: game is TapDetector ? () => (game as TapDetector).onTap() : null,
|
||||||
onTapCancel: game is TapDetector
|
onTapCancel: game is TapDetector ? () => (game as TapDetector).onTapCancel() : null,
|
||||||
? () => (game as TapDetector).onTapCancel()
|
onTapDown:
|
||||||
: null,
|
game is TapDetector ? (TapDownDetails d) => (game as TapDetector).onTapDown(d) : null,
|
||||||
onTapDown: game is TapDetector
|
onTapUp: game is TapDetector ? (TapUpDetails d) => (game as TapDetector).onTapUp(d) : null,
|
||||||
? (TapDownDetails d) => (game as TapDetector).onTapDown(d)
|
|
||||||
: null,
|
|
||||||
onTapUp: game is TapDetector
|
|
||||||
? (TapUpDetails d) => (game as TapDetector).onTapUp(d)
|
|
||||||
: null,
|
|
||||||
|
|
||||||
// Secondary taps
|
// Secondary taps
|
||||||
onSecondaryTapDown: game is SecondaryTapDetector
|
onSecondaryTapDown: game is SecondaryTapDetector
|
||||||
? (TapDownDetails d) =>
|
? (TapDownDetails d) => (game as SecondaryTapDetector).onSecondaryTapDown(d)
|
||||||
(game as SecondaryTapDetector).onSecondaryTapDown(d)
|
|
||||||
: null,
|
: null,
|
||||||
onSecondaryTapUp: game is SecondaryTapDetector
|
onSecondaryTapUp: game is SecondaryTapDetector
|
||||||
? (TapUpDetails d) =>
|
? (TapUpDetails d) => (game as SecondaryTapDetector).onSecondaryTapUp(d)
|
||||||
(game as SecondaryTapDetector).onSecondaryTapUp(d)
|
|
||||||
: null,
|
: null,
|
||||||
onSecondaryTapCancel: game is SecondaryTapDetector
|
onSecondaryTapCancel: game is SecondaryTapDetector
|
||||||
? () => (game as SecondaryTapDetector).onSecondaryTapCancel()
|
? () => (game as SecondaryTapDetector).onSecondaryTapCancel()
|
||||||
: null,
|
: null,
|
||||||
|
|
||||||
// Double tap
|
// Double tap
|
||||||
onDoubleTap: game is DoubleTapDetector
|
onDoubleTap:
|
||||||
? () => (game as DoubleTapDetector).onDoubleTap()
|
game is DoubleTapDetector ? () => (game as DoubleTapDetector).onDoubleTap() : null,
|
||||||
: null,
|
|
||||||
|
|
||||||
// Long presses
|
// Long presses
|
||||||
onLongPress: game is LongPressDetector
|
onLongPress:
|
||||||
? () => (game as LongPressDetector).onLongPress()
|
game is LongPressDetector ? () => (game as LongPressDetector).onLongPress() : null,
|
||||||
: null,
|
|
||||||
onLongPressStart: game is LongPressDetector
|
onLongPressStart: game is LongPressDetector
|
||||||
? (LongPressStartDetails d) =>
|
? (LongPressStartDetails d) => (game as LongPressDetector).onLongPressStart(d)
|
||||||
(game as LongPressDetector).onLongPressStart(d)
|
|
||||||
: null,
|
: null,
|
||||||
onLongPressMoveUpdate: game is LongPressDetector
|
onLongPressMoveUpdate: game is LongPressDetector
|
||||||
? (LongPressMoveUpdateDetails d) =>
|
? (LongPressMoveUpdateDetails d) => (game as LongPressDetector).onLongPressMoveUpdate(d)
|
||||||
(game as LongPressDetector).onLongPressMoveUpdate(d)
|
|
||||||
: null,
|
|
||||||
onLongPressUp: game is LongPressDetector
|
|
||||||
? () => (game as LongPressDetector).onLongPressUp()
|
|
||||||
: null,
|
: null,
|
||||||
|
onLongPressUp:
|
||||||
|
game is LongPressDetector ? () => (game as LongPressDetector).onLongPressUp() : null,
|
||||||
onLongPressEnd: game is LongPressDetector
|
onLongPressEnd: game is LongPressDetector
|
||||||
? (LongPressEndDetails d) =>
|
? (LongPressEndDetails d) => (game as LongPressDetector).onLongPressEnd(d)
|
||||||
(game as LongPressDetector).onLongPressEnd(d)
|
|
||||||
: null,
|
: null,
|
||||||
|
|
||||||
// Vertical drag
|
// Vertical drag
|
||||||
onVerticalDragDown: game is VerticalDragDetector
|
onVerticalDragDown: game is VerticalDragDetector
|
||||||
? (DragDownDetails d) =>
|
? (DragDownDetails d) => (game as VerticalDragDetector).onVerticalDragDown(d)
|
||||||
(game as VerticalDragDetector).onVerticalDragDown(d)
|
|
||||||
: null,
|
: null,
|
||||||
onVerticalDragStart: game is VerticalDragDetector
|
onVerticalDragStart: game is VerticalDragDetector
|
||||||
? (DragStartDetails d) =>
|
? (DragStartDetails d) => (game as VerticalDragDetector).onVerticalDragStart(d)
|
||||||
(game as VerticalDragDetector).onVerticalDragStart(d)
|
|
||||||
: null,
|
: null,
|
||||||
onVerticalDragUpdate: game is VerticalDragDetector
|
onVerticalDragUpdate: game is VerticalDragDetector
|
||||||
? (DragUpdateDetails d) =>
|
? (DragUpdateDetails d) => (game as VerticalDragDetector).onVerticalDragUpdate(d)
|
||||||
(game as VerticalDragDetector).onVerticalDragUpdate(d)
|
|
||||||
: null,
|
: null,
|
||||||
onVerticalDragEnd: game is VerticalDragDetector
|
onVerticalDragEnd: game is VerticalDragDetector
|
||||||
? (DragEndDetails d) =>
|
? (DragEndDetails d) => (game as VerticalDragDetector).onVerticalDragEnd(d)
|
||||||
(game as VerticalDragDetector).onVerticalDragEnd(d)
|
|
||||||
: null,
|
: null,
|
||||||
onVerticalDragCancel: game is VerticalDragDetector
|
onVerticalDragCancel: game is VerticalDragDetector
|
||||||
? () => (game as VerticalDragDetector).onVerticalDragCancel()
|
? () => (game as VerticalDragDetector).onVerticalDragCancel()
|
||||||
@ -80,20 +64,16 @@ class WidgetBuilder {
|
|||||||
|
|
||||||
// Horizontal drag
|
// Horizontal drag
|
||||||
onHorizontalDragDown: game is HorizontalDragDetector
|
onHorizontalDragDown: game is HorizontalDragDetector
|
||||||
? (DragDownDetails d) =>
|
? (DragDownDetails d) => (game as HorizontalDragDetector).onHorizontalDragDown(d)
|
||||||
(game as HorizontalDragDetector).onHorizontalDragDown(d)
|
|
||||||
: null,
|
: null,
|
||||||
onHorizontalDragStart: game is HorizontalDragDetector
|
onHorizontalDragStart: game is HorizontalDragDetector
|
||||||
? (DragStartDetails d) =>
|
? (DragStartDetails d) => (game as HorizontalDragDetector).onHorizontalDragStart(d)
|
||||||
(game as HorizontalDragDetector).onHorizontalDragStart(d)
|
|
||||||
: null,
|
: null,
|
||||||
onHorizontalDragUpdate: game is HorizontalDragDetector
|
onHorizontalDragUpdate: game is HorizontalDragDetector
|
||||||
? (DragUpdateDetails d) =>
|
? (DragUpdateDetails d) => (game as HorizontalDragDetector).onHorizontalDragUpdate(d)
|
||||||
(game as HorizontalDragDetector).onHorizontalDragUpdate(d)
|
|
||||||
: null,
|
: null,
|
||||||
onHorizontalDragEnd: game is HorizontalDragDetector
|
onHorizontalDragEnd: game is HorizontalDragDetector
|
||||||
? (DragEndDetails d) =>
|
? (DragEndDetails d) => (game as HorizontalDragDetector).onHorizontalDragEnd(d)
|
||||||
(game as HorizontalDragDetector).onHorizontalDragEnd(d)
|
|
||||||
: null,
|
: null,
|
||||||
onHorizontalDragCancel: game is HorizontalDragDetector
|
onHorizontalDragCancel: game is HorizontalDragDetector
|
||||||
? () => (game as HorizontalDragDetector).onHorizontalDragCancel()
|
? () => (game as HorizontalDragDetector).onHorizontalDragCancel()
|
||||||
@ -101,38 +81,29 @@ class WidgetBuilder {
|
|||||||
|
|
||||||
// Force presses
|
// Force presses
|
||||||
onForcePressStart: game is ForcePressDetector
|
onForcePressStart: game is ForcePressDetector
|
||||||
? (ForcePressDetails d) =>
|
? (ForcePressDetails d) => (game as ForcePressDetector).onForcePressStart(d)
|
||||||
(game as ForcePressDetector).onForcePressStart(d)
|
|
||||||
: null,
|
: null,
|
||||||
onForcePressPeak: game is ForcePressDetector
|
onForcePressPeak: game is ForcePressDetector
|
||||||
? (ForcePressDetails d) =>
|
? (ForcePressDetails d) => (game as ForcePressDetector).onForcePressPeak(d)
|
||||||
(game as ForcePressDetector).onForcePressPeak(d)
|
|
||||||
: null,
|
: null,
|
||||||
onForcePressUpdate: game is ForcePressDetector
|
onForcePressUpdate: game is ForcePressDetector
|
||||||
? (ForcePressDetails d) =>
|
? (ForcePressDetails d) => (game as ForcePressDetector).onForcePressUpdate(d)
|
||||||
(game as ForcePressDetector).onForcePressUpdate(d)
|
|
||||||
: null,
|
: null,
|
||||||
onForcePressEnd: game is ForcePressDetector
|
onForcePressEnd: game is ForcePressDetector
|
||||||
? (ForcePressDetails d) =>
|
? (ForcePressDetails d) => (game as ForcePressDetector).onForcePressEnd(d)
|
||||||
(game as ForcePressDetector).onForcePressEnd(d)
|
|
||||||
: null,
|
: null,
|
||||||
|
|
||||||
// Pan
|
// Pan
|
||||||
onPanDown: game is PanDetector
|
onPanDown:
|
||||||
? (DragDownDetails d) => (game as PanDetector).onPanDown(d)
|
game is PanDetector ? (DragDownDetails d) => (game as PanDetector).onPanDown(d) : null,
|
||||||
: null,
|
onPanStart:
|
||||||
onPanStart: game is PanDetector
|
game is PanDetector ? (DragStartDetails d) => (game as PanDetector).onPanStart(d) : null,
|
||||||
? (DragStartDetails d) => (game as PanDetector).onPanStart(d)
|
|
||||||
: null,
|
|
||||||
onPanUpdate: game is PanDetector
|
onPanUpdate: game is PanDetector
|
||||||
? (DragUpdateDetails d) => (game as PanDetector).onPanUpdate(d)
|
? (DragUpdateDetails d) => (game as PanDetector).onPanUpdate(d)
|
||||||
: null,
|
: null,
|
||||||
onPanEnd: game is PanDetector
|
onPanEnd:
|
||||||
? (DragEndDetails d) => (game as PanDetector).onPanEnd(d)
|
game is PanDetector ? (DragEndDetails d) => (game as PanDetector).onPanEnd(d) : null,
|
||||||
: null,
|
onPanCancel: game is PanDetector ? () => (game as PanDetector).onPanCancel() : null,
|
||||||
onPanCancel: game is PanDetector
|
|
||||||
? () => (game as PanDetector).onPanCancel()
|
|
||||||
: null,
|
|
||||||
|
|
||||||
// Scales
|
// Scales
|
||||||
onScaleStart: game is ScaleDetector
|
onScaleStart: game is ScaleDetector
|
||||||
@ -147,9 +118,7 @@ class WidgetBuilder {
|
|||||||
|
|
||||||
child: Container(
|
child: Container(
|
||||||
color: game.backgroundColor(),
|
color: game.backgroundColor(),
|
||||||
child: Directionality(
|
child: Directionality(textDirection: TextDirection.ltr, child: EmbeddedGameWidget(game))),
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
child: EmbeddedGameWidget(game))),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,8 +154,7 @@ class _OverlayGameWidgetState extends State<OverlayGameWidget> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Directionality(
|
return Directionality(
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
child:
|
child: Stack(children: [widget.gameChild, ..._overlays.values.toList()]));
|
||||||
Stack(children: [widget.gameChild, ..._overlays.values.toList()]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +165,6 @@ class OverlayWidgetBuilder extends WidgetBuilder {
|
|||||||
Widget build(Game game) {
|
Widget build(Game game) {
|
||||||
final container = super.build(game);
|
final container = super.build(game);
|
||||||
|
|
||||||
return OverlayGameWidget(
|
return OverlayGameWidget(gameChild: container, game: game, key: UniqueKey());
|
||||||
gameChild: container, game: game, key: UniqueKey());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,9 +6,10 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter/widgets.dart' as widgets;
|
import 'package:flutter/widgets.dart' as widgets;
|
||||||
|
|
||||||
import 'animation.dart';
|
import 'animation.dart';
|
||||||
|
import 'game/embedded_game_widget.dart';
|
||||||
|
import 'game/simple_game.dart';
|
||||||
import 'sprite.dart';
|
import 'sprite.dart';
|
||||||
import 'components/animation_component.dart';
|
import 'components/animation_component.dart';
|
||||||
import 'game.dart';
|
|
||||||
import 'position.dart';
|
import 'position.dart';
|
||||||
|
|
||||||
/// Some utilities that did not fit anywhere else.
|
/// Some utilities that did not fit anywhere else.
|
||||||
|
|||||||
@ -4,10 +4,10 @@ import 'package:flame/components/composed_component.dart';
|
|||||||
import 'package:flame/components/mixins/has_game_ref.dart';
|
import 'package:flame/components/mixins/has_game_ref.dart';
|
||||||
import 'package:flame/components/mixins/resizable.dart';
|
import 'package:flame/components/mixins/resizable.dart';
|
||||||
import 'package:flame/components/mixins/tapable.dart';
|
import 'package:flame/components/mixins/tapable.dart';
|
||||||
|
import 'package:flame/game/base_game.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'package:flame/game.dart';
|
|
||||||
import 'package:flame/components/component.dart';
|
import 'package:flame/components/component.dart';
|
||||||
|
|
||||||
class MyGame extends BaseGame {}
|
class MyGame extends BaseGame {}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flame/components/mixins/has_game_ref.dart';
|
import 'package:flame/components/mixins/has_game_ref.dart';
|
||||||
|
import 'package:flame/game/base_game.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'package:flame/game.dart';
|
|
||||||
import 'package:flame/components/component.dart';
|
import 'package:flame/components/component.dart';
|
||||||
|
|
||||||
class MyGame extends BaseGame {
|
class MyGame extends BaseGame {
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flame/game/base_game.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'package:flame/game.dart';
|
|
||||||
import 'package:flame/components/component.dart';
|
import 'package:flame/components/component.dart';
|
||||||
import 'package:flame/components/mixins/resizable.dart';
|
import 'package:flame/components/mixins/resizable.dart';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user