mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-02 03:15:43 +08:00
feat: Add HasWorldReference mixin (#2746)
Adds a mixin for components similar to `HasAncestor`, `HasParent` and `HasGameReference` but which provides access to the `World` which the component has as an ancestor.
This commit is contained in:
@ -189,6 +189,28 @@ that they will appear in the children list in the same order as they were
|
||||
scheduled for addition.
|
||||
|
||||
|
||||
### Access to the World from a Component
|
||||
|
||||
If a component that has a `World` as an ancestor and requires access to that `World` object, one can
|
||||
use the `HasWorldReference` mixin.
|
||||
|
||||
Example:
|
||||
|
||||
```dart
|
||||
class MyComponent extends Component with HasWorldReference<MyWorld>,
|
||||
TapCallbacks {
|
||||
@override
|
||||
void onTapDown(TapDownEvent info) {
|
||||
// world is of type MyWorld
|
||||
world.add(AnotherComponent());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you try to access `world` from a component that doesn't have a `World`
|
||||
ancestor of the correct type an assertion error will be thrown.
|
||||
|
||||
|
||||
### Ensuring a component has a given parent
|
||||
|
||||
When a component requires to be added to a specific parent type the
|
||||
|
||||
@ -27,6 +27,7 @@ export 'src/components/mixins/has_game_reference.dart' show HasGameReference;
|
||||
export 'src/components/mixins/has_paint.dart';
|
||||
export 'src/components/mixins/has_time_scale.dart';
|
||||
export 'src/components/mixins/has_visibility.dart';
|
||||
export 'src/components/mixins/has_world.dart';
|
||||
export 'src/components/mixins/hoverable.dart';
|
||||
export 'src/components/mixins/keyboard_handler.dart';
|
||||
export 'src/components/mixins/notifier.dart';
|
||||
|
||||
41
packages/flame/lib/src/components/mixins/has_world.dart
Normal file
41
packages/flame/lib/src/components/mixins/has_world.dart
Normal file
@ -0,0 +1,41 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flame/camera.dart';
|
||||
import 'package:flame/src/components/core/component.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/// [HasWorldReference] mixin provides the [world] property, which is the cached
|
||||
/// accessor for the world instance that this component belongs to.
|
||||
///
|
||||
/// The type [T] on the mixin is the type of your world class. This type will be
|
||||
/// the type of the [world] reference, and the mixin will check at runtime that
|
||||
/// the actual type matches the expectation.
|
||||
mixin HasWorldReference<T extends World> on Component {
|
||||
T? _world;
|
||||
|
||||
/// Reference to the [World] instance that this component belongs to.
|
||||
T get world => _world ??= _findWorldAndCheck();
|
||||
|
||||
/// Allows you to set the world instance explicitly.
|
||||
/// This may be useful in tests.
|
||||
@visibleForTesting
|
||||
set world(T? value) => _world = value;
|
||||
|
||||
T? findWorld() {
|
||||
return ancestors(includeSelf: true)
|
||||
.firstWhereOrNull((ancestor) => ancestor is T) as T?;
|
||||
}
|
||||
|
||||
T _findWorldAndCheck() {
|
||||
final world = findWorld();
|
||||
assert(
|
||||
world != null,
|
||||
'Could not find a World instance of type $T',
|
||||
);
|
||||
return world!;
|
||||
}
|
||||
|
||||
@override
|
||||
void onRemove() {
|
||||
_world = null;
|
||||
}
|
||||
}
|
||||
103
packages/flame/test/experimental/has_world_test.dart
Normal file
103
packages/flame/test/experimental/has_world_test.dart
Normal file
@ -0,0 +1,103 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame_test/flame_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
void main() {
|
||||
group('HasWorldReference', () {
|
||||
testWithGame(
|
||||
'component with default HasWorldReference',
|
||||
() => FlameGame(world: _ReferenceWorld()),
|
||||
(game) async {
|
||||
final component1 = _Component<World>();
|
||||
final component2 = _Component<_ReferenceWorld>();
|
||||
game.world.addAll([component1, component2]);
|
||||
expect(component1.world, game.world);
|
||||
expect(component2.world, game.world);
|
||||
},
|
||||
);
|
||||
|
||||
testWithGame<_MyGame>(
|
||||
'component with typed HasWorldReference',
|
||||
_MyGame.new,
|
||||
(game) async {
|
||||
final component = _Component<_ReferenceWorld>();
|
||||
game.world.ensureAdd(component);
|
||||
expect(component.world, game.world);
|
||||
},
|
||||
);
|
||||
|
||||
testWithFlameGame(
|
||||
'world reference accessed too early',
|
||||
(game) async {
|
||||
final component = _Component();
|
||||
expect(
|
||||
() => component.world,
|
||||
failsAssert('Could not find a World instance of type World'),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testWithFlameGame(
|
||||
'game reference of wrong type',
|
||||
(game) async {
|
||||
final component = _Component<_ReferenceWorld>();
|
||||
game.world.add(component);
|
||||
expect(
|
||||
() => component.world,
|
||||
failsAssert(
|
||||
'Could not find a World instance of type _ReferenceWorld',
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testWithFlameGame(
|
||||
'game reference propagates quickly',
|
||||
(game) async {
|
||||
final component1 = _Component()..addToParent(game.world);
|
||||
final component2 = _Component()..addToParent(component1);
|
||||
final component3 = _Component()..addToParent(component2);
|
||||
expect(component3.world, game.world);
|
||||
},
|
||||
);
|
||||
|
||||
testWithGame<_MyGame>('simple test', _MyGame.new, (game) async {
|
||||
final c = _FooComponent();
|
||||
game.world.add(c);
|
||||
c.foo();
|
||||
expect(c.world.calledFoo, isTrue);
|
||||
});
|
||||
|
||||
testWithGame<_MyGame>('gameRef can be mocked', _MyGame.new, (game) async {
|
||||
final component = _BarComponent();
|
||||
await game.world.ensureAdd(component);
|
||||
|
||||
component.world = MockWorld();
|
||||
|
||||
expect(component.world, isA<MockWorld>());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class _ReferenceWorld extends World {
|
||||
bool calledFoo = false;
|
||||
void foo() => calledFoo = true;
|
||||
}
|
||||
|
||||
class _Component<T extends World> extends Component with HasWorldReference<T> {}
|
||||
|
||||
class _MyGame extends FlameGame {
|
||||
_MyGame() : super(world: _ReferenceWorld());
|
||||
}
|
||||
|
||||
class _FooComponent extends Component with HasWorldReference<_ReferenceWorld> {
|
||||
void foo() {
|
||||
world.foo();
|
||||
}
|
||||
}
|
||||
|
||||
class _BarComponent extends Component with HasWorldReference<_ReferenceWorld> {}
|
||||
|
||||
class MockWorld extends Mock implements _ReferenceWorld {}
|
||||
Reference in New Issue
Block a user