likes
comments
collection
share

flutter初体验,用flutter+go开发一个小游戏。纠结以后用vue还是flutter作为开发主力呢?

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

前言

早听说flutter多端兼容,上手容易,便学习了下,果然感受到对新手的友好气息。刚好我有个朋友用go开发了小游戏的后台,缺个前端,我便接下当练手项目。

游戏介绍

游戏地址:

http://139.159.234.134/

欢迎大家来体验。

游戏界面:

大厅 flutter初体验,用flutter+go开发一个小游戏。纠结以后用vue还是flutter作为开发主力呢?

房间 flutter初体验,用flutter+go开发一个小游戏。纠结以后用vue还是flutter作为开发主力呢?

游戏 flutter初体验,用flutter+go开发一个小游戏。纠结以后用vue还是flutter作为开发主力呢?

flutter初体验,用flutter+go开发一个小游戏。纠结以后用vue还是flutter作为开发主力呢?

UI设计我觉得还是不错的(doge)

玩法介绍:

该游戏是个抢卡得分游戏。抢牌阶段中,多个玩家从卡堆中抢卡,卡牌点数凑齐20点得分。出牌阶段中,玩家可用抢来的特殊卡干扰对手或调整自己卡牌点数,以凑齐20点得分。总共九个回合,回合结束后得分高者胜出。

开发感受

技术栈:

我朋友写后端也是用来练手的,所以学到的技术都一块整上了。

go后端

核心:微服务(consul+grpc),kong网关,gin,redis,mysql

其它:zap日志,viper,nacos,jwt,gorm,sentinal

同时设计了诸多管道以及协程,保证抢卡游戏的并发及安全

github地址:github.com/ppeew/twent…

前端

flutter以及一些插件。

UI用masterGo设计出来,然后手写页面并封装组件。下次再用flutter,就不手写了,整上别人的组件库。

github地址:github.com/unitiny/sna…

这么看来,还是后端技术整的活多。

开发难点:

需求:

后端传来的websocket消息是异步的,且消息里有个msgType字段,需要前端识别并使用不同处理函数去处理数据,进而更改游戏状态。

而前端需要异步接收消息,同步渲染组件,保证每种类型的消息都会渲染展示出来。

难点1:

对于前端通知并渲染组件的需求,我是用ProvidernotifyListeners去通知监听组件,而监听组件使用SelectorshouldRebuild方法去判断是否要重新渲染。于是,当 触发notifyListeners -》 触发shouldRebuild -》 触发组件重新build 时,又有一个websocket消息过来,触发了notifyListeners,若某组件正在build中,Provider就进行了优化,不会再通知组件重新渲染。即两次重新渲染合并成一次了,导致最新数据没被渲染出来。

因此需要同步渲染组件,等待所有监听组件都渲染完了,才进行下一次notifyListeners。虽然这样对性能会有一些影响,但保证需求实现更重要。

于是我封装了一个notify方法

Future notify() async {
  await _lock.acquire();
  try {
    // 等待渲染完成再开始下一个通知
    await SchedulerBinding.instance.endOfFrame;
    notifyListeners();
  } finally {
    _lock.release();
  }
}

使用锁是为了保证异步的websocket消息能同步触发notifyListeners。

获得锁后,还得等待所有监听组件都渲染完了,才进行notifyListeners。

难点2:

不同的监听组件需要消费不同msgType的消息,且要保证每个组件对同一消息只消费一次。

比如卡堆组件只有接收到卡堆数据的消息,才进行重新渲染。在渲染完成后,卡堆组件得标记该消息,下次不会再渲染。

因此我采用消息队列+isNotify方法来实现组件对同一消息只消费一次。

首先类里边的一些关键字段,msgList即消息队列

class UserWS extends ChangeNotifier {
  Map<String, dynamic> store = {}; // 储存的数据
  List<Map<String, dynamic>> msgList = []; // 消息队列
  Map<int, Map<String, dynamic>> res = {}; // 接收ws数据
  Map<int, Function(Map<String, dynamic>)> dealFunc = {}; // 不同消息的处理函数
}  

将ws消息加入消息队列,消息结构中,type标识消息类型,consumer是一个列表,记录已经消费过的组件id

