flutter初体验,用flutter+go开发一个小游戏。纠结以后用vue还是flutter作为开发主力呢?
前言
早听说flutter多端兼容,上手容易,便学习了下,果然感受到对新手的友好气息。刚好我有个朋友用go开发了小游戏的后台,缺个前端,我便接下当练手项目。
游戏介绍
游戏地址:
欢迎大家来体验。
游戏界面:
大厅
房间
游戏
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:
对于前端通知并渲染组件的需求,我是用Provider
的notifyListeners
去通知监听组件,而监听组件使用Selector
的shouldRebuild
方法去判断是否要重新渲染。于是,当 触发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开发前端体验还是很不错的
- 借鉴了react的思想,将UI和逻辑整合到一个组件类里,内聚性高,因此开发起来特别迅速。
- 方法中可返回Widget,数据渲染及组件分离很方便。
- 内置组件Column,Row,Stack,在布局上既方便快速,又保证兼容性。
- 方方面面都是类,会不自觉地封装组件,或者继承原组件并扩展一些功能。vue也可以做到,但封装的想法没这么强烈,封装起来也没有类方便。
当然flutter的缺点也有
- 对web兼容还不是很好,比如我使用Image.network()时,在安卓上无报错,但web上用不了,得改用 HtmlElementView + ImageElement代替。
- web端和安卓端还是要各自开一个项目编码。比如要使用兼容web端的HtmlElementView组件,得导入dart:html库。而安卓环境下没有这个库,是无法在编译运行的。因此,正确做法应先开发web端。完成后再复制项目到安卓环境下运行,将无法编译的部分更换成安卓端的写法。
vue和flutter该怎么选择呢
对于vue和flutter,以后开发用哪个作为主力呢?
我之前也很犹豫这类问题,后来一想,这就是一个用锤子还是用扳手问题,当然得看场景使用,哪个更方便更贴合需求就用哪个呗。因此:
- 要求多端兼容,但偏向web端,那我会用vue+uniapp开发
- 要求多端兼容,但偏向安卓端或桌面端,那我会用flutter开发
- 多端兼容且平等,vue和flutter哪个熟练,开发快就用哪个。
- 多端部分兼容,那么兼容部分复用,不兼容或需求不同部分各自改动,vue和flutter都可胜任。
- 难以兼容或需求大为不同,干脆分开各自开发,web端用vue,安卓端/桌面端用flutter。
转载自:https://juejin.cn/post/7258607903585353784