废话少说,上小程序码,大家可以先体验一下。
一、Flutter开发小程序想法的由来
大家好,我是移动端开发工程师小周,最近我上班的公司提出一个新需求,要求把公司产品的 移动端 APP 的所有查看功能在移植到小程序,而且时间限制在半个月 ,这可把我难住了,虽然只是查看的功能,但是也有几十个页面,而且之前都是用 Flutter
开发的。
直接用小程序开发是不敢想的,一来小程序开发不熟练,二来开发时间有限,而且UI组件都是定制的。所以我的第一想法是将 Flutter
打包成 Web
端代码,然后使用小程序的 WebView
加载,电脑浏览器运行还能接受,结果发现小程序的 WebView
都 自带顶部导航栏且不可隐藏 ,而且 兼容性问题很多,且性能很差,运行很卡 。
也许是上天眷顾,我无意间在微信公众号上看到了一篇文章,刚好就是说如何将 Flutter
代码转成小程序代码的,点进去看了,使用的是一个小程序开发框架: MpFlutter
我在接入过程中遇到了很多问题,比如文件大小限制、包大小限制、卡顿等问题,联系开发者 PonyCui
之后,都一一耐心解决了,最终小程序如期上线。
二、Flutter开发小游戏想法的由来
之前一直对小游戏开发比较好奇,于是研究了一下游戏如何开发,试用了 Cocos
等引擎,觉得开发工具很不习惯,且无从下手。
刚好发现 MpFlutter
也支持打包成微信小游戏,于是在咨询了 PonyCui
之后,决定使用 MpFlutter
开发一款小游戏。
三、开发流程
先根据 MpFlutter
的 官方文档 , 接入 MpFlutter
到项目里。
然后照常编写 Flutter
代码即可。
这里浅浅说一下开发五子棋的一些难点:
- 棋盘缩放(双指缩放/拖动棋盘)
- 滑动落子(拖动或点击落子)
- 延迟落子(读秒/双击落子)
- 落子确认(按钮确认落子)
- 禁手判断(三三、四四、长连)
- AI算法(人机对战)
这里分享一下 棋盘缩放
和 滑动落子
的实现,重点是解决双指和单指的冲突 ,用到了 InteractiveViewer
和 Listener
控件。
// 拖拽X坐标
double? dragX;
// 拖拽Y坐标
double? dragY;
// 是否正在缩放
bool isScaling = false;
// 是否正在落子
bool isMoving = false;
// 缩放比例
double scale = 1;
// 是否缩放变化
bool scaleChanged = false;
// 触点数量是否变化
bool pointerChanged = false;
// 上次双指操作时间
int lastTwoPointerTime = 0;
// 触点数量
int pointerCount = 0;
// 重置棋盘缩放
resetScale() {
setState(() {
transformationController.value = Matrix4.identity();
});
}
InteractiveViewer(
minScale: 1.0,
panEnabled: false,
transformationController: transformationController,
onInteractionUpdate: (details) {
double newScale = transformationController.value.getMaxScaleOnAxis();
scaleChanged = scale != newScale;
pointerCount = details.pointerCount;
if (details.pointerCount > 1) {
lastTwoPointerTime = Tools.currentTimeMillis();
dragX = null;
dragY = null;
}
if (pointerChanged) {
dragX = null;
dragY = null;
}
},
onInteractionEnd: (details) async {
if (details.pointerCount > 0 &&
pointerCount != details.pointerCount) {
pointerChanged = true;
}
scale = transformationController.value.getMaxScaleOnAxis();
pointerCount = details.pointerCount;
if (!scaleChanged &&
Tools.currentTimeMillis() - lastTwoPointerTime > 100 &&
dragX != null &&
dragY != null &&
pointerCount <= 1 &&
!pointerChanged) {
// 没缩放,落子
Chess? chess = _findChessOfPosition(dragX! + 13.r, dragY! + 13.r);
setState(() {
dragX = null;
dragY = null;
});
if (chess != null) {
// print("触发落子");
//todo 这里处理落子逻辑
}
} else {
setState(() {
dragX = null;
dragY = null;
});
}
if (pointerCount == 0) {
pointerChanged = false;
}
},
child: Listener(
onPointerCancel: (details) {
setState(() {
dragX = null;
dragY = null;
});
},
onPointerDown: (details) {
dragX = min(max(0, details.localPosition.dx - 13.r), maxX);
dragY = min(max(details.localPosition.dy - 13.r, 0), maxX);
},
onPointerMove: (details) {
setState(() {
if (pointerCount <= 1 && !pointerChanged) {
dragX = min(max(0, details.localPosition.dx - 13.r), maxX);
dragY = min(max(details.localPosition.dy - 13.r, 0), maxX);
} else {
dragX = null;
dragY = null;
}
});
},
//todo ... 绘制棋盘
),
)
四、注意事项
- 只能使用纯
dart
的三方库 main.dart
文件里尽量少引用库或者使用deferred
延迟加载,否则打包出来的js文件过大- 加载网络图片
useNativeCodec(url)
包装url - 小游戏分享功能需要使用微信api代码动态打开
wx.showShareMenu(ShowShareMenuOption()
..withShareTicket = true
..menus = ['shareAppMessage', 'shareTimeline']);
- 监听回到小程序
wx.onShow((p0) async {
Log.d("onShow");
});
- 获取启动参数
var enterOptionsSync = wx.getEnterOptionsSync();
五、小游戏上架过程
- 微信认证(2天,30元)
- 代码编写(2周)
- 提交代码审核(审核1周)
- 备案四部曲(审核1个半月)
1. 代码审核小插曲
代码提交上去之后,被拒了,理由是涉嫌和一个叫 全民口算
的小程序代码侵权。
当时我那叫一个冤啊,自己敲出来的代码,哪门子侵权,于是我去社区找找有没有解决方案,发现还有不少同样的情况,但是没有一个给出了明确的解决方案,官方的回答也不好理解。
其实,最直接的申诉方式,还是在微信公众平台后台小程序审核不通过的通知里,直接点点此反馈
,填写申诉理由,不过,这个申诉只有一次机会,我一开始着急没说清楚,申诉没通过。
后来和 PonyCui
沟通后, PonyCui
给我写了一个 申诉模板 ,然后我去社区发了帖子,但是没任何回应,最后,添加了小程序官方助手(minigame3
)的微信,得知现在都是通过 企业微信服务号 解决问题,然后在服务号上也发了申诉内容,不过服务号也是相当于留言形式,不会立即回复。
因此,我也没有闲着,我又去后台提交了一个小游戏版本,在版本更新说明里,放上了申诉说明,并附上了部分源代码的github链接。
最后,代码审核通过了。
2. 游戏内容介绍小插曲
游戏内容介绍里需要提供十几张截图,而且每一张图片都不能重复,一开始我就用了好几张重复图片,导致一直被打回,检测图片重复应该是机器审核,打回很快。
六、总结
这是我开发的第一款小游戏,之前也开发过几款小程序,发现小游戏的审核和上架过程比小程序要麻烦很多,审核时间也长很多,不过后续小游戏的运营和用户量才是一大挑战,希望好心人可以帮我转发一下文章(感谢♪(・ω・)ノ),也能吸引一些用户。
最后,非常感谢在这个过程中耐心帮助我的 PonyCui
,大家也可以参考我的总结,尝试用 MpFlutter
做一个自己的小程序或者小游戏,相信你一定会打开一个新世界的大门,并且在这个过程中收获满满。