增加 sfx_config_panel
This commit is contained in:
parent
86b6bc733b
commit
301ee98c0f
43
manager/archive_manager/debug_panel.gd
Normal file
43
manager/archive_manager/debug_panel.gd
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
extends CanvasLayer
|
||||||
|
|
||||||
|
@onready var quit_debug_button: Button = %QuitDebugModeButton
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
AudioManager.process_mode = Node.PROCESS_MODE_PAUSABLE
|
||||||
|
SceneManager.toggle_pause_counter(true, "debugging")
|
||||||
|
layer = GlobalConfig.CANVAS_LAYER_SETTINGS
|
||||||
|
quit_debug_button.pressed.connect(_on_quit_debug_button_pressed)
|
||||||
|
|
||||||
|
# 恢复上次打开的 tab
|
||||||
|
var tab = ArchiveManager.get_global_value(&"debug_panel_tab", 0)
|
||||||
|
%TabContainer.current_tab = tab
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if (
|
||||||
|
event.is_action_pressed("debugging")
|
||||||
|
or event.is_action_pressed("cancel")
|
||||||
|
or event.is_action_pressed("escape")
|
||||||
|
):
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_quit_debug_button_pressed() -> void:
|
||||||
|
# 不写入配置
|
||||||
|
GlobalConfig.DEBUG = false
|
||||||
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
AudioManager.process_mode = Node.PROCESS_MODE_ALWAYS
|
||||||
|
SceneManager.toggle_pause_counter(false, "debugging")
|
||||||
|
|
||||||
|
|
||||||
|
func quit() -> void:
|
||||||
|
queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_tab_container_tab_changed(tab: int) -> void:
|
||||||
|
ArchiveManager.set_global_entry(&"debug_panel_tab", tab)
|
1
manager/archive_manager/debug_panel.gd.uid
Normal file
1
manager/archive_manager/debug_panel.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://d3aitlnh5hrfc
|
60
manager/archive_manager/debug_panel.tscn
Normal file
60
manager/archive_manager/debug_panel.tscn
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://d4jeeteyq8kk3"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bo4f3sdubdp61" path="res://manager/archive_manager/savings_panel.tscn" id="1_fbybo"]
|
||||||
|
[ext_resource type="Script" uid="uid://d3aitlnh5hrfc" path="res://manager/archive_manager/debug_panel.gd" id="1_k0pa6"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b6ogrp5ec5nr3" path="res://manager/archive_manager/sfx_config_panel.tscn" id="2_t7mby"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k0pa6"]
|
||||||
|
bg_color = Color(0, 0, 0, 1)
|
||||||
|
|
||||||
|
[node name="DebugLayer" type="CanvasLayer"]
|
||||||
|
process_mode = 3
|
||||||
|
scale = Vector2(0.5, 0.5)
|
||||||
|
transform = Transform2D(0.5, 0, 0, 0.5, 0, 0)
|
||||||
|
script = ExtResource("1_k0pa6")
|
||||||
|
|
||||||
|
[node name="PanelContainer" type="PanelContainer" parent="."]
|
||||||
|
offset_left = 68.0
|
||||||
|
offset_top = 28.0
|
||||||
|
offset_right = 1068.0
|
||||||
|
offset_bottom = 606.0
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_k0pa6")
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "Debug 面板"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="QuitDebugModeButton" type="Button" parent="PanelContainer/VBoxContainer/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "关闭 Debug 模式"
|
||||||
|
|
||||||
|
[node name="TabContainer" type="TabContainer" parent="PanelContainer/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(1000, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
mouse_filter = 0
|
||||||
|
current_tab = 0
|
||||||
|
|
||||||
|
[node name="存档控制" parent="PanelContainer/VBoxContainer/TabContainer" instance=ExtResource("1_fbybo")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="音效控制" parent="PanelContainer/VBoxContainer/TabContainer" instance=ExtResource("2_t7mby")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
metadata/_tab_index = 1
|
||||||
|
|
||||||
|
[connection signal="tab_changed" from="PanelContainer/VBoxContainer/TabContainer" to="." method="_on_tab_container_tab_changed"]
|
@ -1,7 +1,6 @@
|
|||||||
extends Control
|
extends Control
|
||||||
|
|
||||||
# UI References
|
# UI References
|
||||||
@onready var quit_debug_button: Button = %QuitDebugModeButton
|
|
||||||
@onready var archive_grid: GridContainer = %ArchiveGrid
|
@onready var archive_grid: GridContainer = %ArchiveGrid
|
||||||
@onready var scroll_container: ScrollContainer = %ScrollContainer
|
@onready var scroll_container: ScrollContainer = %ScrollContainer
|
||||||
@onready var current_archive_label: Label = %CurrentArchiveLabel
|
@onready var current_archive_label: Label = %CurrentArchiveLabel
|
||||||
@ -18,11 +17,9 @@ var next_available_id: int = 2 # Start from 2 since 1 is reserved for main arch
|
|||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
get_parent().layer = GlobalConfig.CANVAS_LAYER_SETTINGS
|
|
||||||
# Setup UI
|
# Setup UI
|
||||||
archive_grid.columns = GRID_COLUMNS
|
archive_grid.columns = GRID_COLUMNS
|
||||||
# Connect signals
|
# Connect signals
|
||||||
quit_debug_button.pressed.connect(_on_quit_debug_button_pressed)
|
|
||||||
save_button.pressed.connect(_on_save_button_pressed)
|
save_button.pressed.connect(_on_save_button_pressed)
|
||||||
refresh_button.pressed.connect(_refresh_archive_list)
|
refresh_button.pressed.connect(_refresh_archive_list)
|
||||||
name_input.text_submitted.connect(_on_name_submitted)
|
name_input.text_submitted.connect(_on_name_submitted)
|
||||||
@ -57,12 +54,6 @@ func _on_name_submitted(_text: String) -> void:
|
|||||||
_on_save_button_pressed()
|
_on_save_button_pressed()
|
||||||
|
|
||||||
|
|
||||||
func _on_quit_debug_button_pressed() -> void:
|
|
||||||
# 不写入配置
|
|
||||||
GlobalConfig.DEBUG = false
|
|
||||||
quit()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_save_button_pressed() -> void:
|
func _on_save_button_pressed() -> void:
|
||||||
# Check limit
|
# Check limit
|
||||||
if manual_archives.size() >= MAX_MANUAL_ARCHIVES:
|
if manual_archives.size() >= MAX_MANUAL_ARCHIVES:
|
||||||
@ -152,7 +143,7 @@ func _copy_current_archive(archive_name: String) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _refresh_archive_list() -> void:
|
func _refresh_archive_list() -> void:
|
||||||
# refresh savings name
|
# refresh debugging name
|
||||||
_update_default_name()
|
_update_default_name()
|
||||||
|
|
||||||
# Clear existing items
|
# Clear existing items
|
||||||
@ -289,26 +280,3 @@ func _load_manual_archives_data() -> void:
|
|||||||
|
|
||||||
func _show_notification(message: String) -> void:
|
func _show_notification(message: String) -> void:
|
||||||
SceneManager.pop_notification(message)
|
SceneManager.pop_notification(message)
|
||||||
|
|
||||||
|
|
||||||
func _enter_tree() -> void:
|
|
||||||
SceneManager.toggle_pause_counter(true, "savings")
|
|
||||||
|
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
|
||||||
SceneManager.toggle_pause_counter(false, "savings")
|
|
||||||
|
|
||||||
|
|
||||||
func quit() -> void:
|
|
||||||
queue_free()
|
|
||||||
|
|
||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> void:
|
|
||||||
# savings 界面接受所有输入事件
|
|
||||||
get_viewport().set_input_as_handled()
|
|
||||||
if (
|
|
||||||
event.is_action_pressed("savings")
|
|
||||||
or event.is_action_pressed("cancel")
|
|
||||||
or event.is_action_pressed("escape")
|
|
||||||
):
|
|
||||||
quit()
|
|
||||||
|
@ -1,85 +1,71 @@
|
|||||||
[gd_scene load_steps=2 format=3 uid="uid://d4jeeteyq8kk3"]
|
[gd_scene load_steps=2 format=3 uid="uid://bo4f3sdubdp61"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://cirf1nw72l315" path="res://manager/archive_manager/savings_panel.gd" id="1_oo2ip"]
|
[ext_resource type="Script" uid="uid://cirf1nw72l315" path="res://manager/archive_manager/savings_panel.gd" id="1_oo2ip"]
|
||||||
|
|
||||||
[node name="SavingsLayer" type="CanvasLayer"]
|
[node name="存档控制" type="PanelContainer"]
|
||||||
|
offset_right = 405.0
|
||||||
[node name="TestArchivePanel" type="PanelContainer" parent="."]
|
offset_bottom = 531.0
|
||||||
process_mode = 3
|
mouse_filter = 1
|
||||||
anchors_preset = 15
|
|
||||||
anchor_right = 1.0
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
offset_left = 59.0
|
|
||||||
offset_top = 21.0
|
|
||||||
offset_right = 395.0
|
|
||||||
offset_bottom = 262.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 2
|
|
||||||
scale = Vector2(0.5, 0.5)
|
|
||||||
script = ExtResource("1_oo2ip")
|
script = ExtResource("1_oo2ip")
|
||||||
|
metadata/_tab_index = 0
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="TestArchivePanel"]
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
[node name="HBoxContainer" type="HBoxContainer" parent="TestArchivePanel/VBoxContainer"]
|
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
[node name="Title" type="Label" parent="TestArchivePanel/VBoxContainer/HBoxContainer"]
|
[node name="Title" type="Label" parent="VBoxContainer/HBoxContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
theme_override_font_sizes/font_size = 24
|
theme_override_font_sizes/font_size = 24
|
||||||
text = "存档测试管理器(测试专用)"
|
text = "存档测试管理器(测试专用)"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="QuitDebugModeButton" type="Button" parent="TestArchivePanel/VBoxContainer/HBoxContainer"]
|
[node name="存档管理" type="VBoxContainer" parent="VBoxContainer"]
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "关闭 Debug 模式"
|
|
||||||
|
|
||||||
[node name="存档管理" type="VBoxContainer" parent="TestArchivePanel/VBoxContainer"]
|
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_constants/separation = 10
|
theme_override_constants/separation = 10
|
||||||
|
|
||||||
[node name="CurrentArchiveLabel" type="Label" parent="TestArchivePanel/VBoxContainer/存档管理"]
|
[node name="CurrentArchiveLabel" type="Label" parent="VBoxContainer/存档管理"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_colors/font_color = Color(0.7, 0.9, 0.7, 1)
|
theme_override_colors/font_color = Color(0.7, 0.9, 0.7, 1)
|
||||||
text = "当前使用存档:1号存档(主存档)"
|
text = "当前使用存档:1号存档(主存档)"
|
||||||
|
|
||||||
[node name="HSeparator" type="HSeparator" parent="TestArchivePanel/VBoxContainer/存档管理"]
|
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/存档管理"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
[node name="SaveContainer" type="HBoxContainer" parent="TestArchivePanel/VBoxContainer/存档管理"]
|
[node name="SaveContainer" type="HBoxContainer" parent="VBoxContainer/存档管理"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_constants/separation = 10
|
theme_override_constants/separation = 10
|
||||||
|
|
||||||
[node name="NameInput" type="LineEdit" parent="TestArchivePanel/VBoxContainer/存档管理/SaveContainer"]
|
[node name="NameInput" type="LineEdit" parent="VBoxContainer/存档管理/SaveContainer"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
placeholder_text = "输入存档名称"
|
placeholder_text = "输入存档名称"
|
||||||
|
|
||||||
[node name="SaveButton" type="Button" parent="TestArchivePanel/VBoxContainer/存档管理/SaveContainer"]
|
[node name="SaveButton" type="Button" parent="VBoxContainer/存档管理/SaveContainer"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
custom_minimum_size = Vector2(120, 0)
|
custom_minimum_size = Vector2(120, 0)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "保存当前进度"
|
text = "保存当前进度"
|
||||||
|
|
||||||
[node name="RefreshButton" type="Button" parent="TestArchivePanel/VBoxContainer/存档管理/SaveContainer"]
|
[node name="RefreshButton" type="Button" parent="VBoxContainer/存档管理/SaveContainer"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
custom_minimum_size = Vector2(60, 0)
|
custom_minimum_size = Vector2(60, 0)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "刷新"
|
text = "刷新"
|
||||||
|
|
||||||
[node name="HSeparator2" type="HSeparator" parent="TestArchivePanel/VBoxContainer/存档管理"]
|
[node name="HSeparator2" type="HSeparator" parent="VBoxContainer/存档管理"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="TestArchivePanel/VBoxContainer/存档管理"]
|
[node name="Label" type="Label" parent="VBoxContainer/存档管理"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_colors/font_color = Color(0.8, 0.8, 0.8, 1)
|
theme_override_colors/font_color = Color(0.8, 0.8, 0.8, 1)
|
||||||
text = "手动存档列表:"
|
text = "手动存档列表:"
|
||||||
|
|
||||||
[node name="ScrollContainer" type="ScrollContainer" parent="TestArchivePanel/VBoxContainer/存档管理"]
|
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/存档管理"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
custom_minimum_size = Vector2(0, 380)
|
custom_minimum_size = Vector2(0, 380)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
@ -87,7 +73,7 @@ size_flags_vertical = 3
|
|||||||
horizontal_scroll_mode = 0
|
horizontal_scroll_mode = 0
|
||||||
vertical_scroll_mode = 2
|
vertical_scroll_mode = 2
|
||||||
|
|
||||||
[node name="ArchiveGrid" type="GridContainer" parent="TestArchivePanel/VBoxContainer/存档管理/ScrollContainer"]
|
[node name="ArchiveGrid" type="GridContainer" parent="VBoxContainer/存档管理/ScrollContainer"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
|
461
manager/archive_manager/sfx_config_panel.gd
Normal file
461
manager/archive_manager/sfx_config_panel.gd
Normal file
@ -0,0 +1,461 @@
|
|||||||
|
class_name SfxConfigPanel
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
# signal config_changed(node_name: String, property: String, value)
|
||||||
|
|
||||||
|
# 面板相关
|
||||||
|
@onready var scroll_container: ScrollContainer = %ScrollContainer
|
||||||
|
@onready var vbox_container: VBoxContainer = %VBoxContainer
|
||||||
|
@onready var import_button: Button = %ImportButton
|
||||||
|
@onready var export_button: Button = %ExportButton
|
||||||
|
# 音频预览播放器
|
||||||
|
@onready var preview_player: AudioStreamPlayer = %AudioStreamPlayer
|
||||||
|
# 文件对话框
|
||||||
|
@onready var file_dialog: FileDialog = %FileDialog
|
||||||
|
|
||||||
|
# 数据存储
|
||||||
|
var sfx_nodes: Array[Node] = []
|
||||||
|
var config_data: Dictionary = {}
|
||||||
|
var current_scene_name: String = ""
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
file_dialog.file_selected.connect(_on_file_selected)
|
||||||
|
import_button.pressed.connect(_on_import_pressed)
|
||||||
|
export_button.pressed.connect(_on_export_pressed)
|
||||||
|
# 初始加载
|
||||||
|
refresh_sfx_list(SceneManager.get_ground())
|
||||||
|
|
||||||
|
|
||||||
|
# 1. 检测 current_scene 下 Sfx 节点
|
||||||
|
func refresh_sfx_list(ground: Ground2D, headless := false):
|
||||||
|
if not headless:
|
||||||
|
clear_ui()
|
||||||
|
sfx_nodes.clear()
|
||||||
|
current_scene_name = GroundLoader.get_ground_scene_readable_name(ground.scene_name)
|
||||||
|
find_sfx_nodes(ground)
|
||||||
|
load_config()
|
||||||
|
if not headless:
|
||||||
|
create_ui_items()
|
||||||
|
|
||||||
|
var ignore_class_list = ["Portal2D", "Interactable2D", "Note2D", "Inspectable2D", "Pickable2D", "Npc2D", "Ambush2D"]
|
||||||
|
|
||||||
|
func find_sfx_nodes(node: Node):
|
||||||
|
var script = node.get_script()
|
||||||
|
if script:
|
||||||
|
if script.get_global_name() in ignore_class_list:
|
||||||
|
return
|
||||||
|
# if node is Sfx or node is VibeSfx:
|
||||||
|
if node is Sfx:
|
||||||
|
if node.has_method("play") and node.get("volume_db") != null and node.get("stream") != null:
|
||||||
|
sfx_nodes.append(node)
|
||||||
|
# 递归检查子节点
|
||||||
|
for child in node.get_children():
|
||||||
|
find_sfx_nodes(child)
|
||||||
|
|
||||||
|
func clear_ui():
|
||||||
|
for child in vbox_container.get_children():
|
||||||
|
child.queue_free()
|
||||||
|
|
||||||
|
# 2. 在面板生成对应条目,双向绑定
|
||||||
|
func create_ui_items():
|
||||||
|
for sfx_node in sfx_nodes:
|
||||||
|
create_sfx_item(sfx_node)
|
||||||
|
|
||||||
|
func create_sfx_item(sfx_node: Node):
|
||||||
|
var item_container = VBoxContainer.new()
|
||||||
|
item_container.add_theme_constant_override("separation", 8)
|
||||||
|
vbox_container.add_child(item_container)
|
||||||
|
|
||||||
|
# 节点名称标签
|
||||||
|
var name_label = Label.new()
|
||||||
|
name_label.text = sfx_node.name
|
||||||
|
name_label.add_theme_font_size_override("font_size", 14)
|
||||||
|
item_container.add_child(name_label)
|
||||||
|
|
||||||
|
var controls_hbox = HBoxContainer.new()
|
||||||
|
controls_hbox.add_theme_constant_override("separation", 10)
|
||||||
|
item_container.add_child(controls_hbox)
|
||||||
|
|
||||||
|
# 音量滑块
|
||||||
|
var volume_vbox = VBoxContainer.new()
|
||||||
|
controls_hbox.add_child(volume_vbox)
|
||||||
|
|
||||||
|
var volume_label = Label.new()
|
||||||
|
volume_label.text = "音量 (dB)"
|
||||||
|
volume_vbox.add_child(volume_label)
|
||||||
|
|
||||||
|
var volume_hbox = HBoxContainer.new()
|
||||||
|
volume_vbox.add_child(volume_hbox)
|
||||||
|
|
||||||
|
var volume_slider = HSlider.new()
|
||||||
|
volume_slider.min_value = -60.0
|
||||||
|
volume_slider.max_value = 20.0
|
||||||
|
volume_slider.step = 0.1
|
||||||
|
volume_slider.value = sfx_node.volume_db
|
||||||
|
volume_slider.custom_minimum_size.x = 200
|
||||||
|
volume_hbox.add_child(volume_slider)
|
||||||
|
|
||||||
|
var volume_value_label = Label.new()
|
||||||
|
volume_value_label.text = str(snapped(sfx_node.volume_db, 0.1))
|
||||||
|
volume_value_label.custom_minimum_size.x = 50
|
||||||
|
volume_hbox.add_child(volume_value_label)
|
||||||
|
|
||||||
|
# 双向绑定音量
|
||||||
|
volume_slider.value_changed.connect(func(value):
|
||||||
|
sfx_node.volume_db = value
|
||||||
|
volume_value_label.text = str(snapped(value, 0.1))
|
||||||
|
save_node_config(sfx_node.name, "volume_db", value)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stream 信息和控制
|
||||||
|
var stream_vbox = VBoxContainer.new()
|
||||||
|
controls_hbox.add_child(stream_vbox)
|
||||||
|
|
||||||
|
var stream_label = Label.new()
|
||||||
|
stream_label.text = "音频文件"
|
||||||
|
stream_vbox.add_child(stream_label)
|
||||||
|
|
||||||
|
var stream_hbox = HBoxContainer.new()
|
||||||
|
stream_vbox.add_child(stream_hbox)
|
||||||
|
|
||||||
|
var stream_name_label = Label.new()
|
||||||
|
if sfx_node.stream:
|
||||||
|
stream_name_label.text = sfx_node.stream.resource_path.get_file()
|
||||||
|
else:
|
||||||
|
stream_name_label.text = "无音频文件"
|
||||||
|
stream_name_label.custom_minimum_size.x = 150
|
||||||
|
stream_hbox.add_child(stream_name_label)
|
||||||
|
|
||||||
|
# 预览播放按钮
|
||||||
|
var preview_button = Button.new()
|
||||||
|
preview_button.text = "▶"
|
||||||
|
preview_button.custom_minimum_size = Vector2(30, 30)
|
||||||
|
preview_button.pressed.connect(func(): preview_audio(sfx_node))
|
||||||
|
stream_hbox.add_child(preview_button)
|
||||||
|
|
||||||
|
# 3. 上传文件按钮
|
||||||
|
var upload_button = Button.new()
|
||||||
|
upload_button.text = "上传音频"
|
||||||
|
upload_button.pressed.connect(func(): open_file_dialog(sfx_node, stream_name_label))
|
||||||
|
stream_hbox.add_child(upload_button)
|
||||||
|
|
||||||
|
# 分割线
|
||||||
|
var separator = HSeparator.new()
|
||||||
|
item_container.add_child(separator)
|
||||||
|
|
||||||
|
# 存储节点引用用于后续操作
|
||||||
|
item_container.set_meta("sfx_node", sfx_node)
|
||||||
|
item_container.set_meta("stream_label", stream_name_label)
|
||||||
|
|
||||||
|
func preview_audio(sfx_node: Node):
|
||||||
|
if sfx_node.stream:
|
||||||
|
preview_player.stream = sfx_node.stream
|
||||||
|
preview_player.volume_db = sfx_node.volume_db
|
||||||
|
preview_player.play()
|
||||||
|
|
||||||
|
var current_upload_node: Node
|
||||||
|
var current_stream_label: Label
|
||||||
|
|
||||||
|
func open_file_dialog(sfx_node: Node, stream_label: Label):
|
||||||
|
current_upload_node = sfx_node
|
||||||
|
current_stream_label = stream_label
|
||||||
|
file_dialog.popup_centered()
|
||||||
|
|
||||||
|
func _on_file_selected(path: String):
|
||||||
|
if not current_upload_node:
|
||||||
|
return
|
||||||
|
|
||||||
|
copy_and_load_audio_file(path, current_upload_node, current_stream_label)
|
||||||
|
|
||||||
|
# 3. 复制文件并加载
|
||||||
|
func copy_and_load_audio_file(source_path: String, sfx_node: Node, stream_label: Label):
|
||||||
|
var file_name = source_path.get_file()
|
||||||
|
var audio_dir = "user://audio/" + current_scene_name + "/"
|
||||||
|
var target_path = audio_dir + file_name
|
||||||
|
|
||||||
|
# 确保目录存在
|
||||||
|
DirAccess.open("user://").make_dir_recursive("audio/" + current_scene_name)
|
||||||
|
|
||||||
|
# 复制文件
|
||||||
|
var source_file = FileAccess.open(source_path, FileAccess.READ)
|
||||||
|
if not source_file:
|
||||||
|
push_error("无法读取源文件: " + source_path)
|
||||||
|
return
|
||||||
|
|
||||||
|
var target_file = FileAccess.open(target_path, FileAccess.WRITE)
|
||||||
|
if not target_file:
|
||||||
|
push_error("无法创建目标文件: " + target_path)
|
||||||
|
source_file.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
target_file.store_buffer(source_file.get_buffer(source_file.get_length()))
|
||||||
|
source_file.close()
|
||||||
|
target_file.close()
|
||||||
|
|
||||||
|
# 加载新的音频流
|
||||||
|
var new_stream = AudioLoader.new().loadfile(target_path)
|
||||||
|
if new_stream:
|
||||||
|
sfx_node.stream = new_stream
|
||||||
|
stream_label.text = file_name
|
||||||
|
save_node_config(sfx_node.name, "stream", target_path)
|
||||||
|
print("音频文件已更新: ", sfx_node.name, " -> ", file_name)
|
||||||
|
else:
|
||||||
|
push_error("无法加载音频文件: " + target_path)
|
||||||
|
|
||||||
|
# 4. 配置保存与加载
|
||||||
|
func save_node_config(node_name: String, property: String, value):
|
||||||
|
if not config_data.has(node_name):
|
||||||
|
config_data[node_name] = {}
|
||||||
|
config_data[node_name][property] = value
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
func save_config():
|
||||||
|
var config_dir = "user://audio/" + current_scene_name + "/"
|
||||||
|
DirAccess.open("user://").make_dir_recursive("audio/" + current_scene_name)
|
||||||
|
|
||||||
|
var config_path = config_dir + "audio_config.dat"
|
||||||
|
var file = FileAccess.open(config_path, FileAccess.WRITE)
|
||||||
|
if file:
|
||||||
|
file.store_string(var_to_str(config_data))
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
func load_config():
|
||||||
|
var config_path = "user://audio/" + current_scene_name + "/audio_config.dat"
|
||||||
|
var file = FileAccess.open(config_path, FileAccess.READ)
|
||||||
|
if file:
|
||||||
|
var config_str = file.get_as_text()
|
||||||
|
file.close()
|
||||||
|
config_data = str_to_var(config_str)
|
||||||
|
if not config_data:
|
||||||
|
config_data = {}
|
||||||
|
apply_config()
|
||||||
|
else:
|
||||||
|
config_data = {}
|
||||||
|
|
||||||
|
func apply_config():
|
||||||
|
for sfx_node in sfx_nodes:
|
||||||
|
if config_data.has(sfx_node.name):
|
||||||
|
var node_config = config_data[sfx_node.name]
|
||||||
|
if node_config.has("volume_db"):
|
||||||
|
sfx_node.volume_db = node_config["volume_db"]
|
||||||
|
if node_config.has("stream"):
|
||||||
|
var stream_path = node_config["stream"]
|
||||||
|
if FileAccess.file_exists(stream_path):
|
||||||
|
var new_stream = AudioLoader.new().loadfile(stream_path)
|
||||||
|
# var new_stream = load(stream_path)
|
||||||
|
if new_stream:
|
||||||
|
sfx_node.stream = new_stream
|
||||||
|
|
||||||
|
# 5. Import/Export 功能
|
||||||
|
func _on_import_pressed():
|
||||||
|
# 打开文件对话框选择导入文件夹或配置文件
|
||||||
|
var import_dialog = FileDialog.new()
|
||||||
|
import_dialog.size = Vector2(1400, 800)
|
||||||
|
import_dialog.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
|
||||||
|
import_dialog.content_scale_factor = 3
|
||||||
|
import_dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR
|
||||||
|
import_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||||
|
import_dialog.dir_selected.connect(_on_import_folder_selected)
|
||||||
|
add_child(import_dialog)
|
||||||
|
import_dialog.popup_centered()
|
||||||
|
|
||||||
|
func _on_import_folder_selected(folder_path: String):
|
||||||
|
var config_file_path = folder_path + "/audio_config.dat"
|
||||||
|
if not FileAccess.file_exists(config_file_path):
|
||||||
|
push_error("所选文件夹中没有找到 audio_config.dat 配置文件")
|
||||||
|
return
|
||||||
|
# 确保目标目录存在
|
||||||
|
var target_dir = "user://audio/" + current_scene_name + "/"
|
||||||
|
DirAccess.open("user://").make_dir_recursive("audio/" + current_scene_name)
|
||||||
|
# 复制配置文件
|
||||||
|
copy_file(config_file_path, target_dir + "audio_config.dat")
|
||||||
|
# 复制所有音频文件
|
||||||
|
var dir = DirAccess.open(folder_path)
|
||||||
|
if dir:
|
||||||
|
dir.list_dir_begin()
|
||||||
|
var file_name = dir.get_next()
|
||||||
|
while file_name != "":
|
||||||
|
if not dir.current_is_dir():
|
||||||
|
var extension = file_name.get_extension().to_lower()
|
||||||
|
if extension in ["wav", "ogg", "mp3"]:
|
||||||
|
var source_path = folder_path + "/" + file_name
|
||||||
|
var target_path = target_dir + file_name
|
||||||
|
copy_file(source_path, target_path)
|
||||||
|
|
||||||
|
file_name = dir.get_next()
|
||||||
|
dir.list_dir_end()
|
||||||
|
# 重新加载配置
|
||||||
|
load_config()
|
||||||
|
refresh_sfx_list(SceneManager.get_ground())
|
||||||
|
print("配置和音频文件已导入")
|
||||||
|
|
||||||
|
func copy_file(source_path: String, target_path: String) -> bool:
|
||||||
|
var source_file = FileAccess.open(source_path, FileAccess.READ)
|
||||||
|
if not source_file:
|
||||||
|
push_error("无法读取文件: " + source_path)
|
||||||
|
return false
|
||||||
|
var target_file = FileAccess.open(target_path, FileAccess.WRITE)
|
||||||
|
if not target_file:
|
||||||
|
push_error("无法创建文件: " + target_path)
|
||||||
|
source_file.close()
|
||||||
|
return false
|
||||||
|
target_file.store_buffer(source_file.get_buffer(source_file.get_length()))
|
||||||
|
source_file.close()
|
||||||
|
target_file.close()
|
||||||
|
return true
|
||||||
|
|
||||||
|
func _on_export_pressed():
|
||||||
|
var desktop_path = OS.get_system_dir(OS.SYSTEM_DIR_DESKTOP)
|
||||||
|
var export_folder = desktop_path + "/" + current_scene_name + "_audio_export"
|
||||||
|
var config_path = "user://audio/" + current_scene_name + "/audio_config.dat"
|
||||||
|
|
||||||
|
# 创建导出文件夹
|
||||||
|
var dir = DirAccess.open(desktop_path)
|
||||||
|
if not dir:
|
||||||
|
push_error("无法访问桌面目录")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 如果文件夹已存在,先删除
|
||||||
|
if dir.dir_exists(export_folder):
|
||||||
|
remove_directory_recursive(export_folder)
|
||||||
|
|
||||||
|
dir.make_dir(current_scene_name + "_audio_export")
|
||||||
|
|
||||||
|
var exported_files = []
|
||||||
|
var failed_files = []
|
||||||
|
|
||||||
|
# 1. 导出配置文件
|
||||||
|
if FileAccess.file_exists(config_path):
|
||||||
|
var source_file = FileAccess.open(config_path, FileAccess.READ)
|
||||||
|
var target_file = FileAccess.open(export_folder + "/audio_config.dat", FileAccess.WRITE)
|
||||||
|
|
||||||
|
if source_file and target_file:
|
||||||
|
target_file.store_buffer(source_file.get_buffer(source_file.get_length()))
|
||||||
|
source_file.close()
|
||||||
|
target_file.close()
|
||||||
|
exported_files.append("audio_config.dat")
|
||||||
|
else:
|
||||||
|
failed_files.append("audio_config.dat")
|
||||||
|
push_error("配置文件导出失败")
|
||||||
|
else:
|
||||||
|
push_error("没有找到配置文件")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 2. 导出所有关联的音频文件
|
||||||
|
var audio_dir = "user://audio/" + current_scene_name + "/"
|
||||||
|
|
||||||
|
# 从配置数据中收集所有音频文件路径
|
||||||
|
var audio_files_to_export = []
|
||||||
|
|
||||||
|
# 收集配置文件中引用的音频文件
|
||||||
|
for node_name in config_data.keys():
|
||||||
|
var node_config = config_data[node_name]
|
||||||
|
if node_config.has("stream"):
|
||||||
|
var stream_path = node_config["stream"]
|
||||||
|
if stream_path.begins_with("user://audio/" + current_scene_name + "/"):
|
||||||
|
audio_files_to_export.append(stream_path)
|
||||||
|
|
||||||
|
# 收集当前目录下的所有音频文件
|
||||||
|
var audio_dir_access = DirAccess.open(audio_dir)
|
||||||
|
if audio_dir_access:
|
||||||
|
audio_dir_access.list_dir_begin()
|
||||||
|
var file_name = audio_dir_access.get_next()
|
||||||
|
|
||||||
|
while file_name != "":
|
||||||
|
if not audio_dir_access.current_is_dir():
|
||||||
|
var full_path = audio_dir + file_name
|
||||||
|
var extension = file_name.get_extension().to_lower()
|
||||||
|
|
||||||
|
# 检查是否是音频文件
|
||||||
|
if extension in ["wav", "ogg", "mp3"]:
|
||||||
|
if not full_path in audio_files_to_export:
|
||||||
|
audio_files_to_export.append(full_path)
|
||||||
|
|
||||||
|
file_name = audio_dir_access.get_next()
|
||||||
|
audio_dir_access.list_dir_end()
|
||||||
|
|
||||||
|
# 导出音频文件
|
||||||
|
for audio_path in audio_files_to_export:
|
||||||
|
if FileAccess.file_exists(audio_path):
|
||||||
|
var file_name = audio_path.get_file()
|
||||||
|
var source_file = FileAccess.open(audio_path, FileAccess.READ)
|
||||||
|
var target_file = FileAccess.open(export_folder + "/" + file_name, FileAccess.WRITE)
|
||||||
|
|
||||||
|
if source_file and target_file:
|
||||||
|
target_file.store_buffer(source_file.get_buffer(source_file.get_length()))
|
||||||
|
source_file.close()
|
||||||
|
target_file.close()
|
||||||
|
exported_files.append(file_name)
|
||||||
|
else:
|
||||||
|
failed_files.append(file_name)
|
||||||
|
if source_file:
|
||||||
|
source_file.close()
|
||||||
|
if target_file:
|
||||||
|
target_file.close()
|
||||||
|
else:
|
||||||
|
failed_files.append(audio_path.get_file() + " (文件不存在)")
|
||||||
|
|
||||||
|
# 3. 创建导出说明文件
|
||||||
|
create_export_readme(export_folder, exported_files, failed_files)
|
||||||
|
|
||||||
|
# 显示导出结果
|
||||||
|
var message = "导出完成!\n导出位置: " + export_folder + "\n"
|
||||||
|
message += "成功导出 " + str(exported_files.size()) + " 个文件"
|
||||||
|
|
||||||
|
if failed_files.size() > 0:
|
||||||
|
message += "\n失败 " + str(failed_files.size()) + " 个文件: " + str(failed_files)
|
||||||
|
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
# 可选:打开导出文件夹
|
||||||
|
OS.shell_open(export_folder)
|
||||||
|
|
||||||
|
func create_export_readme(export_folder: String, exported_files: Array, failed_files: Array):
|
||||||
|
var readme_path = export_folder + "/README.txt"
|
||||||
|
var readme_file = FileAccess.open(readme_path, FileAccess.WRITE)
|
||||||
|
|
||||||
|
if readme_file:
|
||||||
|
var content = "音效配置导出说明\n"
|
||||||
|
content += "===================\n\n"
|
||||||
|
content += "场景名称: " + current_scene_name + "\n"
|
||||||
|
content += "导出时间: " + Time.get_datetime_string_from_system() + "\n\n"
|
||||||
|
|
||||||
|
content += "导出文件清单:\n"
|
||||||
|
content += "-----------------\n"
|
||||||
|
for file_name in exported_files:
|
||||||
|
content += "✓ " + file_name + "\n"
|
||||||
|
|
||||||
|
if failed_files.size() > 0:
|
||||||
|
content += "\n失败文件:\n"
|
||||||
|
content += "-----------------\n"
|
||||||
|
for file_name in failed_files:
|
||||||
|
content += "✗ " + file_name + "\n"
|
||||||
|
|
||||||
|
content += "\n使用说明:\n"
|
||||||
|
content += "-----------------\n"
|
||||||
|
content += "1. audio_config.dat 是配置文件,包含音量和文件关联信息\n"
|
||||||
|
content += "2. 其他 .wav/.ogg/.mp3 文件是对应的音频资源\n"
|
||||||
|
content += "3. 导入时请将所有文件放在同一目录下\n"
|
||||||
|
|
||||||
|
readme_file.store_string(content)
|
||||||
|
readme_file.close()
|
||||||
|
|
||||||
|
func remove_directory_recursive(path: String):
|
||||||
|
var dir = DirAccess.open(path)
|
||||||
|
if dir:
|
||||||
|
dir.list_dir_begin()
|
||||||
|
var file_name = dir.get_next()
|
||||||
|
|
||||||
|
while file_name != "":
|
||||||
|
var full_path = path + "/" + file_name
|
||||||
|
|
||||||
|
if dir.current_is_dir():
|
||||||
|
remove_directory_recursive(full_path)
|
||||||
|
else:
|
||||||
|
dir.remove(file_name)
|
||||||
|
|
||||||
|
file_name = dir.get_next()
|
||||||
|
|
||||||
|
dir.list_dir_end()
|
||||||
|
dir.remove(path)
|
1
manager/archive_manager/sfx_config_panel.gd.uid
Normal file
1
manager/archive_manager/sfx_config_panel.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://dtwvx0vs0uoun
|
62
manager/archive_manager/sfx_config_panel.tscn
Normal file
62
manager/archive_manager/sfx_config_panel.tscn
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://b6ogrp5ec5nr3"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://dtwvx0vs0uoun" path="res://manager/archive_manager/sfx_config_panel.gd" id="1_386ty"]
|
||||||
|
|
||||||
|
[node name="SfxConfigPanel" type="PanelContainer"]
|
||||||
|
custom_minimum_size = Vector2(500, 0)
|
||||||
|
offset_right = 508.0
|
||||||
|
offset_bottom = 335.0
|
||||||
|
mouse_filter = 1
|
||||||
|
script = ExtResource("1_386ty")
|
||||||
|
|
||||||
|
[node name="MainVBox" type="VBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="HeaderHBox" type="HBoxContainer" parent="MainVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="MainVBox/HeaderHBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "音效配置面板"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="ImportButton" type="Button" parent="MainVBox/HeaderHBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 8
|
||||||
|
text = "导入配置"
|
||||||
|
|
||||||
|
[node name="ExportButton" type="Button" parent="MainVBox/HeaderHBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 8
|
||||||
|
text = "导出配置"
|
||||||
|
|
||||||
|
[node name="ScrollContainer" type="ScrollContainer" parent="MainVBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(500, 400)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
horizontal_scroll_mode = 0
|
||||||
|
vertical_scroll_mode = 2
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="MainVBox/ScrollContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
|
[node name="FileDialog" type="FileDialog" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
title = "Open a File"
|
||||||
|
initial_position = 1
|
||||||
|
size = Vector2i(1500, 1000)
|
||||||
|
content_scale_mode = 1
|
||||||
|
content_scale_factor = 3.0
|
||||||
|
ok_button_text = "Open"
|
||||||
|
file_mode = 0
|
||||||
|
access = 2
|
||||||
|
filters = PackedStringArray("*.wav", "*.ogg", "*.mp3")
|
@ -364,7 +364,7 @@ func enter_main_scene() -> void:
|
|||||||
get_tree().paused = false
|
get_tree().paused = false
|
||||||
|
|
||||||
|
|
||||||
#### Savings
|
#### debugging
|
||||||
|
|
||||||
var savings_scene = preload("uid://d4jeeteyq8kk3")
|
var savings_scene = preload("uid://d4jeeteyq8kk3")
|
||||||
|
|
||||||
@ -411,7 +411,7 @@ func show_settings() -> void:
|
|||||||
|
|
||||||
|
|
||||||
#### 游戏场景树暂停计数器,设置、memory、bag 等菜单都会导致 pause
|
#### 游戏场景树暂停计数器,设置、memory、bag 等菜单都会导致 pause
|
||||||
## 目前有(6类): settings, panel, bag, note, memory, savings
|
## 目前有(6类): settings, panel, bag, note, memory, debugging
|
||||||
var pause_counter_arr: Array[String] = []
|
var pause_counter_arr: Array[String] = []
|
||||||
var pause_counter_mutex := Mutex.new()
|
var pause_counter_mutex := Mutex.new()
|
||||||
|
|
||||||
|
@ -211,7 +211,7 @@ panel={
|
|||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":80,"key_label":0,"unicode":112,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":80,"key_label":0,"unicode":112,"location":0,"echo":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
savings={
|
debugging={
|
||||||
"deadzone": 0.2,
|
"deadzone": 0.2,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":96,"key_label":0,"unicode":96,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":96,"key_label":0,"unicode":96,"location":0,"echo":false,"script":null)
|
||||||
]
|
]
|
||||||
|
@ -231,7 +231,14 @@ func _add_ground() -> void:
|
|||||||
# 在 add child 之前,调整 ground 内部元素属性,在 on ground ready 前设置完成
|
# 在 add child 之前,调整 ground 内部元素属性,在 on ground ready 前设置完成
|
||||||
if not Engine.is_editor_hint():
|
if not Engine.is_editor_hint():
|
||||||
_setup_player_position()
|
_setup_player_position()
|
||||||
|
|
||||||
|
# debug 模式在 ground add 之前加载音频
|
||||||
|
if GlobalConfig.DEBUG:
|
||||||
|
# headless 模式
|
||||||
|
SfxConfigPanel.new().refresh_sfx_list(ground, true)
|
||||||
|
|
||||||
add_child(ground)
|
add_child(ground)
|
||||||
|
|
||||||
print(
|
print(
|
||||||
"GroundLoader add_ground finished:",
|
"GroundLoader add_ground finished:",
|
||||||
ground.scene_name,
|
ground.scene_name,
|
||||||
|
@ -37,7 +37,7 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||||||
elif event.is_action_pressed("panel"):
|
elif event.is_action_pressed("panel"):
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
SceneManager.show_panel()
|
SceneManager.show_panel()
|
||||||
elif event.is_action_pressed("savings"):
|
elif event.is_action_pressed("debugging"):
|
||||||
if GlobalConfig.DEBUG:
|
if GlobalConfig.DEBUG:
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
SceneManager.show_savings()
|
SceneManager.show_savings()
|
||||||
|
258
util/audio_loader.gd
Normal file
258
util/audio_loader.gd
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
#GDScriptAudioImport v0.1
|
||||||
|
|
||||||
|
#MIT License
|
||||||
|
#
|
||||||
|
#Copyright (c) 2020 Gianclgar (Giannino Clemente) gianclgar@gmail.com
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
#of this software and associated documentation files (the "Software"), to deal
|
||||||
|
#in the Software without restriction, including without limitation the rights
|
||||||
|
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
#copies of the Software, and to permit persons to whom the Software is
|
||||||
|
#furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all
|
||||||
|
#copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
#SOFTWARE.
|
||||||
|
|
||||||
|
#I honestly don't care that much, Kopimi ftw, but it's my little baby and I want it to look nice :3
|
||||||
|
|
||||||
|
class_name AudioLoader
|
||||||
|
|
||||||
|
func report_errors(err, filepath):
|
||||||
|
# See: https://docs.godotengine.org/en/latest/classes/class_@globalscope.html#enum-globalscope-error
|
||||||
|
var result_hash = {
|
||||||
|
ERR_FILE_NOT_FOUND: "File: not found",
|
||||||
|
ERR_FILE_BAD_DRIVE: "File: Bad drive error",
|
||||||
|
ERR_FILE_BAD_PATH: "File: Bad path error.",
|
||||||
|
ERR_FILE_NO_PERMISSION: "File: No permission error.",
|
||||||
|
ERR_FILE_ALREADY_IN_USE: "File: Already in use error.",
|
||||||
|
ERR_FILE_CANT_OPEN: "File: Can't open error.",
|
||||||
|
ERR_FILE_CANT_WRITE: "File: Can't write error.",
|
||||||
|
ERR_FILE_CANT_READ: "File: Can't read error.",
|
||||||
|
ERR_FILE_UNRECOGNIZED: "File: Unrecognized error.",
|
||||||
|
ERR_FILE_CORRUPT: "File: Corrupt error.",
|
||||||
|
ERR_FILE_MISSING_DEPENDENCIES: "File: Missing dependencies error.",
|
||||||
|
ERR_FILE_EOF: "File: End of file (EOF) error."
|
||||||
|
}
|
||||||
|
if err in result_hash:
|
||||||
|
print("Error: ", result_hash[err], " ", filepath)
|
||||||
|
else:
|
||||||
|
print("Unknown error with file ", filepath, " error code: ", err)
|
||||||
|
|
||||||
|
func loadfile(filepath):
|
||||||
|
var file_access = FileAccess.open(filepath, FileAccess.READ)
|
||||||
|
if file_access.get_error() != OK:
|
||||||
|
report_errors(file_access.get_error(), filepath)
|
||||||
|
file_access.close()
|
||||||
|
return AudioSample.new()
|
||||||
|
|
||||||
|
var bytes = FileAccess.get_file_as_bytes(filepath)
|
||||||
|
# if File is wav
|
||||||
|
if filepath.ends_with(".wav"):
|
||||||
|
var newstream = AudioStreamWAV.load_from_buffer(bytes)
|
||||||
|
newstream.take_over_path(filepath)
|
||||||
|
|
||||||
|
# #---------------------------
|
||||||
|
# #parrrrseeeeee!!! :D
|
||||||
|
|
||||||
|
# var bits_per_sample = 0
|
||||||
|
# var i = 0
|
||||||
|
# while true:
|
||||||
|
# if i >= len(bytes) - 4: # Failsafe, if there is no data bytes
|
||||||
|
# print("Data byte not found")
|
||||||
|
# break
|
||||||
|
|
||||||
|
# var those4bytes = str(char(bytes[i])+char(bytes[i+1])+char(bytes[i+2])+char(bytes[i+3]))
|
||||||
|
|
||||||
|
# if those4bytes == "RIFF":
|
||||||
|
# print ("RIFF OK at bytes " + str(i) + "-" + str(i+3))
|
||||||
|
# #RIP bytes 4-7 integer for now
|
||||||
|
# if those4bytes == "WAVE":
|
||||||
|
# print ("WAVE OK at bytes " + str(i) + "-" + str(i+3))
|
||||||
|
|
||||||
|
# if those4bytes == "fmt ":
|
||||||
|
# print ("fmt OK at bytes " + str(i) + "-" + str(i+3))
|
||||||
|
|
||||||
|
# #get format subchunk size, 4 bytes next to "fmt " are an int32
|
||||||
|
# var formatsubchunksize = bytes[i+4] + (bytes[i+5] << 8) + (bytes[i+6] << 16) + (bytes[i+7] << 24)
|
||||||
|
# print ("Format subchunk size: " + str(formatsubchunksize))
|
||||||
|
|
||||||
|
# #using formatsubchunk index so it's easier to understand what's going on
|
||||||
|
# var fsc0 = i+8 #fsc0 is byte 8 after start of "fmt "
|
||||||
|
|
||||||
|
# #get format code [Bytes 0-1]
|
||||||
|
# var format_code = bytes[fsc0] + (bytes[fsc0+1] << 8)
|
||||||
|
# var format_name
|
||||||
|
# if format_code == 0: format_name = "8_BITS"
|
||||||
|
# elif format_code == 1: format_name = "16_BITS"
|
||||||
|
# elif format_code == 2: format_name = "IMA_ADPCM"
|
||||||
|
# else:
|
||||||
|
# format_name = "UNKNOWN (trying to interpret as 16_BITS)"
|
||||||
|
# format_code = 1
|
||||||
|
# print ("Format: " + str(format_code) + " " + format_name)
|
||||||
|
# #assign format to our AudioSample
|
||||||
|
# newstream.format = format_code
|
||||||
|
|
||||||
|
# #get channel num [Bytes 2-3]
|
||||||
|
# var channel_num = bytes[fsc0+2] + (bytes[fsc0+3] << 8)
|
||||||
|
# print ("Number of channels: " + str(channel_num))
|
||||||
|
# #set our AudioSample to stereo if needed
|
||||||
|
# if channel_num == 2: newstream.stereo = true
|
||||||
|
|
||||||
|
# #get sample rate [Bytes 4-7]
|
||||||
|
# var sample_rate = bytes[fsc0+4] + (bytes[fsc0+5] << 8) + (bytes[fsc0+6] << 16) + (bytes[fsc0+7] << 24)
|
||||||
|
# print ("Sample rate: " + str(sample_rate))
|
||||||
|
# #set our AudioSample mixrate
|
||||||
|
# newstream.mix_rate = sample_rate
|
||||||
|
|
||||||
|
# #get byte_rate [Bytes 8-11] because we can
|
||||||
|
# var byte_rate = bytes[fsc0+8] + (bytes[fsc0+9] << 8) + (bytes[fsc0+10] << 16) + (bytes[fsc0+11] << 24)
|
||||||
|
# print ("Byte rate: " + str(byte_rate))
|
||||||
|
|
||||||
|
# #same with bits*sample*channel [Bytes 12-13]
|
||||||
|
# var bits_sample_channel = bytes[fsc0+12] + (bytes[fsc0+13] << 8)
|
||||||
|
# print ("BitsPerSample * Channel / 8: " + str(bits_sample_channel))
|
||||||
|
|
||||||
|
# #aaaand bits per sample/bitrate [Bytes 14-15]
|
||||||
|
# bits_per_sample = bytes[fsc0+14] + (bytes[fsc0+15] << 8)
|
||||||
|
# print ("Bits per sample: " + str(bits_per_sample))
|
||||||
|
|
||||||
|
# if those4bytes == "data":
|
||||||
|
# assert(bits_per_sample != 0)
|
||||||
|
|
||||||
|
# var audio_data_size = bytes[i+4] + (bytes[i+5] << 8) + (bytes[i+6] << 16) + (bytes[i+7] << 24)
|
||||||
|
# print ("Audio data/stream size is " + str(audio_data_size) + " bytes")
|
||||||
|
|
||||||
|
# var data_entry_point = (i+8)
|
||||||
|
# print ("Audio data starts at byte " + str(data_entry_point))
|
||||||
|
|
||||||
|
# var data = bytes.subarray(data_entry_point, data_entry_point+audio_data_size-1)
|
||||||
|
|
||||||
|
# if bits_per_sample in [24, 32]:
|
||||||
|
# newstream.data = convert_to_16bit(data, bits_per_sample)
|
||||||
|
# else:
|
||||||
|
# newstream.data = data
|
||||||
|
|
||||||
|
# break # the data will be at the end, end searching here
|
||||||
|
|
||||||
|
# i += 1
|
||||||
|
# # end of parsing
|
||||||
|
# #---------------------------
|
||||||
|
|
||||||
|
# #get samples and set loop end
|
||||||
|
# var samplenum = newstream.data.size() / 4
|
||||||
|
# newstream.loop_end = samplenum
|
||||||
|
# newstream.loop_mode = 1 #change to 0 or delete this line if you don't want loop, also check out modes 2 and 3 in the docs
|
||||||
|
return newstream #:D
|
||||||
|
|
||||||
|
#if file is ogg
|
||||||
|
elif filepath.ends_with(".ogg"):
|
||||||
|
var newstream = AudioStreamOggVorbis.load_from_buffer(bytes)
|
||||||
|
newstream.take_over_path(filepath)
|
||||||
|
# newstream.loop = true #set to false or delete this line if you don't want to loop
|
||||||
|
return newstream
|
||||||
|
|
||||||
|
#if file is mp3
|
||||||
|
elif filepath.ends_with(".mp3"):
|
||||||
|
var newstream = AudioStreamMP3.load_from_buffer(bytes)
|
||||||
|
newstream.take_over_path(filepath)
|
||||||
|
# newstream.loop = true #set to false or delete this line if you don't want to loop
|
||||||
|
return newstream
|
||||||
|
|
||||||
|
else:
|
||||||
|
print ("ERROR: Wrong filetype or format")
|
||||||
|
|
||||||
|
# Converts .wav data from 24 or 32 bits to 16
|
||||||
|
#
|
||||||
|
# These conversions are SLOW in GDScript
|
||||||
|
# on my one test song, 32 -> 16 was around 3x slower than 24 -> 16
|
||||||
|
#
|
||||||
|
# I couldn't get threads to help very much
|
||||||
|
# They made the 24bit case about 2x faster in my test file
|
||||||
|
# And the 32bit case abour 50% slower
|
||||||
|
# I don't wanna risk it always being slower on other files
|
||||||
|
# And really, the solution would be to handle it in a low-level language
|
||||||
|
func convert_to_16bit(data: PackedByteArray, from: int) -> PackedByteArray:
|
||||||
|
print("converting to 16-bit from %d" % from)
|
||||||
|
var time = Time.get_ticks_msec()
|
||||||
|
# 24 bit .wav's are typically stored as integers
|
||||||
|
# so we just grab the 2 most significant bytes and ignore the other
|
||||||
|
if from == 24:
|
||||||
|
var j = 0
|
||||||
|
for i in range(0, data.size(), 3):
|
||||||
|
data[j] = data[i+1]
|
||||||
|
data[j+1] = data[i+2]
|
||||||
|
j += 2
|
||||||
|
data.resize(data.size() * 2 / 3)
|
||||||
|
# 32 bit .wav's are typically stored as floating point numbers
|
||||||
|
# so we need to grab all 4 bytes and interpret them as a float first
|
||||||
|
if from == 32:
|
||||||
|
var spb := StreamPeerBuffer.new()
|
||||||
|
var single_float: float
|
||||||
|
var value: int
|
||||||
|
for i in range(0, data.size(), 4):
|
||||||
|
# spb.data_array = data.subarray(i, i+3)
|
||||||
|
spb.data_array = data.slice(i, i+4)
|
||||||
|
single_float = spb.get_float()
|
||||||
|
value = single_float * 32768
|
||||||
|
data[i/2] = value
|
||||||
|
data[i/2+1] = value >> 8
|
||||||
|
data.resize(data.size() / 2)
|
||||||
|
print("Took %f seconds for slow conversion" % ((Time.get_ticks_msec() - time) / 1000.0))
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- REFERENCE ---------------
|
||||||
|
# note: typical values doesn't always match
|
||||||
|
|
||||||
|
#Positions Typical Value Description
|
||||||
|
#
|
||||||
|
#1 - 4 "RIFF" Marks the file as a RIFF multimedia file.
|
||||||
|
# Characters are each 1 byte long.
|
||||||
|
#
|
||||||
|
#5 - 8 (integer) The overall file size in bytes (32-bit integer)
|
||||||
|
# minus 8 bytes. Typically, you'd fill this in after
|
||||||
|
# file creation is complete.
|
||||||
|
#
|
||||||
|
#9 - 12 "WAVE" RIFF file format header. For our purposes, it
|
||||||
|
# always equals "WAVE".
|
||||||
|
#
|
||||||
|
#13-16 "fmt " Format sub-chunk marker. Includes trailing null.
|
||||||
|
#
|
||||||
|
#17-20 16 Length of the rest of the format sub-chunk below.
|
||||||
|
#
|
||||||
|
#21-22 1 Audio format code, a 2 byte (16 bit) integer.
|
||||||
|
# 1 = PCM (pulse code modulation).
|
||||||
|
#
|
||||||
|
#23-24 2 Number of channels as a 2 byte (16 bit) integer.
|
||||||
|
# 1 = mono, 2 = stereo, etc.
|
||||||
|
#
|
||||||
|
#25-28 44100 Sample rate as a 4 byte (32 bit) integer. Common
|
||||||
|
# values are 44100 (CD), 48000 (DAT). Sample rate =
|
||||||
|
# number of samples per second, or Hertz.
|
||||||
|
#
|
||||||
|
#29-32 176400 (SampleRate * BitsPerSample * Channels) / 8
|
||||||
|
# This is the Byte rate.
|
||||||
|
#
|
||||||
|
#33-34 4 (BitsPerSample * Channels) / 8
|
||||||
|
# 1 = 8 bit mono, 2 = 8 bit stereo or 16 bit mono, 4
|
||||||
|
# = 16 bit stereo.
|
||||||
|
#
|
||||||
|
#35-36 16 Bits per sample.
|
||||||
|
#
|
||||||
|
#37-40 "data" Data sub-chunk header. Marks the beginning of the
|
||||||
|
# raw data section.
|
||||||
|
#
|
||||||
|
#41-44 (integer) The number of bytes of the data section below this
|
||||||
|
# point. Also equal to (#ofSamples * #ofChannels *
|
||||||
|
# BitsPerSample) / 8
|
||||||
|
#
|
||||||
|
#45+ The raw audio data.
|
1
util/audio_loader.gd.uid
Normal file
1
util/audio_loader.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://dho44deftogm8
|
Loading…
Reference in New Issue
Block a user