Files
mtgodot-poc/project/audio_driver.gd
T
shenandshen c84f913cfd port: complete python ledger mapping, audio commands bridge and platform stubs
- Added Client/root (74 modules, 3350 funcs) mapped to RUN_AS_IS
- Added UserInterface/PythonNetworkStreamModule.cpp.json mapped to PORTED
- Implemented AudioCommands queue bridging MilesLib SoundManager to Godot AudioDriver
- Implemented AudioDriver node for BGM crossfading, 2D UI SFX and 3D spatial sounds
- Implemented core platform stubs in PythonApplication, GrpScreen, and PythonGraphic
- Passed all 12 port tests (port_gate macos) and python_game_render_test
2026-09-23 23:44:48 -07:00

273 lines
8.1 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# audio_driver.gd —— 桥接 40250 Python/MilesLib 音频调用至 Godot 引擎。
# 每帧通过 Metin2PythonHost.audio_commands() 拉取原生端产生的音频播放/音量指令并执行。
class_name AudioDriver
extends Node
const AssetRoot = preload("res://asset_root.gd")
var _cache := {} # 绝对路径 -> AudioStream
var _bgm_root: String
var _sound_roots: PackedStringArray = []
var _bgm := [null, null] # 两个 AudioStreamPlayer 用于 BGM 交叉淡入淡出
var _bgm_cur := 0
var _bgm_name := ""
var _bgm_fade_tween: Tween = null
var _ui_pool: Array[AudioStreamPlayer] = []
var _ui_next := 0
var master_sound := 1.0
var master_music := 1.0
# 3D 循环音效实例:id -> AudioStreamPlayer3D
var _active_3d := {}
func _ready() -> void:
_init_roots()
_init_players()
func _init_roots() -> void:
var client := AssetRoot.client_path()
var assets := AssetRoot.path()
# BGM 根目录:优先 Client/BGM,次选 assets/BGM
if DirAccess.dir_exists_absolute(client.path_join("BGM")):
_bgm_root = client.path_join("BGM")
elif DirAccess.dir_exists_absolute(assets.path_join("BGM")):
_bgm_root = assets.path_join("BGM")
else:
_bgm_root = client.path_join("BGM")
# 音效查找根目录
var candidate_roots := [
assets.path_join("Sound/sound"),
assets.path_join("sound2/sound"),
assets.path_join("sound_m/sound"),
assets.path_join("metin2_patch_w20_sound/sound"),
assets.path_join("Sound"),
assets,
client,
]
for r in candidate_roots:
if DirAccess.dir_exists_absolute(r) and not _sound_roots.has(r):
_sound_roots.append(r)
func _init_players() -> void:
# 2 个 BGM 播放器
for i in 2:
var pl := AudioStreamPlayer.new()
pl.bus = "Master"
pl.name = "BgmPlayer%d" % i
add_child(pl)
_bgm[i] = pl
# 16 个 2D 音效池播放器
for i in 16:
var pl := AudioStreamPlayer.new()
pl.bus = "Master"
pl.name = "UiPlayer%d" % i
add_child(pl)
_ui_pool.append(pl)
func _process(_delta: float) -> void:
if not ClassDB.class_exists("Metin2PythonHost") or not Metin2PythonHost.is_running():
return
var commands: Array = Metin2PythonHost.audio_commands()
if commands.is_empty():
return
for cmd in commands:
var type: String = cmd.get("type", "")
match type:
"play_sound_2d":
play_sound_2d(cmd.get("filename", ""), cmd.get("volume", 1.0))
"play_sound_3d":
play_sound_3d(cmd.get("filename", ""), Vector3(cmd.get("x", 0.0) / 100.0, cmd.get("z", 0.0) / 100.0, -cmd.get("y", 0.0) / 100.0), cmd.get("volume", 1.0), cmd.get("id", 0))
"stop_sound_3d":
stop_sound_3d(cmd.get("id", 0))
"stop_all_sound_3d":
stop_all_sound_3d()
"play_music":
play_music(cmd.get("filename", ""), cmd.get("volume", 1.0))
"fade_out_music":
fade_out_music(cmd.get("speed", 0.0))
"fade_out_all_music":
fade_out_all_music()
"set_sound_volume":
set_sound_volume(cmd.get("volume", 1.0))
"set_music_volume":
set_music_volume(cmd.get("volume", 1.0))
# --- 音效路径检索与加载 ---------------------------------------------------
func _find_sound(rel: String) -> String:
if rel.is_empty():
return ""
rel = rel.replace("\\", "/").strip_edges()
if rel.begins_with("d:/ymir work/") or rel.begins_with("D:/ymir work/"):
rel = rel.substr(13)
elif rel.begins_with("d:/") or rel.begins_with("D:/"):
rel = rel.substr(3)
# 1. 尝试直接路径
if FileAccess.file_exists(rel):
return rel
# 2. 补全扩展名候选
var cands := PackedStringArray([rel])
if rel.get_extension().is_empty():
cands = PackedStringArray([rel + ".wav", rel + ".mp3", rel + ".ogg"])
# 3. 在所有已知根目录中遍历查找
for root in _sound_roots:
for c in cands:
var p := root.path_join(c)
if FileAccess.file_exists(p):
return p
# 若路径带 sound/,剥掉后再试一次(防止 root 自身已带 sound/)
if c.begins_with("sound/"):
var p_sub := root.path_join(c.substr(6))
if FileAccess.file_exists(p_sub):
return p_sub
return ""
func _load_stream(path: String) -> AudioStream:
if _cache.has(path):
return _cache[path]
var s: AudioStream = null
var ext := path.get_extension().to_lower()
if ext == "mp3":
var m := AudioStreamMP3.new()
m.data = FileAccess.get_file_as_bytes(path)
s = m if m.data.size() > 0 else null
elif ext == "ogg":
s = AudioStreamOggVorbis.load_from_file(path)
elif ext == "wav":
s = AudioStreamWAV.load_from_file(path)
_cache[path] = s
return s
# --- 2D 音效播放 -----------------------------------------------------------
func play_sound_2d(rel: String, volume_scale := 1.0) -> void:
var path := _find_sound(rel)
if path.is_empty():
return
var stream := _load_stream(path)
if stream == null:
return
if _ui_pool.is_empty():
return
var pl := _ui_pool[_ui_next]
_ui_next = (_ui_next + 1) % _ui_pool.size()
pl.stream = stream
var vol: float = clampf(master_sound * volume_scale, 0.0, 1.0)
pl.volume_db = linear_to_db(vol) if vol > 0.001 else -80.0
pl.play()
# --- 3D 空间音效播放 -------------------------------------------------------
func play_sound_3d(rel: String, pos: Vector3, volume_scale := 1.0, sound_id := 0) -> void:
var path := _find_sound(rel)
if path.is_empty():
return
var stream := _load_stream(path)
if stream == null:
return
var pl := AudioStreamPlayer3D.new()
pl.stream = stream
pl.unit_size = 8.0
pl.max_distance = 80.0
var vol: float = clampf(master_sound * volume_scale, 0.0, 1.0)
pl.volume_db = linear_to_db(vol) if vol > 0.001 else -80.0
add_child(pl)
pl.global_position = pos
pl.play()
if sound_id > 0:
_active_3d[sound_id] = pl
pl.finished.connect(func(): _active_3d.erase(sound_id); pl.queue_free())
else:
pl.finished.connect(pl.queue_free)
func stop_sound_3d(sound_id: int) -> void:
if _active_3d.has(sound_id):
var pl: AudioStreamPlayer3D = _active_3d[sound_id]
if is_instance_valid(pl):
pl.stop()
pl.queue_free()
_active_3d.erase(sound_id)
func stop_all_sound_3d() -> void:
for id in _active_3d.keys():
stop_sound_3d(id)
_active_3d.clear()
# --- BGM 背景音乐播放 -----------------------------------------------------
func play_music(name: String, volume_scale := 1.0) -> void:
if name.is_empty() or name == _bgm_name:
return
_bgm_name = name
var path := name.replace("\\", "/")
if not path.is_absolute_path():
var direct := _bgm_root.path_join(name)
if FileAccess.file_exists(direct):
path = direct
elif FileAccess.file_exists(direct + ".mp3"):
path = direct + ".mp3"
else:
path = _find_sound(name)
var stream := _load_stream(path)
if stream == null:
return
if stream is AudioStreamMP3:
stream.loop = true
var nxt := 1 - _bgm_cur
_bgm[nxt].stream = stream
_bgm[nxt].volume_db = -40.0
_bgm[nxt].play()
var target_vol: float = clampf(master_music * volume_scale, 0.0, 1.0)
var target_db: float = linear_to_db(target_vol) if target_vol > 0.001 else -80.0
if _bgm_fade_tween and _bgm_fade_tween.is_valid():
_bgm_fade_tween.kill()
_bgm_fade_tween = create_tween()
_bgm_fade_tween.parallel().tween_property(_bgm[nxt], "volume_db", target_db, 1.2)
var cur_player: AudioStreamPlayer = _bgm[_bgm_cur]
if cur_player and cur_player.playing:
_bgm_fade_tween.parallel().tween_property(cur_player, "volume_db", -40.0, 1.2)
_bgm_fade_tween.chain().tween_callback(cur_player.stop)
_bgm_cur = nxt
func fade_out_music(speed := 0.0) -> void:
_bgm_name = ""
var dur := 1.5 if speed <= 0.0 else maxf(0.5, 3.0 / speed)
var cur_player: AudioStreamPlayer = _bgm[_bgm_cur]
if cur_player and cur_player.playing:
if _bgm_fade_tween and _bgm_fade_tween.is_valid():
_bgm_fade_tween.kill()
_bgm_fade_tween = create_tween()
_bgm_fade_tween.tween_property(cur_player, "volume_db", -50.0, dur)
_bgm_fade_tween.tween_callback(cur_player.stop)
func fade_out_all_music() -> void:
fade_out_music(0.0)
# --- 音量控制 -------------------------------------------------------------
func set_sound_volume(v: float) -> void:
master_sound = clampf(v, 0.0, 1.0)
func set_music_volume(v: float) -> void:
master_music = clampf(v, 0.0, 1.0)
var pl: AudioStreamPlayer = _bgm[_bgm_cur]
if pl and pl.playing:
pl.volume_db = linear_to_db(master_music) if master_music > 0.001 else -80.0