说明
小编也是初学者,为了了解flutter动画的使用与效果, 决定亲自定手用flutte写一款小游戏出来. 并将过程中的跳过的坑记录下来.
开发准备
具体参考flutter环境搭建, 笔者环境信息
- Android Studio 3.2
- macOS 10.14
- flutter v1.1.10-pre.136
- dart 2.0
New Flutter Project
我们大概目录为:
- assets
- images
- lib
- main.dart
- src
- enter.dart
main.dart是我们代码的主入口. lib.src用来存放我们整个游戏的逻辑代码文件. assets则是用来存放我们的图片资源文件.
项目入口 main.dart
在这个文件中, 我们可以制作一个菜单, 先保留着. 我们只留一下按钮, 点击按钮后. 将我们的游戏界面, 入栈到Router中, 开始我们的游戏.部份代码如下:
RaisedButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return GameEnter();
}));
},
child: Text("开始游戏")
)
游戏入口enter.dart
enter.dart是我们整个游戏的主入口. 在这个入口中, 我们加载资源, 进行整体的绘图操作.
我们在enter.dart中定义我们的主画板, 关于CustomPaint的说明参考: 官方DOC,
MainPainter 继承自 CustomPainter, 按官方的说明我们继承并实现他的二个方法 paint 和 shouldRepaint, 在当前状态下. 整个界面是空白的, 什么都没有. 接下来我们定义我们的游戏背景.
// CustomPaint.painter
class MainPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// TODO: implement paint
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return oldDelegate != this;
}
}
// GameEnter.build
Widget build(BuildContext context) {
return CustomPaint(
painter: MainPainter(),
);
}
游戏背景
我们在 src 下, 新建一个叫做bg.dart的文件, 并新建一个 Background的类, init函数, 是用来在Enter 中加载我们游戏所需要的资源文件, paint 函数是用来在 MainPainter 中绘制我们的背景动画.
class Background {
// 初始背景的偏移量
double offsetY = -100.0;
// 屏幕的宽度
double screenWidth;
// 屏幕的高度
double screenHeight;
// 画布滚动的速度
double speed = 10;
// 加载的背景图片
ui.Image image;
// 二张背景图的纵坐标点
double y1 = 100.0;
double y2 = 0.0;
// 构造函数
Background();
// 初始化, 各种资源
Future init() async {
return null;
}
// 绘图函数
paint(Canvas canvas, Size size) async {
Rect screenWrap = Offset(0.0, 0.0) & Size(screenWidth, screenHeight);
Paint screenWrapPainter = new Paint();
screenWrapPainter.color = Colors.red;
screenWrapPainter.style = PaintingStyle.fill;
canvas.drawRect(screenWrap, screenWrapPainter);
}
}
Enter入口绘制背景
接下来我们要将我们的游戏背景真正的绘制在我们的手机上. 我们在 Enter 的初始化函数 initeState 中 初始化 Background 实例, 并进行资源初始化. 然后在 MainPainter 的绘图接口上, 增加我们的绘图逻辑
void paint(Canvas canvas, Size size) {
background.paint(canvas, size);
}
运行效果如下
接下来我们需要将游戏的背景图绘制到背景中, 这里我们调用的是
Canvas.drawImage(Image image, Offset offset, Painter paint) API
在 Background.paint 函数中我们增加以下代码, 然后执行
Paint paint = new Paint();
canvas.drawImage(image, Offset(0, 0), paint);
效果如下:
让背景动起来
在本次探动画探究中, 我使用 AnimationController 与 CurvedAnimation 完成我们的效果. 有关这二个类的具体文档参考AnimationController 与 CurvedAnimation.
我们先在 Enter 的 initeState 中声明二个实例,
animation = CurvedAnimation(
parent: controller,
curve: Curves.linear,
);
animation.addListener(() {
setState(() {});
});
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.repeat();
}
});
controller在有几个控制动画的方法
- forward() 向前运动
- stop() 停止
- reverse() 向后运动 (这个概念, 我也没懂. 暂时搁这)
我们去监听 animation 每次动画值的改变, 增加监听函数, 通过 setState 去触发当前视图的刷新.
我们去监听 animation 动画状态, 判断是否动画结束,从而调用 repeat 方法, 使动画一直循环下去.
通过以上代码. 运行后发现, 每一帧都会触发 Background的重绘, 通过这点我们每次更改背景图起绘点的坐标, 就可以达到动画的效果.
paint(Canvas canvas, Size size) async {
...
y1 += 10;
}
让背景循环起来
在正常的2D飞机类游戏中, 游戏的背景是循环滚动的 ,常见的处理方法是, 二张背景图,头尾相连循环绘制, 当其中某个背景图, 滚出屏幕视野, 将其重新定位到上一张背景图的正上方, 来回往复, 从而达到背景循环滚动的效果. 在这里我们为二张图景图, 起绘点坐标增加以下的逻辑,
y1 = y1 + 1 * speed;
y2 = y2 + 1 * speed;
if (y2 > image.height) {
y2 = y1 - image.height;
}
if (y1 > image.height) {
y1 = y2 - image.height;
}
在这次项目中, 由于我找到的背景图比较小, 没有办法撑满整个屏幕, 所以我在绘制的时候, 将Canvas进行了缩放操作.
canvas.scale(1, screenHeight / image.height);
最后让我们看一下效果:
总结
第一部份, 大工告成. 在接下来几天. 我会把其他的元素的相关逻辑加上.
git传送门