diff --git a/doc/examples/effects/lib/main.dart b/doc/examples/effects/lib/main.dart index c3b772b79..d8a052651 100644 --- a/doc/examples/effects/lib/main.dart +++ b/doc/examples/effects/lib/main.dart @@ -1,48 +1,113 @@ import 'package:flutter/material.dart'; -import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; -import 'package:flame/components/component.dart'; -import 'package:flame/effects/effects.dart'; -import 'package:flame/position.dart'; - -class Square extends PositionComponent { - static final _paint = Paint()..color = Color(0xFFFFFFFF); - - Square() { - width = 100; - height = 100; - } - - @override - void render(Canvas canvas) { - canvas.drawRect(toRect(), _paint); - } -} - -class MyGame extends BaseGame with TapDetector { - Square square; - MyGame() { - add( - square = Square() - ..x = 100 - ..y = 100 - ); - } - - @override - void onTapUp(details) { - square.addEffect(MoveEffect( - destination: Position( - details.localPosition.dx, - details.localPosition.dy, - ), - speed: 250.0, - curve: Curves.easeOutQuad, - )); - } -} void main() { - runApp(MyGame().widget); + runApp(MyApp()); } +class MyApp extends StatelessWidget { + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // Try running your application with "flutter run". You'll see the + // application has a blue toolbar. Then, without quitting the app, try + // changing the primarySwatch below to Colors.green and then invoke + // "hot reload" (press "r" in the console where you ran "flutter run", + // or simply save your changes to "hot reload" in a Flutter IDE). + // Notice that the counter didn't reset back to zero; the application + // is not restarted. + primarySwatch: Colors.blue, + ), + home: MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + MyHomePage({Key key, this.title}) : super(key: key); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int _counter = 0; + + void _incrementCounter() { + setState(() { + // This call to setState tells the Flutter framework that something has + // changed in this State, which causes it to rerun the build method below + // so that the display can reflect the updated values. If we changed + // _counter without calling setState(), then the build method would not be + // called again, and so nothing would appear to happen. + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + // This method is rerun every time setState is called, for instance as done + // by the _incrementCounter method above. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: Center( + // Center is a layout widget. It takes a single child and positions it + // in the middle of the parent. + child: Column( + // Column is also a layout widget. It takes a list of children and + // arranges them vertically. By default, it sizes itself to fit its + // children horizontally, and tries to be as tall as its parent. + // + // Invoke "debug painting" (press "p" in the console, choose the + // "Toggle Debug Paint" action from the Flutter Inspector in Android + // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) + // to see the wireframe for each widget. + // + // Column has various properties to control how it sizes itself and + // how it positions its children. Here we use mainAxisAlignment to + // center the children vertically; the main axis here is the vertical + // axis because Columns are vertical (the cross axis would be + // horizontal). + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'You have pushed the button this many times:', + ), + Text( + '$_counter', + style: Theme.of(context).textTheme.headline4, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: Icon(Icons.add), + ), // This trailing comma makes auto-formatting nicer for build methods. + ); + } +} diff --git a/doc/examples/effects/lib/main_move.dart b/doc/examples/effects/lib/main_move.dart new file mode 100644 index 000000000..066680a1b --- /dev/null +++ b/doc/examples/effects/lib/main_move.dart @@ -0,0 +1,32 @@ +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 './square.dart'; + +class MyGame extends BaseGame with TapDetector { + Square square; + MyGame() { + add(square = Square() + ..x = 100 + ..y = 100); + } + + @override + void onTapUp(details) { + square.addEffect(MoveEffect( + destination: Position( + details.localPosition.dx, + details.localPosition.dy, + ), + speed: 250.0, + curve: Curves.bounceInOut, + )); + } +} + +void main() { + runApp(MyGame().widget); +} diff --git a/doc/examples/effects/lib/main_scale.dart b/doc/examples/effects/lib/main_scale.dart new file mode 100644 index 000000000..e80f53482 --- /dev/null +++ b/doc/examples/effects/lib/main_scale.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flame/game.dart'; +import 'package:flame/gestures.dart'; +import 'package:flame/anchor.dart'; +import 'package:flame/effects/effects.dart'; + +import './square.dart'; + +class MyGame extends BaseGame with TapDetector { + Square square; + bool grow = true; + + MyGame() { + add(square = Square() + ..anchor = Anchor.center + ..x = 200 + ..y = 400); + } + + @override + void onTap() { + final s = grow ? 300.0 : 100.0; + + grow = !grow; + square.addEffect(ScaleEffect( + size: Size(s, s), + speed: 250.0, + curve: Curves.bounceInOut, + )); + } +} + +void main() { + runApp(MyGame().widget); +} diff --git a/doc/examples/effects/lib/square.dart b/doc/examples/effects/lib/square.dart new file mode 100644 index 000000000..e02a1ae76 --- /dev/null +++ b/doc/examples/effects/lib/square.dart @@ -0,0 +1,17 @@ +import 'package:flame/components/component.dart'; + +import 'dart:ui'; + +class Square extends PositionComponent { + static final _paint = Paint()..color = const Color(0xFFFFFFFF); + + Square() { + width = 100; + height = 100; + } + + @override + void render(Canvas canvas) { + canvas.drawRect(toRect(), _paint); + } +} diff --git a/lib/components/component.dart b/lib/components/component.dart index 8611cf634..8a1b74ac8 100644 --- a/lib/components/component.dart +++ b/lib/components/component.dart @@ -154,10 +154,7 @@ abstract class PositionComponent extends Component { } void addEffect(PositionComponentEffect effect) { - _effects.add( - effect - ..component = this - ); + _effects.add(effect..component = this); } @mustCallSuper diff --git a/lib/effects/effects.dart b/lib/effects/effects.dart index 040085d18..f8ba78394 100644 --- a/lib/effects/effects.dart +++ b/lib/effects/effects.dart @@ -2,6 +2,7 @@ import 'package:flutter/animation.dart'; import 'package:meta/meta.dart'; import 'dart:math'; +import 'dart:ui'; import '../position.dart'; import '../components/component.dart'; @@ -10,11 +11,66 @@ abstract class PositionComponentEffect { void update(double dt); bool hasFinished(); PositionComponent component; +} +double _direction(double p, double d) => p < d ? -1 : 1; +double _size(double a, double b) => (a - b).abs(); + +class ScaleEffect extends PositionComponentEffect { + Size size; + double speed; + Curve curve; + + double _scaleTime; + double _ellapsedTime = 0.0; + + Size _original; + Size _diff; + final Position _dir = Position(0, 0); + + ScaleEffect({ + @required this.size, + @required this.speed, + this.curve, + }); + + @override + set component(_comp) { + super.component = _comp; + + _original = Size(component.width, component.height); + _diff = Size( + _size(_original.width, size.width), + _size(_original.height, size.height), + ); + + _dir.x = _direction(size.width, _original.width); + _dir.y = _direction(size.height, _original.height); + + _scaleTime = max( + _diff.width / speed, + _diff.height / speed, + ); + } + + @override + void update(double dt) { + if (!hasFinished()) { + final double percent = min(1.0, _ellapsedTime / _scaleTime); + final double c = curve != null ? curve.transform(percent) : 1.0; + + component.width = _original.width + _diff.width * c * _dir.x; + component.height = _original.height + _diff.height * c * _dir.y; + } + + _ellapsedTime += dt; + } + + @override + bool hasFinished() => _ellapsedTime >= _scaleTime; } class MoveEffect extends PositionComponentEffect { - Position destination; double speed; Curve curve; @@ -31,33 +87,33 @@ class MoveEffect extends PositionComponentEffect { double _ellapsedTime = 0.0; - - bool _valuesInitted = false; - MoveEffect({ @required this.destination, @required this.speed, this.curve, }); + @override + set component(_comp) { + super.component = _comp; + + _xOriginal = component.x; + _yOriginal = component.y; + + _xDistance = _size(destination.x, component.x); + _yDistance = _size(destination.y, component.y); + + _xDirection = _direction(destination.x, component.x); + _yDirection = _direction(destination.y, component.y); + + _travelTime = max( + _xDistance / speed, + _yDistance / speed, + ); + } + @override void update(double dt) { - if (!_valuesInitted) { - _xOriginal = component.x; - _xDistance = (destination.x - component.x).abs(); - final _xTravelTime = _xDistance / speed; - _xDirection = destination.x < component.x ? - 1 : 1; - - _yOriginal = component.y; - _yDistance = (destination.y - component.y).abs(); - final _yTravelTime = _yDistance / speed; - _yDirection = destination.y < component.y ? - 1 : 1; - - _travelTime = max(_xTravelTime, _yTravelTime); - - _valuesInitted = true; - } - if (!hasFinished()) { final double percent = min(1.0, _ellapsedTime / _travelTime); final double c = curve != null ? curve.transform(percent) : 1.0; @@ -68,6 +124,7 @@ class MoveEffect extends PositionComponentEffect { _ellapsedTime += dt; } + @override - bool hasFinished() => _ellapsedTime >= _travelTime; + bool hasFinished() => _ellapsedTime >= _travelTime; }