news 2026/10/3 13:35:18

Godot 演示项目:Runtime Save/Load —— 绕过资源导入系统的运行时文件加载与保存实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Godot 演示项目:Runtime Save/Load —— 绕过资源导入系统的运行时文件加载与保存实战
  • 示例工程

【免费下载链接】godot-demo-projects

Demonstration and Template Projects

项目地址:https://gitcode.com/GitHub_Trending/go/godot-demo-projects
点击查看免费下载

本指南围绕 godot-demo-projects 仓库中的 runtime_save_load 演示项目,系统讲解如何在不经过 Godot 资源导入系统(Import System)的前提下,于运行时直接加载与保存图片、音频、3D 场景(glTF)、ZIP 压缩包、字体与纯文本等各类文件。读完本文,你将掌握Image、AudioStreamOggVorbis/MP3/WAV、GLTFDocument/FBXDocument、FontFile、ZIPReader/ZIPPacker与FileAccess等核心类的运行时读写用法,并理解其与 PCK 打包、资源导入、游戏存档序列化之间的边界,可直接应用到用户生成内容(UGC)、DLC 扩展包等真实场景。

项目概览:为什么需要绕过资源导入系统

在 Godot 中,常规的资源使用方式是把图片、音频、场景等资源导入(Import)进项目,最终随导出打包进 PCK 文件。这种方式适合「开发者预先准备、内容固定」的资源。但当内容来自用户运行时产生(如玩家上传的头像、自制的模型、自定义关卡包)时,不可能在每次导出前重新导入,因此需要一套「运行时直接从磁盘读写文件」的方案。

runtime_save_load 演示项目 正是展示这一能力的官方示例。它的核心主张体现在项目自述中:

本项目展示如何在不经过 Godot 资源导入系统的情况下,加载和保存各种文件类型。这对于在运行时加载/保存图片、声音、3D 场景和 ZIP 压缩包等用户生成内容非常有用,且无需用户通过 Godot 生成 PCK 文件。

项目基于 GDScript 编写,渲染后端为 Compatibility(OpenGL),对应配置可在 project.godot 中看到:renderer/rendering_method="gl_compatibility",主场景为res://runtime_save_load.tscn,并开启了run/low_processor_mode=true以降低闲置时的 CPU 占用。

支持的文件类型清单

能力文件类型
可运行时加载并保存图片(JPEG、PNG、WebP)、3D 场景(glTF 2.0)、ZIP 压缩包、纯文本文件
可运行时加载(仅加载)图片(TGA、BMP、SVG)、3D 场景(FBX)、音频(Ogg Vorbis、MP3、WAV)、字体(TTF、OTF、WOFF、WOFF2、PFB、PFM、BMFont)

演示目录下 examples 为每种类型都准备了可直接试用的样例:images/下同源的godot_icon共 6 种格式(png、jpg、webp、tga、bmp、svg),audio/下的item_spawn有 ogg、mp3、wav 三种编码,fonts/提供 TTF/OTF/WOFF2 三种字体,3d_scenes/gltf/提供带纹理贴图的塑料椅 glTF 场景,misc/下则是example.zip与file.txt。

快速上手:运行演示项目

  1. 用 Godot 4.x 打开 loading/runtime_save_load/project.godot(项目配置config/features=PackedStringArray("4.7"),需 Godot 4.7 或兼容版本)。
  2. 点击运行,主场景 runtime_save_load.tscn 会加载一个文件浏览器 UI。
  3. 在顶部的FilePath输入框中直接输入文件路径,或点击Browse按钮通过文件对话框选择examples/目录中的任一文件。
  4. 程序会根据文件扩展名自动识别类型,并在下方的Result区域显示对应预览(图片纹理、音频播放按钮、3D 场景视图、字体字形预览、ZIP 文件列表或纯文本内容)。
  5. 预览成功后,点击Export按钮可将当前内容保存为指定格式的新文件。

界面结构:一个场景承载六种查看器

