news 2026/9/15 18:47:39

Flame 游戏引擎启动画面定制实战:flame_splash_screen 组件使用与源码解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flame 游戏引擎启动画面定制实战:flame_splash_screen 组件使用与源码解析

Flame 游戏引擎启动画面定制实战:flame_splash_screen 组件使用与源码解析

【免费下载链接】flameA Flutter based game engine.项目地址: https://gitcode.com/GitHub_Trending/fl/flame

flame_splash_screen是 Flame 官方生态中用于为游戏添加"启动画面(Splash Screen)"动画的专用包,它内置了火焰 Logo 的三层粒子级淡入淡出动画,并支持通过主题、自定义内容与控制器对动画进行深度定制。读完本文,你将掌握该组件的安装引入、基础接入、动画播放机制、showBefore/showAfter扩展、主题定制以及基于FlameSplashController的时长与启动时机控制,并能结合源码理解其内部工作原理。

本文以仓库中的 关联文档 为核心骨架,结合 flame_splash_screen 包源码 及其测试与示例展开。

一、包定位与核心思路

Flame 是一个基于 Flutter 的 2D 游戏引擎,而flame_splash_screen为其解决了"游戏启动阶段展示品牌动画"这一常见需求。它不是一个普通的静态图片页,而是一个可编排的多步骤动画播放器

  • 整个播放过程被拆分为若干step(步骤),每个步骤是一个 Widget;
  • 每个步骤内部按"淡入 → 停留 → 淡出"三段节奏播放;
  • 全部步骤播完后触发onFinish回调,由你决定进入游戏首页还是其他路由。

从 源码 可以看到,FlameSplashScreen是一个StatefulWidget,其对外可配置的参数全部集中在构造器中:

参数类型说明
onFinishValueChanged<BuildContext>必填,全部动画结束后回调,通常在此跳转到游戏初始页面
themeFlameSplashTheme必填,控制背景、Logo 及外层约束,内置dark/white两套
showBeforeWidgetBuilder?可选,在 Flame Logo 之前额外展示的自定义 Widget(如自家工作室 Logo)
showAfterWidgetBuilder?可选,在 Flame Logo 之后额外展示的自定义 Widget
controllerFlameSplashController?可选,外部传入控制器以接管时长与启动时机

二、安装与引入

flame_splash_screen声明为依赖即可。当前仓库内该包的定义位于 packages/flame_splash_screen/pubspec.yaml:

name: flame_splash_screen version: 0.3.1+3 description: Style your flame game with a beautiful splash screen with logo reveal. Simple to use but still customizable. environment: sdk: ">=3.12.0 <4.0.0" flutter: ">=3.44.0" dependencies: flutter: sdk: flutter meta: ^1.12.0

在你的游戏项目的pubspec.yaml中加入依赖后,执行flutter pub get,然后在 Dart 文件中引入:

import 'package:flame_splash_screen/flame_splash_screen.dart';

该包运行时只依赖 Flutter SDK 与meta,不引入任何重型第三方依赖,适合作为游戏冷启动阶段的首屏组件。

三、最小可用接入

FlameSplashScreen的必填参数只有两个:themeonFinish。最简单的接入方式:

FlameSplashScreen( theme: FlameSplashTheme.dark, onFinish: (BuildContext context) => Navigator.pushNamed(context, '/your-game-initial-screen'), )

将这段代码放在你的路由页面(例如SplashScreenbuild中)即可:动画播放期间展示火焰 Logo,播放完毕后通过onFinish导航到游戏主界面。

动画自动播放的前提

组件挂载后会自动开始动画,因为内部FlameSplashScreenState.initState中默认创建了FlameSplashController(),而该控制器默认autoStart = true(详见 controller.dart)。同时,initState会完成步骤编排并将onFinish注入控制器:

controller.setup(steps.length, () => widget.onFinish(context));

因此你不需要也不应该手动调用start(),除非你主动设置了autoStart: false(下文"控制器进阶"会展开)。

四、动画播放机制与步骤编排

1. 步骤列表的构建