void addMsg(int msgType) {
  // 不重要消息不放入队列
  List<int> notAddType = [ServiceType.checkHealthMsg];
  for (var type in notAddType) {
    if (type == msgType) {
      return;
    }
  }

  // 如果大于10条,清除旧消息
  if (msgList.length > 10) {
    msgList.removeRange(0, msgList.length - 10);
  }

  Map<String, dynamic> msg = {"type": msgType, "consumer": []};
  msgList.add(msg);
}

某个监听组件如下,通过isNotify方法判断是否要重新渲染

Selector<UserWS, UserWS>(
    shouldRebuild: (pre, next) =>
        next.isNotify(ServiceType.gameStateResponseType, id: 4),
    selector: (context, provider) => provider,
    builder: (context, userWS, child) {
      return Text(
        userWS.store["gameCurCount"] != null
            ? "${userWS.store["gameCurCount"]}/${userWS.store["gameCount"]}"
            : "${TheGame(context).curRound}/${TheGame(context).totalRound}",
        style: const TextStyle(fontSize: 20, color: Colors.white),
      );
    }),qu

isNotify方法如下,需要组件传入自己要监听的消息类型,以及组件的id,用于记录该组件消费情况。

首先要从消息列表中找到自己要渲染的消息,如果不存在,则返回false不更新组件。如果存在,则判断自己是否消费过该消息,已经消费过也返回false不更新组件,否则记录自己的id,并返回true更新组件

bool isNotify(int type, {int? id = 1}) {
  if (msgList.isEmpty) {
    return false;
  }

  for (var msg in msgList) {
    // 找到对应消息,并判断是否有消费过
    if (msg["type"] == type) {
      int index = msg["consumer"].indexOf(id);
      if (index == -1) {
        msg["consumer"].add(id);
        return true;
      }
    }
  }
  return false;
}

最终整个接收并处理ws消息的代码如下:

void receiveData(data) async {
  print("websocket-data:$data");

  // 接收消息
  // 由于异步,相同type消息会被覆盖,而res是个map,保证不同type的消息不会被覆盖
  Map<String, dynamic> msg = json.decode(data);
  res[msg["msgType"]] = msg;

  // 步骤不能错,先处理完数据,再将消息加入消息队列,最后才通知组件渲染
  
  await dealFunc[msg["msgType"]]!({"msgType": msg["msgType"]}); // 根据不同消息类型调用不同处理函数
  
  addMsg(msg["msgType"]); // 加入消息队列
  
  notify(); // 同步通知监听组件
}

体验:

用flutter开发前端体验还是很不错的

  1. 借鉴了react的思想,将UI和逻辑整合到一个组件类里,内聚性高,因此开发起来特别迅速。
  2. 方法中可返回Widget,数据渲染及组件分离很方便。
  3. 内置组件Column,Row,Stack,在布局上既方便快速,又保证兼容性。
  4. 方方面面都是类,会不自觉地封装组件,或者继承原组件并扩展一些功能。vue也可以做到,但封装的想法没这么强烈,封装起来也没有类方便。

当然flutter的缺点也有

  1. 对web兼容还不是很好,比如我使用Image.network()时,在安卓上无报错,但web上用不了,得改用 HtmlElementView + ImageElement代替。
  2. web端和安卓端还是要各自开一个项目编码。比如要使用兼容web端的HtmlElementView组件,得导入dart:html库。而安卓环境下没有这个库,是无法在编译运行的。因此,正确做法应先开发web端。完成后再复制项目到安卓环境下运行,将无法编译的部分更换成安卓端的写法。

vue和flutter该怎么选择呢

对于vue和flutter,以后开发用哪个作为主力呢?

我之前也很犹豫这类问题,后来一想,这就是一个用锤子还是用扳手问题,当然得看场景使用,哪个更方便更贴合需求就用哪个呗。因此:

  • 要求多端兼容,但偏向web端,那我会用vue+uniapp开发
  • 要求多端兼容,但偏向安卓端或桌面端,那我会用flutter开发
  • 多端兼容且平等,vue和flutter哪个熟练,开发快就用哪个。
  • 多端部分兼容,那么兼容部分复用,不兼容或需求不同部分各自改动,vue和flutter都可胜任。
  • 难以兼容或需求大为不同,干脆分开各自开发,web端用vue,安卓端/桌面端用flutter。