runtime_save_load.tscn 将全部 UI 组织在一个Control根节点下,并通过visible切换不同查看器,其结构对理解脚本逻辑很有帮助:

  • 输入区:FilePath(LineEdit)、Browse按钮与FileDialog(file_mode=0即打开文件模式)。
  • 结果区(Result/CenterContainer):
    • PlainTextViewer:滚动容器 + Label,用于显示纯文本。
    • TextureViewer:TextureRect,显示图片。
    • AudioPlayer:按钮内含AudioStreamPlayer(volume_db=-10.0)与时长信息 Label。
    • SceneViewer:SubViewportContainer + SubViewport(开启 MSAA、scaling_3d_scale=2.0)+ Camera3D + 三盏方向光(KeyLight/FillLight/BackLight)+ Zoom 滑块(HSlider,min_value=-100.0、max_value=-0.1)。
    • FontViewer:Label,用预览字体渲染字母、数字与符号表。
    • ZIPViewer:HSplitContainer,左侧FileList(ItemList)列出压缩包内文件,右侧FilePreview(Label)显示选中文件的 UTF-8 文本内容。
    • ErrorLabel:加载失败时的错误提示。
  • 导出区:Export按钮与独立FileDialog(access=2即文件系统访问)。

场景中所有信号均在 tscn 末尾通过[connection]声明绑定,例如text_submitted、file_selected、item_selected、value_changed分别对应脚本中的_on_file_path_text_submitted、_on_file_dialog_file_selected、_on_zip_viewer_item_selected、_on_scene_viewer_zoom_value_changed等回调。

核心实现:open_file 的分发逻辑

整个加载流程集中在 runtime_save_load.gd 的open_file(path)方法中。它将路径转为小写后,按扩展名依次匹配图片、音频、3D 场景、字体、ZIP,最后以纯文本作为兜底:

func open_file(path: String) -> void: var path_lower := path.to_lower() # 图片分支 if path_lower.ends_with(".jpg") or path_lower.ends_with(".jpeg") \ or path_lower.ends_with(".png") or path_lower.ends_with(".webp") \ or path_lower.ends_with(".svg") or path_lower.ends_with(".tga") \ or path_lower.ends_with(".bmp"): # ... # 音频分支 elif path_lower.ends_with(".ogg") or path_lower.ends_with(".mp3") \ or path_lower.ends_with(".wav"): # ... # 3D 场景分支 elif path_lower.ends_with(".gltf") or path_lower.ends_with(".glb") \ or path_lower.ends_with(".fbx"): # ... # 字体分支 elif path_lower.ends_with(".ttf") or path_lower.ends_with(".otf") \ or path_lower.ends_with(".woff") or path_lower.ends_with(".woff2") \ or path_lower.ends_with(".pfb") or path_lower.ends_with(".pfm") \ or path_lower.ends_with(".fnt") or path_lower.ends_with(".font"): # ... # ZIP 分支 elif path_lower.ends_with(".zip"): # ... # 兜底:按纯文本打开 else: var file_contents := FileAccess.get_file_as_string(path) # ...

图片:一行代码完成格式探测与加载

图片分支是整个演示中代码最简洁的,因为它直接调用Image.load_from_file(path)—— 该方法会根据文件扩展名自动探测格式并完成读取,同时覆盖 JPEG/PNG/WebP/SVG/TGA/BMP 七种格式:

var image := Image.load_from_file(path) reset_visibility() export_file_dialog.filters = ["*.png ; PNG Image", "*.jpg, *.jpeg ; JPEG Image", "*.webp ; WebP Image"] texture_viewer.visible = true texture_viewer.texture = ImageTexture.create_from_image(image)

脚本注释明确指出,如果需要错误处理或更细粒度的控制(例如控制 SVG 的加载缩放比例),应改用Image类的load_*_from_buffer()系列方法与load_svg_from_string()。

音频:按格式选择对应的 AudioStream 类

