Flutter&Flame 游戏实践#22 | 全平台游戏盒#1,从零基础到精通,收藏这篇就够了!

.markdown-body pre,.markdown-body pre>code.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-built_in,.hljs-class .hljs-title{color:#e6c07b}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}

Flutter&Flame 游戏开发系列前言:

该系列是 [张风捷特烈] 的 Flame 游戏开发教程。Flutter 作为 全平台原生级 渲染框架,兼具 全端 跨平台和高性能的特点。目前官方对休闲游戏的宣传越来越多,以 Flame 游戏引擎为基础,Flutter 有游戏方向发展的前景。本系列教程旨在让更多的开发者了解 Flutter 游戏开发。

第一季:30 篇短文章,快速了解 Flame 基础。[已完结]
第二季:从休闲游戏实践,进阶 Flutter&Flame 游戏开发。

两季知识是独立存在的,第二季 不需要 第一季作为基础。本系列教程源码地址在 【toly1994328/toly_game】,系列文章列表可在《文章总集》【github 项目首页】 查看。


0. 简介与本文目标

距离上一篇已经七个多月了,Flame 最新版本已经来到 1.26.0,之前用的还是 1.18.0。以前我们已经通过Flame 搭建了不少小游戏,接下来打算做一个 全平台游戏盒 ,将之前的东西整合到一个全平台的 App 里:

TolyGameBox 游戏盒计划有几个好处:

    1. 可以借此将之前代码的升级到 Flame 1.26.0。
    1. 可以将之前零碎的东西整合在一起。
    1. 可以基于 Flutter 3.29.x 搭建一个全平台的应用,打通游戏和应用。
    1. 可以作为我的 fx 架构 和 tolyui 的一块试验田。

游戏盒将采用模块化的设计,每个游戏会作为独立的模块维护,每个模块可以独立运行,以便单独维护开发。新版代码将在 toly_game/game_box 分支进行开发。 Github 开源地址 (多多Star 哦~)


TolyGameBox 将先在桌面端开发,后续适配移动端。本文主要目的是:

  • [1]. 搭建如下整体结构
  • [2]. 处理界面导航
  • [3]. 展示出游戏中心数据
  • [4]. 完成扫雷游戏的接入


1. 应用启动

TolyGameBox 将使用 tolyuifx_framework 进行构建。其中:

  • tolyui 主要是 视图层框架,拓展了更多的 Flutter 组件,更易于快速搭建常用的视图效果;
  • fx_framework 是 应用层框架 ,它脱离视图,封装 App 开发所需的常用功能。比如应用启动、路由拓展、数据库、网络请求等。

相较而言, tolyui 中的组件随意在任何 Flutter 项目中使用,是非常灵活无侵入的。而 fx_framework 则是希望制定一套 App 开发的流程,便于快速开发,但必须遵循 fx 的使用方式。fx_framework 目前处于尝试阶段 api 并不稳定,感兴趣的朋友可以体验一下:

--->[pubspec.yaml]---
dependencies:
  tolyui: 0.0.4+8
  fx_framework: 0.0.1



对于一个应用程序而言,如何优雅地启动是个问题。比如加载资源的时机、确定异常检测、成功时跳转等。 fx_framework 中的 fx_boot_starter 模块封装了启动流程,它规定了在哪里写初始化的逻辑、如何监听启动的状态。
如下所示,进入应用时展示 Splash 界面,资源加载成功后跳转到首页:


启动的代码如下,主要在 starter 文件夹下,其中自定义了 TolyGameBoxApp 启动器,在 main 中调用启动器的 run 方法即可工作:

---->[lib/main.dart]----
void main(List<String> args) => const TolyGameBoxApp().run(args);


启动器要指定一个泛型作为初始化的结果数据;需要提供 app 组件作为应用的入口,以及 AppStartRepository 仓储来处理异步的初始化任务。在整个启动流程中中,基于 onLoadedonStartSuccessonStartError 可以定制不同的业务逻辑; onGlobalError 可以监听全局的异常。
这里在 onStartSuccess 时跳转到 gameCenter 主界面:

class TolyGameBoxApp with FxStarter<AppConfig> {
  const TolyGameBoxApp();

  @override
  Widget get app => const TolyGameBox();

  @override
  AppStartRepository<AppConfig> get repository => const TolyGameBoxRepo();
  
  
  @override
  void onGlobalError(Object error, StackTrace stack) { }

  @override
  void onLoaded(BuildContext context, int cost, AppConfig state) {}

  @override
  void onStartError(BuildContext context, Object error, StackTrace trace) {}

  @override
  void onStartSuccess(BuildContext context, AppConfig state) {
    context.go(AppRoute.gameCenter.url);
  }

}



启动器的仓储单独通过一个类来维护,是考虑到将应用初始化的逻辑强行剥离出来,引导开发者做好逻辑的隔离。这里在其中设置了桌面端的窗口尺寸。后期可以定制一些 App 的配置参数,在这里进行加载初始化:

class TolyGameBoxRepo implements AppStartRepository<AppConfig>{

  const TolyGameBoxRepo();

  @override
  Future<AppConfig> initApp() async {
    WindowSizeAdapter.setSize();
    // TODO 加载资源,创建 AppConfig 对象
    return AppConfig();
  }

}



2. 应用导航树

应用导航基于官方的 go_router, 导航 2.0 可以很方便地实现局部嵌套路由,如下所示,界面切换的过程中可以保持左侧的导航栏不懂,右侧的面板内容进行局部导航。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


导航相关的代码在 navigation 文件夹下,其中:

  • router 文件夹定义路树,其中包含字符串和界面的映射关系
  • view 文件夹盛放导航相关的视图,在 desktop 文件夹下盛放桌面端的导航视图:


app_route 中定义路由树的根节点: 注意要在初始路由界面组件中加上 AppStartListener 来监听应用的启动事件,比如这里启动的首屏是 splash 界面:

---->[lib/navigation/router/app_route.dart]----
RouteBase get appRoute {
  return GoRoute(
    path: AppRoute.home.path,
    redirect: (_, __) => null,
    routes: [
      GoRoute(
        path: AppRoute.splash.path,
        builder: (_, __) => const AppStartListener<AppConfig>(child: Splash()),
      ),
      if(kAppEnv.isDesktopUI)
      deskHomeRoute,
    ],
  );
}


为了更好的维护全局通知、主题,TolyUI 对 MaterialApp 进行了全量的封装,提供了 TolyUiApp,使用方式和 MaterialApp 完全一致:


deskHomeRoute 是桌面端的嵌套路由,通过 ShellRoute 定义,在 DeskNavigation 组件的局部区域进行导航:这里通过 pageBuilder 指定 NoTransitionPage 可以让局部路由切换时立刻改变,不进行动画。当然你喜欢动画效果的话,也可以定制路由动画:

RouteBase get deskHomeRoute => ShellRoute(
      builder: (_, __, Widget child) => DeskNavigation(content: child),
      routes: [
        GoRoute(
          path: AppRoute.gameCenter.path,
          pageBuilder: (_, __) => const NoTransitionPage(child: GameCenterPage()),
        ),
        GoRoute(
          path: AppRoute.save.path,
          pageBuilder: (_, __) => const NoTransitionPage(child: SavePage()),
        ),
        GoRoute(
          path: AppRoute.collect.path,
          pageBuilder: (_, __) => const NoTransitionPage(child: CollectPage()),
        ),
        GoRoute(
          path: AppRoute.mine.path,
          pageBuilder: (_, __) => const NoTransitionPage(child: MinePage()),
        ),
        GoRoute(
          path: AppRoute.settings.path,
          pageBuilder: (_, __) => const NoTransitionPage(child: SettingsPage()),
        ),
      ],
    );


另外,路由相关的固定字符串,通过 AppRoute 枚举统一维护:

enum AppRoute {
  home('/', url: '/'),
  splash('splash', url: '/splash'),
  startError('start_error', url: '/start_error'),
  globalError('404', url: '/404'),
  gameCenter('game_center', url: '/game_center'),
  save('save', url: '/save'),
  collect('collect', url: '/collect'),
  mine('mine', url: '/mine'),
  settings('settings', url: '/settings'),
  ;

  final String path;
  final String url;

  const AppRoute(this.path, {required this.url});
}



3. 导航视图构建

DeskNavigation 组件负责构建桌面端整体视图,它呈左右结构,左侧是导航菜单, 右侧整体是局部导航区:

class DeskNavigation extends StatelessWidget {
  final Widget content;

  const DeskNavigation({super.key, required this.content});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: Container(
        decoration: const BoxDecoration(gradient: bgGradient),
        child: Row(
          children: [
            const DeskNavigationRail(),
            const VerticalDivider(),
            Expanded(child: content),
          ],
        ),
      ),
    );
  }
}

