学习内容:
安装 Flame 依赖(
flame: ^1.14.0,按官网最新版本)Flame 核心:
GameWidget(游戏容器)、FlameGame(游戏基类)实践任务:
创建第一个 Flame 项目:运行一个空白游戏页面,显示 “我的第一个 Flame 游戏”
理解游戏循环(
update刷新逻辑、render绘制画面)
一、环境搭建※
在pubspec.yaml中dependencies节点下添加依赖:
dependencies:
flutter:
sdk: flutter
# 添加 Flame 依赖(从官网获取最新版)
flame: ^1.34.0二、Flame核心组件解析※
1. GameWidget:游戏容器(UI层)※
作用
GameWidget是Flame与Flutter UI交互的“桥梁”,本质是一个Flutter Widget,负责将Flame游戏内容嵌入到Flutter页面中,支持与其他Flutter组件(如:AppBar、Button)混合使用。
核心属性
属性名 类型 作用 game FlameGame 必传参数,绑定对应的F辣么Game实例,将游戏逻辑与容器关联。 backgroundBuilder WidgetBuilder? 可选,用于构建游戏背景,支持纯色、图片等任意Flutter Widget size Size 可选,指定游戏容器的固定大小,不设置则默认占满父组件空间。 overlayBuilderMap Map<String, WidgetBuilder> 可选,用于添加游戏覆盖层(如:游戏暂停界面、分数显示)
2. FlameGame:游戏基类(逻辑层)※
作用
FlameGame是所有Flame游戏的“父类”,封装了游戏的核心生命周期(初始化、更新、绘制、销毁),提供了游戏循环(update + render)的默认实现,开发者只需要继承它并按需重写方法即可。
核心方法(生命周期)
方法名 作用 调用时机 onLoad() 游戏初始化(加载资源、初始化变量) 游戏启动时调用一次,在update和render之前执行 update(double dt) 状态刷新(逻辑计算) 每帧调用一次,dt为当前帧与上一帧的时间间隔(单位:秒),用于处理移动、碰撞等逻辑 render(Canvas canvas) 画面绘制(渲染内容) 每帧调用一次,在update之后执行,通过Canvas绘制游戏元素(文字、图片、图形) onDetach() 游戏销毁(释放资源) 游戏容器(GameWidget)被销毁时调用,用于释放图片、音频等资源,避免内存泄露
三、深入理解Flame游戏循环※
循环的本质
Flame游戏循环是“连续执行的帧序列”,每一帧包含两个核心步骤:update(逻辑刷新)→render(画面绘制),默认帧率为60帧/秒,即:每秒执行60次update和render,确保画面流畅。
- update方法:状态刷新逻辑
核心作用
处理游戏的“动态逻辑”,不涉及画面绘制,例如:
- 角色位置更新(根据速度和时间间隔计算新坐标)
- 碰撞检测(判断角色是否与障碍物重叠)
- 分数计算(每帧累加时间分)
- 敌人AI行为(每帧判断敌人移动方向)
- 关键参数dt(delta time)
- dt表示“当前帧与上一帧的时间间隔”,单位为:秒,例如:60帧每秒时,dt≈0.0167
- 为什么需要dt:因为不同设备帧率不同,用dt做“时间补偿”,确保游戏速度在不同设备上一致。
示例:让文字随时间移动
class MyFirstGame extends FlameGame { // 添加文字位置变量 double _textX = 0; // 文字移动速度(像素/秒) final double _textSpeed = 100; @override Future<void> onLoad() async { super.onLoad(); // 初始化文字 X 坐标为左侧(Y 坐标仍居中) _textX = 0; } @override void update(double dt) { super.update(dt); // 计算每帧文字移动的距离:速度 × 时间间隔 final moveDistance = _textSpeed * dt; // 更新文字 X 坐标 _textX += moveDistance; // 文字超出屏幕右侧后,重置到左侧 if (_textX > size.x) { _textX = -200; // -200 是文字宽度(避免突然消失) } } @override void render(Canvas canvas) { super.render(canvas); // 绘制时使用更新后的 _textX final textStyle = TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold); final textPainter = TextPainter( text: TextSpan(text: "我的第一个 Flame 游戏", style: textStyle), textDirection: TextDirection.ltr, ); textPainter.layout(); // Y 坐标仍居中,X 坐标用 _textX final textY = (size.y - textPainter.height) / 2; textPainter.paint(canvas, Offset(_textX, textY)); } }
- render方法:画面绘制逻辑
核心作用
将
update后的状态“绘制”到屏幕上,通过Canvas对象实现,支持绘制:- 文字
- 图形(矩形、圆形、线段,通过
Paint和Path实现) - 图片(通过Flame提供的
Sprite组件加载和绘制) - 动画(通过
SpriteAnimation实现序列帧动画)
绘制顺序
render方法中,代码执行顺序即“绘制层级”,先执行的代码绘制在下层,后执行的代码绘制在上层。
四、综合实战※
任务
创建一个空白游戏页面,背景为黑色,居中显示白色文字“我的第一个Flame游戏”,无复杂交互,聚焦核心组件与游戏循环的理解。
- 代码实现
创建游戏类(
my_first_game.dart)在lib目录下新建
my_first_game.dart文件,编写自定义FlameGame类import 'dart:async'; import 'package:flame/game.dart'; import 'package:flutter/material.dart'; // 自定义游戏类,继承FlamGame class MyFirstGame extends FlameGame{ // 游戏初始化:onLoad只执行一次 @override Future<void> onLoad() async { super.onLoad(); // 此处可执行资源架子逻辑 print('游戏初始化完成,准备启动。'); } // 游戏循环:update每帧执行(处理逻辑,不负责绘制) 非必须重载函数 @override void update(double dt) { super.update(dt); // dt = 时间间隔(约1/60秒,因设备帧率而异) } // 游戏循环:render每帧执行(处理绘制,不负责逻辑) @override void render(Canvas canvas) { super.render(canvas); _drawGameText(canvas); } // 辅助方法:绘制游戏文字 void _drawGameText(Canvas canvas) { // 自定义文字样式 final textStyle = TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold ); // 创建TextPainter(Flame绘制文字依赖的工具) final textPainter = TextPainter( text: TextSpan(text: '我的第一个Flame游戏', style: textStyle), textDirection: TextDirection.ltr //文字方向:从左向右 ); // 布局文字(计算文字宽高) textPainter.layout(); final textX = (size.x - textPainter.width) / 2; // 水平居中 final textY = (size.y - textPainter.height) / 2; // 垂直居中 // 绘制文字 textPainter.paint(canvas, Offset(textX, textY)); } }创建游戏页面(
game_screen.dart)在lib目录下新建
game_screen.dart文件,编写包含GameWidget的Flutter页面import 'package:flutter/material.dart'; import 'package:flame/game.dart'; import 'my_first_game.dart'; // 游戏展示页面 class GameScreen extends StatelessWidget { const GameScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( // 页面标题栏 appBar: AppBar( title: const Text('Flame游戏示例'), centerTitle: true, backgroundColor: Colors.blueAccent, ), // 页面主体:导入GameWidget body: GameWidget( // 绑定自定义游戏实例 game: MyFirstGame(), // 设置游戏容器背景色为黑色 backgroundBuilder: (context) => Container( color: Colors.black, ), // 可选:添加加载占位界面(游戏初始化时显示) loadingBuilder: (context) => const Center( child: CircularProgressIndicator(color: Colors.blueAccent,), ), // 可选,添加错误提示界面(游戏初始化失败时显示) errorBuilder: (context, error) => Center( child: Text( '游戏加载失败:$error', style: const TextStyle(color: Colors.red), ), ), ), ); } }修改入口文件(
main.dart)import 'package:flutter/material.dart'; import 'game_screen.dart'; void main() => runApp(const App()); class App extends StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flame入门', theme: ThemeData(primarySwatch: Colors.blue), home: const GameScreen(), ); } }
运行效果
