mirror of
https://github.com/godotengine/godot-demo-projects.git
synced 2026-01-04 23:10:08 +01:00
Improve Run-time File Saving and Loading demo (#1196)
- Add runtime MP3 and WAV loading with example files. - Add runtime FBX loading (but no example files, due to known issues with the current FBX runtime loading implementation). - Use supersampling for the 3D preview to further improve quality. - Shorten file path to example glTF scene. - Mention audio duration in the UI when loading an audio file.
This commit is contained in:
@@ -17,7 +17,8 @@ Can be loaded and saved at run-time:
|
|||||||
Can be loaded at run-time:
|
Can be loaded at run-time:
|
||||||
|
|
||||||
- Images (TGA, BMP, SVG[^2])
|
- Images (TGA, BMP, SVG[^2])
|
||||||
- Audio (Ogg Vorbis)
|
- 3D scenes (FBX[^3])
|
||||||
|
- Audio (Ogg Vorbis, MP3, WAV)
|
||||||
- Fonts (TTF, OTF, WOFF, WOFF2, PFB, PFM, BMFont)
|
- Fonts (TTF, OTF, WOFF, WOFF2, PFB, PFM, BMFont)
|
||||||
|
|
||||||
[^1]: Manipulating custom binary formats is possible using the FileAccess and
|
[^1]: Manipulating custom binary formats is possible using the FileAccess and
|
||||||
@@ -27,6 +28,9 @@ PackedByteArray classes, but this is not shown in this demo.
|
|||||||
with `.svg` extension using the FileAccess class, but this is not shown in
|
with `.svg` extension using the FileAccess class, but this is not shown in
|
||||||
this demo.
|
this demo.
|
||||||
|
|
||||||
|
[^3]: There are known issues with runtime FBX loading, as mentioned in issue
|
||||||
|
[#96043](https://github.com/godotengine/godot/issues/96043).
|
||||||
|
|
||||||
See the [Saving and Loading (Serialization)](/loading/serialization/) demo for
|
See the [Saving and Loading (Serialization)](/loading/serialization/) demo for
|
||||||
an example of saving/loading game progress.
|
an example of saving/loading game progress.
|
||||||
|
|
||||||
@@ -42,7 +46,7 @@ Check out this demo on the asset library: https://godotengine.org/asset-library/
|
|||||||
|
|
||||||
## Licenses
|
## Licenses
|
||||||
|
|
||||||
- Files in `examples/3d_scenes/plastic_monobloc_chair_01_1k/` are copyright
|
- Files in `examples/3d_scenes/gltf/` are copyright
|
||||||
[Poly Haven](https://polyhaven.com/a/plastic_monobloc_chair_01)
|
[Poly Haven](https://polyhaven.com/a/plastic_monobloc_chair_01)
|
||||||
and are licensed under [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/).
|
and are licensed under [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||||
- Files in `examples/audio/` are copyright [Red Eclipse](https://redeclipse.net)
|
- Files in `examples/audio/` are copyright [Red Eclipse](https://redeclipse.net)
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 620 KiB After Width: | Height: | Size: 620 KiB |
|
Before Width: | Height: | Size: 548 KiB After Width: | Height: | Size: 548 KiB |
|
Before Width: | Height: | Size: 615 KiB After Width: | Height: | Size: 615 KiB |
BIN
loading/runtime_save_load/examples/audio/item_spawn.mp3
Normal file
BIN
loading/runtime_save_load/examples/audio/item_spawn.mp3
Normal file
Binary file not shown.
BIN
loading/runtime_save_load/examples/audio/item_spawn.wav
Normal file
BIN
loading/runtime_save_load/examples/audio/item_spawn.wav
Normal file
Binary file not shown.
@@ -6,6 +6,7 @@ extends Control
|
|||||||
@onready var plain_text_viewer_label := $MarginContainer/VBoxContainer/Result/PlainTextViewer/Label as Label
|
@onready var plain_text_viewer_label := $MarginContainer/VBoxContainer/Result/PlainTextViewer/Label as Label
|
||||||
@onready var texture_viewer := $MarginContainer/VBoxContainer/Result/TextureViewer as TextureRect
|
@onready var texture_viewer := $MarginContainer/VBoxContainer/Result/TextureViewer as TextureRect
|
||||||
@onready var audio_player := $MarginContainer/VBoxContainer/Result/AudioPlayer as Button
|
@onready var audio_player := $MarginContainer/VBoxContainer/Result/AudioPlayer as Button
|
||||||
|
@onready var audio_player_information := $MarginContainer/VBoxContainer/Result/AudioPlayer/Information as Label
|
||||||
@onready var audio_stream_player := $MarginContainer/VBoxContainer/Result/AudioPlayer/AudioStreamPlayer as AudioStreamPlayer
|
@onready var audio_stream_player := $MarginContainer/VBoxContainer/Result/AudioPlayer/AudioStreamPlayer as AudioStreamPlayer
|
||||||
@onready var scene_viewer := $MarginContainer/VBoxContainer/Result/SceneViewer as SubViewportContainer
|
@onready var scene_viewer := $MarginContainer/VBoxContainer/Result/SceneViewer as SubViewportContainer
|
||||||
@onready var scene_viewer_camera := $MarginContainer/VBoxContainer/Result/SceneViewer/SubViewport/Camera3D as Camera3D
|
@onready var scene_viewer_camera := $MarginContainer/VBoxContainer/Result/SceneViewer/SubViewport/Camera3D as Camera3D
|
||||||
@@ -24,19 +25,6 @@ var zip_reader := ZIPReader.new()
|
|||||||
# so that it can be exported later.
|
# so that it can be exported later.
|
||||||
var scene_viewer_root_node: Node
|
var scene_viewer_root_node: Node
|
||||||
|
|
||||||
func _on_browse_pressed() -> void:
|
|
||||||
file_dialog.popup_centered_ratio()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_file_path_text_submitted(new_text: String) -> void:
|
|
||||||
open_file(new_text)
|
|
||||||
# Put the caret at the end of the submitted text.
|
|
||||||
file_path_edit.caret_column = file_path_edit.text.length()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_file_dialog_file_selected(path: String) -> void:
|
|
||||||
open_file(path)
|
|
||||||
|
|
||||||
|
|
||||||
func reset_visibility() -> void:
|
func reset_visibility() -> void:
|
||||||
plain_text_viewer.visible = false
|
plain_text_viewer.visible = false
|
||||||
@@ -58,6 +46,20 @@ func reset_visibility() -> void:
|
|||||||
export_button.disabled = false
|
export_button.disabled = false
|
||||||
|
|
||||||
|
|
||||||
|
func _on_browse_pressed() -> void:
|
||||||
|
file_dialog.popup_centered_ratio()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_file_path_text_submitted(new_text: String) -> void:
|
||||||
|
open_file(new_text)
|
||||||
|
# Put the caret at the end of the submitted text.
|
||||||
|
file_path_edit.caret_column = file_path_edit.text.length()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_file_dialog_file_selected(path: String) -> void:
|
||||||
|
open_file(path)
|
||||||
|
|
||||||
|
|
||||||
func _on_audio_player_pressed() -> void:
|
func _on_audio_player_pressed() -> void:
|
||||||
audio_stream_player.play()
|
audio_stream_player.play()
|
||||||
|
|
||||||
@@ -98,8 +100,8 @@ func _on_export_file_dialog_file_selected(path: String) -> void:
|
|||||||
image.save_webp(path)
|
image.save_webp(path)
|
||||||
|
|
||||||
elif audio_player.visible:
|
elif audio_player.visible:
|
||||||
# Ogg Vorbis audio can't be exported at runtime to a standard format
|
# Ogg Vorbis and MP3 audio can't be exported at runtime to a standard format
|
||||||
# (only WAV files can be using `AudioStreamWAV.save_to_wav()`).
|
# (only WAV files can be saved from WAV sources using `AudioStreamWAV.save_to_wav()`).
|
||||||
pass
|
pass
|
||||||
|
|
||||||
elif scene_viewer.visible:
|
elif scene_viewer.visible:
|
||||||
@@ -166,31 +168,53 @@ func open_file(path: String) -> void:
|
|||||||
texture_viewer.texture = ImageTexture.create_from_image(image)
|
texture_viewer.texture = ImageTexture.create_from_image(image)
|
||||||
|
|
||||||
# Audio.
|
# Audio.
|
||||||
# Run-time MP3 and WAV loading aren't supported by the engine yet.
|
elif path_lower.ends_with(".ogg") or path_lower.ends_with(".mp3") or path_lower.ends_with(".wav"):
|
||||||
elif path_lower.ends_with(".ogg"):
|
|
||||||
# `AudioStreamOggVorbis.load_from_buffer()` can alternatively be used
|
# `AudioStreamOggVorbis.load_from_buffer()` can alternatively be used
|
||||||
# if you have Ogg Vorbis data in a PackedByteArray instead of a file.
|
# if you have Ogg Vorbis data in a PackedByteArray instead of a file.
|
||||||
audio_stream_player.stream = AudioStreamOggVorbis.load_from_file(path)
|
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)
|
||||||
reset_visibility()
|
reset_visibility()
|
||||||
export_button.disabled = true
|
export_button.disabled = true
|
||||||
audio_player.visible = true
|
audio_player.visible = true
|
||||||
|
var duration := roundi(audio_stream_player.stream.get_length())
|
||||||
|
@warning_ignore("integer_division")
|
||||||
|
audio_player_information.text = "Duration: %02d:%02d" % [duration / 60, duration % 60]
|
||||||
|
|
||||||
# 3D scenes.
|
# 3D scenes.
|
||||||
elif path_lower.ends_with(".gltf") or path_lower.ends_with(".glb"):
|
elif path_lower.ends_with(".gltf") or path_lower.ends_with(".glb") or path_lower.ends_with(".fbx"):
|
||||||
# GLTFState is used by GLTFDocument to store the loaded scene's state.
|
# GLTFState is used by GLTFDocument to store the loaded scene's state.
|
||||||
# GLTFDocument is the class that handles actually loading glTF data into a Godot node tree,
|
# GLTFDocument is the class that handles actually loading glTF data into a Godot node tree,
|
||||||
# which means it supports glTF features such as lights and cameras.
|
# which means it supports glTF features such as lights and cameras.
|
||||||
var gltf_document := GLTFDocument.new()
|
#
|
||||||
var gltf_state := GLTFState.new()
|
# The same applies to FBX, except FBXState and FBXDocument are used instead.
|
||||||
var error := gltf_document.append_from_file(path, gltf_state)
|
if path_lower.ends_with(".gltf") or path_lower.ends_with(".glb"):
|
||||||
if error == OK:
|
var gltf_document := GLTFDocument.new()
|
||||||
scene_viewer_root_node = gltf_document.generate_scene(gltf_state)
|
var gltf_state := GLTFState.new()
|
||||||
reset_visibility()
|
var error := gltf_document.append_from_file(path, gltf_state)
|
||||||
scene_viewer.add_child(scene_viewer_root_node)
|
if error == OK:
|
||||||
export_file_dialog.filters = ["*.gltf ; glTF Text Scene", "*.glb ; glTF Binary Scene"]
|
scene_viewer_root_node = gltf_document.generate_scene(gltf_state)
|
||||||
scene_viewer.visible = true
|
reset_visibility()
|
||||||
else:
|
scene_viewer.add_child(scene_viewer_root_node)
|
||||||
show_error('Couldn\'t load "%s" as a glTF scene (error code: %s).' % [path.get_file(), error_string(error)])
|
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)])
|
||||||
|
elif path_lower.ends_with(".fbx"):
|
||||||
|
var fbx_document := FBXDocument.new()
|
||||||
|
var fbx_state := FBXState.new()
|
||||||
|
var error := fbx_document.append_from_file(path, fbx_state)
|
||||||
|
if error == OK:
|
||||||
|
scene_viewer_root_node = fbx_document.generate_scene(fbx_state)
|
||||||
|
reset_visibility()
|
||||||
|
scene_viewer.add_child(scene_viewer_root_node)
|
||||||
|
export_file_dialog.filters = ["*.fbx ; FBX Scene"]
|
||||||
|
scene_viewer.visible = true
|
||||||
|
else:
|
||||||
|
show_error('Couldn\'t load "%s" as a FBX scene (error code: %s).' % [path.get_file(), error_string(error)])
|
||||||
|
|
||||||
# Fonts.
|
# Fonts.
|
||||||
elif (
|
elif (
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ text = "Browse"
|
|||||||
|
|
||||||
[node name="FileDialog" type="FileDialog" parent="MarginContainer/VBoxContainer/HBoxContainer"]
|
[node name="FileDialog" type="FileDialog" parent="MarginContainer/VBoxContainer/HBoxContainer"]
|
||||||
title = "Open a File"
|
title = "Open a File"
|
||||||
size = Vector2i(392, 159)
|
size = Vector2i(392, 175)
|
||||||
ok_button_text = "Open"
|
ok_button_text = "Open"
|
||||||
file_mode = 0
|
file_mode = 0
|
||||||
access = 2
|
access = 2
|
||||||
@@ -83,7 +83,6 @@ layout_mode = 2
|
|||||||
expand_mode = 3
|
expand_mode = 3
|
||||||
|
|
||||||
[node name="AudioPlayer" type="Button" parent="MarginContainer/VBoxContainer/Result"]
|
[node name="AudioPlayer" type="Button" parent="MarginContainer/VBoxContainer/Result"]
|
||||||
visible = false
|
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_font_sizes/font_size = 24
|
theme_override_font_sizes/font_size = 24
|
||||||
text = "Play Audio"
|
text = "Play Audio"
|
||||||
@@ -91,6 +90,12 @@ text = "Play Audio"
|
|||||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="MarginContainer/VBoxContainer/Result/AudioPlayer"]
|
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="MarginContainer/VBoxContainer/Result/AudioPlayer"]
|
||||||
volume_db = -10.0
|
volume_db = -10.0
|
||||||
|
|
||||||
|
[node name="Information" type="Label" parent="MarginContainer/VBoxContainer/Result/AudioPlayer"]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_top = 48.0
|
||||||
|
offset_right = 40.0
|
||||||
|
offset_bottom = 71.0
|
||||||
|
|
||||||
[node name="SceneViewer" type="SubViewportContainer" parent="MarginContainer/VBoxContainer/Result"]
|
[node name="SceneViewer" type="SubViewportContainer" parent="MarginContainer/VBoxContainer/Result"]
|
||||||
visible = false
|
visible = false
|
||||||
custom_minimum_size = Vector2(1050, 400)
|
custom_minimum_size = Vector2(1050, 400)
|
||||||
@@ -100,6 +105,7 @@ stretch = true
|
|||||||
[node name="SubViewport" type="SubViewport" parent="MarginContainer/VBoxContainer/Result/SceneViewer"]
|
[node name="SubViewport" type="SubViewport" parent="MarginContainer/VBoxContainer/Result/SceneViewer"]
|
||||||
handle_input_locally = false
|
handle_input_locally = false
|
||||||
msaa_3d = 2
|
msaa_3d = 2
|
||||||
|
scaling_3d_scale = 2.0
|
||||||
size = Vector2i(1050, 400)
|
size = Vector2i(1050, 400)
|
||||||
render_target_update_mode = 0
|
render_target_update_mode = 0
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user