前言
大家好,我是一名it
小菜鸟,寻思着业余时间写写笔记,方便以后的翻看,并养成一个好的习惯👉🏻与君共勉
。
场景说明
本文使用 Flame
引擎来简单的实现一个下雪的小场景。
- 游戏组件的挂载
- 简易雪花的绘制
- 浪漫的雪花场景
正文
1.添加项目依赖
首先在 pubspec.yaml
中引入 flame
包。
yaml
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
flame: ^1.8.1
2.代码结构
目前lib
的代码结构如下:
dart
├── lib
│ ├── component
│ │ ├──snow_background.dart //背景组件
│ │ └──snow_sprite.dart //雪花组件
│ │
│ ├── game
│ │ └──game_snow.dart //游戏主入口
│ │
│ └── main.dart //程序主入口
3.代码入口
在 main.dart
中的runApp
方法中传入 GameWidget
组件,其中 game
入参对象是自定义的 GameSnow
,继承自 FlameGame
,并重写 onLoad
方法,添加"背景组件"和多个自定义的"雪花组件" SnowSprite
。
dart
---->[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
接下来咱们介绍下雪花组件
,根据上图可知,其父类是CustomPainterComponent
,职责:"用画笔来绘制自己图案的",再往上走就看到了CustomPainterComponent的父类:PositionComponent
,简单瞄一眼,它具有大小
、位置
等属性。ok开搞。
首先混入HasGameRef
这个mixin,混入他的目的就是为了得到游戏窗口的相关信息,比如游戏窗口的尺寸信息。 紧接着重写onLoad
方法,对雪花的大小
、位置
、形状
、以及下落速度
的定义。
- 大小:取了大小5~15的随机数(宽高1:1,雪花如果都一样大,那就不好看了)。
- 位置:在整个游戏窗口取随机数(不然刚开始的时候,有的地方齐刷刷的空白不太好看)。
- 速度:取1~2的随机数(参差错落、大家可根据自己的喜好调节)。
- 形状:一种是绘制一个小白点、一种是绘制简易的小雪花。
dart
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次旋转,一个小雪花就被绘制出来了。
dart
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
:
dart
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;
}
}
}
完 ~