在 splash.dart 的_computeSteps中,步骤列表按如下顺序组装:

steps = [ if (widget.showBefore != null) widget.showBefore!, widget.theme.logoBuilder, if (widget.showAfter != null) widget.showAfter!, ];

即:自定义前导内容 → 火焰 Logo → 自定义尾部内容,中间的 Logo 步骤恒存在。步骤数量决定控制器播放几轮,随后_tickStep会按索引逐轮推进(见 controller.dart 的_tickStep实现):

Future<void> _tickStep(int index) async { stepController.value = index; await Future<void>.delayed(durations.total); final finished = index >= _stepsAmount - 1; if (finished) { _state = FlameSplashControllerState.finished; _onFinish(); return; } _tickStep(index + 1); }

stepController是一个ValueNotifier<int>,负责把"当前播放到第几步"广播给 UI;FlameSplashScreenState.build中通过ValueListenableBuilder监听它并切换当前步骤的 Widget。

2. 单步动画的三段式节奏

每个步骤对应一个_SplashScreenStep,其内部使用AnimationController+Opacity实现淡入 → 停留 → 淡出

// 淡入 controller ..value = 0.0 ..duration = widget.durations.fadeInDuration; await controller.forward(); await Future<void>.delayed(widget.durations.waitDuration); // 淡出 controller ..value = 1.0 ..duration = widget.durations.fadeOutDuration ..reverse();

三段时长分别由fadeInDurationwaitDurationfadeOutDuration控制,其默认值定义在FlameSplashController构造器中:

参数默认值作用
fadeInDuration750msLogo 从透明淡入到完全可见
waitDuration2s完全可见后的停留时间
fadeOutDuration450ms淡出到透明
autoStarttrue组件挂载后是否自动开始播放

FlameSplashDurations还提供了total便捷属性(fadeIn + fadeOut + wait),用于控制器计算单步总时长。

五、扩展你的内容:showBefore 与 showAfter

很多游戏希望启动时先展示自家品牌,再展示 Flame 徽标(或反之)。showBefore/showAfter正是为此设计,二者类型均为WidgetBuilder,可以返回任意 Widget(文本、图片、自绘组件均可)。

在 Flame Logo 之前展示内容:

FlameSplashScreen( theme: FlameSplashTheme.dark, showBefore: (BuildContext context) { return Text("To be shown before flame animation"); }, onFinish: (BuildContext context) => Navigator.pushNamed(context, '/your-game-initial-screen'), )

在 Flame Logo 之后展示内容:

FlameSplashScreen( theme: FlameSplashTheme.dark, showAfter: (BuildContext context) { return Text("To be shown after flame animation"); }, onFinish: (BuildContext context) => Navigator.pushNamed(context, '/your-game-initial-screen'), )

两者也可以同时指定,此时动画顺序为:showBefore内容 → 火焰 Logo →showAfter内容。值得注意的是,源码中当showBefore/showAfter/theme.logoBuilder发生变化时,didUpdateWidget会在动画未开始时重新计算步骤并重启动画,因此这些内容理论上可以热更新。

六、主题系统:从内置两套到完全自定义

1. 内置主题

FlameSplashTheme提供两个开箱即用的静态主题(见 theme.dart):

// 白底主题,适合浅色应用 static FlameSplashTheme white = const FlameSplashTheme( backgroundDecoration: BoxDecoration(color: Color(0xFFFFFFFF)), logoBuilder: _logoBuilder, ); // 黑底主题,适合深色应用 static FlameSplashTheme dark = const FlameSplashTheme( backgroundDecoration: BoxDecoration(color: Color(0xFF000000)), logoBuilder: _logoBuilder, );

两者仅背景色不同,Logo 相同。切换主题只需替换theme参数:

FlameSplashScreen( theme: FlameSplashTheme.white, onFinish: (BuildContext context) => Navigator.pushNamed(context, '/your-game-initial-screen'), )

2. 火焰 Logo 的分层渲染

