如何用Flutter开发游戏

1

添加依赖包

首先在pubspec.yaml 里面添加依赖包,如下所示:

dependencies:
  flutter:
    sdk: flutter

  flame: ^0.17.2
  box2d_flame: ^0.4.4
  sensors: ^0.4.1+1

2

在main.dart中添加相关设置要素

接下来看看 main.dart 里面的代码,如下所示:

1import 'dart:async';
 2import 'package:flame/util.dart';
 3import 'package:flutter/material.dart';
 4import 'package:flutter/services.dart';
 5import 'game.dart';
 6
 7void main() async {
 8  // 启动游戏前要先设置flame的相关属性
 9  await setupFlame();
10  var game = new FlutterGame();
11  runApp(game.widget);
12}
13
14/// 构建Flame所需要的属性设置
15Future setupFlame() async {
16  // 创建 Util对象
17  var flameUtil = Util();
18  // 设置全屏
19  await flameUtil.fullScreen();
20  // 固定为纵向位置,不允许旋转
21  await flameUtil.setOrientation(DeviceOrientation.portraitUp);
22}

首先解释一下每个包的作用:

// MD组件
import 'package:flutter/material.dart';

// 我们需要Flame构建游戏
import 'package:flame/util.dart';

// setupFlame() 方法中需要用到
import 'package:flutter/services.dart';

// 异步支持
import 'dart:async';

// 具体的游戏界面
import 'FlutterGame.dart';

main()函数用了一个async表示异步,因为这涉及到里面的“setupFlame()”函数。setupFlame()函数是一个异步的,它里面主要是构建Flame所需要的属性设置,我们在启动游戏前要先设置flame的相关属性。

setupFlame() 函数里面做了3件事:

● 创建 Util对象

● 设置全屏

● 固定为纵向位置,不允许旋转

游戏部分的代码

接下来看看 game.dart 里面的代码,如下所示:

1import 'dart:ui';
 2
 3import 'package:box2d_flame/box2d.dart';
 4import 'package:flame/flame.dart';
 5import 'package:flame/game.dart';
 6
 7class FlutterGame extends Game {
 8  static const int WORLD_POOL_SIZE = 100;
 9  static const int WORLD_POOL_CONTAINER_SIZE = 10;
10
11  World world;
12
13  final Vector2 _gravity = Vector2.zero();
14
15  FlutterGame() {
16    world = new World.withPool(
17      _gravity,
18      DefaultWorldPool(WORLD_POOL_SIZE, WORLD_POOL_CONTAINER_SIZE),
19    );
20    initialize();
21  }
22
23  Future initialize() async {
24    resize(await Flame.util.initialDimensions());
25  }
26
27  void resize(Size size) {
28    super.resize(size);
29  }
30
31  @override
32  void render(Canvas canvas) {
33  }
34
35  @override
36  void update(double t) {
37  }
38}

解释一下上面的代码的意思:

FlutterGame类是具体游戏功能的实现,继承了Flame提供的Game类,所以可以放心大胆的写我们自己的功能,比如游戏循环或者调整大小事件等。Box2D将负责我们所有与物理相关的操作,这包括处理碰撞,加速/减速物体/质量以及模拟我们游戏中的不同形状/材料。

World是主要物理对象。

gravity 设置向量为0 表示无重力(gravity),Vector2你可以把它看做是平面二维坐标系统内的一个点,每个Vector2都有一个X和一个Y值。我们的gravity向量坐标值为(0,0)手机坐标系统左上角为(0,0),上部水平向右x轴正方向,左侧垂直向下位y轴正方向重力设置位(0,1)表示元素从屏幕顶部掉落到底部。

Box2D 需要用到 这两个常量值,这里面定义了两个,分别是:WORLD_POOL_SIZE和WORLD_POOL_CONTAINER_SIZE。

然后使用World.withPool创建World对象。

接下来调用initialize()方法,初始化我们所有需要的东西,包括:需要调整大小和不需要调整大小的东西。然后它的返回值调用Flame的初始化的方法,一旦Flutter准备就绪,立即调用resize()方法检查显示尺寸。

接下来看看几个函数的说明:

● render:在画布上绘制元素。

● update: 目标是60fps的速度进行,这意味着在一秒内将调用该函数60次。由于并不一直是60fps的速度,time参数会告诉你自上次调用以来经过了多少时间。

开启引擎,绘制元素

如果你按照以上步骤都都设置好了,这时候运行程序到你的设备,你会发现是一片黑色或空白的,因为我们还没有绘制任何元素在我们的界面上面。

接下来可以试着画一点东西上去。

新增代码如下所示:

Paint paint;
Size screenSize;
Rect _screenRect;
final int scale = 5;

void resize(Size size) {
  paint = Paint();
  paint.color = Color(0xffffffff);
  screenSize = size;
  _screenRect = Rect.fromLTWH(0, 0, screenSize.width, screenSize.height);
  super.resize(size);
 }

void render(Canvas canvas) {
  if (screenSize == null) {
    return;
  }
  canvas.save();
  canvas.scale(screenSize.width / scale);
  canvas.translate(_screenRect.width/2, _screenRect.height/2);
  canvas.drawCircle(Offset(0, 0), .1, paint);
  canvas.restore();
  }
}

这里发生的事情如下:

首先声明一个Paint对象,它是画笔,需要它来画一些东西。

screenSize和screenRect分别用于保存屏幕大小和相关的矩形以供以后使用。在resize()方法中,通过调用Flame的相关API,获得了size和Rect,然后再分别赋值给全局变量screenSize和screenRect。

定义一个比例因子(scale)这个变量来增加我们的应用程序中的元素。后面我们在物理交互中需要用到它。

render()函数内部主要是绘制的过程:

设置screenSize属性,确保我们准备好绘制了。然后保存画布。

调用scale()方法,设置缩放。使用屏幕的宽度除以比例因子,screenSize.width / scale,然后作为参数传入scale()方法。

使用translate()方法将画布的(0,0)点移动到屏幕中心位置。

使用drawCircle()方法,我们使用画图对象在(0,0)处绘制半径为0.1的圆

调用restore()方法将完成我们的渲染循环,仅显示一帧。

立即启动应用程序,应在屏幕中心附近绘制一个小圆圈,如下图所示:

最后我们把绘制部分的代码修改一下,加上重力传感器的代码,最终效果是这样的(具体的请进入Github查看源码),如下图所示:

源码及总结

本文的源码来自于GitHub:        

https://github.com/Dev-Owl/Mazeball

发表评论

电子邮件地址不会被公开。 必填项已用*标注