Periodic movement of element. Problem with small delay in Animation

Issue

So I create game and I want to make movement of the enemy. The simple movement for now is just going right-left all the time. I want to make it in a way that I put next move into array so I don’t want to make infinite loop but rather a mechanism of applying new animations.

AnimationController enemy;
Animation _enemyAnimation;
  

void initEnemyAnim(double from, double to, Duration in_time ) {

    enemy = AnimationController(
    vsync: this,
    duration: in_time);

    _enemyAnimation = Tween<double>(begin: from, end: to).animate(CurvedAnimation(parent: enemy, curve: Curves.linear));
    _enemyAnimation.addListener(() {
      _changeMove(_enemyAnimation.value); // this function steers my character position
    });
  }

  void runAi() async {


    double from = 0.0;
    double to = 800.0;

    await Future.delayed(Duration(milliseconds: 8200)); // it's intro, doesnt matter
  
    const oneSec = Duration(seconds:1);

   
    double temp = 0.0;
   
    initEnemyAnim(from, to, Duration(milliseconds: 1000));
    enemy.forward();

    temp=to;
    to=from;
    from=temp;

    Timer.periodic(oneSec, (Timer t)  {
      debugPrint("run $_enemyAnimation.value");

      enemy.reset();
      initEnemyAnim(from, to, Duration(milliseconds: 1000));
      enemy.forward();

      temp=to;
      to=from;
      from=temp;
                
    });

    
  }

the position of enemy is range between 0 and 800.
I use TickerProviderStateMixin for AnimationController

for delegating a movement I use Timer.periodic.

The thing is that every 2 periodic actions there is like 200milliseconds(or more actually) of pause.

My enemy is moving towards right at start. It’s allright.
It’s going left and instantly its goin right. Excellent.
Then it is some kind of pause. I don’t know why
It behaves like windshield wipers. It’s going left right pause left right pause.

I want to fix it in a way that it moves left right left right …

When I use to add next animation using

_enemyAnimation.addStatusListener((status) {
      
      if(status == AnimationStatus.completed){ }
}

It also has this lag

Do you have idea how to chain and add up to animation in a way that there is no break between them ?

Solution

  1. The animation controller is not supposed to be reassigned when the animation is changing. Just reassign the animation. The controller should be assigned once in initState and disposed in dispose.
  2. Do not rely on a separate Timer to change the animation. Use the controller listener:
enemy.addListener(() {
  if(enemy.isCompleted) {
    _enemyAnimation = ... // Change animation here
    enemy.forward()
  }
});
  1. I guess that you are calling setState in _changeMove which is not recommended as you are building the whole widget tree when the value changes. Use AnimatedBuilder instead and provide the animation.
  2. If it’s only about aligning top, left, right and bottom then use Alignment instead of double.

Full code example:

import 'dart:collection';

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin{
 
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Material App',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Material App Bar'),
        ),
        body: AnimationWidget(),
      ),
    );
  }
}

class AnimationWidget extends StatefulWidget {

  @override
  State<AnimationWidget> createState() => _AnimationWidgetState();
}

class _AnimationWidgetState extends State<AnimationWidget> with SingleTickerProviderStateMixin {

   late AnimationController _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 1)
   );

  late Animation<Alignment> _animation;

  var topLeft = Alignment.topLeft;
  var topRight = Alignment.topRight;
  var bottomLeft = Alignment.bottomLeft;
  var bottomRight = Alignment.bottomRight;

  @override
  void initState() {
    _animation = Tween<Alignment>(begin: topLeft, end: topRight)
    .animate(_controller);
     _controller.addListener(() {
      if (_animation.isCompleted && directions.isNotEmpty) {
        changeAnimation();
      }
    });
    _controller.forward();
    super.initState();
  }

  late Queue<Alignment> directions = Queue.from([
    topLeft,
    bottomLeft,
    bottomRight
  ]);

  @override
  Widget build(BuildContext context) {
    print(_animation);
    return Stack(
      alignment: Alignment.center,
      children: [
        AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Align(
              alignment: _animation.value,
              child: Container(
                color: Colors.red,
                width: 50,
                height: 50,
              ),
            );
          }
        ),
        Center(
          child: ButtonBar(
            alignment: MainAxisAlignment.center,
            children: [
              TextButton(
                child: Text("Up"),
                onPressed: () {
                  press(Alignment(_animation.value.x, -1));
                },
              ),
              TextButton(
                child: Text("Down"),
                onPressed: () {
                  press(Alignment(_animation.value.x, 1));
                },
              ),
              TextButton(
                child: Text("Left"),
                onPressed: () {
                  press(Alignment(-1, _animation.value.y));
                },
              ),
              TextButton(
                child: Text("Right"),
                onPressed: () {
                  press(Alignment(1, _animation.value.y));
                },
              ),
            ],
          ),
        )
      ],
    );
  }

  void press(Alignment alignment) {
    if (alignment == _animation.value) return;
    directions.add(alignment);
    if (!_controller.isAnimating) changeAnimation();
  }

  void changeAnimation() {
     _animation = Tween<Alignment>(
        begin: _animation.value, 
        end: directions.removeFirst()
    ).animate(_controller);
    _controller.reset();
    _controller.forward();
  }
}

Answered By – TripleNine

Answer Checked By – David Marino (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *