在刚刚结束的 FlutterNFriends 大会上,Flame 展示了它们关于 3D 游戏的支持:flame_3d ,Flame 是一个以组件系统(Flame Component System, FCS)、游戏循环、碰撞检测和输入处理为核心的 Flutter 游戏框架,而这个架构的一个关键特点就是:纯 Dart 和 Flutter 的开发模式,在此之前 flame 在 2D 领域已经开发了不少小游戏。

而本次要聊的 flame_3d 属于 Flame 生态系统的一个官方扩展包,flame_3d 是在 Flame 已有的 FCS 上进行扩展的支持,比如:

  • 在Flame 2D中,游戏对象通常是 Component 的子类 ,例如 SpriteComponent 用于渲染图像
  • 在 flame_3d 则是引入了新的三维组件类型,比如 MeshComponent,它们也继承自 Component 基类,但是在其之上有 Component3D 的相关实现

这种继承关系主要是为了新的 3D 对象,能够无缝地融入已有的 Flame 游戏循环和组件管理,例如一个 MeshComponent 可以像 2D 的 SpriteComponent 一样被添加到 World ,并自动参与到游戏的更新(update)和渲染(render)循环,也能让原本熟悉 Flame 2D 的开发者更便捷进入到 3D 领域:

而在 flame_3d 里,三维场景的根节点是还是 FlameGame 类,它负责管理整个游戏 ,而所有 3D 对象都通常被添加到一个 World3D 组件:

  • World3D 组件作为一个逻辑容器来组织场景内容
  • CameraComponent3D 它定义了三维世界的投影方式和视点位置

而在内部,flame_3d 会拦截 Flame 的渲染循环,从而利用 Flutter GPU 的低级API来执行三维渲染任务,例如:

将三维网格、材质和灯光信息发送到 GPU 进行着色和光栅化,这个过程发生在 Flutter 的 build 之外,也避免了造成 Flutter UI 层的性能瓶颈的可能。

所以目前而言,flame_3d 十分依赖于 Flutter GPU + Impeller , flutter_gpu 作为 Flutter 3.24 提供的一个实验性功能包,它为 Dart 语言暴露了 Impeller 渲染引擎的低级接口,它可以通过编写 Dart 代码和 GLSL 着色器在 Flutter 中构建和集成自定义渲染器,而无需 Native 平台代码,允许开发者直接访问 GPU 资源和执行自定义着色器。

简单来说就是,Flutter GPU 是 Impeller 对于 HAL 的一层很轻的包装,并搭配了关于着色器和管道编排的自动化能力,也通过 Flutter GPU 就可以使用 Dart 直接构建自定义渲染器。

Flutter GPU 和 Impeller 一样,它的着色器也是使用 impellerc 提前编译,所以 Flutter GPU 也只支持 Impeller 的平台上可用。

当然,实际上直接使用 Flutter GPU 十分复杂,比如一个简单的绘制就需要:

  • 获取 GPUContext
  • GpuContext.createCommandBuffer 创建一个 CommandBuffer
  • CommandBuffer.createRenderPass 创建一个 RenderPass
  • 使用各种方法设置状态/管道并绑定资源 RenderPass
  • 附加绘图命令 RenderPass.draw
  • CommandBuffer 使用 CommandBuffer.submit (异步)提交绘制,所有 RenderPass 会按照其创建顺序进行编码

而在 flame_3d 通过抽象出一系列核心三维组件来简化开发:

  • MeshComponent: 这是最基本的可渲染三维组件,用于表示三维网格(Mesh),通过属性可以加载不同类型的网格,例如圆锥体(ConeMesh)和圆柱体(CylinderMesh),甚至支持复杂的模型解析和骨骼动画
  • LightComponent: 负责在场景中添加光源,影响 3D 物体的着色效果
  • Material: 材质定义了 3D 对象表面的外观特性,例如颜色和纹理,目前默认提供了一个SpatialMaterial,开发者也可以编写自定义材质来使用自己的着色器
  • VectorQuaternion: 主要是用于方便进行三维空间的向量运算和旋转变换

这里需要注意的是,Flutter 目前并不原生支持着色器文件的打包,而为了解决这个问题,flame_3d 提供了一个自定义的 Dart 脚本,开发者可以将他们的顶点着色器(.vert)和片段着色器(.frag)文件存放在一个指定的shaders 目录下,并确保文件名称相同,,然后 :

