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,其对外可配置的参数全部集中在构造器中:
| 参数 | 类型 | 说明 |
|---|---|---|
onFinish | ValueChanged<BuildContext> | 必填,全部动画结束后回调,通常在此跳转到游戏初始页面 |
theme | FlameSplashTheme | 必填,控制背景、Logo 及外层约束,内置dark/white两套 |
showBefore | WidgetBuilder? | 可选,在 Flame Logo 之前额外展示的自定义 Widget(如自家工作室 Logo) |
showAfter | WidgetBuilder? | 可选,在 Flame Logo 之后额外展示的自定义 Widget |
controller | FlameSplashController? | 可选,外部传入控制器以接管时长与启动时机 |
二、安装与引入
将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的必填参数只有两个:theme与onFinish。最简单的接入方式:
FlameSplashScreen( theme: FlameSplashTheme.dark, onFinish: (BuildContext context) => Navigator.pushNamed(context, '/your-game-initial-screen'), )将这段代码放在你的路由页面(例如SplashScreen的build中)即可:动画播放期间展示火焰 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();三段时长分别由fadeInDuration、waitDuration、fadeOutDuration控制,其默认值定义在FlameSplashController构造器中:
| 参数 | 默认值 | 作用 |
|---|---|---|
fadeInDuration | 750ms | Logo 从透明淡入到完全可见 |
waitDuration | 2s | 完全可见后的停留时间 |
fadeOutDuration | 450ms | 淡出到透明 |
autoStart | true | 组件挂载后是否自动开始播放 |
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.png、layer2.png、layer3.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(), });backgroundDecoration:BoxDecoration,控制 Logo 底层的背景(颜色、渐变、图片等);logoBuilder:WidgetBuilder,替换默认的三层火焰 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;- 播放完所有步骤后状态为
finished且onFinish被调用; autoStart: true时setup即自动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中应使用pushReplacement或pushNamed完成页面切换,避免用户能通过返回键回到启动画面。
九、测试保障与质量验证
该包围绕两个核心维度提供测试覆盖:
- 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),仅供参考