【问题标题】:How to reset an animation in specific offset, when animation status is forwarding?当动画状态为转发时,如何重置特定偏移量的动画?
【发布时间】:2021-03-16 12:44:51
【问题描述】:

我想设计一个简单的游戏,在这个游戏中,球击中了盒子,用户必须尝试用光标将球举起。 当球返回时,球运动结束,是屏幕底部的偏移量,如果球偏移量等于光标,我想重置动画,然后给它一个新的方向,但这永远不会发生。

请查看我打印的值。

532.0cursor.position.dy,其他人是 positionBall.dy + renderBall.size.height
为什么只有当球向上移动时(我在屏幕上点击的那一刻),球的偏移量和光标的偏移量才相等,而不是相反?
---更新---
当我增加持续时间(例如,10 秒),或从 flutter 检查器 激活 Slow Animations 按钮时,数字会彼此靠近,并通过调整它们到int,条件成立。

I/flutter (21563): 532.0
I/flutter (21563): 532.45585

我真的很困惑,我不知道后台发生了什么。

  void initState() {
    super.initState();
    Offset init = initialBallPosition();
    final g = Provider.of<GameStatus>(context, listen: false);
    var key = ball.key;
    _animationController = AnimationController(duration: Duration(seconds: 1), vsync: this);
    _tweenOffset = Tween<Offset>(begin: init, end: init);
    _animationOffset = _tweenOffset.animate(
      CurvedAnimation(parent: _animationController, curve: Curves.linear),
    )..addListener(() {
        if (_animationController.isAnimating) {
          //if (_animationController.status == AnimationStatus.forward) {
          RenderBox renderBall = key.currentContext.findRenderObject();
          final positionBall = renderBall.localToGlobal(Offset.zero);
          print(cursor.position.dy);
          print(positionBall.dy + renderBall.size.height);
          if (positionBall.dy + renderBall.size.height == cursor.position.dy && g.ballDirection == 270) {
            print('bang');
            colideWithCursor();
          }
        }
        if (_animationController.status == AnimationStatus.completed) {
          if (bottomOfBall().dy == Screen.screenHeight / ball.width) {
            gameOver();
          } else
            collision();
        }
      });
    _animationController.isDismissed;
  }

  @override
  Widget build(BuildContext context) {
    final game = Provider.of<GameStatus>(context, listen: false);

    return Selector<GameStatus, bool>(
        selector: (ctx, game) => game.firstShoot,
        builder: (context, startGame, child) {
          if (startGame) {
            game.ballDirection = 90;
            routing(game.ballDirection);
          }
          return UnconstrainedBox(child: (SlideTransition(position: _animationOffset, child: ball.createBall())));
        });
  }

