diff --git a/loading/runtime_save_load/README.md b/loading/runtime_save_load/README.md index b3c78245..1617f05c 100644 --- a/loading/runtime_save_load/README.md +++ b/loading/runtime_save_load/README.md @@ -17,7 +17,8 @@ Can be loaded and saved at run-time: Can be loaded at run-time: - 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) [^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 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 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 -- 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) 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) diff --git a/loading/runtime_save_load/examples/3d_scenes/plastic_monobloc_chair_01_1k/plastic_monobloc_chair_01.bin b/loading/runtime_save_load/examples/3d_scenes/gltf/plastic_monobloc_chair_01.bin similarity index 100% rename from loading/runtime_save_load/examples/3d_scenes/plastic_monobloc_chair_01_1k/plastic_monobloc_chair_01.bin rename to loading/runtime_save_load/examples/3d_scenes/gltf/plastic_monobloc_chair_01.bin diff --git a/loading/runtime_save_load/examples/3d_scenes/plastic_monobloc_chair_01_1k/plastic_monobloc_chair_01_1k.gltf b/loading/runtime_save_load/examples/3d_scenes/gltf/plastic_monobloc_chair_01_1k.gltf similarity index 100% rename from loading/runtime_save_load/examples/3d_scenes/plastic_monobloc_chair_01_1k/plastic_monobloc_chair_01_1k.gltf rename to loading/runtime_save_load/examples/3d_scenes/gltf/plastic_monobloc_chair_01_1k.gltf diff --git a/loading/runtime_save_load/examples/3d_scenes/plastic_monobloc_chair_01_1k/textures/plastic_monobloc_chair_01_arm_1k.jpg b/loading/runtime_save_load/examples/3d_scenes/gltf/textures/plastic_monobloc_chair_01_arm_1k.jpg similarity index 100% rename from loading/runtime_save_load/examples/3d_scenes/plastic_monobloc_chair_01_1k/textures/plastic_monobloc_chair_01_arm_1k.jpg rename to loading/runtime_save_load/examples/3d_scenes/gltf/textures/plastic_monobloc_chair_01_arm_1k.jpg diff --git a/loading/runtime_save_load/examples/3d_scenes/plastic_monobloc_chair_01_1k/textures/plastic_monobloc_chair_01_diff_1k.jpg b/loading/runtime_save_load/examples/3d_scenes/gltf/textures/plastic_monobloc_chair_01_diff_1k.jpg similarity index 100% rename from loading/runtime_save_load/examples/3d_scenes/plastic_monobloc_chair_01_1k/textures/plastic_monobloc_chair_01_diff_1k.jpg rename to loading/runtime_save_load/examples/3d_scenes/gltf/textures/plastic_monobloc_chair_01_diff_1k.jpg diff --git a/loading/runtime_save_load/examples/3d_scenes/plastic_monobloc_chair_01_1k/textures/plastic_monobloc_chair_01_nor_gl_1k.jpg b/loading/runtime_save_load/examples/3d_scenes/gltf/textures/plastic_monobloc_chair_01_nor_gl_1k.jpg similarity index 100% rename from loading/runtime_save_load/examples/3d_scenes/plastic_monobloc_chair_01_1k/textures/plastic_monobloc_chair_01_nor_gl_1k.jpg rename to loading/runtime_save_load/examples/3d_scenes/gltf/textures/plastic_monobloc_chair_01_nor_gl_1k.jpg diff --git a/loading/runtime_save_load/examples/audio/item_spawn.mp3 b/loading/runtime_save_load/examples/audio/item_spawn.mp3 new file mode 100644 index 00000000..78d1eea4 Binary files /dev/null and b/loading/runtime_save_load/examples/audio/item_spawn.mp3 differ diff --git a/loading/runtime_save_load/examples/audio/item_spawn.wav b/loading/runtime_save_load/examples/audio/item_spawn.wav new file mode 100644 index 00000000..742e05c1 Binary files /dev/null and b/loading/runtime_save_load/examples/audio/item_spawn.wav differ diff --git a/loading/runtime_save_load/runtime_save_load.gd b/loading/runtime_save_load/runtime_save_load.gd index c8475eb0..c817f204 100644 --- a/loading/runtime_save_load/runtime_save_load.gd +++ b/loading/runtime_save_load/runtime_save_load.gd @@ -6,6 +6,7 @@ extends Control @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 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 scene_viewer := $MarginContainer/VBoxContainer/Result/SceneViewer as SubViewportContainer @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. 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: plain_text_viewer.visible = false @@ -58,6 +46,20 @@ func reset_visibility() -> void: 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: audio_stream_player.play() @@ -98,8 +100,8 @@ func _on_export_file_dialog_file_selected(path: String) -> void: image.save_webp(path) elif audio_player.visible: - # Ogg Vorbis audio can't be exported at runtime to a standard format - # (only WAV files can be using `AudioStreamWAV.save_to_wav()`). + # Ogg Vorbis and MP3 audio can't be exported at runtime to a standard format + # (only WAV files can be saved from WAV sources using `AudioStreamWAV.save_to_wav()`). pass elif scene_viewer.visible: @@ -166,31 +168,53 @@ func open_file(path: String) -> void: texture_viewer.texture = ImageTexture.create_from_image(image) # Audio. - # Run-time MP3 and WAV loading aren't supported by the engine yet. - elif path_lower.ends_with(".ogg"): + elif path_lower.ends_with(".ogg") or path_lower.ends_with(".mp3") or path_lower.ends_with(".wav"): # `AudioStreamOggVorbis.load_from_buffer()` can alternatively be used # 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() export_button.disabled = 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. - 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. # 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. - 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)]) + # + # The same applies to FBX, except FBXState and FBXDocument are used instead. + if path_lower.ends_with(".gltf") or path_lower.ends_with(".glb"): + 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)]) + 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. elif ( diff --git a/loading/runtime_save_load/runtime_save_load.tscn b/loading/runtime_save_load/runtime_save_load.tscn index 08423e7d..64bd550f 100644 --- a/loading/runtime_save_load/runtime_save_load.tscn +++ b/loading/runtime_save_load/runtime_save_load.tscn @@ -55,7 +55,7 @@ text = "Browse" [node name="FileDialog" type="FileDialog" parent="MarginContainer/VBoxContainer/HBoxContainer"] title = "Open a File" -size = Vector2i(392, 159) +size = Vector2i(392, 175) ok_button_text = "Open" file_mode = 0 access = 2 @@ -83,7 +83,6 @@ layout_mode = 2 expand_mode = 3 [node name="AudioPlayer" type="Button" parent="MarginContainer/VBoxContainer/Result"] -visible = false layout_mode = 2 theme_override_font_sizes/font_size = 24 text = "Play Audio" @@ -91,6 +90,12 @@ text = "Play Audio" [node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="MarginContainer/VBoxContainer/Result/AudioPlayer"] 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"] visible = false custom_minimum_size = Vector2(1050, 400) @@ -100,6 +105,7 @@ stretch = true [node name="SubViewport" type="SubViewport" parent="MarginContainer/VBoxContainer/Result/SceneViewer"] handle_input_locally = false msaa_3d = 2 +scaling_3d_scale = 2.0 size = Vector2i(1050, 400) render_target_update_mode = 0