内置 Logo 并非单张图片,而是三层 PNG 的叠加(layer1.pnglayer2.pnglayer3.png,位于包的 assets 目录)。AnimatedLogo通过Stack叠加三层,其中中间层由Animation<double>控制透明度,从而产生火焰"呼吸"般的脉动效果:

Stack( fit: StackFit.expand, alignment: Alignment.center, children: [ Image.asset('assets/layer1.png', package: 'flame_splash_screen'), Opacity( opacity: animation.value, child: Image.asset('assets/layer2.png', package: 'flame_splash_screen'), ), Image.asset('assets/layer3.png', package: 'flame_splash_screen'), ], )

LogoComposite负责驱动这段 500ms 的正反向循环动画:动画完成后反向播放,回到起点再正向播放,形成无限脉动;Logo 整体被限制在 300×300 的宽松约束内并向上偏移 25%,配合背景形成居中展示效果。

3. 自定义主题

FlameSplashTheme的构造器对外开放了三个字段,可以完全替换默认外观:

const FlameSplashTheme({ required this.backgroundDecoration, required this.logoBuilder, this.constraints = const BoxConstraints.expand(), });
  • backgroundDecorationBoxDecoration,控制 Logo 底层的背景(颜色、渐变、图片等);
  • logoBuilderWidgetBuilder,替换默认的三层火焰 Logo,例如换成自家游戏 Logo;
  • constraints:外层BoxConstraints,默认BoxConstraints.expand()占满可用空间。

例如将默认火焰 Logo 换成自定义 Logo 并配以渐变背景:

FlameSplashScreen( theme: FlameSplashTheme( backgroundDecoration: const BoxDecoration( gradient: LinearGradient( colors: [Color(0xFF1A237E), Color(0xFF0D47A1)], ), ), logoBuilder: (context) => Image.asset('assets/my_logo.png'), ), onFinish: (context) => Navigator.pushNamed(context, '/game'), )

七、控制器进阶:时长与启动时机的完全掌控

当默认的 750ms 淡入 + 2s 停留 + 450ms 淡出节奏不满足需求,或希望"等资源加载完再播动画"时,可以传入外部FlameSplashController

1. 创建并传入控制器

官方示例 example/lib/main.dart 之外,README 给出了标准用法:控制器与State同生命周期,并在dispose中释放:

class SplashScreenGameState extends State<SplashScreenGame> { FlameSplashController controller; @override void initState() { super.initState(); controller = FlameSplashController( fadeInDuration: Duration(seconds: 1), fadeOutDuration: Duration(milliseconds: 250), waitDuration: Duration(seconds: 2), autoStart: false, ); } @override void dispose() { controller.dispose(); // dispose it when necessary super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: FlameSplashScreen( showBefore: (context) => Text("Before the logo"), showAfter: (context) => Text("After the logo"), theme: FlameSplashTheme.white, onFinish: (context) => Navigator.pushNamed(context, '/the-game-initial-screen'), controller: controller, ), ); } }

2. 手动触发 start

autoStart: false时,动画不会在组件挂载后自动播放,需要在你认为合适的时机(例如异步资源加载完毕)手动调用:

// 资源加载完成后 await loadGameAssets(); controller.start();

源码在start()中设置了断言保护:控制器必须先被FlameSplashScreen挂载(setup之后)才能启动,且已启动的控制器不允许重复start(),否则会抛出断言错误。

3. 状态机与生命周期

控制器内部维护了三个状态(FlameSplashControllerState):

状态含义
idle已就绪但尚未开始(autoStart: false时处于此状态)
started正在依次播放各个步骤
finished所有步骤播放完毕,onFinish已触发

从 controller_test.dart 可以看出几个关键行为约束:

  • setup前调用start()抛断言异常;
  • setup后若autoStart为 false,状态保持idle,调用start()后变为started
  • 播放完所有步骤后状态为finishedonFinish被调用;
  • autoStart: truesetup即自动start,随后再调start()会抛断言;
  • 每个 step 结束后还会继续推进下一 step,直到最后一个。

dispose()用于释放控制器内部资源。当控制器由外部传入时,组件不会替它调用dispose(见splash.dart_externallyControlled分支),所以外部传入的控制器必须由你负责释放。

八、完整可运行的接入示例

综合以上内容,一个"自定义前后内容 + 自定义主题 + 外部控制器 + 完成后跳转"的完整页面如下:

import 'package:flame_splash_screen/flame_splash_screen.dart'; import 'package:flutter/material.dart'; class SplashScreenGame extends StatefulWidget { const SplashScreenGame({super.key}); @override SplashScreenGameState createState() => SplashScreenGameState(); } class SplashScreenGameState extends State<SplashScreenGame> { late final FlameSplashController _controller; @override void initState() { super.initState(); _controller = FlameSplashController( fadeInDuration: const Duration(milliseconds: 800), waitDuration: const Duration(seconds: 2), fadeOutDuration: const Duration(milliseconds: 400), autoStart: true, ); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: FlameSplashScreen( showBefore: (context) => const Center(child: Text('MY STUDIO')), showAfter: (context) => const Center(child: Text('Loading...')), theme: FlameSplashTheme.dark, controller: _controller, onFinish: (context) => Navigator.pushReplacement<void, void>( context, MaterialPageRoute(builder: (context) => const GameHomePage()), ), ), ); } }

与仓库 示例工程 一致,onFinish中应使用pushReplacementpushNamed完成页面切换,避免用户能通过返回键回到启动画面。

九、测试保障与质量验证

该包围绕两个核心维度提供测试覆盖:

  • controller_test.dart:验证控制器在autoStart开关下的启动时机、状态流转(idle → started → finished)、onFinish回调触发时机以及重复启动的断言保护;
  • theme_test.dart:验证white/dark两套内置主题的backgroundDecoration颜色值(0xFFFFFFFF/0xFF000000)与默认约束BoxConstraints.expand()

这些测试从行为层面锁定了"组件挂载即播、播完必回调、控制器状态严格流转"等关键契约,你在接入时可以参考它们理解组件的行为边界。

十、小结

flame_splash_screen用非常克制的 API 设计解决了游戏启动画面这一高频需求:两个必填参数即可完成"火焰 Logo 动画 + 完成后跳转"的最小闭环;showBefore/showAfter与自定义FlameSplashTheme提供了品牌化扩展空间;FlameSplashController则把时长、启动时机与生命周期控制权完整交还给开发者。其源码的"步骤编排 + 三段式动画 + 状态机"设计也值得在阅读时细细品味——这本身就是一套优雅的 Flutter 动画编排范式。

【免费下载链接】flameA Flutter based game engine.项目地址: https://gitcode.com/GitHub_Trending/fl/flame

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 18:46:39

分布式系统服务根目录安全防护体系设计与实践

1. 服务根目录的安全防护体系设计在分布式系统架构中&#xff0c;服务根目录作为核心入口节点&#xff0c;其安全性直接关系到整个系统的稳定运行。我曾在某大型电商平台的微服务架构改造项目中&#xff0c;亲历过因根目录配置不当导致的全局性服务雪崩。那次事故让我们团队深刻…

作者头像 李华
网站建设 2026/9/15 18:44:11

MyBatis-Plus时间字段自动更新的四种实现方式与性能对比

1. MyBatis-Plus 时间字段自动更新的四种实现方式在数据库操作中&#xff0c;记录修改时间是一个高频需求。MyBatis-Plus 作为 MyBatis 的增强工具&#xff0c;提供了多种实现时间字段自动更新的方案。下面我将结合实战经验&#xff0c;详细介绍四种主流实现方式及其适用场景。…

作者头像 李华
网站建设 2026/9/15 18:44:01

四阶有限差分声波方程正演:从空间离散到合成地震记录的实现要点

简介&#xff1a;fd.zip是一份面向地球物理勘探与数值模拟初学者的四阶声波有限差分正演程序包。压缩包共三个文件&#xff0c;包含一个C语言源码和两个数据文件&#xff0c;整体仅51KB。源码以四阶有限差分方法求解声波波动方程&#xff0c;覆盖网格离散化、时间步进、边界条件…

作者头像 李华