【问题讨论】:

    标签: flutter animation offset animationcontroller


    【解决方案1】:

    这两个数字永远不会完全匹配,因为每帧都会检查动画值并且帧之间会发生重叠。

    您可能想要添加一个容差(例如,如果它们在一定数量内,则考虑匹配的值)或创建一些插值逻辑来检查球是否即将与光标之间的光标发生碰撞当前帧和下一帧。例如替换:

    positionBall.dy + renderBall.size.height == cursor.position.dy &amp;&amp; g.ballDirection == 270

    与:

    positionBall.dy + renderBall.size.height + &lt;current_speed_per_frame_of_ball&gt; &lt;= cursor.position.dy &amp;&amp; g.ballDirection == 270

    这里重要的是动画实际上并不流畅。动画不会从 0.0 连续通过每个可能的值到 1.0。动画的值仅在渲染帧时计算,因此您实际获得的值可能类似于:0.0、0.14、0.30、0.44、0.58....0.86、0.99、1.0。确切的值将取决于动画的持续时间和 Flutter 框架渲染每一帧的确切时间。

    【讨论】:

    • 感谢您的提示,但我不明白“current_speed_per_frame_of_ball”的含义。如何实现或计算?您能否向我解释更多或提供示例或链接以供阅读?再次感谢。
    • "current_speed_per_frame_of_ball" 是球在每帧之间移动的单位数。您需要知道球是否会在当前帧和下一帧之间击中光标。更高级别的说明:使用动画可能会对您的项目产生影响。它们真的不是用来构建游戏逻辑的。您可能应该: A. 在 Unity 之类的游戏引擎中构建您的游戏(您会有更好的时间)或 B. 使用较低级别的类并在 onTick 回调中管理状态。 api.flutter.dev/flutter/widgets/…
    • 我尝试使用建议 B,使用较低级别的类并在 onTick 回调中管理状态。 但不能。为此,我多次研究flutter.dev的许多文章。我不知道如何使用方法来管理onTick 回调。可以给我一个示例代码吗?
    【解决方案2】:

    由于您(在 cmets 中)询问了一个使用 onTick 的示例,因此这是我为一个在屏幕上随机弹跳的球编写的示例应用程序。您可以点击以随机化它的方向和速度。现在它有点伤你的眼睛,因为它在每一帧都将球重新绘制到一个新的位置。

    您可能希望在每次方向变化之间平滑地为球设置动画(例如,将Positioned 替换为AnimatedPositioned)以消除眼睛疲劳。这个重构超出了我的时间。

    import 'dart:math';
    
    import 'package:flutter/material.dart';
    import 'package:flutter/scheduler.dart';
    import 'package:vector_math/vector_math.dart' hide Colors;
    
    Random _rng = Random();
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
      get randomizedDirection =>
          _randomDirectionWithVelocity((150 + _rng.nextInt(600)).toDouble());
    
      Ticker _ticker;
      Vector2 _initialDirection;
      Duration prevT = Duration.zero;
    
      BallModel _ballModel;
    
      @override
      void dispose() {
        super.dispose();
        _ticker.dispose();
      }
    
      void _init(Size size) {
        _ballModel = BallModel(
          Vector2(size.width / 2, size.height / 2),
          randomizedDirection,
          16.0,
        );
        _ticker = createTicker((t) {
          // This sets state and forces a rebuild on every frame. A good optimization would be
          // to only build when the ball changes direction and use AnimatedPositioned to fluidly
          // draw the ball between changes in direction.
          setState(() {
            _ballModel.updateBall(t - prevT, size);
          });
          prevT = t;
        });
        _ticker.start();
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: GestureDetector(
            child: Scaffold(
              body: LayoutBuilder(
                builder: (context, constraints) {
                  // Initialize everything here because we need to access the constraints.
                  if (_ticker == null) _init(constraints.biggest);
                  return Stack(children: [
                    Ball(_ballModel),
                  ]);
                },
              ),
            ),
            onTap: () => setState(() => _ballModel.v = randomizedDirection),
          ),
        );
      }
    }
    
    class BallModel {
      // The current x,y position of the ball.
      Vector2 p;
    
      // The direction, including speed in pixels per second, of the ball
      Vector2 v;
    
      // The radius of the ball.
      double r;
    
      BallModel(this.p, this.v, this.r);
    
      void updateBall(Duration elapsed, Size size) {
        // Move the ball by v, scaled by what fraction of a second has passed
        // since the last frame.
        p = p + v * (elapsed.inMilliseconds / 1000);
        // If the ball overflows on a given dimension, correct the overflow and update v.
        var newX = _correctOverflow(p.x, r, 0, size.width);
        var newY = _correctOverflow(p.y, r, 0, size.height);
        if (newX != p.x) v.x = -v.x;
        if (newY != p.y) v.y = -v.y;
        p = Vector2(newX, newY);
      }
    }
    
    class Ball extends StatelessWidget {
      final BallModel b;
    
      Ball(this.b);
    
      @override
      Widget build(BuildContext context) {
        return Positioned(
            left: b.p.x - b.r,
            bottom: b.p.y - b.r,
            child: DecoratedBox(
                decoration:
                    BoxDecoration(shape: BoxShape.circle, color: Colors.black)),
            width: 2 * b.r,
            height: 2 * b.r);
      }
    }
    
    double _correctOverflow(s, r, lowerBound, upperBound) {
      var underflow = s - r - lowerBound;
      // Reflect s across lowerBound.
      if (underflow < 0) return s - 2 * underflow;
      var overflow = s + r - upperBound;
      // Reflect s across upper bound.
      if (overflow > 0) return s - 2 * overflow;
      // No over or underflow, return s.
      return s;
    }
    
    Vector2 _randomDirectionWithVelocity(double velocity) {
      return Vector2(_rng.nextDouble() - .5, _rng.nextDouble() - 0.5).normalized() *
          velocity;
    }
    

    从头开始编写游戏和物理逻辑会很快变得非常复杂。我鼓励你使用像 Unity 这样的游戏引擎,这样你就不必自己构建所有东西。还有一个基于 Flutter 的游戏引擎,称为火焰,您可以尝试一下: https://github.com/flame-engine/flame.

    【讨论】:

    • 非常感谢您抽出宝贵的时间。我将审查、研究和处理您编写的代码,以及火焰和统一引擎。非常感谢。
    猜你喜欢
    • 1970-01-01
    • 2016-06-05
    • 1970-01-01
    • 1970-01-01
    • 2021-09-03
    • 1970-01-01
    • 2019-10-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多