音频分支依据扩展名分别使用三个静态工厂方法,将文件直接构造成可播放的AudioStream资源:

if path_lower.ends_with(".ogg"): audio_stream_player.stream = AudioStreamOggVorbis.load_from_file(path) elif path_lower.ends_with(".mp3"): audio_stream_player.stream = AudioStreamMP3.load_from_file(path) elif path_lower.ends_with(".wav"): audio_stream_player.stream = AudioStreamWAV.load_from_file(path)

加载成功后脚本会显示播放按钮并计算音频时长(roundi(stream.get_length())后格式化为MM:SS)。需要注意:如果数据已在内存中(PackedByteArray),可改用AudioStreamOggVorbis.load_from_buffer()这类缓冲区方法。

3D 场景:GLTFDocument 与 FBXDocument 的运行时装配

glTF 分支使用GLTFDocument+GLTFState完成「文件 → 节点树」的转换,前者负责加载数据,后者保存加载状态。成功加载后调用generate_scene(gltf_state)生成根节点并挂到 SubViewport 下的场景查看器中:

var gltf_document := GLTFDocument.new() var gltf_state := GLTFState.new() var error := gltf_document.append_from_file(path, gltf_state) if error == OK: scene_viewer_root_node = gltf_document.generate_scene(gltf_state) reset_visibility() scene_viewer.add_child(scene_viewer_root_node) export_file_dialog.filters = ["*.gltf ; glTF Text Scene", "*.glb ; glTF Binary Scene"] scene_viewer.visible = true else: show_error('Couldn\'t load "%s" as a glTF scene (error code: %s).' % [path.get_file(), error_string(error)])

FBX 分支的结构与之完全对称,只是换用FBXDocument与FBXState。场景查看器中的Zoom滑块通过_on_scene_viewer_zoom_value_changed控制正交相机(projection=1)的size:滑块取值范围为负值(-100.0 ~ -0.1),scene_viewer_camera.size = abs(value),数值越小(绝对值越大)表示放大倍率越高。

字体:动态字体与位图字体的区分加载

字体分支创建FontFile,根据扩展名区分两类加载方式:.fnt/.font(BMFont 位图字体)走load_bitmap_font(),其余 TTF/OTF/WOFF/WOFF2/PFB/PFM 走load_dynamic_font():

var font_file := FontFile.new() if path_lower.ends_with(".fnt") or path_lower.ends_with(".font"): font_file.load_bitmap_font(path) else: font_file.load_dynamic_font(path) if not font_file.data.is_empty(): font_viewer.add_theme_font_override(&"font", font_file) reset_visibility() font_viewer.visible = true export_button.disabled = true

成功加载后通过add_theme_font_override把字体应用到一个 48 号字号的 Label 上,直接预览字形。脚本用font_file.data.is_empty()判断加载是否成功,失败则进入show_error流程。

ZIP 压缩包:ZIPReader 的只读浏览

ZIP 分支创建ZIPReader,打开压缩包后列出内部全部文件,并禁用列表中以/结尾的目录项:

zip_reader.open(path) var files := zip_reader.get_files() files.sort() export_file_dialog.filters = ["*.zip ; ZIP Archive"] reset_visibility() for file in files: zip_viewer_file_list.add_item(file, null) zip_viewer_file_list.set_item_disabled(-1, file.ends_with("/")) zip_viewer.visible = true

当用户选中列表项时,_on_zip_viewer_item_selected用zip_reader.read_file(文件名)取出原始字节,再get_string_from_utf8()转为文本预览。值得一提的扩展场景:Godot 的「Export PCK/ZIP」功能生成的 ZIP 文件同样可以被读取(其中包含的是导入后的 Godot 资源而非原始工程文件);而若要作为 DLC 追加数据包无缝集成到虚拟文件系统,演示注释推荐改用ProjectSettings.load_resource_pack()。

运行时保存:Export 按钮背后的写入逻辑

