Fix reached macOS UI primitives and audio fade behavior
This commit is contained in:
+110
-49
@@ -9,10 +9,10 @@ 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 _bgm: Array[AudioStreamPlayer] = [] # 40250 CSoundManagerStream has three music slots.
|
||||
var _bgm_names: Array[String] = []
|
||||
var _bgm_volumes: Array[float] = []
|
||||
var _bgm_fades := {} # slot -> {target, speed, mode}
|
||||
|
||||
var _ui_pool: Array[AudioStreamPlayer] = []
|
||||
var _ui_next := 0
|
||||
@@ -55,13 +55,14 @@ func _init_roots() -> void:
|
||||
_sound_roots.append(r)
|
||||
|
||||
func _init_players() -> void:
|
||||
# 2 个 BGM 播放器
|
||||
for i in 2:
|
||||
for i in 3:
|
||||
var pl := AudioStreamPlayer.new()
|
||||
pl.bus = "Master"
|
||||
pl.name = "BgmPlayer%d" % i
|
||||
add_child(pl)
|
||||
_bgm[i] = pl
|
||||
_bgm.append(pl)
|
||||
_bgm_names.append("")
|
||||
_bgm_volumes.append(0.0)
|
||||
|
||||
# 16 个 2D 音效池播放器
|
||||
for i in 16:
|
||||
@@ -76,30 +77,34 @@ func _process(_delta: float) -> void:
|
||||
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))
|
||||
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), cmd.get("play_count", 1))
|
||||
"stop_sound_3d":
|
||||
stop_sound_3d(cmd.get("id", 0))
|
||||
"stop_all_sound_3d":
|
||||
stop_all_sound_3d()
|
||||
"set_sound_volume_3d":
|
||||
set_sound_volume_3d(cmd.get("id", 0), cmd.get("volume", 1.0))
|
||||
"play_music":
|
||||
play_music(cmd.get("filename", ""), cmd.get("volume", 1.0))
|
||||
"fade_in_music":
|
||||
fade_in_music(cmd.get("filename", ""), cmd.get("volume", 1.0), cmd.get("speed", 0.016))
|
||||
"fade_out_music":
|
||||
fade_out_music(cmd.get("speed", 0.0))
|
||||
fade_out_music(cmd.get("filename", ""), cmd.get("speed", 0.016))
|
||||
"fade_limit_out_music":
|
||||
fade_limit_out_music(cmd.get("filename", ""), cmd.get("volume", 0.0), cmd.get("speed", 0.016))
|
||||
"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))
|
||||
_update_music_fades()
|
||||
|
||||
# --- 音效路径检索与加载 ---------------------------------------------------
|
||||
|
||||
@@ -186,7 +191,7 @@ func play_sound_2d(rel: String, volume_scale := 1.0) -> void:
|
||||
|
||||
# --- 3D 空间音效播放 -------------------------------------------------------
|
||||
|
||||
func play_sound_3d(rel: String, pos: Vector3, volume_scale := 1.0, sound_id := 0) -> void:
|
||||
func play_sound_3d(rel: String, pos: Vector3, volume_scale := 1.0, sound_id := 0, play_count := 1) -> void:
|
||||
var path := _find_sound(rel)
|
||||
if path.is_empty():
|
||||
return
|
||||
@@ -205,12 +210,24 @@ func play_sound_3d(rel: String, pos: Vector3, volume_scale := 1.0, sound_id := 0
|
||||
pl.play()
|
||||
if sound_id > 0:
|
||||
_active_3d[sound_id] = pl
|
||||
var state := {"remaining": play_count}
|
||||
pl.finished.connect(func():
|
||||
if state.remaining == 0 or state.remaining > 1:
|
||||
if state.remaining > 1:
|
||||
state.remaining -= 1
|
||||
pl.play()
|
||||
return
|
||||
_all_3d.erase(pl)
|
||||
if sound_id > 0 and _active_3d.get(sound_id) == pl:
|
||||
_active_3d.erase(sound_id)
|
||||
pl.queue_free())
|
||||
|
||||
func set_sound_volume_3d(sound_id: int, volume: float) -> void:
|
||||
var pl: AudioStreamPlayer3D = _active_3d.get(sound_id)
|
||||
if pl != null and is_instance_valid(pl):
|
||||
var level := clampf(volume, 0.0, 1.0)
|
||||
pl.volume_db = linear_to_db(level) if level > 0.001 else -80.0
|
||||
|
||||
func stop_sound_3d(sound_id: int) -> void:
|
||||
if _active_3d.has(sound_id):
|
||||
var pl: AudioStreamPlayer3D = _active_3d[sound_id]
|
||||
@@ -230,10 +247,7 @@ func stop_all_sound_3d() -> void:
|
||||
|
||||
# --- BGM 背景音乐播放 -----------------------------------------------------
|
||||
|
||||
func play_music(name: String, volume_scale := 1.0) -> void:
|
||||
if name.is_empty() or name == _bgm_name:
|
||||
return
|
||||
|
||||
func _music_stream(name: String) -> AudioStream:
|
||||
var path := name.replace("\\", "/")
|
||||
if not path.is_absolute_path():
|
||||
var direct := _bgm_root.path_join(name)
|
||||
@@ -243,45 +257,92 @@ func play_music(name: String, volume_scale := 1.0) -> void:
|
||||
path = direct + ".mp3"
|
||||
else:
|
||||
path = _find_sound(name)
|
||||
return _load_stream(path)
|
||||
|
||||
var stream := _load_stream(path)
|
||||
func _music_slot(name: String) -> int:
|
||||
var normalized := name.replace("\\", "/").to_lower()
|
||||
for i in _bgm_names.size():
|
||||
if _bgm_names[i].replace("\\", "/").to_lower() == normalized and _bgm[i].playing:
|
||||
return i
|
||||
return -1
|
||||
|
||||
func _set_music_volume(slot: int, volume: float) -> void:
|
||||
_bgm_volumes[slot] = clampf(volume, 0.0, 1.0)
|
||||
_bgm[slot].volume_db = linear_to_db(_bgm_volumes[slot]) if _bgm_volumes[slot] > 0.001 else -80.0
|
||||
|
||||
func _start_music(slot: int, name: String, volume: float) -> bool:
|
||||
var stream := _music_stream(name)
|
||||
if stream == null:
|
||||
return
|
||||
_bgm_name = name
|
||||
return false
|
||||
if stream is AudioStreamMP3:
|
||||
stream.loop = true
|
||||
_bgm[slot].stop()
|
||||
_bgm[slot].stream = stream
|
||||
_bgm_names[slot] = name
|
||||
_set_music_volume(slot, volume)
|
||||
_bgm[slot].play()
|
||||
return true
|
||||
|
||||
var nxt := 1 - _bgm_cur
|
||||
_bgm[nxt].stream = stream
|
||||
_bgm[nxt].volume_db = -40.0
|
||||
_bgm[nxt].play()
|
||||
func play_music(name: String, volume_scale := 1.0) -> void:
|
||||
if name.is_empty():
|
||||
return
|
||||
# 40250 PlayMusic(name) always replaces stream slot 0.
|
||||
_bgm_fades.erase(0)
|
||||
_start_music(0, name, volume_scale)
|
||||
|
||||
var target_vol: float = clampf(volume_scale, 0.0, 1.0)
|
||||
var target_db: float = linear_to_db(target_vol) if target_vol > 0.001 else -80.0
|
||||
func fade_in_music(name: String, target_volume := 1.0, speed := 0.016) -> void:
|
||||
if name.is_empty():
|
||||
return
|
||||
var slot := _music_slot(name)
|
||||
if slot < 0:
|
||||
fade_out_all_music()
|
||||
for i in _bgm.size():
|
||||
if not _bgm[i].playing:
|
||||
slot = i
|
||||
break
|
||||
if slot < 0 or not _start_music(slot, name, 0.0):
|
||||
return
|
||||
_bgm_fades[slot] = {"target": clampf(target_volume, 0.0, 1.0), "speed": speed, "mode": "in"}
|
||||
|
||||
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(name: String, speed := 0.016) -> void:
|
||||
var slot := _music_slot(name)
|
||||
if slot >= 0:
|
||||
_bgm_fades[slot] = {"target": 0.0, "speed": speed, "mode": "out"}
|
||||
|
||||
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_limit_out_music(name: String, limit: float, speed := 0.016) -> void:
|
||||
var slot := _music_slot(name)
|
||||
if slot >= 0:
|
||||
_bgm_fades[slot] = {"target": clampf(limit, 0.0, 1.0), "speed": speed, "mode": "limit"}
|
||||
|
||||
func fade_out_all_music() -> void:
|
||||
fade_out_music(0.0)
|
||||
for i in _bgm.size():
|
||||
if _bgm[i].playing:
|
||||
_bgm_fades[i] = {"target": 0.0, "speed": 0.01, "mode": "out"}
|
||||
|
||||
func _update_music_fades() -> void:
|
||||
# 40250 advances fVolume by fVolumeSpeed once per SoundManager::Update frame.
|
||||
for slot in _bgm_fades.keys():
|
||||
if not _bgm[slot].playing:
|
||||
_bgm_fades.erase(slot)
|
||||
_bgm_names[slot] = ""
|
||||
continue
|
||||
var fade: Dictionary = _bgm_fades[slot]
|
||||
var target: float = fade.target
|
||||
var step: float = fade.speed
|
||||
var volume: float = _bgm_volumes[slot]
|
||||
match fade.mode:
|
||||
"in":
|
||||
volume = minf(target, volume + step)
|
||||
"limit":
|
||||
volume = maxf(target, volume - step)
|
||||
"out":
|
||||
volume = maxf(0.0, volume - step)
|
||||
_set_music_volume(slot, volume)
|
||||
if (fade.mode == "in" and volume >= target) or (fade.mode != "in" and volume <= target):
|
||||
if fade.mode == "out":
|
||||
_bgm[slot].stop()
|
||||
_bgm_names[slot] = ""
|
||||
_bgm_fades.erase(slot)
|
||||
|
||||
# --- 音量控制 -------------------------------------------------------------
|
||||
|
||||
@@ -290,6 +351,6 @@ func set_sound_volume(v: float) -> void:
|
||||
|
||||
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
|
||||
for i in _bgm.size():
|
||||
if _bgm[i].playing and not (_bgm_fades.has(i) and _bgm_fades[i].mode == "out"):
|
||||
_set_music_volume(i, master_music)
|
||||
|
||||
@@ -27,8 +27,55 @@ func _run() -> void:
|
||||
"sound volume was applied more than once")
|
||||
driver.play_sound_3d(sound, Vector3.ZERO, 0.5)
|
||||
_check(driver._all_3d.size() == 1, "3D sound not tracked")
|
||||
driver.play_sound_3d(sound, Vector3.ZERO, 0.5, 17, 0)
|
||||
driver.set_sound_volume_3d(17, 0.25)
|
||||
_check(absf(driver._active_3d[17].volume_db - linear_to_db(0.25)) < 0.01,
|
||||
"indexed 3D sound volume did not update")
|
||||
_check(driver._all_3d.size() == 2, "looping 3D sound not tracked")
|
||||
driver._active_3d[17].finished.emit()
|
||||
_check(driver._active_3d.has(17), "zero-loop-count ambience did not restart")
|
||||
driver.play_sound_3d(sound, Vector3.ZERO, 0.5, 18, 2)
|
||||
var twice: AudioStreamPlayer3D = driver._active_3d[18]
|
||||
twice.finished.emit()
|
||||
_check(driver._active_3d.has(18), "two-play ambience stopped after first play")
|
||||
twice.finished.emit()
|
||||
_check(not driver._active_3d.has(18), "two-play ambience did not finish")
|
||||
driver.stop_sound_3d(17)
|
||||
_check(not driver._active_3d.has(17), "StopSound3D left indexed sound running")
|
||||
driver.stop_all_sound_3d()
|
||||
_check(driver._all_3d.is_empty(), "StopAllSound3D left a player running")
|
||||
driver.play_music("login_window.mp3", 0.8)
|
||||
_check(driver._music_slot("login_window.mp3") == 0, "PlayMusic did not use slot 0")
|
||||
driver.fade_limit_out_music("login_window.mp3", 0.2, 0.1)
|
||||
for i in 8:
|
||||
driver._update_music_fades()
|
||||
_check(absf(driver._bgm_volumes[0] - 0.2) < 0.001,
|
||||
"FadeLimitOutMusic did not retain the requested limit")
|
||||
driver.fade_limit_out_music("login_window.mp3", 0.7, 0.1)
|
||||
driver._update_music_fades()
|
||||
_check(absf(driver._bgm_volumes[0] - 0.7) < 0.001,
|
||||
"40250 limit fade must clamp immediately when already below the limit")
|
||||
driver.fade_limit_out_music("login_window.mp3", 0.2, 0.5)
|
||||
driver._update_music_fades()
|
||||
driver.fade_out_music("another_track.mp3", 0.1)
|
||||
_check(not driver._bgm_fades.has(1), "FadeOutMusic affected an unrelated track")
|
||||
driver.fade_out_music("login_window.mp3", 0.1)
|
||||
for i in 2:
|
||||
driver._update_music_fades()
|
||||
_check(not driver._bgm[0].playing, "FadeOutMusic did not stop named track")
|
||||
driver.fade_in_music("characterselect.mp3", 0.3, 0.1)
|
||||
for i in 3:
|
||||
driver._update_music_fades()
|
||||
_check(absf(driver._bgm_volumes[0] - 0.3) < 0.001,
|
||||
"FadeInMusic did not advance by the requested frame step")
|
||||
for player in driver._bgm:
|
||||
player.stop()
|
||||
player.stream = null
|
||||
for player in driver._ui_pool:
|
||||
player.stop()
|
||||
player.stream = null
|
||||
driver._cache.clear()
|
||||
driver.free()
|
||||
await process_frame
|
||||
print("PASS: audio_driver_test" if failures == 0 else "FAIL: audio_driver_test")
|
||||
quit(0 if failures == 0 else 1)
|
||||
|
||||
@@ -177,6 +177,22 @@ func _draw() -> void:
|
||||
used = _segment(used, RID())
|
||||
plain = true
|
||||
RenderingServer.canvas_item_add_rect(_segments[used - 1], visible, color)
|
||||
elif command["kind"] == "gradient_bar":
|
||||
var visible := Rect2(p1, p2 - p1).intersection(clip)
|
||||
if visible.has_area():
|
||||
if not plain:
|
||||
used = _segment(used, RID())
|
||||
plain = true
|
||||
var bottom_argb: int = command["end_argb"]
|
||||
var bottom := Color8((bottom_argb >> 16) & 255, (bottom_argb >> 8) & 255,
|
||||
bottom_argb & 255, (bottom_argb >> 24) & 255)
|
||||
var top_factor := (visible.position.y - p1.y) / (p2.y - p1.y)
|
||||
var bottom_factor := (visible.end.y - p1.y) / (p2.y - p1.y)
|
||||
RenderingServer.canvas_item_add_polygon(_segments[used - 1],
|
||||
PackedVector2Array([visible.position, Vector2(visible.end.x, visible.position.y),
|
||||
visible.end, Vector2(visible.position.x, visible.end.y)]),
|
||||
PackedColorArray([color.lerp(bottom, top_factor), color.lerp(bottom, top_factor),
|
||||
color.lerp(bottom, bottom_factor), color.lerp(bottom, bottom_factor)]))
|
||||
elif command["kind"] == "line":
|
||||
var segment := _clip_line(p1, p2, clip)
|
||||
if segment.size() == 2:
|
||||
|
||||
Reference in New Issue
Block a user