likes
comments
collection
share

【用flutter做点啥捏】(一)飘雪

作者站长头像
站长
· 阅读数 16

前言

大家好,我是一名it小菜鸟,寻思着业余时间写写笔记,方便以后的翻看,并养成一个好的习惯👉🏻与君共勉


场景说明

本文使用 Flame引擎来简单的实现一个下雪的小场景。

  • 游戏组件的挂载
  • 简易雪花的绘制
  • 浪漫的雪花场景

【用flutter做点啥捏】(一)飘雪

正文

1.添加项目依赖

首先在 pubspec.yaml 中引入 flame 包。

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2
  flame: ^1.8.1
2.代码结构

目前lib 的代码结构如下:

├── lib 
│   ├── component
│   │   ├──snow_background.dart //背景组件
│   │   └──snow_sprite.dart  //雪花组件
│   │
│   ├── game
│   │   └──game_snow.dart //游戏主入口
│   │
│   └── main.dart //程序主入口
3.代码入口

在 main.dart 中的runApp 方法中传入 GameWidget 组件,其中 game 入参对象是自定义的 GameSnow ,继承自 FlameGame ,并重写 onLoad 方法,添加"背景组件"和多个自定义的"雪花组件" SnowSprite 。

---->[main.dart]<----

void main() {
  runApp(GameWidget(game: GameSnow()));
}


---->[game_snow.dart]<----

class GameSnow extends FlameGame {
  @override
  FutureOr<void> onLoad() async {
    await super.onLoad();
    //第一层:背景组件挂载
    add(SnowBackGround());

    //最外层:雪花组件挂载
    List.generate(500, (index) {
      add(SnowSprite());
    });
  }
}

4.组件讲解

在flutter 中一切皆组件,那flame框架也应如此。今天介绍的主角就是这个组件CustomPainterComponent,简单瞄一下源码,对绘制熟悉的小伙伴是不是嘴角漏出了浅浅的微笑呢😁😁😁,仅有一个属于自己的入参painter他的类型是CustomPainter

【用flutter做点啥捏】(一)飘雪

接下来咱们介绍下雪花组件,根据上图可知,其父类是CustomPainterComponent,职责:"用画笔来绘制自己图案的",再往上走就看到了CustomPainterComponent的父类:PositionComponent,简单瞄一眼,它具有大小位置等属性。ok开搞。

【用flutter做点啥捏】(一)飘雪

首先混入HasGameRef这个mixin,混入他的目的就是为了得到游戏窗口的相关信息,比如游戏窗口的尺寸信息。 紧接着重写onLoad方法,对雪花的大小位置形状、以及下落速度的定义。

  • 大小:取了大小5~15的随机数(宽高1:1,雪花如果都一样大,那就不好看了)。
  • 位置:在整个游戏窗口取随机数(不然刚开始的时候,有的地方齐刷刷的空白不太好看)。
  • 速度:取1~2的随机数(参差错落、大家可根据自己的喜好调节)。
  • 形状:一种是绘制一个小白点、一种是绘制简易的小雪花。
class SnowSprite extends CustomPainterComponent with HasGameRef<GameSnow> {
  double speed = 1;

  @override
  FutureOr<void> onLoad() async {
    super.onLoad();
    size = getRandomSize();
    position = getRandomPosition();
    painter = SnowPainter();
    speed = Random().nextDouble() * 1 + 1;
  }

  ///屏幕内的随机数
  Vector2 getRandomPosition() {
    double x = Random().nextDouble() * gameRef.size.x;
    double y = Random().nextDouble() * gameRef.size.y;
    return Vector2(x, y);
  }

  ///5~15的大小
  Vector2 getRandomSize() {
    double size = Random().nextDouble() * 10+5;
    return Vector2(size, size);
  }

  @override
  void update(double dt) {
    super.update(dt);
    //下落的过程如果超出屏幕的话,将y坐标从头开始,大小和横向坐标重新随机
    if (position.y > gameRef.size.y) {
      position.y = 0;
      position.x = Random().nextDouble() * gameRef.size.x;
      size = getRandomSize();
    }
    //update 毎帧都会被执行到,所以下降距离就等于单位时间内的速度
    position.y += speed;
  }
}

其实形状这块可以简单也可以复杂,看个人追求,我呢就取简单的了。如果有人觉得自己的绘制能力突出,可以绘制一些炫酷的雪花来;如果有人觉得自己一点绘制都不会,不要担心后续文章还会涉及到其他得精灵组件,不用绘制也能达到很好的效果。本文讲到了两个简单实现方案:

  • 用小圆点"顶替"雪花: 在_drawSnowCircle方法中,绘制一个白色的填充圆即可。

  • 绘制简易雪花: 在_drawSnowCustom方法中,首先将画布移到了中心位置,然后绘制一条线,线的宽度为雪花的宽度,在距离中心点左右1/6和2/6位置处分别绘制一定长度的竖线,然后经过4次旋转,一个小雪花就被绘制出来了。

