preface

I’ve previously created a “Man for 100 seconds” game with Flare, the game engine inside Flutter, here

With the Flare engine, the unique code style of the Flutter application is completely absent. Although more suitable for developers like me with previous game development experience, it is not conducive for us to learn the Flutter framework. That’s why I said at the end of the article that I should rewrite the game with widgets sometime.

The first thing to do is to have a Widget that supports “Sprite maps”. If you’re learning, you can’t develop the Widget by someone else. You have to build the wheel yourself.

What is a Sprite map?

It’s called a spritesheet, which is a way to put multiple shapes on a single chart that only needs to be loaded into memory once. During presentation, only areas of a single figure are shown. Generally, multiple graphics are used to place multiple key frames of continuous animation. In addition to being common in game engines, it is also common in the front-end domain to reduce Web requests.

Principle of dismantling

Load a large image, but only show a specific area of the image at a time

For example, in this Sprite image of an airplane, the size is 330×82 (pixels), and the size of each image is 330/5 = 66. The area we show each time is x=66* screen number, y=0, width=66, height=82.

Horizontal or vertical arrangement can be set

Sprites can be laid out horizontally or vertically, and some game engines have tiles with a maximum size of 4096×4096, so there are some situations where we need to switch lines, but the principle is not that different and I won’t discuss it here.

You can set the playing time interval, automatically switch multiple continuous areas

Most of the time we need sprites to show animations, like this airplane Sprite. The first and second pictures are used to show the animation of the flight state of the aircraft, which needs to be played in a loop.

The third, fourth and fifth frames are used to show the animation of the airplane explosion, which only needs to be played once.

Think about which widgets to build with

Take a look at what widgets we need with an animated demo

  • Widgets (Containers) that control the display area
  • You need a Widget (Stack+Positioned) that can give you coordinates.

Now that I know how it works and what widgets to use, the rest of the code is easy, right

Turn ideas into code

@override
Widget build(BuildContext context) {
return Container(
    width: 66,
    height: 82,
    child: Stack(
      children: [
        Positioned(
          left: 66*currentIndex,
          top: 0,
          child: widget.image
        )
      ],
    ),
);
}
Copy the code

Add a timer and change the currentIndex at a set interval so the image looks like it’s moving.

Timer.periodic(widget.duration, (timer) { 
    setState(() {
      if(currentIndex>=4){
        currentIndex=0;
      }
      elsecurrentIndex++; }); }});Copy the code

Let’s take this one step further and wrap it into our own Widget. Here’s the code for this Widget

import 'dart:async';

import 'package:flutter/widgets.dart';

class AnimatedSpriteImage extends StatefulWidget {

  final Image image;
  final Size spriteSize;
  final int startIndex;
  final int endIndex;
  final int playTimes;
  final Duration duration;
  final Axis axis;

  AnimatedSpriteImage({
    Key? key,
    required this.image,
    required this.spriteSize,
    required this.duration,
    this.axis = Axis.horizontal,
    this.startIndex = 0.this.endIndex = 0.this.playTimes = 0.//0 = loop
  }) : super(key: key);

  @override
  _AnimatedSpriteImageState createState() => _AnimatedSpriteImageState();
}

class _AnimatedSpriteImageState extends State<AnimatedSpriteImage> {

  int currentIndex = 0;
  int currentTimes = 0;

  @override
  void initState() {

    currentIndex = widget.startIndex;

    Timer.periodic(widget.duration, (timer) { 
      if(currentTimes<=widget.playTimes){
        setState(() {
          if(currentIndex>=widget.endIndex){
            if(widget.playTimes! =0)currentTimes++;
            if(currentTimes<widget.playTimes||widget.playTimes==0)currentIndex=widget.startIndex;
            else currentIndex = widget.endIndex;
          }
          elsecurrentIndex++; }); }});super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        width: widget.spriteSize.width,
        height: widget.spriteSize.height,
        
        child: Stack(
          children: [
            Positioned(
              left: widget.axis==Axis.horizontal?-widget.spriteSize.width*currentIndex:0, top: widget.axis==Axis.vertical? -widget.spriteSize.height*currentIndex:0, child: widget.image ) ], ), ); }}Copy the code

Well packaged and especially easy to use.

// Play the animation of aircraft flight status
AnimatedSpriteImage(
  duration: Duration(milliseconds: 200),// The interval of the animation
  image: Image.asset("assets/images/player.png"),/ / the elves figure
  spriteSize: Size(66.82),// Single screen size
  startIndex: 0.// Start screen number of animation
  endIndex: 1.// Animation end screen number
  playTimes: 0.// Number of playback times, 0 is a loop
)

// Play plane explosion animation
AnimatedSpriteImage(
  duration: Duration(milliseconds: 200),// The interval of the animation
  image: Image.asset("assets/images/player.png"),/ / the elves figure
  spriteSize: Size(66.82),// Single screen size
  startIndex: 2.// Start screen number of animation
  endIndex: 4.// Animation end screen number
  playTimes: 1.// Number of playback times, 0 is a loop
)
Copy the code

Pay attention to a guard

An old programmer who loves front-end development and only shares content on three platforms

  • The Denver nuggets
  • B station: Handsome old ape
  • Wechat official account: Da Shuai Lao Ape