_on_export_file_dialog_file_selected(path)根据当前可见的查看器类型决定写入方式:

  • 纯文本:用FileAccess.open(path, FileAccess.WRITE)+store_string()写回,最后close()。
  • 图片:从texture_viewer.texture.get_image()取回Image,再按目标扩展名调用save_png()、save_jpg()(质量常量为JPG_QUALITY = 0.9)或save_webp()。注释特别说明 WebP 默认无损保存,需要有损时可通过Image.save_webp()的可选参数调整。
  • 3D 场景:用GLTFDocument.append_from_scene(scene_viewer_root_node, gltf_state)把预览中的节点树转回 glTF 状态,再write_to_filesystem(gltf_state, path)落盘。输出是文本(.gltf)还是二进制(.glb)由目标路径扩展名决定:二进制写盘更快、体积更小、更适合内嵌纹理,但文本格式更易于调试。
  • ZIP:创建ZIPPacker,open(path)后遍历zip_reader.get_files(),对每个文件依次start_file()→write_file()→close_file(),最后close()。若打开失败会push_error并中止。

两处「无法导出」的限制也在代码中明确标注:音频中的 Ogg Vorbis 与 MP3 无法在运行时导出为标准格式(只有 WAV 源可通过AudioStreamWAV.save_to_wav()保存为 WAV);字体同样只能通过ResourceSaver保存为 Godot 专属的.res格式。因此这两个分支下export_button.disabled = true。

边界与注意事项

原文档的脚注部分给出了三条关键边界,项目中均有对应实现佐证:

  1. 自定义二进制格式:操作自定义二进制格式可以通过FileAccess与PackedByteArray类实现,但演示未展示。这意味着文本/压缩包之外的结构化数据读写需要自行封装字节流逻辑。
  2. SVG 程序化生成:可以先用文本方式程序化生成 SVG 内容,再用FileAccess写入带.svg扩展名的文件。仓库中 examples/images/godot_icon.svg 正是可被Image.load_from_file()直接加载的 SVG 样例。
  3. FBX 运行时加载的已知问题:文档明确提到 Godot 的 issue #96043,即运行时 FBX 加载存在已知缺陷。FBX 分支在append_from_file返回非OK时同样会通过show_error展示error_string(error)错误码,实际项目中建议优先使用 glTF 作为跨引擎交换格式。

与 Serialization 演示的互补关系

本演示关注「文件级 I/O」,而仓库中的 loading/serialization 演示(Saving and Loading / Serialization)关注「游戏进度级 I/O」,展示如何使用ConfigFile与JSON保存游戏状态。原文档明确给出分工建议:前者解决「导出的游戏如何在运行时读取未导入的文件」,后者解决「游戏进度如何持久化」,两者配合即可覆盖从存档数据到 UGC 资源的完整读写需求。

示例资产与许可

examples/3d_scenes/gltf/ 下的plastic_monobloc_chair_01_1k.gltf及配套纹理(diff/nor/arm 三张 JPG 与一个.bin缓冲文件)来自 Poly Haven,采用 CC0 1.0 通用许可;examples/audio/ 下的音频文件来自 Red Eclipse 项目,采用 CC BY-SA 4.0 国际许可;examples/fonts/ 内的 Inter 字体遵循 SIL Open Font License 1.1。在自有项目中使用或二次分发这些示例资产时,需保留对应的许可声明。

  • 示例工程

【免费下载链接】godot-demo-projects

Demonstration and Template Projects

项目地址:https://gitcode.com/GitHub_Trending/go/godot-demo-projects
点击查看免费下载

相关推荐

上一篇:金融数据输入输出:Python处理CSV、Excel和数据库的完整教程
下一篇:如何为深蓝词库转换添加新的输入法格式:Source Generator 自动注册的完整指南

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

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

PowerHarmony电力嵌入式设备模型开发实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 13:26:16

MT9V034全局快门摄像头在智能车循迹中的确定性设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华