通过运行命令 dart pub run flame_3d:build_shaders 自动编译并打包着色器,并放置到assets/shaders目录中提供运行时加载 。

而针对 flame_3d 官方也提供了一些 demo ,例如 collect_the_donut 就是一个非常不错的例子,它很好的展示了 flame 如何 3D 领域的开发转变为大家熟悉的 Flutter 面向对象的开发模式

例如在项目里,你可以通过 ModelParser 加载对应的模型资源,对应上面动图,在这里:

  • rogue 就是我们操作的角色模型
  • floor 是地板模型
  • donut 是甜甜圈模型
  • skeleton 是小兵模型
  • walls 是墙体模型

而在实际使用上也并不复杂,比如对于我们操作的角色,在项目里对应的是 Player 封装,Player 类是一个继承自 ModelComponent 的自定义组件,并且通过混入 HasGameReference 获取对游戏实例的引用,并实现了 KeyboardHandlerTapCallbacks 接口,用于处理键盘输入和点击事件。

Player 类定义了一些关键属性,例如玩家的动作 _action、武器 _weapon、是否奔跑 _isRunning、死亡计时器 _deathTimer 等,而构造函数中默认将玩家的武器设置为 knife,并通过 _updateWeapon 方法隐藏其他武器节点,仅显示当前武器。

对于玩家动作,这里通过 action 属性管理,通过设置动作时启动计时器 _actionTimer,并调用 stopAnimation 停止当前动画,这里的动画对应的是 flame_3d 里的 AnimationState 动画状态机

set action(PlayerAction? value) {if (_actionTimer != 0.0) {return;}_action = value;_actionTimer = value?.timer ?? 0.0;stopAnimation();
}

而玩家的视角可以通过 lookAngle 属性管理,设置时会更新模型的旋转,lookAt 属性返回玩家当前视角方向的向量,同时玩家位置通过 _input_handleMovement 方法更新,支持基于键盘输入的移动逻辑:

lookAngle += -_input.x * _rotationSpeed * dt;
final movement = lookAt.scaled(-_input.y * speed * dt);
position.add(movement);

_updateAnimation 方法根据玩家当前状态(动作、移动、奔跑等)播放对应的动画,例如攻击时播放攻击动画,移动时播放行走或奔跑动画,静止时播放待机动画:

if (action != null) {playAnimationByIndex(0, resetClock: false);
} else if (isMoving && _isRunning) {playAnimationByName('Running_A', resetClock: false);
}

可以看到,很多底层操作 flame_3d 都帮我们做了隔离,在上层你只需要操作熟悉的对象和 API ,比如将 PlayerWeapon.knife 换成 PlayerWeapon.twoHandedCrossbow

而对于地板,在项目里对应的是Floor 类,它是一个自定义的地板组件,继承了 flame.Component,用于在游戏场景中生成一个由多个地板段组成的地板网格。

Floor 的会接收一个 Vector2 size 参数,表示地板的宽度和深度,地板的生成逻辑基于网格划分,网格的单元大小由常量 _floorSegmentSize 定义,起始位置 start 是一个 Vector3,计算方式确保地板网格居中于场景:

网格的生成逻辑是通过嵌套的 for 循环,遍历地板的宽度和深度,将每个网格单元的位置偏移量计算出来,并创建 _FloorSection 实例,每个实例的 position 属性设置为计算后的位置,并添加到当前组件中:

final position = start.clone()..x += x * _floorSegmentSize..z += y * _floorSegmentSize;
add(_FloorSection()..position.setFrom(position));

_FloorSection 继承自 ModelComponent,表示地板的单个网格单元,对应模型是通过 Loader.models.floor 加载,并设置了初始位置偏移量,使地板稍微低于默认高度:

同理,Demo 项目里的Wall 也是继承自 flame.Component,用于在游戏场景中生成一段由多个墙段组成的墙体:

image-20250903141910222

对于 Wall 的来说主要接收两个参数:startend,分别表示墙体的起点和终点,然后通过计算 end - start 得到方向向量 direction,并将墙体的初始位置设置为起点加上方向向量的一半长度:

final direction = end - start;
final position = start + direction.scaledTo(_wallSegmentSize / 2);

对于墙段生成逻辑,主要由多个固定大小的墙段组成,每段的大小由常量 _wallSegmentSize 定义,通过 while 循环,逐步减少剩余距离 totalDistance,并在每次迭代中添加一个 _WallSection 实例,每个墙段的旋转通过 Quaternion.axisAngle 计算,使其与墙体方向对齐:

final rotation = Quaternion.axisAngle(up,atan2(start.z - end.z, start.x - end.x),
);
add(_WallSection(wallIndex: randomInt(0, Loader.models.walls.length),position: position,rotation: rotation,),
);

同样道理,这里的 _WallSection 也继承自 ModelComponent,表示墙体的单个段,它的构造函数接收墙段的索引、位置和旋转,并从 Loader.models.walls 中加载对应的墙体模型:

另外还有光源演示, Demo 里的光源主要体现在几个随机移动的黑点,对应项目里的 Wisp 对象,它继承自 LightComponent,并通过 HasGameRef 混入获取对游戏实例的引用,它的主要功能是创建一个动态移动的光源,模拟萤火虫的效果:

对于光源,同样有一个内部模型对象 _VisualLight ,它同样继承自 MeshComponent,用于渲染光源的视觉效果,这里主要使用了一个小型球体网格 SphereMesh,半径为 0.05,材质为 SpatialMaterial,颜色与光源一致:

最后少不了 Camera,在 Demo 里使用 ThirdPersonCamera 实现了一个自定义的 3D 摄像机组件,它主要是继承自 CameraComponent3D,并通过 HasGameReference 混入获取对游戏实例的引用,而它的主要功能是实现第三人称视角,跟随玩家角色的移动和方向:

ThirdPersonCamera 里它主要是设置了摄像机的视野角度(fovY)、初始位置(position)、上方向向量(up)以及目标点(target),例如position 设置为 Vector3(-18, 6, -18),表示摄像机初始位于玩家后方偏左上方的位置。

position: Vector3(-18, 6, -18),
up: Vector3(0.8, 1, 0.8),
target: Vector3(0, 0, 0),

update 方法在每帧调用,用于更新摄像机的位置和目标点,首先计算目标偏移量 targetOffset 和目标视角点 targetLookAt,分别基于玩家的位置和视角方向进行偏移:

final targetOffset = player.position + _positionOffset;
final targetLookAt = player.position + player.lookAt;

接着,使用线性插值公式更新摄像机的位置和目标点,使其平滑地跟随玩家移动和旋转,插值速度由 _cameraLinearSpeed_cameraRotationSpeed 控制:

position += (targetOffset - position) * _cameraLinearSpeed * dt;
target += (targetLookAt - target) * _cameraRotationSpeed * dt;

另外还有个值得聊的是 HUD,实际上也就是,用于在屏幕右上角显示当前分数,事实上其实就是使用 flame_3d 里的 TextPaint ,它可以把你需要的文本内容直接选入到屏幕:

await camera.viewport.add(Hud());static final textHuge = TextPaint(style: _style.copyWith(fontSize: 64));class Hud extends Component with HasGameRef<CollectTheDonutGame> {void render(Canvas canvas) {super.render(canvas);Styles.textHuge.render(canvas,game.score.toString().padLeft(2, '0'),Vector2(game.size.x - _margin,_margin,),anchor: Anchor.topRight,);}
}

可以看到,flame_3d 大大简化了 Flutter GPU 的使用,同时也给了沉寂这么久的 Flutter GPU 一个落地场景,由于需要 Flutter GPU 和 Impeller 支持,目前 flame_3d 只支持 Android、iOS 和 macOS ,同时由于 flame_3d 还是实验性阶段,所以 API 稳定性还没有保证。

对于 flame 而言,在理想情况下他们甚至希望 flame_3d 的用户完全不需要知道和理解 Flutter GPU,他们的目标是将 Flutter GPU 抽象为一个方便 3D 开发的 API,这不仅简化了创建渲染目标、设置颜色和深度纹理以及配置深度模板等操作,还包含支持更高级的 API,例如几何形状、纹理/材质渲染以及创建可以使用这些形状和材质的网格,最终把这一切和 有的 FCS 紧密结合。

另外本次 Flame 现场还在现在不是用 Flutter GPU 制作了一个小 Demo ship_game,通过覆盖 Raymarching 、 Volumetric Raymarching 、Weight maps 和 Ordered Dithering 来展示了 Flame 原生的能力:

可以看到,在 flame 在加持下,Flutter 在游戏领域的能力确实越来越强,也希望 Flutter GPU 可以早日发布稳定版本,把这个老饼给画完。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/95736.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/95736.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/95736.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无需公网IP,电脑随时与异地飞牛同步互联保持数据一致性

最近小白有这样一个烦恼&#xff1a;随身带着的电脑每天都在更新内容&#xff0c;于是就会有很多很多的存稿。电脑的空间开始变得不够用了。各式各样的图片、视频、文稿等内容&#xff0c;如果要整理到飞牛NAS上&#xff0c;好像很麻烦&#xff0c;而且每次都是需要回到家里才能…

数据库中间件ShardingSphere v5.2.1

数据库中间件ShardingSphere v5.2.1 文章目录数据库中间件ShardingSphere v5.2.1一 概述1 数据库的瓶颈2 优化的手段3 主从复制4 读写分离5 分库分表5.1 背景5.2 垂直分片5.3 水平分片6 ShardingSphere简介二 ShardingSphere-JDBC讲解1 读写分离实现1.1 基于Docker搭建MySQL主从…

[Upscayl图像增强] Electron主进程命令 | 进程间通信IPC

第三章&#xff1a;Electron主进程命令 欢迎回来&#x1f43b;‍❄️ 在第一章&#xff1a;渲染器用户界面&#xff08;前端&#xff09;中&#xff0c;我们探索了您与之交互的按钮和菜单。然后在第二章&#xff1a;AI模型中&#xff0c;我们了解了让您的图像看起来更棒的&qu…

电竞护航小程序成品搭建三角洲行动护航小程序开发俱乐部点单小程序成品游戏派单小程序定制

功能列表&#xff1a;商家入驻 成为管事 平台公告 客服密钥 客服管理 发单模板 快捷发单 自定义发单 打手入驻 订单裁决 即时通讯 &#xff08;接单者员与发单者&#xff09; 打手排行 邀请排行 余额提现技术栈&#xff1a;前端uniapp 后端java

Redis数据库基础

1.关系型数据库和NoSQL数据库数据库主要分为两大类:关系型数据库与NoSQL数据库关系型数据库&#xff0c;是建立在关系模型基础是的数据库&#xff0c;其借助集合代数等数学概念和方法来处理数据库中的数据主流的MySQL&#xff0c;Oracle&#xff0c;MS SQL Server 和DB2都属于这…

【Java实战㉗】Java日志框架实战:Logback与Log4j2的深度探索

目录一、日志框架概述1.1 日志的作用1.2 常见日志框架1.3 日志级别二、Logback 框架实战2.1 Logback 依赖导入2.2 Logback 配置文件2.3 日志输出格式自定义2.4 Logback 进阶配置三、Log4j2 框架实战3.1 Log4j2 依赖导入3.2 Log4j2 配置文件3.3 Log4j2 与 SLF4J 整合3.4 日志框架…

基于WFOA与BP神经网络回归模型的特征选择方法研究(Python实现)

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取 或者私信获取。 1.项目背景 在大数据分析与智能建模领域&#xff0c;高维数据广泛存在于金融预测、环境监测和工业过程控制等场景…

​​AI生成PPT工具推荐,从此以后再也不用担心不会做PPT了​​

对于很多人老说&#xff0c;做ppt实在太麻烦了&#xff0c;快速制作出专业且美观的PPT成为众多人的需求&#xff0c;AI生成PPT工具应运而生&#xff0c;极大地提升了PPT制作的效率。以下为大家推荐多个实用的AI生成PPT工具。 1、AiPPT星级评分&#xff1a;★★★★★ AiPPT是一…

CentOS系统停服,系统迁移Ubuntu LTS

CentOS官方已全面停止维护CentOS Linux项目&#xff0c;公告指出 CentOS 7在2024年6月30日停止技术服务支持&#xff0c;(在此之前 2022年1月1日起CentOS官方已经不再对CentOS 8提供服务支持&#xff09;&#xff0c;详情见CentOS官方公告。 一、系统迁移评估 用户需要开始计…

Linux知识回顾总结----文件系统

上章讲的是 os 如果管理被打开的文件&#xff0c;那么没有被打开的文件&#xff08;也就是在磁盘单中的文件&#xff09;使用文件系统进行管理。了解完这一章&#xff0c;我们就可以理解我们如果想要打开一个文件的是如何找到整个文件&#xff0c;然后如何把它加载到内存中的&a…