const Gradient bgGradient = LinearGradient(
  colors: [Color(0xFF0A0A12), Color(0xFF1A1A2C)],
  stops: [0.3, 0.8],
  begin: Alignment.topLeft,
  end: Alignment.bottomRight,
);



左侧导航使用 tolyui 中的 TolyRailMenuBar 组件构建,首先准备侧栏菜单的数据 MenuMeta 列表:

---->[lib/navigation/view/desktop/rail_navigation.dart]---
class DeskNavigationRail extends StatelessWidget {
  const DeskNavigationRail({super.key});

  List<MenuMeta> get navMenus => [
        MenuMeta(
          icon: TolyGameIcon.game_center,
          label: "首页",
          router: AppRoute.gameCenter.url
        ),
        MenuMeta(
          icon: TolyGameIcon.save,
          label: "存档",
          router: AppRoute.save.url,
        ),
        MenuMeta(
          icon: TolyGameIcon.collect,
          label: "收藏",
          router: AppRoute.collect.url,
        ),
        MenuMeta(
          icon: TolyGameIcon.mine,
          label: "我的",
          router: AppRoute.mine.url,
        ),
      ];

  @override
  Widget build(BuildContext context) {
    // TODO 构建侧栏
  }
}


视图构建逻辑如下:

  • 通过 GoRouterState.of 可以通过上下文得到 当前激活路由,以此得到激活路径为 activeId 赋值,这样界面路由变化时,就可以重新构建激活对应的索引;
  • onSelected 回调可以感知菜单的点击事件,回调菜单的路径,传入 context.go 就可以在点击时跳转到对应菜单的路由地址;
  • TolyRailMenuBar 支持对菜单条目的灵活自定义,这里通过自定义 GameMenuCell 组件实现悬浮时颜色渐变的菜单项效果:
@override
Widget build(BuildContext context) {
  final String activePath = GoRouterState.of(context).uri.toString();
  final bool isSetting = activePath == AppRoute.settings.url;
  return DragToMoveWrapper(
    child: TolyRailMenuBar(
      width: 68,
      gap: 10,
      padding: const EdgeInsets.symmetric(horizontal: 6),
      cellBuilder: GameMenuCell.create,
      animationConfig: const AnimationConfig(type: AnimTickType.hove),
      leading: (type) => const TolyGameLogo(),
      menus: navMenus,
      activeId: activePath,
      backgroundColor: Colors.transparent,
      onSelected: context.go,
      tail: (_) => SettingButton(active: isSetting),
    ),
  );
}


此时导航区就可以支持切换了,本文主要关注首页追踪游戏中心的展示,其他几个界面后面有时间再继续完善:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


4. 游戏中心展示

现在希望在游戏中心展示展示之前完成的五个游戏,并且在点击时进入对应的游戏界面。这样就完成了 Flutter 应用和 Flutter 游戏的整合,我们要牢记一点,Flame 的游戏视图本质上也是一个 Widget,所以可以无缝地集成到任何的 Flutter 界面中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

游戏中心界面目前就是一个很简单的 GridView 网格列表,以下面的数据来填充界面。这里就不展开介绍了,感兴趣的可以参见源码 GameCenterPage

[
  {
    "title": "经典扫雷",
    "id":"sweeper",
    "image": "assets/images/cover/sweeper.webp",
    "create_at": "2024-05-07"
  },
  {
    "title": "恐龙快跑",
    "id":"trex",
    "image": "assets/images/cover/trex.webp",
    "create_at": "2024-03-04"
  },
  {
    "title": "经典打砖块",
    "id":"brick",
    "image": "assets/images/cover/brick.webp",
    "create_at": "2024-03-15"
  },
  {
    "title": "生命游戏",
    "id":"life_game",
    "image": "assets/images/cover/life_game.webp",
    "create_at": "2024-03-15"
  },
  {
    "title": "贪吃蛇",
    "id":"snake",
    "image": "assets/images/cover/snake.webp",
    "create_at": "2024-08-19"
  }
]



这里重点介绍一下,一个游戏如何以独立的模块存在;一个游戏模块具有它所依赖的所有资源,外界只需要引入就可以访问该游戏界面。这里拿扫雷来说,通过如下命令可以创建一个 sweeper 模块包:

flutter create --template=package sweeper

扫雷相关的所有代码都在这里维护,包括游戏的图片资源。把之前的代码全部拷过来,就完成了 99% 的迁移工作:

对于模块包来说,最需要注意的一点是,资源使用时需要加上包名前缀:完整路径为:

packages/<模块包名称>/<资源在包内的相对路径>

之前加载资源使用的是自定义的资源加载器 TextureLoader, 现在需要对其稍加改造,让它支持指定模块加载资源。扫雷中的资源都是 svg ,从源码中可以看出,load 资源时可以传入 AssetsCache,它可以指定资源的前缀:

现在将 TextureLoader 修改如下,构造时可以传入 package 参数,表示当前模块。构造时如果 package 非空,创建指定包名的前缀,另外 Images 对象是 flame 中加载普通图片资源的类。加载资源时使用 cache 对象即可:

class TextureLoader {
  final String? package;
  AssetsCache? cache;
  Images imageCache = Flame.images;

  TextureLoader({this.package}) {
    if (package != null) {
      cache = AssetsCache(prefix: 'packages/$package/assets/');
      imageCache = Images(prefix: 'packages/$package/assets/');
    }
  }


对于扫雷游戏来说, Flame 1.18.0 -> 1.26.0 间并没什么破坏性的更新。迁移后,游戏运转正常。嵌入到当前的游戏盒中也非常简单,添加一个跳转的 route 即可,点击时,推入路由:

其中 SweeperPage 使用 sweeper 模块提供的 SweeperGamePanel 视图,作为界面的一部分,你还可以自定义一些其他的信息,让游戏和应用完美融合:

class SweeperPage extends StatelessWidget {
  const SweeperPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.transparent,
      body:  Column(
        children: [
          CustomDeskTopBar(title: '经典扫雷',leading: BackButton(
            onPressed: context.pop,
          ),),
          const Expanded(child: SweeperGamePanel()),
        ],
      ),
    );
  }
}



尾声

到这里,就完成了一个最简单的游戏展示和点击打开的游戏盒。后面继续把其他几个小游戏也按照模块的方式集成进去即可。以后再写什么小游戏,就可以在这里安家了。如果有朋友写了什么好玩的游戏,也可以放一个模块进来,让 TolyGameBox 不断壮大。那本文就到这里,后续更多精彩内容,敬请期待 ~

题外话

黑客&网络安全如何学习

今天只要你给我的文章点赞,我私藏的网安学习资料一样免费共享给你们,来看看有哪些东西。

1.学习路线图

在这里插入图片描述

攻击和防守要学的东西也不少,具体要学的东西我都写在了上面的路线图,如果你能学完它们,你去就业和接私活完全没有问题。

2.视频教程
网上虽然也有很多的学习资源,但基本上都残缺不全的,这是我们和网安大厂360共同研发的网安视频教程,之前都是内部资源,专业方面绝对可以秒杀国内99%的机构和个人教学!全网独一份,你不可能在网上找到这么专业的教程。

内容涵盖了入门必备的操作系统、计算机网络和编程语言等初级知识,而且包含了中级的各种渗透技术,并且还有后期的CTF对抗、区块链安全等高阶技术。总共200多节视频,200多G的资源,不用担心学不全。
在这里插入图片描述
因篇幅有限,仅展示部分资料,需要见下图即可前往获取
在这里插入图片描述

🐵这些东西我都可以免费分享给大家,需要的可以点这里自取👉:网安入门到进阶资源

3.技术文档和电子书
技术文档也是我自己整理的,包括我参加大型网安行动、CTF和挖SRC漏洞的经验和技术要点,电子书也有200多本,由于内容的敏感性,我就不一一展示了。

在这里插入图片描述

因篇幅有限,仅展示部分资料,需要见下图即可前往获取
在这里插入图片描述

🐵这些东西我都可以免费分享给大家,需要的可以点这里自取👉:网安入门到进阶资源

4.工具包、面试题和源码
“工欲善其事必先利其器”我为大家总结出了最受欢迎的几十款款黑客工具。涉及范围主要集中在 信息收集、Android黑客工具、自动化工具、网络钓鱼等,感兴趣的同学不容错过。

还有我视频里讲的案例源码和对应的工具包,需要的话见下图即可前往获取
在这里插入图片描述

🐵这些东西我都可以免费分享给大家,需要的可以点这里自取👉:网安入门到进阶资源

最后就是我这几年整理的网安方面的面试题,如果你是要找网安方面的工作,它们绝对能帮你大忙。

这些题目都是大家在面试深信服、奇安信、腾讯或者其它大厂面试时经常遇到的,如果大家有好的题目或者好的见解欢迎分享。

参考解析:深信服官网、奇安信官网、Freebuf、csdn等

内容特点:条理清晰,含图像化表示更加易懂。

内容概要:包括 内网、操作系统、协议、渗透测试、安服、漏洞、注入、XSS、CSRF、SSRF、文件上传、文件下载、文件包含、XXE、逻辑漏洞、工具、SQLmap、NMAP、BP、MSF…

在这里插入图片描述

因篇幅有限,仅展示部分资料,需要见下图即可前往获取
在这里插入图片描述

🐵这些东西我都可以免费分享给大家,需要的可以点这里自取👉:网安入门到进阶资源
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

网安导师小李

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值