class SnowPainter extends CustomPainter {
  late final Paint snowPaint;

  SnowPainter() {
    snowPaint = Paint();
    snowPaint.strokeWidth = Random().nextDouble() + 0.5;
    snowPaint.color = Colors.white;
  }

  @override
  void paint(Canvas canvas, Size size) {
    _drawSnowCustom(canvas, size);
    // _drawSnowCircle(canvas, size);
  }

  @override
  bool shouldRepaint(covariant SnowPainter oldDelegate) {
    return true;
  }

  void _drawSnowCircle(Canvas canvas, Size size) {
    canvas.save();
    double r = size.width / 2;
    canvas.translate(r, r);
    canvas.drawCircle(Offset.zero, r, snowPaint);
    canvas.restore();
  }

  void _drawSnowCustom(Canvas canvas, Size size) {
    canvas.save();
    //半径
    double r = size.width / 2;
    //内圈点位
    double dxInner = size.width / 6;
    double dyInner = dxInner / 3;
    //外圈点位
    double dxOuter = dxInner * 2;
    double dyOuter = dxOuter / 3;

    //圆心
    canvas.translate(r, r);
    for (int i = 0; i < 4; i++) {
      //旋转弧度
      canvas.rotate(pi / 180 * 45 * i);
      canvas.drawLine(Offset(-r, 0), Offset(r, 0), snowPaint);
      //内线
      canvas.drawLine(Offset(-dxInner, -dyInner), Offset(-dxInner, dyInner), snowPaint);
      canvas.drawLine(Offset(dxInner, -dyInner), Offset(dxInner, dyInner), snowPaint);
      //外线
      canvas.drawLine(Offset(-dxOuter, -dyOuter), Offset(-dxOuter, dyOuter), snowPaint);
      canvas.drawLine(Offset(dxOuter, -dyOuter), Offset(dxOuter, dyOuter), snowPaint);
    }
    canvas.restore();
  }
}

同理贴一下背景(浅灰和蓝灰相间的图案)绘制的代码SnowBackGround:

class SnowBackGround extends CustomPainterComponent with HasGameRef<GameSnow> {
  @override
  FutureOr<void> onLoad() async {
    super.onLoad();
    size = gameRef.size;
    position = Vector2.zero();
    painter = SnowBackGroundPainter();
  }

  //游戏窗口发生变化的监听
  @override
  void onGameResize(Vector2 size) {
    this.size = size;
    super.onGameResize(size);
  }
}

class SnowBackGroundPainter extends CustomPainter {
  late final Paint snowBackgroundPaint;

  final double sizeUnit = 10;

  SnowBackGroundPainter() {
    snowBackgroundPaint = Paint();
    snowBackgroundPaint.strokeWidth = Random().nextDouble() + 0.5;
    snowBackgroundPaint.color = Colors.white;
  }

  @override
  void paint(Canvas canvas, Size size) {
    _drawSnowBackground(canvas, size);
  }

  @override
  bool shouldRepaint(covariant SnowBackGroundPainter oldDelegate) {
    return true;
  }

  ///绘制背景
  void _drawSnowBackground(Canvas canvas, Size size) {
    canvas.save();
    //横向格子数量
    int hNum = (size.width / sizeUnit).ceil();
    //纵向格子数量
    int vNum = (size.height / sizeUnit).ceil();
    //绘制
    for (int v = 0; v < vNum; v++) {
      for (int h = 0; h < hNum; h++) {
        _chooseColor(h, v);
        canvas.drawRect(
          Offset(h * sizeUnit, v * sizeUnit) & Size.square(sizeUnit),
          snowBackgroundPaint,
        );
      }
    }
    canvas.restore();
  }

  ///h 横向第几格
  ///v 纵向第几格
  void _chooseColor(int h, int v) {
    //处理第一列
    if (v % 2 == 0 && h == 0) {
      snowBackgroundPaint.color = Colors.blueGrey;
      return;
    } else if (h == 0) {
      snowBackgroundPaint.color = Colors.grey.shade500;
      return;
    }

    //剩余反向取色
    if (snowBackgroundPaint.color.value == Colors.blueGrey.value) {
      snowBackgroundPaint.color = Colors.grey.shade500;
    } else {
      snowBackgroundPaint.color = Colors.blueGrey;
    }
  }
}

完 ~