Merge branch 'v1.0.0' into erick.assets-refactor-2

This commit is contained in:
Erick Zanardo
2020-09-29 23:54:11 -03:00
125 changed files with 1213 additions and 820 deletions

View File

@ -3,6 +3,16 @@
## 1.0.0
- Move all box2d related code and examples to the flame_box2d repo
- Rename Animation to SpriteAnimation
- Create extension of Vector2 and unify all tuples to use that class
- Remove Position class in favor of new Vector2 extension
- Remove Box2D as a dependency
## 0.27.0
- Improved the accuracy of the `FPSCounter` by using Flutter's internal frame timings.
- Adding MouseMovementDetector
- Adding ScrollDetector
- Fixes BGM error
- Adding Isometric Tile Maps
## 0.26.0
- Improving Flame image auto cache

View File

@ -19,7 +19,7 @@ The goal of this project is to provide a complete set of out-of-the-way solution
Currently it provides you with:
- a game loop
- a component/object system
- bundles a physics engine (box2d)
- a physics engine (box2d, available through [flame_box2d](https://github.com/flame-engine/flame_box2d))
- audio support
- effects and particles
- gesture and input support

View File

@ -18,7 +18,7 @@ Put the pub package as your dependency by dropping the following in your `pubspe
```yaml
dependencies:
flame: ^0.26.0
flame: ^0.27.0
```
And start using it!

View File

@ -118,18 +118,18 @@ Let's you have your `build` method in one of your pages; pretty normal Flutter s
}
```
Note that it could be any component, however complex, inside your widgets tree. Note also that I have omitted the "magic" of the equation here. How is it that we create a component for an animation? Very basically (more details in the flame tutorial), Flame provides components, one of which is the `SpriteAnimationComponent` that receives a `SpriteAnimation` object describing the animation and does exactly what we want. All components live inside a `Game` instance, that can add custom logic relating to the game loop. For our case, we just want to create a simple, empty game and add a single `AnimationComponent` with a simple `Animation` inside. So Flame provides a helper to do that, the `Flame.util.animationAsWidget` method. It takes the size of the object as a Flame's `Position` instance (a generic class to represent a pair of doubles), and also takes in an `Animation` instance representing our frame list. To use that, let's import both `Flame` and the `Animation` class. However, since Flutter adds it's own animation classes, let's use an alias in order to not mess up the names. Therefore, add these imports to the top of the file:
Note that it could be any component, however complex, inside your widgets tree. Note also that I have omitted the "magic" of the equation here. How do we create a component for an animation? Very basically (more details in the flame tutorial), Flame provides components, one of which is the `SpriteAnimationComponent` that receives a `SpriteAnimation` object describing the animation and does exactly what we want. All components live inside a `Game` instance, that can add custom logic relating to the game loop. For our case, we just want to create a simple, empty game and add a single `AnimationComponent` with a simple `Animation` inside. So Flame provides a helper to do that, the `Flame.util.animationAsWidget` method. It takes the size of the object as a Flame's `Position` instance (a generic class to represent a pair of doubles), and also takes in an `Animation` instance representing our frame list. To use that, let's import both `Flame` and the `Animation` class. However, since Flutter adds it's own animation classes, let's use an alias in order to not mess up the names. Therefore, add these imports to the top of the file:
```dart
import 'package:flame/sprite_animation.dart'; // imports the SpriteAnimation class
import 'package:flame/flame.dart'; // imports the Flame helper class
import 'package:flame/position.dart'; // imports the Position class
import 'package:flame/vector2.dart'; // imports the Vector2 class
```
How we do the magic then? Just add the following to your widget tree:
How do we do the magic then? Just add the following to your widget tree:
```dart
Flame.util.animationAsWidget(Position(WIDTH, HEIGHT), SpriteAnimation.sequenced('minotaur.png', AMOUNT, textureWidth: FRAME_WIDTH))
Flame.util.animationAsWidget(Vector2(WIDTH, HEIGHT), SpriteAnimation.sequenced('minotaur.png', AMOUNT, textureWidth: FRAME_WIDTH))
```
The first parameter's `WIDTH` and `HEIGHT` are the actual size of the widget on the screen. This does not need to match the sprite size, as Flame will scale it for you. You might, however, wanna keep the aspect, so things don't get distorted. In your case, the minotaur asset is a row of 96x96 pixels, so squares, therefore we can scale keeping `WIDTH/HEIGHT = 1`. We will choose the size as 256 px. The `sequenced` constructor is a helper that easily creates the animation assuming equal-sized frames in a row, in order. You can configure the start x, start y, texture width and height, but those will default gracefully to (0,0) and the actual width and height of the file. You can create your animation passing in the frame list, each frame with a different step time and sprite (source rectangle).

View File

@ -1,11 +1,15 @@
# Box2D
Although Flame does not implement Box2d itself, it bundles a forked port of the Java Box2d to Dart by Google.
Although Flame does not fully implement Box2d itself, we maintain a forked and refactored port of the Java Box2d to Dart.
The source of the bundled box2d on Flame can be found [here](https://github.com/flame-engine/box2d.dart).
The source of the box2d version that Flame can use can be found [here](https://github.com/flame-engine/box2d.dart).
To use Box2D in your game you should add flame_box2d to your pubspec.yaml, which can be seen in the examples.
It can be a bit confusing at first that there are two projects, [box2d.dart](https://github.com/flame-engine/box2d.dart) and [flame_box2d](https://github.com/flame-engine/flame_box2d), so I'll explain them quickly here:
* box2d.dart is the actual physics engine, but that one can be used by any dart project, it doesn't have to have any connection to Flame or Flutter.
* flame_box2d is the bridge between flame and box2d, it contains classes that will make your life a lot easier when using it with flame (for example Box2DGame).
The examples of how to use it can be found [here](https://github.com/flame-engine/flame_box2d/blob/master/examples/), but they are not full game implementations.
## Box2DGame (BaseGame extension)

View File

@ -239,6 +239,26 @@ Currently we have a very basic implementation of a Tiled component. This API use
An example of how to use the API can be found [here](/doc/examples/tiled).
# Isometric Tile Map Component
This component allows you to render an isometric map based on a cartesian matrix of blocks and an isometric tileset.
A simple example on how to use it:
```dart
// creates a tileset, the block ids are automatically assigned sequentially starting at 0, from left to right and then top to bottom.
final tileset = await IsometricTileset.load('tileset.png', 32);
// each element is a block id, -1 means nothing
final matrix = [[0, 1, 0], [1, 0, 0], [1, 1, 1]];
add(IsometricTileMapComponent(tileset, matrix));
```
It also provides methods for converting coordinates so you can handle clicks, hovers, render entities on top of tiles, add a selector, etc.
A more in-depth example can be found [here](/doc/examples/isometric).
![An example of a isometric map with selector](images/isometric.png)
# Nine Tile Box Component
A Nine Tile Box is a rectangle drawn using a grid sprite.

View File

@ -4,7 +4,7 @@ import 'package:flame/flame.dart';
import 'package:flame/sprite_animation.dart';
import 'package:flame/sprite.dart';
import 'package:flame/spritesheet.dart';
import 'package:flame/position.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flame/widgets/animation_widget.dart';
import 'package:flame/widgets/sprite_widget.dart';
import 'package:flutter/material.dart';
@ -51,7 +51,7 @@ class MyHomePage extends StatefulWidget {
}
class _MyHomePageState extends State<MyHomePage> {
Position _position = Position(256.0, 256.0);
Vector2 _position = Vector2(256.0, 256.0);
@override
void initState() {
@ -62,7 +62,7 @@ class _MyHomePageState extends State<MyHomePage> {
void changePosition() async {
await Future.delayed(const Duration(seconds: 1));
setState(() {
_position = Position(10 + _position.x, 10 + _position.y);
_position = Vector2(10 + _position.x, 10 + _position.y);
});
}

View File

@ -4,7 +4,7 @@ description: A sample Flame project to showcase the SpriteAnimationWidget widget
version: 0.1.0
environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -2,6 +2,7 @@ import 'package:flame/gestures.dart';
import 'package:flutter/gestures.dart';
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flame/sprite_animation.dart';
import 'package:flame/components/sprite_animation_component.dart';
import 'package:flutter/material.dart' hide Image;
@ -10,7 +11,7 @@ import 'dart:ui';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final Size size = await Flame.util.initialDimensions();
final Vector2 size = await Flame.util.initialDimensions();
final game = MyGame(size);
runApp(game.widget);
}
@ -67,7 +68,7 @@ class MyGame extends BaseGame with TapDetector {
s,
animation.reversed(),
);
reversedAnimationComponent.x = size.width / 2;
reversedAnimationComponent.x = size.x / 2;
reversedAnimationComponent.y = s;
add(animationComponent2);
@ -79,7 +80,7 @@ class MyGame extends BaseGame with TapDetector {
addAnimation(evt.globalPosition.dx, evt.globalPosition.dy);
}
MyGame(Size screenSize) {
MyGame(Vector2 screenSize) {
size = screenSize;
}
}

View File

@ -4,7 +4,7 @@ description: Flame sample game showcasing animations features
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -1,17 +1,18 @@
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'package:flame/sprite_animation.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flame/components/sprite_animation_component.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final Size size = await Flame.util.initialDimensions();
final Vector2 size = await Flame.util.initialDimensions();
runApp(MyGame(size).widget);
}
class MyGame extends BaseGame {
MyGame(Size screenSize) {
MyGame(Vector2 screenSize) {
size = screenSize;
}
@ -22,11 +23,8 @@ class MyGame extends BaseGame {
image,
'chopper.json',
);
final animationComponent = SpriteAnimationComponent(200, 200, animation);
animationComponent.x = (size.width / 2) - 100;
animationComponent.y = (size.height / 2) - 100;
final animationComponent = SpriteAnimationComponent(200, 200, animation)
..setPosition(size / 2 - Vector2(100, 100));
add(animationComponent);
}

View File

@ -4,7 +4,7 @@ description: Flame sample for using Aseprite animations
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -5,14 +5,14 @@ import 'package:flame/audio_pool.dart';
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'package:flame/palette.dart';
import 'package:flame/position.dart';
import 'package:flame/text_config.dart';
import 'package:flame/gestures.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final Size size = await Flame.util.initialDimensions();
final Vector2 size = await Flame.util.initialDimensions();
final MyGame game = MyGame(size);
runApp(game.widget);
}
@ -23,14 +23,14 @@ AudioPool pool = AudioPool('laser.mp3');
class MyGame extends BaseGame with TapDetector {
static final black = BasicPalette.black.paint;
MyGame(Size screenSize) {
MyGame(Vector2 screenSize) {
size = screenSize;
}
@override
void render(Canvas canvas) {
canvas.drawRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height), black);
final p = Position.fromSize(size).div(2);
canvas.drawRect(size.toOriginRect(), black);
final p = size / 2;
regular.render(canvas, 'hit me!', p, anchor: Anchor.center);
super.render(canvas);
}

View File

@ -3,7 +3,7 @@ description: Flame example application showcasing the audiopool class
version: 0.1.0
environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -1,9 +1,11 @@
#!/bin/bash -xe
rm -rf */build
rm -rf */android
rm -rf */ios
rm -rf */web
rm -rf */macos
rm -rf */test
rm -rf */.dart_tool
shopt -s globstar
rm -rf **/build
rm -rf **/android
rm -rf **/ios
rm -rf **/web
rm -rf **/macos
rm -rf **/test
rm -rf **/.dart_tool

View File

@ -1,6 +1,6 @@
import 'package:flame/game.dart';
import 'package:flame/flame.dart';
import 'package:flame/position.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flame/components/sprite_component.dart';
import 'package:flame/components/mixins/resizable.dart';
import 'package:flame/text_config.dart';
@ -36,14 +36,14 @@ class AndroidComponent extends SpriteComponent with Resizable {
final rect = toRect();
if ((x <= 0 && xDirection == -1) ||
(rect.right >= size.width && xDirection == 1)) {
(rect.right >= size.x && xDirection == 1)) {
xDirection = xDirection * -1;
}
y += yDirection * SPEED * dt;
if ((y <= 0 && yDirection == -1) ||
(rect.bottom >= size.height && yDirection == 1)) {
(rect.bottom >= size.y && yDirection == 1)) {
yDirection = yDirection * -1;
}
}
@ -86,7 +86,7 @@ class MyGame extends BaseGame {
super.render(canvas);
if (debugMode()) {
fpsTextConfig.render(canvas, fps(120).toString(), Position(0, 50));
fpsTextConfig.render(canvas, fps(120).toString(), Vector2(0, 50));
}
}
}

View File

@ -4,7 +4,7 @@ description: Flame sample for using debug features
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -3,7 +3,7 @@ import 'package:flame/effects/move_effect.dart';
import 'package:flame/effects/scale_effect.dart';
import 'package:flame/effects/rotate_effect.dart';
import 'package:flame/gestures.dart';
import 'package:flame/position.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
@ -37,7 +37,7 @@ class MyGame extends BaseGame with TapDetector {
greenSquare.clearEffects();
final move = MoveEffect(
destination: Position(dx, dy),
destination: Vector2(dx, dy),
speed: 250.0,
curve: Curves.linear,
isInfinite: false,
@ -45,7 +45,7 @@ class MyGame extends BaseGame with TapDetector {
);
final scale = ScaleEffect(
size: Size(dx, dy),
size: Vector2(dx, dy),
speed: 250.0,
curve: Curves.linear,
isInfinite: false,

View File

@ -4,7 +4,7 @@ description: Flame sample game showcasing combined effects
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -2,7 +2,7 @@ import 'package:flame/effects/move_effect.dart';
import 'package:flame/effects/scale_effect.dart';
import 'package:flame/effects/rotate_effect.dart';
import 'package:flame/gestures.dart';
import 'package:flame/position.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
@ -44,7 +44,7 @@ class MyGame extends BaseGame with TapDetector {
orangeSquare.clearEffects();
greenSquare.addEffect(MoveEffect(
destination: Position(dx, dy),
destination: Vector2(dx, dy),
speed: 250.0,
curve: Curves.bounceInOut,
isInfinite: true,
@ -52,7 +52,7 @@ class MyGame extends BaseGame with TapDetector {
));
redSquare.addEffect(ScaleEffect(
size: Size(dx, dy),
size: Vector2(dx, dy),
speed: 250.0,
curve: Curves.easeInCubic,
isInfinite: true,

View File

@ -4,7 +4,7 @@ description: Flame sample game showcasing infinite effects
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -3,7 +3,7 @@ import 'package:flame/effects/scale_effect.dart';
import 'package:flame/effects/rotate_effect.dart';
import 'package:flame/effects/sequence_effect.dart';
import 'package:flame/gestures.dart';
import 'package:flame/position.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
@ -34,7 +34,7 @@ class MyGame extends BaseGame with TapDetector {
greenSquare.clearEffects();
final move1 = MoveEffect(
destination: Position(dx, dy),
destination: Vector2(dx, dy),
speed: 250.0,
curve: Curves.bounceInOut,
isInfinite: false,
@ -42,7 +42,7 @@ class MyGame extends BaseGame with TapDetector {
);
final move2 = MoveEffect(
destination: Position(dx, dy + 150),
destination: Vector2(dx, dy + 150),
speed: 150.0,
curve: Curves.easeIn,
isInfinite: false,
@ -50,7 +50,7 @@ class MyGame extends BaseGame with TapDetector {
);
final scale = ScaleEffect(
size: Size(dx, dy),
size: Vector2(dx, dy),
speed: 250.0,
curve: Curves.easeInCubic,
isInfinite: false,

View File

@ -4,7 +4,7 @@ description: Flame sample game showcasing the sequence effect
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flame/game.dart';
import 'package:flame/gestures.dart';
import 'package:flame/effects/effects.dart';
import 'package:flame/position.dart';
import 'package:flame/extensions/offset.dart';
import './square.dart';
@ -17,10 +17,7 @@ class MyGame extends BaseGame with TapDetector {
@override
void onTapUp(details) {
square.addEffect(MoveEffect(
destination: Position(
details.localPosition.dx,
details.localPosition.dy,
),
destination: details.localPosition.toVector2(),
speed: 250.0,
curve: Curves.bounceInOut,
));

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flame/game.dart';
import 'package:flame/gestures.dart';
import 'package:flame/anchor.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flame/effects/effects.dart';
import './square.dart';
@ -23,7 +24,7 @@ class MyGame extends BaseGame with TapDetector {
grow = !grow;
square.addEffect(ScaleEffect(
size: Size(s, s),
size: Vector2(s, s),
speed: 250.0,
curve: Curves.bounceInOut,
));

View File

@ -4,7 +4,7 @@ description: A Flame game showcasing the use of the effects api
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:flame/game.dart';
import 'package:flame/gestures.dart';
import 'package:flame/palette.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flame/extensions/offset.dart';
void main() {
final game = MyGame();
runApp(game.widget);
}
class MyGame extends Game with MouseMovementDetector {
static const SPEED = 200;
Vector2 position = Vector2(0, 0);
Vector2 target;
final Paint _blue = Paint()..color = const Color(0xFF0000FF);
bool _onTarget = false;
@override
void onMouseMove(event) {
target = event.localPosition.toVector2();
}
Rect _toRect() => Rect.fromLTWH(
position.x,
position.y,
50,
50,
);
@override
void render(Canvas canvas) {
canvas.drawRect(
_toRect(),
_onTarget ? _blue : BasicPalette.white.paint,
);
}
@override
void update(double dt) {
if (target != null) {
_onTarget = _toRect().contains(target.toOffset());
if (!_onTarget) {
final dir = (target - position).normalized();
position += dir * (SPEED * dt);
}
}
}
}

View File

@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:flame/game.dart';
import 'package:flame/gestures.dart';
import 'package:flame/palette.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flame/extensions/offset.dart';
void main() {
final game = MyGame();
runApp(game.widget);
}
class MyGame extends Game with ScrollDetector {
static const SPEED = 200;
Vector2 position = Vector2(0, 0);
Vector2 target;
@override
void onScroll(event) {
target = position - event.scrollDelta.toVector2();
}
@override
void render(Canvas canvas) {
canvas.drawRect(
Rect.fromLTWH(
position.x,
position.y,
50,
50,
),
BasicPalette.white.paint,
);
}
@override
void update(double dt) {
if (target != null) {
final dir = (target - position).normalized();
position += dir * (SPEED * dt);
}
}
}

View File

@ -4,7 +4,7 @@ description: A flame game showcasing the use of gestures callbacks
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -4,7 +4,7 @@ description: Flame sample game on using the go flutter desktop framework
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 7fc14a55af64462763d28abfb4e610086c6e0f39
channel: dev
project_type: app

View File

@ -0,0 +1,3 @@
# isometric
A Flame game showcasing how to use the Isometric Tile Map component.

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,86 @@
import 'package:flame/components/sprite_component.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flame/extensions/offset.dart';
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'package:flame/components/isometric_tile_map_component.dart';
import 'package:flame/gestures.dart';
import 'package:flame/sprite.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
const x = 500.0;
const y = 500.0;
const s = 64;
final topLeft = Vector2(x, y);
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final size = await Flame.util.initialDimensions();
final game = MyGame(size);
runApp(game.widget);
}
class Selector extends SpriteComponent {
bool show = false;
Selector(double s)
: super.fromSprite(s, s, Sprite('selector.png', width: 32, height: 32));
@override
void render(Canvas canvas) {
if (!show) {
return;
}
super.render(canvas);
}
}
class MyGame extends BaseGame with MouseMovementDetector {
IsometricTileMapComponent base;
Selector selector;
MyGame(Vector2 size) {
init();
}
void init() async {
final tileset = await IsometricTileset.load('tiles.png', 32);
final matrix = [
[3, 1, 1, 1, 0, 0],
[-1, 1, 2, 1, 0, 0],
[-1, 0, 1, 1, 0, 0],
[-1, 1, 1, 1, 0, 0],
[1, 1, 1, 1, 0, 2],
[1, 3, 3, 3, 0, 2],
];
add(
base = IsometricTileMapComponent(tileset, matrix, destTileSize: s)
..x = x
..y = y,
);
add(selector = Selector(s.toDouble()));
}
@override
void render(Canvas canvas) {
super.render(canvas);
canvas.drawRect(
const Rect.fromLTWH(x - 1, y - 1, 3, 3),
Paint()..color = const Color(0xFFFF00FF),
);
}
@override
void onMouseMove(PointerHoverEvent event) {
if (base == null || selector == null) {
return; // loading
}
final screenPosition = event.position.toVector2();
final block = base.getBlock(screenPosition);
selector.show = base.containsBlock(block);
selector.setPosition(base.getBlockPosition(block) + topLeft);
}
}

View File

@ -0,0 +1,22 @@
name: isometric
description: Example of isometric tilemap using Flame
version: 0.1.0
environment:
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
flame:
path: ../../../
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
assets:
- assets/images/tiles.png
- assets/images/selector.png

View File

@ -5,6 +5,7 @@ import 'package:flame/components/component.dart';
import 'package:flame/components/joystick/joystick_component.dart';
import 'package:flame/components/joystick/joystick_events.dart';
import 'package:flame/palette.dart';
import 'package:flame/extensions/vector2.dart';
class Player extends Component implements JoystickListener {
final _whitePaint = BasicPalette.white.paint;
@ -42,10 +43,10 @@ class Player extends Component implements JoystickListener {
}
@override
void resize(Size size) {
void resize(Vector2 size) {
_rect = Rect.fromLTWH(
(size.width / 2) - 25,
(size.height / 2) - 25,
(size.x / 2) - 25,
(size.y / 2) - 25,
50,
50,
);

View File

@ -4,7 +4,7 @@ description: A flame game showcasing the use of joystick
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -4,7 +4,7 @@ description: Simple Flame project showcasing how to use the Keyboard events
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -2,6 +2,7 @@ import 'package:flame/game.dart';
import 'package:flame/flame.dart';
import 'package:flame/nine_tile_box.dart';
import 'package:flame/sprite.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flutter/material.dart';
@ -14,7 +15,7 @@ void main() async {
}
class MyGame extends Game {
Size size;
Vector2 size;
NineTileBox nineTileBox;
MyGame(this.size);
@ -28,9 +29,9 @@ class MyGame extends Game {
@override
void render(Canvas canvas) {
const length = 300.0;
final x = (size.width - length) / 2;
final y = (size.height - length) / 2;
nineTileBox.draw(canvas, x, y, length, length);
final boxSize = Vector2.all(length);
final position = (size - boxSize) / 2;
nineTileBox.draw(canvas, position, boxSize);
}
@override

View File

@ -4,7 +4,7 @@ description: Flame sample for using the nine_tile_box feature
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -1,6 +1,7 @@
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'package:flame/components/parallax_component.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flutter/material.dart';
void main() async {
@ -21,8 +22,8 @@ class MyGame extends BaseGame {
final parallaxComponent = ParallaxComponent(
images,
baseSpeed: const Offset(20, 0),
layerDelta: const Offset(30, 0),
baseSpeed: Vector2(20, 0),
layerDelta: Vector2(30, 0),
);
add(parallaxComponent);

View File

@ -4,7 +4,7 @@ description: Flame sample game showcasing the parallax features
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -21,7 +21,7 @@ import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'package:flame/time.dart' as flame_time;
import 'package:flame/particle.dart';
import 'package:flame/position.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flame/sprite.dart';
import 'package:flame/spritesheet.dart';
import 'package:flame/text_config.dart';
@ -33,7 +33,7 @@ class MyGame extends BaseGame {
/// Defines dimensions of the sample
/// grid to be displayed on the screen,
/// 5x5 in this particular case
static const gridSize = 5;
static const gridSize = 5.0;
static const steps = 5;
/// Miscellaneous values used
@ -48,17 +48,15 @@ class MyGame extends BaseGame {
/// Defines the lifespan of all the particles in these examples
final sceneDuration = const Duration(seconds: 1);
Offset cellSize;
Offset halfCellSize;
Vector2 cellSize;
Vector2 halfCellSize;
@override
bool recordFps() => true;
MyGame({
Size screenSize,
}) {
MyGame({Vector2 screenSize}) {
size = screenSize;
cellSize = Offset(size.width / gridSize, size.height / gridSize);
cellSize = size / gridSize;
halfCellSize = cellSize * .5;
// Spawn new particles every second
@ -104,17 +102,17 @@ class MyGame extends BaseGame {
// as per defined grid parameters
do {
final particle = particles.removeLast();
final col = particles.length % gridSize;
final row = particles.length ~/ gridSize;
final double col = particles.length % gridSize;
final double row = (particles.length ~/ gridSize).toDouble();
final cellCenter =
cellSize.scale(col.toDouble(), row.toDouble()) + (cellSize * .5);
(cellSize.clone()..multiply(Vector2(col, row))) + (cellSize * .5);
add(
// Bind all the particles to a [Component] update
// lifecycle from the [BaseGame].
TranslatedParticle(
lifespan: 1,
offset: cellCenter,
offset: cellCenter.toOffset(),
child: particle,
).asComponent(),
);
@ -168,10 +166,10 @@ class MyGame extends BaseGame {
return Particle.generate(
count: 5,
generator: (i) {
final currentColumn = (cellSize.dx / 5) * i - halfCellSize.dx;
final currentColumn = (cellSize.x / 5) * i - halfCellSize.x;
return MovingParticle(
from: Offset(currentColumn, -halfCellSize.dy),
to: Offset(currentColumn, halfCellSize.dy),
from: Offset(currentColumn, -halfCellSize.y),
to: Offset(currentColumn, halfCellSize.y),
child: CircleParticle(
radius: 2.0,
paint: Paint()..color = Colors.blue,
@ -240,7 +238,7 @@ class MyGame extends BaseGame {
return ComputedParticle(
renderer: (canvas, particle) => canvas.drawCircle(
Offset.zero,
particle.progress * halfCellSize.dx,
particle.progress * halfCellSize.x,
Paint()
..color = Color.lerp(
Colors.red,
@ -264,7 +262,7 @@ class MyGame extends BaseGame {
canvas.drawCircle(
Offset.zero,
(1 - steppedProgress) * halfCellSize.dx,
(1 - steppedProgress) * halfCellSize.x,
Paint()
..color = Color.lerp(
Colors.red,
@ -300,7 +298,7 @@ class MyGame extends BaseGame {
/// be reused across particles. See example below for more details.
Particle imageParticle() {
return ImageParticle(
size: const Size.square(24),
size: Vector2.all(24),
image: images.fromCache('zap.png'),
);
}
@ -315,8 +313,8 @@ class MyGame extends BaseGame {
const count = 9;
const perLine = 3;
const imageSize = 24.0;
final colWidth = cellSize.dx / perLine;
final rowHeight = cellSize.dy / perLine;
final colWidth = cellSize.x / perLine;
final rowHeight = cellSize.y / perLine;
reusableImageParticle ??= imageParticle();
@ -324,8 +322,8 @@ class MyGame extends BaseGame {
count: count,
generator: (i) => TranslatedParticle(
offset: Offset(
(i % perLine) * colWidth - halfCellSize.dx + imageSize,
(i ~/ perLine) * rowHeight - halfCellSize.dy + imageSize,
(i % perLine) * colWidth - halfCellSize.x + imageSize,
(i ~/ perLine) * rowHeight - halfCellSize.y + imageSize,
),
child: reusableImageParticle),
);
@ -392,7 +390,7 @@ class MyGame extends BaseGame {
Particle spriteParticle() {
return SpriteParticle(
sprite: Sprite(images.fromCache('zap.png')),
size: Position.fromOffset(cellSize * .5),
size: cellSize * .5,
);
}
@ -401,7 +399,7 @@ class MyGame extends BaseGame {
Particle animationParticle() {
return SpriteAnimationParticle(
animation: getBoomAnimation(),
size: Position(128, 128),
size: Vector2(128, 128),
);
}
@ -411,8 +409,8 @@ class MyGame extends BaseGame {
/// which is independent from the parent [Particle].
Particle componentParticle() {
return MovingParticle(
from: -halfCellSize * .2,
to: halfCellSize * .2,
from: (-halfCellSize * .2).toOffset(),
to: (halfCellSize * .2).toOffset(),
curve: SineCurve(),
child: ComponentParticle(component: trafficLight),
);
@ -479,19 +477,22 @@ class MyGame extends BaseGame {
),
);
final cellSizeOffset = cellSize.toOffset();
final halfCellSizeOffset = halfCellSize.toOffset();
return ComposedParticle(children: <Particle>[
rect
.rotating(to: pi / 2)
.moving(to: -cellSize)
.moving(to: -cellSizeOffset)
.scaled(2)
.accelerated(acceleration: halfCellSize * 5)
.translated(halfCellSize),
.accelerated(acceleration: halfCellSizeOffset * 5)
.translated(halfCellSizeOffset),
rect
.rotating(to: -pi)
.moving(to: cellSize.scale(1, -1))
.moving(to: cellSizeOffset.scale(1, -1))
.scaled(2)
.translated(halfCellSize.scale(-1, 1))
.accelerated(acceleration: halfCellSize.scale(-5, 5))
.translated(halfCellSizeOffset.scale(-1, 1))
.accelerated(acceleration: halfCellSizeOffset.scale(-5, 5))
]);
}
@ -503,15 +504,18 @@ class MyGame extends BaseGame {
super.render(canvas);
if (debugMode()) {
fpsTextConfig.render(canvas, '${fps(120).toStringAsFixed(2)}fps',
Position(0, size.height - 24));
fpsTextConfig.render(
canvas, '${fps(120).toStringAsFixed(2)}fps', Vector2(0, size.y - 24));
}
}
/// Returns random [Offset] within a virtual
/// grid cell
Offset randomCellOffset() {
return cellSize.scale(rnd.nextDouble(), rnd.nextDouble()) - halfCellSize;
return Offset(
cellSize.x * rnd.nextDouble() - halfCellSize.x,
cellSize.y * rnd.nextDouble() - halfCellSize.y,
);
}
/// Returns random [Color] from primary swatches
@ -550,6 +554,7 @@ class MyGame extends BaseGame {
Future<BaseGame> loadGame() async {
Flame.initializeWidget();
final gameSize = await Flame.util.initialDimensions();
WidgetsFlutterBinding.ensureInitialized();
return MyGame(screenSize: gameSize);
}

View File

@ -4,7 +4,7 @@ description: Flame sample game showcasing particle effects
version: 1.0.0
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -2,11 +2,12 @@ import 'package:flame/sprite_animation.dart';
import 'package:flame/components/sprite_animation_component.dart';
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final Size size = await Flame.util.initialDimensions();
final Vector2 size = await Flame.util.initialDimensions();
final game = MyGame(size);
runApp(game.widget);
}
@ -46,7 +47,7 @@ class MyGame extends BaseGame {
return ac;
}
MyGame(Size screenSize) {
MyGame(Vector2 screenSize) {
size = screenSize;
}
}

View File

@ -4,7 +4,7 @@ description: Flame sample game showcasing animations features
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -1,16 +1,17 @@
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flame/components/position_component.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final Size size = await Flame.util.initialDimensions();
final Vector2 size = await Flame.util.initialDimensions();
runApp(MyGame(size).widget);
}
class Ball extends PositionComponent {
final Size gameSize;
final Vector2 gameSize;
final paint = Paint()..color = const Color(0xFFFFFFFF);
bool forward = true;
@ -28,9 +29,9 @@ class Ball extends PositionComponent {
super.update(dt);
x += (forward ? 1 : -1) * 100 * dt;
if (x <= 0 || x + width >= gameSize.width) {
if (x <= 0 || x + width >= gameSize.x) {
if (forward) {
x = gameSize.width - width - 1;
x = gameSize.x - width - 1;
} else {
x = 1;
}
@ -42,7 +43,7 @@ class Ball extends PositionComponent {
}
class MyGame extends BaseGame {
MyGame(Size screenSize) {
MyGame(Vector2 screenSize) {
size = screenSize;
Flame.audio.disableLog();
@ -51,7 +52,7 @@ class MyGame extends BaseGame {
add(
Ball(size)
..y = (size.height / 2) - 50
..y = (size.y / 2) - 50
..width = 100
..height = 100,
);

View File

@ -3,7 +3,7 @@ description: Flame example application showcasing the audio features
version: 0.1.0
environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -4,11 +4,12 @@ import 'package:flame/flame.dart';
import 'package:flutter/material.dart';
import 'package:flame/game.dart';
import 'package:flame/sprite_batch.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flame/components/sprite_batch_component.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final Size size = await Flame.util.initialDimensions();
final Vector2 size = await Flame.util.initialDimensions();
final game = MyGame(size);
runApp(game.widget);
}
@ -16,7 +17,7 @@ void main() async {
class MyGame extends BaseGame {
SpriteBatch spriteBatch;
MyGame(Size screenSize) {
MyGame(Vector2 screenSize) {
size = screenSize;
initData();
@ -44,8 +45,8 @@ class MyGame extends BaseGame {
for (int i = 0; i < NUM; ++i) {
final sx = r.nextInt(8) * 128.0;
final sy = r.nextInt(8) * 128.0;
final x = r.nextInt(size.width.toInt()).toDouble();
final y = r.nextInt(size.height ~/ 2).toDouble() + size.height / 2.0;
final x = r.nextInt(size.x.toInt()).toDouble();
final y = r.nextInt(size.y ~/ 2).toDouble() + size.y / 2.0;
spriteBatch.add(
rect: Rect.fromLTWH(sx, sy, 128, 128),
offset: Offset(x - 64, y - 64),

View File

@ -4,7 +4,7 @@ description: Showcasing SpriteBatch features
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -3,17 +3,18 @@ import 'dart:math';
import 'package:flame/components/sprite_component.dart';
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final Size size = await Flame.util.initialDimensions();
final Vector2 size = await Flame.util.initialDimensions();
final game = MyGame(size);
runApp(game.widget);
}
class MyGame extends BaseGame {
MyGame(Size screenSize) {
MyGame(Vector2 screenSize) {
size = screenSize;
}
@ -23,8 +24,8 @@ class MyGame extends BaseGame {
final image = await images.load('test.png');
List.generate(500, (i) => SpriteComponent.square(32, image))
.forEach((sprite) {
sprite.x = r.nextInt(size.width.toInt()).toDouble();
sprite.y = r.nextInt(size.height.toInt()).toDouble();
sprite.x = r.nextInt(size.x.toInt()).toDouble();
sprite.y = r.nextInt(size.y.toInt()).toDouble();
add(sprite);
});
}

View File

@ -4,7 +4,7 @@ description: Flame sample game showcasing rendering 500 sprites
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -4,7 +4,7 @@ description: Flame sample game showcasing spritesheet features
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -7,10 +7,11 @@ import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'package:flame/palette.dart';
import 'package:flame/text_config.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flutter/material.dart';
void main() async {
final Size size = await Flame.util.initialDimensions();
final Vector2 size = await Flame.util.initialDimensions();
runApp(MyGame(size).widget);
}
@ -34,27 +35,25 @@ class MyTextBox extends TextBoxComponent {
}
class MyGame extends BaseGame {
MyGame(Size screenSize) {
MyGame(Vector2 screenSize) {
size = screenSize;
add(TextComponent('Hello, Flame', config: regular)
..anchor = Anchor.topCenter
..x = size.width / 2
..x = size.x / 2
..y = 32.0);
add(TextComponent('center', config: tiny)
..anchor = Anchor.center
..x = size.width / 2
..y = size.height / 2);
..setPosition(size / 2));
add(TextComponent('bottomRight', config: tiny)
..anchor = Anchor.bottomRight
..x = size.width
..y = size.height);
..setPosition(size));
add(MyTextBox(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eget ligula eu lectus lobortis condimentum.',
)
..anchor = Anchor.bottomLeft
..y = size.height);
..y = size.y);
}
}

View File

@ -4,7 +4,7 @@ description: A sample Flame project that renders texts.
version: 0.1.0
environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flame/game.dart';
import 'package:flame/time.dart';
import 'package:flame/text_config.dart';
import 'package:flame/position.dart';
import 'package:flame/gestures.dart';
import 'package:flame/extensions/vector2.dart';
import 'package:flame/components/timer_component.dart';
void main() {
@ -42,7 +42,7 @@ class RenderedTimeComponent extends TimerComponent {
textConfig.render(
canvas,
'Elapsed time: ${timer.current}',
Position(10, 150),
Vector2(10, 150),
);
}
}
@ -90,8 +90,8 @@ class MyGame extends Game with TapDetector {
textConfig.render(
canvas,
'Countdown: ${countdown.current}',
Position(10, 100),
Vector2(10, 100),
);
textConfig.render(canvas, 'Elapsed time: $elapsedSecs', Position(10, 150));
textConfig.render(canvas, 'Elapsed time: $elapsedSecs', Vector2(10, 150));
}
}

View File

@ -4,7 +4,7 @@ description: Example app using Timer class
version: 1.0.0+1
environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -4,7 +4,7 @@ description: A Flutter project showcasing Flame's widgets.
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

View File

@ -4,7 +4,7 @@ description: A Flame game showcasing how to use the WithWidgetsOverlay feature
version: 1.0.0+1
environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:

BIN
doc/images/isometric.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -4,6 +4,8 @@
Inside `package:flame/gestures.dart` you can find a whole set of `mixin`s which can be included on your game class instance to be able to receive touch input events. Below you can see the full list of these `mixin`s and its methods:
## Touch and mouse detectors
```
- TapDetector
- onTap
@ -68,11 +70,19 @@ Inside `package:flame/gestures.dart` you can find a whole set of `mixin`s which
- onReceiveDrag
```
Mouse only events
```
- MouseMovementDetector
- onMouseMove
- ScrollDetector
- onScroll
```
Many of these detectors can conflict with each other. For example, you can't register both Vertical and Horizontal drags, so not all of them can be used together.
It is also not possible to mix advanced detectors (`MultiTouch*`) with basic detectors as they will *always win the gesture arena* and the basic detectors will never be triggered. So for example, you can use both `MultiTouchDragDetector` and `MultiTouchDragDetector` together, but if you try to use `MultiTouchTapDetector` and `PanDetector`, no events will be triggered for the later.
Flame's GestureApi is provided byt Flutter's Gestures Widgets, including [GestureDetector widget](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html) and [RawGestureDetector widget](https://api.flutter.dev/flutter/widgets/RawGestureDetector-class.html), you can also read more about Flutter's gestures [here](https://api.flutter.dev/flutter/gestures/gestures-library.html).
Flame's GestureApi is provided byt Flutter's Gestures Widgets, including [GestureDetector widget](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html), [RawGestureDetector widget](https://api.flutter.dev/flutter/widgets/RawGestureDetector-class.html) and [MouseRegion widget](https://api.flutter.dev/flutter/widgets/MouseRegion-class.html), you can also read more about Flutter's gestures [here](https://api.flutter.dev/flutter/gestures/gestures-library.html).
## Example

View File

@ -226,7 +226,7 @@ game.add(
ParticleComponent(
particle: SpriteParticle(
sprite: Sprite('sprite.png'),
size: Position(64, 64),
size: Vector2(64, 64),
)
)
);

View File

@ -58,9 +58,9 @@ __Countdown example:__
import 'dart:ui';
import 'package:flame/game.dart';
import 'package:flame/position.dart';
import 'package:flame/text_config.dart';
import 'package:flame/time.dart';
import 'package:flame/vector2.dart';
class MyGame extends Game {
final TextConfig textConfig = TextConfig(color: const Color(0xFFFFFFFF));
@ -81,7 +81,7 @@ class MyGame extends Game {
@override
void render(Canvas canvas) {
textConfig.render(canvas, "Countdown: ${countdown.current.toString()}",
Position(10, 100));
Vector2(10, 100));
}
}
@ -93,9 +93,9 @@ __Interval example:__
import 'dart:ui';
import 'package:flame/game.dart';
import 'package:flame/position.dart';
import 'package:flame/text_config.dart';
import 'package:flame/time.dart';
import 'package:flame/vector2.dart';
class MyGame extends Game {
final TextConfig textConfig = TextConfig(color: const Color(0xFFFFFFFF));
@ -117,7 +117,7 @@ class MyGame extends Game {
@override
void render(Canvas canvas) {
textConfig.render(canvas, "Elapsed time: $elapsedSecs", Position(10, 150));
textConfig.render(canvas, "Elapsed time: $elapsedSecs", Vector2(10, 150));
}
}

View File

@ -4,7 +4,7 @@ description: A new Flutter project.
version: 0.1.0
environment:
sdk: ">=2.0.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flame:

View File

@ -1,25 +1,25 @@
import 'dart:ui';
import 'position.dart';
import 'extensions/vector2.dart';
class Anchor {
static const Anchor topLeft = Anchor(Offset(0.0, 0.0));
static const Anchor topCenter = Anchor(Offset(0.5, 0.0));
static const Anchor topRight = Anchor(Offset(1.0, 0.0));
static const Anchor centerLeft = Anchor(Offset(0.0, 0.5));
static const Anchor center = Anchor(Offset(0.5, 0.5));
static const Anchor centerRight = Anchor(Offset(1.0, 0.5));
static const Anchor bottomLeft = Anchor(Offset(0.0, 1.0));
static const Anchor bottomCenter = Anchor(Offset(0.5, 1.0));
static const Anchor bottomRight = Anchor(Offset(1.0, 1.0));
static const Anchor topLeft = Anchor(0.0, 0.0);
static const Anchor topCenter = Anchor(0.5, 0.0);
static const Anchor topRight = Anchor(1.0, 0.0);
static const Anchor centerLeft = Anchor(0.0, 0.5);
static const Anchor center = Anchor(0.5, 0.5);
static const Anchor centerRight = Anchor(1.0, 0.5);
static const Anchor bottomLeft = Anchor(0.0, 1.0);
static const Anchor bottomCenter = Anchor(0.5, 1.0);
static const Anchor bottomRight = Anchor(1.0, 1.0);
final Offset relativePosition;
final double x;
final double y;
const Anchor(this.relativePosition);
Vector2 get relativePosition => Vector2(x, y);
Position translate(Position p, Position size) {
return p.clone().minus(
Position(size.x * relativePosition.dx, size.y * relativePosition.dy),
);
const Anchor(this.x, this.y);
Vector2 translate(Vector2 p, Vector2 size) {
return p - relativePosition
..multiply(size);
}
}

View File

@ -1,9 +1,9 @@
import 'dart:async';
import 'dart:convert' show base64;
import 'dart:typed_data';
import 'dart:ui';
import 'dart:convert' show base64;
import 'package:flame/flame.dart';
import 'flame.dart';
class Images {
final Map<String, _ImageAssetLoader> _loadedFiles = {};

View File

@ -1,6 +1,7 @@
import 'package:flutter/services.dart' show rootBundle;
import 'dart:typed_data';
import 'package:flutter/services.dart' show rootBundle;
/// A class that loads, and cache files
///
/// it automatically looks for files on the assets folder

View File

@ -1,9 +1,10 @@
import 'dart:io';
import 'package:audioplayers/audioplayers.dart';
import 'package:flame/flame.dart';
import 'package:flutter/widgets.dart';
import 'flame.dart';
/// The looping background music class.
///
/// This class helps with looping background music management that reacts to
@ -43,7 +44,7 @@ class Bgm extends WidgetsBindingObserver {
///
/// It is safe to call this function even when a current BGM track is
/// playing.
void play(String filename, {double volume}) async {
Future<void> play(String filename, {double volume}) async {
volume ??= 1;
if (audioPlayer != null && audioPlayer.state != AudioPlayerState.STOPPED) {
@ -51,14 +52,11 @@ class Bgm extends WidgetsBindingObserver {
}
isPlaying = true;
audioPlayer = await Flame.audio.loopLongAudio(
filename,
volume: volume,
);
audioPlayer = await Flame.audio.loopLongAudio(filename, volume: volume);
}
/// Stops the currently playing background music track (if any).
void stop() async {
Future<void> stop() async {
isPlaying = false;
if (audioPlayer != null) {
await audioPlayer.stop();
@ -66,28 +64,28 @@ class Bgm extends WidgetsBindingObserver {
}
/// Resumes the currently played (but resumed) background music.
void resume() {
Future<void> resume() async {
if (audioPlayer != null) {
isPlaying = true;
audioPlayer.resume();
await audioPlayer.resume();
}
}
/// Pauses the background music without unloading or resetting the audio
/// player.
void pause() {
Future<void> pause() async {
if (audioPlayer != null) {
isPlaying = false;
audioPlayer.pause();
await audioPlayer.pause();
}
}
/// Prefetch an audio and store it in the cache.
/// Pre-fetch an audio and store it in the cache.
///
/// Alias of `FlameAudio.load();`.
Future<File> load(String file) => Flame.audio.load(file);
/// Prefetch a list of audios and store them in the cache.
/// Pre-fetch a list of audios and store them in the cache.
///
/// Alias of `FlameAudio.loadAll();`.
Future<List<File>> loadAll(List<String> files) => Flame.audio.loadAll(files);
@ -98,8 +96,8 @@ class Bgm extends WidgetsBindingObserver {
void clear(String file) => Flame.audio.clear(file);
/// Clears all the audios in the cache.
/// Alias of `FlameAudio.clearAll();`.
///
/// Alias of `FlameAudio.clearAll();`.
void clearAll() => Flame.audio.clearAll();
/// Handler for AppLifecycleState changes.
@ -110,13 +108,11 @@ class Bgm extends WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
if (isPlaying &&
audioPlayer != null &&
audioPlayer.state == AudioPlayerState.PAUSED) {
if (isPlaying && audioPlayer?.state == AudioPlayerState.PAUSED) {
audioPlayer.resume();
}
} else {
audioPlayer.pause();
audioPlayer?.pause();
}
}
}

View File

@ -2,6 +2,8 @@ import 'dart:ui';
import 'package:flutter/painting.dart';
import '../extensions/vector2.dart';
/// This represents a Component for your game.
///
/// Components can be bullets flying on the screen, a spaceship or your player's fighter.
@ -22,7 +24,7 @@ abstract class Component {
///
/// It receives the new size.
/// You can use the [Resizable] mixin if you want an implementation of this hook that keeps track of the current size.
void resize(Size size) {}
void resize(Vector2 size) {}
/// Whether this component has been loaded yet. If not loaded, [BaseGame] will not try to render it.
///
@ -48,7 +50,7 @@ abstract class Component {
/// If two components share the same priority, they will probably be drawn in the order they were added.
int priority() => 0;
/// Called when the component has been added and preperad by the game instance.
/// Called when the component has been added and prepared by the game instance.
///
/// This can be used to make initializations on your component as, when this method is called,
/// things like resize (and other mixins) are already set and usable.

View File

@ -0,0 +1,156 @@
import 'dart:ui';
import 'package:flame/components/position_component.dart';
import '../flame.dart';
import '../sprite.dart';
import '../extensions/vector2.dart';
/// This represents an isometric tileset to be used in a tilemap.
///
/// It's basically a grid of squares, each square has a tile, in order.
/// The block ids are calculated going row per row, left to right, top to
/// bottom.
///
/// This class will cache the usage of sprites to improve performance.
class IsometricTileset {
/// The image for this tileset.
final Image tileset;
/// The size of each square block within the image.
///
/// The image width and height must be multiples of this number.
final int size;
final Map<int, Sprite> _spriteCache = {};
IsometricTileset(this.tileset, this.size);
/// Compute the number of columns the image has
/// by using the image width and tile size.
int get columns => tileset.width ~/ size;
/// Compute the number of rows the image has
/// by using the image height and tile size.
int get rows => tileset.height ~/ size;
/// Get a sprite to render one specific tile given its id.
///
/// The ids are assigned left to right, top to bottom, row per row.
/// The returned sprite will be cached, so don't modify it!
Sprite getTile(int tileId) {
return _spriteCache[tileId] ??= _computeTile(tileId);
}
Sprite _computeTile(int tileId) {
final i = tileId % columns;
final j = tileId ~/ columns;
final s = size.toDouble();
return Sprite.fromImage(tileset, x: s * i, y: s * j, width: s, height: s);
}
/// Load a tileset based on a file name.
static Future<IsometricTileset> load(String fileName, int size) async {
final image = await Flame.images.load(fileName);
return IsometricTileset(image, size);
}
}
/// This is just a pair of int, int.
///
/// Represents a position in a matrix, or in this case, on the tilemap.
class Block {
/// x and y coordinates on the matrix
int x, y;
Block(this.x, this.y);
@override
String toString() => '($x, $y)';
}
/// This component renders a tilemap, represented by an int matrix, given a
/// tileset, in witch the integers are the block ids.
///
/// It can change the scale of each block by using the optional destTileSize
/// property.
class IsometricTileMapComponent extends PositionComponent {
/// This is the tileset that will be used to render this map.
IsometricTileset tileset;
/// The positions of each block will be placed respecting this matrix.
List<List<int>> matrix;
/// Optionally provide a new tile size to render it scaled.
int destTileSize;
IsometricTileMapComponent(this.tileset, this.matrix, {this.destTileSize});
/// This is the size the tiles will be drawn (either original or overwritten).
int get effectiveTileSize => destTileSize ?? tileset.size;
@override
void render(Canvas c) {
super.render(c);
final size = Vector2(
effectiveTileSize.toDouble(),
effectiveTileSize.toDouble(),
);
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
final element = matrix[i][j];
if (element != -1) {
final sprite = tileset.getTile(element);
final p = getBlockPositionInts(j, i);
sprite.renderRect(c, p.toRect(size));
}
}
}
}
/// Get the position in witch a block must be in the isometric space.
///
/// This does not include the (x,y) PositionComponent offset!
Vector2 getBlockPosition(Block block) {
return getBlockPositionInts(block.x, block.y);
}
Vector2 getBlockPositionInts(int i, int j) {
final s = effectiveTileSize.toDouble() / 2;
return cartToIso(Vector2(i * s, j * s)) - Vector2(s, 0);
}
/// Converts a coordinate from the isometric space to the cartesian space.
Vector2 isoToCart(Vector2 p) {
final x = (2 * p.y + p.x) / 2;
final y = (2 * p.y - p.x) / 2;
return Vector2(x, y);
}
/// Converts a coordinate from the cartesian space to the isometric space.
Vector2 cartToIso(Vector2 p) {
final x = p.x - p.y;
final y = (p.x + p.y) / 2;
return Vector2(x, y);
}
/// Get what block is at isometric position p.
///
/// This can be used to handle clicks or hovers.
Block getBlock(Vector2 p) {
final s = effectiveTileSize.toDouble() / 2;
final cart = isoToCart(p - position);
final px = cart.x ~/ s;
final py = cart.y ~/ s;
return Block(px, py);
}
/// Return whether the matrix contains a block in its bounds.
bool containsBlock(Block block) {
return block.x >= 0 &&
block.x < matrix.length &&
block.y >= 0 &&
block.y < matrix[block.x].length;
}
}

View File

@ -1,13 +1,15 @@
import 'dart:math';
import 'dart:ui';
import 'package:flame/components/joystick/joystick_component.dart';
import 'package:flame/components/joystick/joystick_events.dart';
import 'package:flame/gestures.dart';
import 'package:flame/sprite.dart';
import 'joystick_component.dart';
import 'joystick_events.dart';
import '../../gestures.dart';
import '../../sprite.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import '../../position.dart';
import '../../extensions/offset.dart';
import '../../extensions/vector2.dart';
enum JoystickActionAlign { TOP_LEFT, BOTTOM_LEFT, TOP_RIGHT, BOTTOM_RIGHT }
@ -58,7 +60,7 @@ class JoystickAction {
_tileSize = _sizeBackgroundDirection / 2;
}
void initialize(Size _screenSize, JoystickController joystickController) {
void initialize(Vector2 _screenSize, JoystickController joystickController) {
_joystickController = joystickController;
final double radius = size / 2;
double dx = 0, dy = 0;
@ -69,15 +71,15 @@ class JoystickAction {
break;
case JoystickActionAlign.BOTTOM_LEFT:
dx = margin.left + radius;
dy = _screenSize.height - (margin.bottom + radius);
dy = _screenSize.y - (margin.bottom + radius);
break;
case JoystickActionAlign.TOP_RIGHT:
dx = _screenSize.width - (margin.right + radius);
dx = _screenSize.x - (margin.right + radius);
dy = margin.top + radius;
break;
case JoystickActionAlign.BOTTOM_RIGHT:
dx = _screenSize.width - (margin.right + radius);
dy = _screenSize.height - (margin.bottom + radius);
dx = _screenSize.x - (margin.right + radius);
dy = _screenSize.y - (margin.bottom + radius);
break;
}
_rectAction = Rect.fromCircle(
@ -152,17 +154,16 @@ class JoystickAction {
);
// Distance between the center of joystick background & drag position
final centerPosition =
Position.fromOffset(_rectBackgroundDirection.center);
final dragPosition = Position.fromOffset(_dragPosition);
double dist = centerPosition.distance(dragPosition);
final centerPosition = _rectBackgroundDirection.center.toVector2();
final dragPosition = _dragPosition.toVector2();
double dist = centerPosition.distanceTo(dragPosition);
// The maximum distance for the knob position the edge of
// The maximum distance for the knob position to the edge of
// the background + half of its own size. The knob can wander in the
// background image, but not outside.
dist = min(dist, _tileSize);
// Calculation the knob position
// Calculate the knob position
final double nextX = dist * cos(_radAngle);
final double nextY = dist * sin(_radAngle);
final Offset nextPoint = Offset(nextX, nextY);

View File

@ -1,12 +1,13 @@
import 'dart:ui';
import 'package:flame/components/component.dart';
import 'package:flame/components/joystick/joystick_action.dart';
import 'package:flame/components/joystick/joystick_directional.dart';
import 'package:flame/components/joystick/joystick_events.dart';
import 'package:flame/components/mixins/has_game_ref.dart';
import 'package:flame/game/base_game.dart';
import 'package:flame/gestures.dart';
import '../../extensions/vector2.dart';
import '../../game/base_game.dart';
import '../../gestures.dart';
import '../component.dart';
import '../mixins/has_game_ref.dart';
import 'joystick_action.dart';
import 'joystick_directional.dart';
import 'joystick_events.dart';
mixin JoystickListener {
void joystickChangeDirectional(JoystickDirectionalEvent event);
@ -69,7 +70,7 @@ class JoystickComponent extends JoystickController {
}
@override
void resize(Size size) {
void resize(Vector2 size) {
directional?.initialize(size, this);
actions?.forEach((action) => action.initialize(size, this));
super.resize(size);

View File

@ -1,12 +1,13 @@
import 'dart:math';
import 'package:flame/components/joystick/joystick_component.dart';
import 'package:flame/components/joystick/joystick_events.dart';
import 'package:flame/gestures.dart';
import 'package:flame/sprite.dart';
import 'package:flutter/material.dart';
import '../../position.dart';
import '../../extensions/offset.dart';
import '../../extensions/vector2.dart';
import '../../gestures.dart';
import '../../sprite.dart';
import 'joystick_component.dart';
import 'joystick_events.dart';
class JoystickDirectional {
final double size;
@ -33,7 +34,7 @@ class JoystickDirectional {
JoystickController _joystickController;
Size _screenSize;
Vector2 _screenSize;
DragEvent _currentDragEvent;
@ -65,11 +66,11 @@ class JoystickDirectional {
_tileSize = size / 2;
}
void initialize(Size _screenSize, JoystickController joystickController) {
void initialize(Vector2 _screenSize, JoystickController joystickController) {
this._screenSize = _screenSize;
_joystickController = joystickController;
final Offset osBackground =
Offset(margin.left, _screenSize.height - margin.bottom);
Offset(margin.left, _screenSize.y - margin.bottom);
_backgroundRect = Rect.fromCircle(center: osBackground, radius: size / 2);
final Offset osKnob =
@ -117,9 +118,9 @@ class JoystickDirectional {
final double degrees = _radAngle * 180 / pi;
// Distance between the center of joystick background & drag position
final centerPosition = Position.fromOffset(_backgroundRect.center);
final dragPosition = Position.fromOffset(_dragPosition);
double dist = centerPosition.distance(dragPosition);
final centerPosition = _backgroundRect.center.toVector2();
final dragPosition = _dragPosition.toVector2();
double dist = centerPosition.distanceTo(dragPosition);
// The maximum distance for the knob position the edge of
// the background + half of its own size. The knob can wander in the
@ -186,8 +187,8 @@ class JoystickDirectional {
void _updateDirectionalRect(Offset position) {
if (_screenSize != null &&
(position.dx > _screenSize.width / 2 ||
position.dy < _screenSize.height / 2 ||
(position.dx > _screenSize.x / 2 ||
position.dy < _screenSize.y / 2 ||
isFixed)) {
return;
}

View File

@ -1,4 +1,4 @@
import 'dart:ui';
import '../../extensions/vector2.dart';
/// Useful mixin to add to your components if you want to hold a reference to the current screen size.
///
@ -6,12 +6,12 @@ import 'dart:ui';
/// Also, it updates its [children], if any.
class Resizable {
/// This is the current updated screen size.
Size size;
Vector2 size;
/// Implementation provided by this mixin to the resize hook.
void resize(Size size) {
void resize(Vector2 size) {
this.size = size;
resizableChildren().where((e) => e != null).forEach((e) => e.resize(size));
resizableChildren().forEach((e) => e?.resize(size));
}
/// Overwrite this to add children to this [Resizable].

View File

@ -4,6 +4,8 @@ import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';
import '../extensions/vector2.dart';
import '../extensions/rect.dart';
import '../flame.dart';
import 'position_component.dart';
@ -37,10 +39,10 @@ class ParallaxLayer {
Future<Image> future;
Image _image;
Size _screenSize;
Rect _paintArea;
Offset _scroll;
Offset _imageSize;
Vector2 _screenSize;
Vector2 _scroll;
Vector2 _imageSize;
double _scale = 1.0;
ParallaxLayer(this.parallaxImage) {
@ -49,9 +51,9 @@ class ParallaxLayer {
bool loaded() => _image != null;
Offset currentOffset() => _scroll;
Vector2 currentOffset() => _scroll;
void resize(Size size) {
void resize(Vector2 size) {
if (!loaded()) {
_screenSize = size;
return;
@ -60,9 +62,9 @@ class ParallaxLayer {
double scale(LayerFill fill) {
switch (fill) {
case LayerFill.height:
return _image.height / size.height;
return _image.height / size.y;
case LayerFill.width:
return _image.width / size.width;
return _image.width / size.x;
default:
return _scale;
}
@ -71,52 +73,52 @@ class ParallaxLayer {
_scale = scale(parallaxImage.fill);
// The image size so that it fulfills the LayerFill parameter
_imageSize = Offset(_image.width / _scale, _image.height / _scale);
_imageSize =
Vector2(_image.width.toDouble(), _image.height.toDouble()) / _scale;
// Number of images that can fit on the canvas plus one
// to have something to scroll to without leaving canvas empty
final countX = 1 + size.width / _imageSize.dx;
final countY = 1 + size.height / _imageSize.dy;
final count = Vector2.all(1) + size.clone()
..divide(_imageSize);
// Percentage of the image size that will overflow
final overflowX = (_imageSize.dx * countX - size.width) / _imageSize.dx;
final overflowY = (_imageSize.dy * countY - size.height) / _imageSize.dy;
final overflow = ((_imageSize.clone()..multiply(count)) - size)
..divide(_imageSize);
// Align image to correct side of the screen
final alignment = parallaxImage.alignment;
final marginX = alignment.x == 0 ? overflowX / 2 : alignment.x;
final marginY = alignment.y == 0 ? overflowY / 2 : alignment.y;
_scroll ??= Offset(marginX, marginY);
final marginX = alignment.x == 0 ? overflow.x / 2 : alignment.x;
final marginY = alignment.y == 0 ? overflow.y / 2 : alignment.y;
_scroll ??= Vector2(marginX, marginY);
// Size of the area to paint the images in
final rectWidth = countX * _imageSize.dx;
final rectHeight = countY * _imageSize.dy;
_paintArea = Rect.fromLTWH(0, 0, rectWidth, rectHeight);
// Size of the area to paint the images on
final paintSize = count..multiply(_imageSize);
_paintArea = paintSize.toOriginRect();
}
void update(Offset delta) {
void update(Vector2 delta) {
if (!loaded()) {
return;
}
// Scale the delta so that images that are larger don't scroll faster
_scroll += delta.scale(1 / _imageSize.dx, 1 / _imageSize.dy);
_scroll += delta..divide(_imageSize);
switch (parallaxImage.repeat) {
case ImageRepeat.repeat:
_scroll = _scroll % 1;
_scroll = Vector2(_scroll.x % 1, _scroll.y % 1);
break;
case ImageRepeat.repeatX:
_scroll = Offset(_scroll.dx % 1, _scroll.dy);
_scroll = Vector2(_scroll.x % 1, _scroll.y);
break;
case ImageRepeat.repeatY:
_scroll = Offset(_scroll.dx, _scroll.dy % 1);
_scroll = Vector2(_scroll.x, _scroll.y % 1);
break;
case ImageRepeat.noRepeat:
break;
}
final dx = _scroll.dx * _imageSize.dx;
final dy = _scroll.dy * _imageSize.dy;
final dx = _scroll.x * _imageSize.x;
final dy = _scroll.y * _imageSize.y;
_paintArea = Rect.fromLTWH(-dx, -dy, _paintArea.width, _paintArea.height);
}
@ -153,29 +155,31 @@ enum LayerFill { height, width, none }
/// A full parallax, several layers of images drawn out on the screen and each
/// layer moves with different speeds to give an effect of depth.
class ParallaxComponent extends PositionComponent {
Offset baseSpeed;
Offset layerDelta;
Vector2 baseSpeed;
Vector2 layerDelta;
List<ParallaxLayer> _layers;
bool _loaded = false;
ParallaxComponent(
List<ParallaxImage> images, {
this.baseSpeed = Offset.zero,
this.layerDelta = Offset.zero,
this.baseSpeed,
this.layerDelta,
}) {
baseSpeed ??= Vector2.zero();
layerDelta ??= Vector2.zero();
_load(images);
}
/// The base offset of the parallax, can be used in an outer update loop
/// if you want to transition the parallax to a certain position.
Offset currentOffset() => _layers[0].currentOffset();
Vector2 currentOffset() => _layers[0].currentOffset();
@override
bool loaded() => _loaded;
@mustCallSuper
@override
void resize(Size size) {
void resize(Vector2 size) {
super.resize(size);
_layers.forEach((layer) => layer.resize(size));
}

View File

@ -8,8 +8,8 @@ import 'package:ordered_set/ordered_set.dart';
import '../anchor.dart';
import '../effects/effects.dart';
import '../game.dart';
import '../position.dart';
import '../text_config.dart';
import '../extensions/vector2.dart';
import 'component.dart';
/// A [Component] implementation that represents a component that has a
@ -72,14 +72,14 @@ abstract class PositionComponent extends Component {
TextConfig get debugTextConfig => TextConfig(color: debugColor, fontSize: 12);
Position toPosition() => Position(x, y);
void setByPosition(Position position) {
Vector2 get position => Vector2(x, y);
void setPosition(Vector2 position) {
x = position.x;
y = position.y;
}
Position toSize() => Position(width, height);
void setBySize(Position size) {
Vector2 toSize() => Vector2(width, height);
void setBySize(Vector2 size) {
width = size.x;
height = size.y;
}
@ -92,8 +92,8 @@ abstract class PositionComponent extends Component {
/// Returns the relative position/size of this component.
/// Relative because it might be translated by their parents (which is not considered here).
Rect toRect() => Rect.fromLTWH(
x - anchor.relativePosition.dx * width,
y - anchor.relativePosition.dy * height,
x - anchor.relativePosition.x * width,
y - anchor.relativePosition.y * height,
width,
height,
);
@ -101,8 +101,8 @@ abstract class PositionComponent extends Component {
/// Mutates x, y, width and height using the provided [rect] as basis.
/// This is a relative rect, same definition that [toRect] use (therefore both methods are compatible, i.e. setByRect ∘ toRect = identity).
void setByRect(Rect rect) {
x = rect.left + anchor.relativePosition.dx * rect.width;
y = rect.top + anchor.relativePosition.dy * rect.height;
x = rect.left + anchor.relativePosition.x * rect.width;
y = rect.top + anchor.relativePosition.y * rect.height;
width = rect.width;
height = rect.height;
}
@ -112,31 +112,33 @@ abstract class PositionComponent extends Component {
}
double distance(PositionComponent c) {
return c.toPosition().distance(toPosition());
return c.position.distanceTo(position);
}
void renderDebugMode(Canvas canvas) {
canvas.drawRect(toOriginRect(), _debugPaint);
debugTextConfig.render(
canvas,
'x: ${x.toStringAsFixed(2)} y:${y.toStringAsFixed(2)}',
Position(-50, -15));
canvas,
'x: ${x.toStringAsFixed(2)} y:${y.toStringAsFixed(2)}',
Vector2(-50, -15),
);
final Rect rect = toRect();
final dx = rect.right;
final dy = rect.bottom;
debugTextConfig.render(
canvas,
'x:${dx.toStringAsFixed(2)} y:${dy.toStringAsFixed(2)}',
Position(width - 50, height));
canvas,
'x:${dx.toStringAsFixed(2)} y:${dy.toStringAsFixed(2)}',
Vector2(width - 50, height),
);
}
void _prepareCanvas(Canvas canvas) {
canvas.translate(x, y);
canvas.rotate(angle);
final double dx = -anchor.relativePosition.dx * width;
final double dy = -anchor.relativePosition.dy * height;
final double dx = -anchor.relativePosition.x * width;
final double dy = -anchor.relativePosition.y * height;
canvas.translate(dx, dy);
// Handle inverted rendering by moving center and flipping.
@ -193,7 +195,7 @@ abstract class PositionComponent extends Component {
@mustCallSuper
@override
void resize(Size size) {
void resize(Vector2 size) {
super.resize(size);
_children.forEach((child) => child.resize(size));
}

View File

@ -5,8 +5,8 @@ import 'dart:ui';
import 'package:flutter/widgets.dart' as widgets;
import '../palette.dart';
import '../position.dart';
import '../text_config.dart';
import '../extensions/vector2.dart';
import 'mixins/resizable.dart';
import 'position_component.dart';
@ -28,7 +28,7 @@ class TextBoxComponent extends PositionComponent with Resizable {
static final Paint _imagePaint = BasicPalette.white.paint
..filterQuality = FilterQuality.high;
Position p = Position.empty();
Vector2 p = Vector2.zero();
String _text;
TextConfig _config;

View File

@ -1,4 +1,5 @@
import 'dart:ui';
import './component.dart';
import '../time.dart';

View File

@ -29,7 +29,7 @@ class CombinedEffect extends PositionComponentEffect {
effects.forEach((effect) {
effect.initialize(_comp);
final isSameSize = effect.endSize == _comp.toSize();
final isSamePosition = effect.endPosition == _comp.toPosition();
final isSamePosition = effect.endPosition == _comp.position;
final isSameAngle = effect.endAngle == _comp.angle;
endSize = isSameSize ? endSize : effect.endSize;
endPosition = isSamePosition ? endPosition : effect.endPosition;

View File

@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../components/position_component.dart';
import '../position.dart';
import '../extensions/vector2.dart';
export './move_effect.dart';
export './rotate_effect.dart';
@ -31,9 +31,9 @@ abstract class PositionComponentEffect {
int curveDirection = 1;
/// Used to be able to determine the end state of a sequence of effects
Position endPosition;
Vector2 endPosition;
double endAngle;
Position endSize;
Vector2 endSize;
/// If the effect is alternating the travel time is double the normal
/// travel time
@ -76,7 +76,7 @@ abstract class PositionComponentEffect {
/// If these aren't modified by the extending effect it is assumed that the
/// effect didn't bring the component to another state than the one it
/// started in
endPosition = _comp.toPosition();
endPosition = _comp.position;
endAngle = _comp.angle;
endSize = _comp.toSize();
}

View File

@ -1,16 +1,16 @@
import 'dart:math';
import 'package:flutter/animation.dart';
import 'package:meta/meta.dart';
import 'dart:math';
import './effects.dart';
import '../position.dart';
import '../extensions/vector2.dart';
import 'effects.dart';
double _direction(double p, double d) => (p - d).sign;
double _distance(double a, double b) => (a - b).abs();
class MoveEffect extends PositionComponentEffect {
Position destination;
Vector2 destination;
double speed;
Curve curve;

View File

@ -1,23 +1,20 @@
import 'package:flutter/animation.dart';
import 'package:meta/meta.dart';
import 'dart:ui';
import 'dart:math';
import './effects.dart';
import '../position.dart';
import '../extensions/vector2.dart';
import 'effects.dart';
double _direction(double p, double d) => (p - d).sign;
double _size(double a, double b) => (a - b).abs();
double _length(double a, double b) => (a - b).abs();
class ScaleEffect extends PositionComponentEffect {
Size size;
Vector2 size;
double speed;
Curve curve;
Size _original;
Size _diff;
final Position _dir = Position.empty();
Vector2 _original;
Vector2 _diff;
final Vector2 _dir = Vector2.zero();
ScaleEffect({
@required this.size,
@ -32,19 +29,19 @@ class ScaleEffect extends PositionComponentEffect {
void initialize(_comp) {
super.initialize(_comp);
if (!isAlternating) {
endSize = Position.fromSize(size);
endSize = size.clone();
}
_original = Size(component.width, component.height);
_diff = Size(
_size(_original.width, size.width),
_size(_original.height, size.height),
_original = component.toSize();
_diff = Vector2(
_length(_original.x, size.x),
_length(_original.y, size.y),
);
_dir.x = _direction(size.width, _original.width);
_dir.y = _direction(size.height, _original.height);
_dir.x = _direction(size.x, _original.x);
_dir.y = _direction(size.y, _original.y);
final scaleDistance = sqrt(pow(_diff.width, 2) + pow(_diff.height, 2));
final scaleDistance = _diff.length;
travelTime = scaleDistance / speed;
}
@ -52,8 +49,6 @@ class ScaleEffect extends PositionComponentEffect {
void update(double dt) {
super.update(dt);
final double c = curve?.transform(percentage) ?? 1.0;
component.width = _original.width + _diff.width * c * _dir.x;
component.height = _original.height + _diff.height * c * _dir.y;
component.setBySize(_original + (_diff.clone()..multiply(_dir)) * c);
}
}

View File

@ -1,7 +1,7 @@
import 'package:meta/meta.dart';
import './effects.dart';
import '../components/position_component.dart';
import 'effects.dart';
class SequenceEffect extends PositionComponentEffect {
final List<PositionComponentEffect> effects;
@ -27,12 +27,12 @@ class SequenceEffect extends PositionComponentEffect {
super.initialize(_comp);
_currentIndex = 0;
final originalSize = _comp.toSize();
final originalPosition = _comp.toPosition();
final originalPosition = _comp.position;
final originalAngle = _comp.angle;
effects.forEach((effect) {
effect.reset();
_comp.setBySize(endSize);
_comp.setByPosition(endPosition);
_comp.setPosition(endPosition);
_comp.angle = endAngle;
effect.initialize(_comp);
endSize = effect.endSize;
@ -44,7 +44,7 @@ class SequenceEffect extends PositionComponentEffect {
(time, effect) => time + effect.totalTravelTime,
);
component.setBySize(originalSize);
component.setByPosition(originalPosition);
component.setPosition(originalPosition);
component.angle = originalAngle;
currentEffect = effects.first;
_currentWasAlternating = currentEffect.isAlternating;

View File

@ -0,0 +1,20 @@
export 'dart:ui' show Offset;
import 'dart:math';
import 'dart:ui';
import './vector2.dart';
extension OffsetExtension on Offset {
/// Creates an [Vector2] from the [Offset]
Vector2 toVector2() => Vector2(dx, dy);
/// Creates a [Size] from the [Offset]
Size toSize() => Size(dx, dy);
/// Creates a [Point] from the [Offset]
Point toPoint() => Point(dx, dy);
/// Creates a [Rect] starting in origin and going the [Offset]
Rect toRect() => Rect.fromLTWH(0, 0, dx, dy);
}

32
lib/extensions/rect.dart Normal file
View File

@ -0,0 +1,32 @@
export 'dart:ui' show Rect;
import 'dart:math';
import 'dart:ui';
import './vector2.dart';
extension RectExtension on Rect {
/// Creates an [Offset] from the [Vector2]
Offset toOffset() => Offset(width, height);
/// Creates a [Vector2] starting in top left and going to [width, height].
Vector2 toVector2() => Vector2(width, height);
}
// Until [extension] will allow static methods we need to keep these functions
// in a utility class
class RectFactory {
/// Creates a [Rect] with the size of [Size]
static Rect fromSize(Size size) {
return Rect.fromLTWH(0, 0, size.width, size.height);
}
/// Creates bounds in from of a [Rect] from a list of [Vector2]
static Rect fromBounds(List<Vector2> pts) {
final double minx = pts.map((e) => e.x).reduce(min);
final double maxx = pts.map((e) => e.x).reduce(max);
final double miny = pts.map((e) => e.y).reduce(min);
final double maxy = pts.map((e) => e.y).reduce(max);
return Rect.fromPoints(Offset(minx, miny), Offset(maxx, maxy));
}
}

20
lib/extensions/size.dart Normal file
View File

@ -0,0 +1,20 @@
export 'dart:ui' show Size;
import 'dart:math';
import 'dart:ui';
import 'vector2.dart';
extension SizeExtension on Size {
/// Creates an [Offset] from the [Size]
Offset toOffset() => Offset(width, height);
/// Creates a [Vector2] from the [Size]
Vector2 toVector2() => Vector2(width, height);
/// Creates a [Point] from the [Size]
Point toPoint() => Point(width, height);
/// Creates a [Rect] from the [Size]
Rect toRect() => Rect.fromLTWH(0, 0, width, height);
}

View File

@ -0,0 +1,46 @@
export 'package:vector_math/vector_math_64.dart' show Vector2;
import 'dart:math';
import 'dart:ui';
import 'package:vector_math/vector_math_64.dart';
extension Vector2Extension on Vector2 {
/// Creates an [Offset] from the [Vector2]
Offset toOffset() => Offset(x, y);
/// Creates a [Size] from the [Vector2]
Size toSize() => Size(x, y);
/// Creates a [Point] from the [Vector2]
Point toPoint() => Point(x, y);
/// Creates a [Rect] starting from [x, y] and having size [to].
Rect toRect(Vector2 to) => Rect.fromLTWH(x, y, to.x, to.y);
/// Creates a [Rect] starting in origin and having size [to].
Rect toOriginRect() => Rect.fromLTWH(0, 0, x, y);
/// Linearly interpolate towards another Vector2
void lerp(Vector2 to, double t) {
setFrom(this + (to - this) * t);
}
/// Rotates the [Vector2] with [angle] in radians
void rotate(double angle) {
setValues(
x * cos(angle) - y * sin(angle),
x * sin(angle) + y * cos(angle),
);
}
/// Changes the [length] of the vector to the length provided, without changing direction.
///
/// If you try to scale the zero (empty) vector, it will remain unchanged, and no error will be thrown.
void scaleTo(double newLength) {
final l = length;
if (l != 0) {
scale(newLength.abs() / l);
}
}
}

View File

@ -4,10 +4,10 @@ import 'package:audioplayers/audio_cache.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'flame_audio.dart';
import 'bgm.dart';
import 'assets/images.dart';
import 'assets_cache.dart';
import 'assets/images.dart';
import 'bgm.dart';
import 'flame_audio.dart';
import 'util.dart';
/// This class holds static references to some useful objects to use in your game.

View File

@ -1,9 +1,8 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:audioplayers/audio_cache.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/foundation.dart';
/// Handles flame audio functions
class FlameAudio {

View File

@ -1,10 +1,11 @@
import 'dart:ui';
import 'dart:math';
import 'dart:ui';
import "flame.dart";
import "package:flare_flutter/flare.dart";
import "package:flare_flutter/flare_actor.dart";
import "flame.dart";
@Deprecated("Use flame_flare package instead")
class FlareAnimation {
final FlutterActorArtboard _artboard;

View File

@ -1,39 +1,33 @@
import 'dart:math' as math;
import 'package:flutter/scheduler.dart';
import 'game/game.dart';
mixin FPSCounter on Game {
/// List of deltas used in debug mode to calculate FPS
final List<double> _dts = [];
const _maxFrames = 60;
const frameInterval =
Duration(microseconds: Duration.microsecondsPerSecond ~/ _maxFrames);
/// Returns whether this [Game] is should record fps or not
mixin FPSCounter on Game {
List<FrameTiming> _previousTimings = [];
@override
void onTimingsCallback(List<FrameTiming> timings) =>
_previousTimings = timings;
/// Returns whether this [Game] is should record fps or not.
///
/// Returns `false` by default. Override to use the `fps` counter method.
/// In recording fps, the [recordDt] method actually records every `dt` for statistics.
/// Then, you can use the [fps] method to check the game FPS.
@Deprecated('Flame is now using Flutter frame times, will be removed in v1')
bool recordFps();
/// This is a hook that comes from the RenderBox to allow recording of render times and statistics.
@override
void recordDt(double dt) {
if (recordFps()) {
_dts.add(dt);
}
}
/// Returns the average FPS for the last [average] measures.
///
/// The values are only saved if in debug mode (override [recordFps] 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.
/// Returns the FPS based on the frame times from [onTimingsCallback].
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;
return _previousTimings.length *
_maxFrames /
_previousTimings.map((t) {
return (t.totalSpan.inMicroseconds ~/ frameInterval.inMicroseconds) +
1;
}).fold(0, (a, b) => a + b);
}
}

View File

@ -1,3 +1,3 @@
// Keeping compatible with earlier versions of Flame
export './game/game.dart';
export './game/base_game.dart';
export './game/game.dart';

View File

@ -1,7 +1,6 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flame/fps_counter.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart' hide WidgetBuilder;
import 'package:ordered_set/comparing.dart';
@ -11,7 +10,8 @@ import '../components/component.dart';
import '../components/mixins/has_game_ref.dart';
import '../components/mixins/tapable.dart';
import '../components/position_component.dart';
import '../position.dart';
import '../extensions/vector2.dart';
import '../fps_counter.dart';
import 'game.dart';
/// This is a more complete and opinionated implementation of Game.
@ -31,10 +31,10 @@ class BaseGame extends Game with FPSCounter {
final List<Component> _removeLater = [];
/// Current screen size, updated every resize via the [resize] method hook
Size size;
Vector2 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();
Vector2 camera = Vector2.zero();
/// This method is called for every component added, both via [add] and [addLater] methods.
///
@ -138,7 +138,7 @@ class BaseGame extends Game with FPSCounter {
/// 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) {
void resize(Vector2 size) {
this.size = size;
components.forEach((c) => c.resize(size));
}

View File

@ -1,10 +1,9 @@
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart' hide WidgetBuilder;
import '../position.dart';
import 'game_render_box.dart';
import '../extensions/vector2.dart';
import 'game.dart';
import 'game_render_box.dart';
/// This a widget to embed a game inside the Widget tree. You can use it in pair with [BaseGame] or any other more complex [Game], as desired.
///
@ -14,7 +13,7 @@ import 'game.dart';
/// 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;
final Vector2 size;
EmbeddedGameWidget(this.game, {this.size});

View File

@ -1,16 +1,17 @@
import 'dart:ui';
import 'dart:async';
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart' hide WidgetBuilder;
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart' hide WidgetBuilder;
import '../assets/images.dart';
import '../extensions/vector2.dart';
import '../keyboard.dart';
import 'widget_builder.dart';
import '../keyboard.dart';
import '../assets/images.dart';
/// Represents a generic game.
///
@ -38,7 +39,7 @@ abstract class Game {
/// This is the resize hook; every time the game widget is resized, this hook is called.
///
/// The default implementation does nothing; override to use the hook.
void resize(Size size) {}
void resize(Vector2 size) {}
/// This is the lifecycle state change hook; every time the game is resumed, paused or suspended, this is called.
///
@ -47,8 +48,12 @@ abstract class Game {
void lifecycleStateChange(AppLifecycleState state) {}
/// Used for debugging
@Deprecated('Gets called for backward compatibility, will be removed in v1')
void recordDt(double dt) {}
/// Use for caluclating the FPS.
void onTimingsCallback(List<FrameTiming> timings) {}
/// 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).
Widget get widget => builder.build(this);

View File

@ -5,8 +5,9 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart' hide WidgetBuilder;
import 'game_loop.dart';
import '../extensions/size.dart';
import 'game.dart';
import 'game_loop.dart';
class GameRenderBox extends RenderBox with WidgetsBindingObserver {
BuildContext context;
@ -15,6 +16,7 @@ class GameRenderBox extends RenderBox with WidgetsBindingObserver {
GameRenderBox(this.context, this.game) {
gameLoop = GameLoop(gameLoopCallback);
WidgetsBinding.instance.addTimingsCallback(game.onTimingsCallback);
}
@override
@ -23,7 +25,7 @@ class GameRenderBox extends RenderBox with WidgetsBindingObserver {
@override
void performResize() {
super.performResize();
game.resize(constraints.biggest);
game.resize(constraints.biggest.toVector2());
}
@override
@ -53,6 +55,7 @@ class GameRenderBox extends RenderBox with WidgetsBindingObserver {
if (!attached) {
return;
}
// ignore: deprecated_member_use_from_same_package
game.recordDt(dt);
game.update(dt);
markNeedsPaint();
@ -61,8 +64,8 @@ class GameRenderBox extends RenderBox with WidgetsBindingObserver {
@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);
final delta = game.builder.offset + offset;
context.canvas.translate(delta.dx, delta.dy);
game.render(context.canvas);
context.canvas.restore();
}

View File

@ -1,8 +1,8 @@
import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
import '../gestures.dart';
import '../components/mixins/tapable.dart';
import '../gestures.dart';
import 'embedded_game_widget.dart';
import 'game.dart';
@ -22,6 +22,9 @@ bool _hasAdvancedGesturesDetectors(Game game) =>
game is MultiTouchDragDetector ||
game is HasTapableComponents;
bool _hasMouseDetectors(Game game) =>
game is MouseMovementDetector || game is ScrollDetector;
class _GenericTapEventHandler {
void Function(int pointerId) onTap;
void Function(int pointerId) onTapCancel;
@ -204,6 +207,19 @@ Widget _applyBasicGesturesDetectors(Game game, Widget child) {
);
}
Widget _applyMouseDetectors(game, Widget child) {
return MouseRegion(
child: Listener(
child: child,
onPointerSignal: (event) =>
game is ScrollDetector && event is PointerScrollEvent
? game.onScroll(event)
: null,
),
onHover: game is MouseMovementDetector ? game.onMouseMove : null,
);
}
class WidgetBuilder {
Offset offset = Offset.zero;
@ -224,6 +240,10 @@ class WidgetBuilder {
widget = _applyAdvancedGesturesDetectors(game, widget);
}
if (_hasMouseDetectors(game)) {
widget = _applyMouseDetectors(game, widget);
}
return FutureBuilder(
future: game.onLoad(),
builder: (_, snapshot) {

View File

@ -99,3 +99,11 @@ mixin ScaleDetector on Game {
void onScaleUpdate(ScaleUpdateDetails details) {}
void onScaleEnd(ScaleEndDetails details) {}
}
mixin MouseMovementDetector on Game {
void onMouseMove(PointerHoverEvent event) {}
}
mixin ScrollDetector on Game {
void onScroll(PointerScrollEvent event) {}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/services.dart';
import './game.dart';
mixin KeyboardEvents on Game {

Some files were not shown because too many files have changed in this diff Show More