iOS蓝牙使用及深入剖析高频高负载传输丢包解决方案(附源码)

最近开发了一套iOS原生的蓝牙SDK&#xff0c;总结了一些有价值的踩过的坑&#xff0c;分享出来给有需要的同学做个参考。 一、蓝牙的使用 iOS有一套封装好的完善的蓝牙API &#xff0c;可以很便捷的实现与蓝牙的连接和通信&#xff0c;蓝牙通信的大体流程如下&#xff0c;先对基…

Python 正则表达式实战:用 Match 对象轻松解析拼接数据流

摘要 这篇文章围绕 Python 的正则表达式 Match 对象&#xff08;特别是 endpos、lastindex、lastgroup 以及 group / groups 等方法/属性&#xff09;做一个从浅入深、贴近日常开发场景的讲解。我们会给出一个真实又常见的使用场景&#xff1a;解析由设备/服务发来的“拼接式”…

基于Pygame的六边形战术推演系统深度剖析——从数据结构到3D渲染的完整实现(附完整代码)

1. 项目概述与技术选型 战术推演系统是军事训练和游戏开发中的重要组成部分,它能够模拟真实的战术场景,为用户提供策略思考的平台。本文将深入分析一套基于Python Pygame框架开发的城市巷战战术推演系统,该系统采用六边形网格布局,实现了恐怖分子与反恐精英的对抗模拟,具…

支持二次开发的代练App源码:订单管理、代练监控、安全护航功能齐全,一站式解决代练护航平台源码(PHP+ Uni-app)

一、技术架构&#xff1a;高性能与跨平台的核心支撑前端框架Uni-app&#xff1a;基于Vue.js的跨平台框架&#xff0c;支持编译至微信小程序、H5、iOS/Android App及PC端&#xff0c;代码复用率超80%&#xff0c;显著降低开发成本。实时通信&#xff1a;集成WebSocket实现订单状…

AI热点周报(8.31~9.6): Qwen3‑Max‑Preview上线、GLM-4.5提供一键迁移、Gemini for Home,AI风向何在?

名人说&#xff1a;博观而约取&#xff0c;厚积而薄发。——苏轼《稼说送张琥》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录一、3分钟速览版&#xff1a;一张表看懂本周AI大事二、国内&#xff1a;模型与生态的…

异步操作终止2

您提的这个问题非常棒&#xff0c;说明您思考得非常深入&#xff01;您完全正确&#xff0c;我之前的示例中使用的 return; 会中断 handleDraw 函数中所有后续的逻辑&#xff0c;这在很多场景下并不是我们想要的。 我们的目标是只中断画图这一个特定的逻辑&#xff0c;而让函数…

《AI大模型应知应会100篇》第67篇 Web应用与大模型集成开发实践——1小时打造国产大模型智能客服系统

第67篇&#xff1a;Web应用与大模型集成开发实践——1小时打造国产大模型智能客服系统 一句话核心价值&#xff1a;无需翻墙&#xff01;用Flask国产大模型API&#xff08;通义/文心一言/讯飞&#xff09;快速构建合规Web问答系统&#xff0c;电商客服人力成本直降70%&#xff…

python系列之综合项目:智能个人任务管理系统

不为失败找理由&#xff0c;只为成功找方法。所有的不甘&#xff0c;因为还心存梦想&#xff0c;所以在你放弃之前&#xff0c;好好拼一把&#xff0c;只怕心老&#xff0c;不怕路长。 python系列之文件操作&#xff1a;让程序拥有"记忆"的超能力&#xff01;一、项目…

鸿蒙UI开发实战:解决布局错乱与响应异常

文章目录鸿蒙UI开发实战指南&#xff1a;解决ArkUI声明式布局错乱、组件不显示与事件响应异常引言ArkUI声明式开发的技术优势开发痛点与本文价值布局错乱问题常见原因固定像素单位使用不当布局嵌套层级过深Flex布局属性配置错误响应式布局缺失解决方案弹性单位适配&#xff1a;…

B.50.10.09-RPC核心原理与电商应用

RPC核心原理与电商应用实战 第1章&#xff1a;RPC核心概念与价值 1.1. 什么是 RPC&#xff1f; RPC (Remote Procedure Call)&#xff0c;即远程过程调用&#xff0c;是一种允许一台计算机&#xff08;客户端&#xff09;上的程序&#xff0c;调用另一台计算机&#xff08;服务…