feat(settings): add player voice volume and voice input type options

- Add player voice volume slider and voice input type combo in settings UI.
- Add localization strings for player voice and voice input types.
- Add config overloads for const char* default values.
- Implement voice input types (PTT, always, off) in world scene.
- Refactor WorldScene member order and add load_config method.
- Update audio engine to use player voice volume.
- Downgrade some logging to debug.
This commit is contained in:
2026-07-20 17:47:05 +08:00
parent 4f2f439d83
commit e5bd7aa4d1
13 changed files with 100 additions and 25 deletions

View File

@@ -17,11 +17,15 @@
"settings.distance": "Distance: {distance}", "settings.distance": "Distance: {distance}",
"settings.music_volume": "Music: {music}", "settings.music_volume": "Music: {music}",
"settings.sfx_volume": "SFX: {sfx}", "settings.sfx_volume": "SFX: {sfx}",
"settings.player_voice": "Player Voice: {volume}",
"settings.fullscreen": "fullscreen: {state}", "settings.fullscreen": "fullscreen: {state}",
"settings.aniso": "Aniso: {aniso}", "settings.aniso": "Aniso: {aniso}",
"settings.vsync": "V-Sync: {state}", "settings.vsync": "V-Sync: {state}",
"settings.language": "Language: {lang}", "settings.language": "Language: {lang}",
"settings.restart_game_info": "You need to restart the game to apply this changes.", "settings.restart_game_info": "You need to restart the game to apply this changes.",
"settings.voice_input": "Voice Input: {type}",
"voice_input.ptt": "PTT",
"voice_input.always": "always",
"common.on": "on", "common.on": "on",
"common.off": "off", "common.off": "off",
"hostgame.create_a_new_world": "Create A New World", "hostgame.create_a_new_world": "Create A New World",

View File

@@ -17,11 +17,15 @@
"settings.distance": "距离:{distance}", "settings.distance": "距离:{distance}",
"settings.music_volume": "音乐:{music}", "settings.music_volume": "音乐:{music}",
"settings.sfx_volume": "音效:{sfx}", "settings.sfx_volume": "音效:{sfx}",
"settings.player_voice": "玩家语音:{volume}",
"settings.fullscreen": "全屏:{state}", "settings.fullscreen": "全屏:{state}",
"settings.aniso": "各向异性过滤:{aniso}", "settings.aniso": "各向异性过滤:{aniso}",
"settings.vsync": "垂直同步: {state}", "settings.vsync": "垂直同步: {state}",
"settings.language": "语言: {lang}", "settings.language": "语言: {lang}",
"settings.restart_game_info": "您需要重启游戏以应用这些更改。", "settings.restart_game_info": "您需要重启游戏以应用这些更改。",
"settings.voice_input": "语音输入: {type}",
"voice_input.ptt": "按键通话",
"voice_input.always": "始终",
"common.on": "开", "common.on": "开",
"common.off": "关", "common.off": "关",
"hostgame.create_a_new_world": "创建新世界", "hostgame.create_a_new_world": "创建新世界",

View File

@@ -71,6 +71,7 @@ private:
bool m_underwater = false; bool m_underwater = false;
float m_music_volume = 1.0f; float m_music_volume = 1.0f;
float m_sfx_volume = 1.0f; float m_sfx_volume = 1.0f;
float m_player_voice_volume = 1.0f;
std::unique_ptr<AudioStreamSource> m_voice_source; std::unique_ptr<AudioStreamSource> m_voice_source;
std::unique_ptr<AudioFilter> m_low_pass_filter; std::unique_ptr<AudioFilter> m_low_pass_filter;

View File

@@ -57,6 +57,14 @@ public:
save_to_file(); save_to_file();
} }
std::string get(std::string_view key, const char* default_value) {
return get(key, std::string(default_value));
}
void set(std::string_view key, const char* val) {
set(key, std::string(val));
}
private: private:
toml::table m_tbl; toml::table m_tbl;
bool m_init = false; bool m_init = false;

View File

@@ -8,6 +8,7 @@ struct SliderVariable {
float mouse_sensitivity = 0.15f; float mouse_sensitivity = 0.15f;
float sfx = 1.0f; float sfx = 1.0f;
float music = 0.5f; float music = 0.5f;
float player_voice = 1.0f;
int rendering_distance = 24; int rendering_distance = 24;
}; };

View File

@@ -9,6 +9,7 @@
#include "Cubed/ui/pasue_menu_ui_manager.hpp" #include "Cubed/ui/pasue_menu_ui_manager.hpp"
#include "Cubed/ui/world_ui_manager.hpp" #include "Cubed/ui/world_ui_manager.hpp"
namespace Cubed { namespace Cubed {
enum class VoiceInputType { OFF, PTT, ALWAYS };
class SceneManager; class SceneManager;
class WorldScene : public Scene { class WorldScene : public Scene {
public: public:
@@ -39,12 +40,6 @@ public:
void handle_chat_message(ChatMessage& message); void handle_chat_message(ChatMessage& message);
private: private:
bool handle_mouse_move_event(const MouseMoveEvent& e) override;
bool handle_mouse_button_event(const MouseButtonEvent& e) override;
bool handle_window_resize_event(const WindowResizeEvent& e) override;
bool handle_mouse_wheel_event(const MouseWheelEvent& e) override;
bool handle_key_event(const KeyEvent& e) override;
bool handle_text_input_event(const TextInputEvent&) override;
SceneManager& m_scene_manager; SceneManager& m_scene_manager;
DevPanel m_dev_panel; DevPanel m_dev_panel;
Camera m_camera; Camera m_camera;
@@ -59,5 +54,13 @@ private:
WorldUIManager m_hud_ui; WorldUIManager m_hud_ui;
ErrorUI m_error_ui; ErrorUI m_error_ui;
const Argument& m_argument; const Argument& m_argument;
VoiceInputType m_input_type;
bool handle_mouse_move_event(const MouseMoveEvent& e) override;
bool handle_mouse_button_event(const MouseButtonEvent& e) override;
bool handle_window_resize_event(const WindowResizeEvent& e) override;
bool handle_mouse_wheel_event(const MouseWheelEvent& e) override;
bool handle_key_event(const KeyEvent& e) override;
bool handle_text_input_event(const TextInputEvent&) override;
void load_config();
}; };
} // namespace Cubed } // namespace Cubed

View File

@@ -13,9 +13,7 @@ float AudioBuffer::duration() const { return m_duration; }
uint32_t AudioBuffer::channels() const { return m_channels; } uint32_t AudioBuffer::channels() const { return m_channels; }
void AudioBuffer::set_data(const AudioData& data) { void AudioBuffer::set_data(const AudioData& data) {
Logger::info("buffer={} valid={}", m_buffer, (int)alIsBuffer(m_buffer));
Logger::info("buffer={} isBuffer={}", m_buffer, (int)alIsBuffer(m_buffer));
ALenum format; ALenum format;
if (data.channels == 1) { if (data.channels == 1) {
format = AL_FORMAT_MONO16; format = AL_FORMAT_MONO16;
@@ -24,9 +22,9 @@ void AudioBuffer::set_data(const AudioData& data) {
} }
m_channels = data.channels; m_channels = data.channels;
Logger::info("channels={} rate={} samples={} bytes={} format={}", Logger::debug("channels={} rate={} samples={} bytes={} format={}",
data.channels, data.sample_rate, data.pcm.size(), data.channels, data.sample_rate, data.pcm.size(),
data.pcm.size() * sizeof(int16_t), format); data.pcm.size() * sizeof(int16_t), format);
alBufferData(m_buffer, format, data.pcm.data(), alBufferData(m_buffer, format, data.pcm.data(),
static_cast<ALsizei>(data.pcm.size() * sizeof(int16_t)), static_cast<ALsizei>(data.pcm.size() * sizeof(int16_t)),
static_cast<ALsizei>(data.sample_rate)); static_cast<ALsizei>(data.sample_rate));
@@ -35,7 +33,7 @@ void AudioBuffer::set_data(const AudioData& data) {
alGetBufferi(m_buffer, AL_SIZE, &size); alGetBufferi(m_buffer, AL_SIZE, &size);
check_al_error(); check_al_error();
Logger::info("buffer size={}", size); Logger::debug("buffer size={}", size);
m_duration = static_cast<float>(data.pcm.size()) / m_duration = static_cast<float>(data.pcm.size()) /
(data.channels * data.sample_rate); (data.channels * data.sample_rate);
} }

View File

@@ -94,7 +94,7 @@ void AudioEngine::init() {
m_recording.init(); m_recording.init();
m_music_volume = m_config.get("volume.music", 1.0f); m_music_volume = m_config.get("volume.music", 1.0f);
m_sfx_volume = m_config.get("volume.SFX", 1.0f); m_sfx_volume = m_config.get("volume.SFX", 1.0f);
m_player_voice_volume = m_config.get("volume.player_voice", 1.0f);
m_sounds.init(); m_sounds.init();
m_bgm = std::make_unique<AudioSource>(m_music_volume); m_bgm = std::make_unique<AudioSource>(m_music_volume);
@@ -103,6 +103,7 @@ void AudioEngine::init() {
m_fade_map.try_emplace("bgm", m_bgm.get(), 5.0f, 2.0f); m_fade_map.try_emplace("bgm", m_bgm.get(), 5.0f, 2.0f);
m_voice_source = std::make_unique<AudioStreamSource>(); m_voice_source = std::make_unique<AudioStreamSource>();
m_voice_source->set_volume(m_player_voice_volume);
ALCint max_mono = 0; ALCint max_mono = 0;
alcGetIntegerv(device, ALC_MONO_SOURCES, 1, &max_mono); alcGetIntegerv(device, ALC_MONO_SOURCES, 1, &max_mono);
@@ -216,9 +217,13 @@ void AudioEngine::update() {
void AudioEngine::reload_config() { void AudioEngine::reload_config() {
m_music_volume = m_config.get("volume.music", 1.0f); m_music_volume = m_config.get("volume.music", 1.0f);
m_sfx_volume = m_config.get("volume.SFX", 1.0f); m_sfx_volume = m_config.get("volume.SFX", 1.0f);
m_player_voice_volume = m_config.get("volume.player_voice", 1.0f);
if (m_bgm) { if (m_bgm) {
m_bgm->set_target_volume(m_music_volume); m_bgm->set_target_volume(m_music_volume);
} }
if (m_voice_source) {
m_voice_source->set_volume(m_player_voice_volume);
}
} }
void AudioEngine::underwater_change(bool underwater) { void AudioEngine::underwater_change(bool underwater) {

View File

@@ -35,6 +35,9 @@ void AudioRecording::init() {
} }
void AudioRecording::start() { void AudioRecording::start() {
if (m_recording) {
return;
}
ALCint samples; ALCint samples;
alcGetIntegerv(m_capture, ALC_CAPTURE_SAMPLES, 1, &samples); alcGetIntegerv(m_capture, ALC_CAPTURE_SAMPLES, 1, &samples);
@@ -46,6 +49,9 @@ void AudioRecording::start() {
} }
void AudioRecording::stop() { void AudioRecording::stop() {
if (!m_recording) {
return;
}
alcCaptureStop(m_capture); alcCaptureStop(m_capture);
m_recording = false; m_recording = false;
} }

View File

@@ -653,7 +653,6 @@ void ClientPlayer::init(std::string_view name) {
glm::ivec3 block = glm::floor(m_player_pos); glm::ivec3 block = glm::floor(m_player_pos);
block.y -= 1; block.y -= 1;
BlockType id = m_world.get_block_tpye(block); BlockType id = m_world.get_block_tpye(block);
Logger::info("player Block {} Walk Sound", id);
if (id == 0) { if (id == 0) {
return; return;
} }
@@ -661,7 +660,7 @@ void ClientPlayer::init(std::string_view name) {
std::string sound = "block/" + name + "/walk.ogg"; std::string sound = "block/" + name + "/walk.ogg";
auto& audio = m_world.get_audio(); auto& audio = m_world.get_audio();
audio.play_3d(sound, m_player_pos); audio.play_3d(sound, m_player_pos);
Logger::info("Player block {} walk sound", name); Logger::debug("Player block {} walk sound", name);
}); });
} }

View File

@@ -29,6 +29,8 @@ void SettingsScene::on_enter() {
m_slider_variable.sfx = config.get("volume.SFX", m_slider_variable.sfx); m_slider_variable.sfx = config.get("volume.SFX", m_slider_variable.sfx);
m_slider_variable.rendering_distance = config.get( m_slider_variable.rendering_distance = config.get(
"world.rendering_distance", m_slider_variable.rendering_distance); "world.rendering_distance", m_slider_variable.rendering_distance);
m_slider_variable.player_voice =
config.get("vloume.player_voice", m_slider_variable.player_voice);
} }
void SettingsScene::on_leave() { void SettingsScene::on_leave() {
save_and_apply(); save_and_apply();
@@ -54,6 +56,7 @@ void SettingsScene::save_and_apply() {
m_slider_variable.rendering_distance); m_slider_variable.rendering_distance);
config.set("volume.SFX", m_slider_variable.sfx); config.set("volume.SFX", m_slider_variable.sfx);
config.set("volume.music", m_slider_variable.music); config.set("volume.music", m_slider_variable.music);
config.set("volume.player_voice", m_slider_variable.player_voice);
config.save_to_file(); config.save_to_file();
auto& app = m_scene_manager.app(); auto& app = m_scene_manager.app();
app.audio().reload_config(); app.audio().reload_config();

View File

@@ -115,7 +115,7 @@ bool WorldScene::handle_event(const Event& e) {
} }
void WorldScene::on_enter() { void WorldScene::on_enter() {
auto& param = m_scene_manager.world_scene_param(); auto& param = m_scene_manager.world_scene_param();
load_config();
m_error_ui.init(); m_error_ui.init();
if (param.host_game) { if (param.host_game) {
@@ -163,6 +163,7 @@ void WorldScene::on_re_enter() {
m_error_ui.on_re_enter(); m_error_ui.on_re_enter();
m_pasue_menu.on_re_enter(); m_pasue_menu.on_re_enter();
m_client_world.reload_config(); m_client_world.reload_config();
load_config();
auto width = m_scene_manager.app().renderer().window_width(); auto width = m_scene_manager.app().renderer().window_width();
auto height = m_scene_manager.app().renderer().window_height(); auto height = m_scene_manager.app().renderer().window_height();
@@ -170,6 +171,20 @@ void WorldScene::on_re_enter() {
WindowResizeEvent{static_cast<int>(width), static_cast<int>(height)}); WindowResizeEvent{static_cast<int>(width), static_cast<int>(height)});
} }
void WorldScene::load_config() {
auto type = m_scene_manager.app().config().get("voice_input", "PTT");
if (type == "off") {
m_input_type = VoiceInputType::OFF;
m_client_world.get_audio().audio_recording().stop();
} else if (type == "PTT") {
m_input_type = VoiceInputType::PTT;
m_client_world.get_audio().audio_recording().stop();
} else if (type == "always") {
m_input_type = VoiceInputType::ALWAYS;
m_client_world.get_audio().audio_recording().start();
}
}
bool WorldScene::handle_mouse_move_event(const MouseMoveEvent& e) { bool WorldScene::handle_mouse_move_event(const MouseMoveEvent& e) {
if (m_chatting) { if (m_chatting) {
return true; return true;
@@ -291,14 +306,16 @@ bool WorldScene::handle_key_event(const KeyEvent& e) {
} }
if (e.key == Key::V) { if (e.key == Key::V) {
auto& recording = m_client_world.get_audio().audio_recording(); if (m_input_type == VoiceInputType::PTT) {
if (e.action == KeyAction::PRESS) { auto& recording = m_client_world.get_audio().audio_recording();
recording.start(); if (e.action == KeyAction::PRESS) {
return true; recording.start();
} return true;
if (e.action == KeyAction::RELEASE) { }
recording.stop(); if (e.action == KeyAction::RELEASE) {
return true; recording.stop();
return true;
}
} }
} }

View File

@@ -69,15 +69,41 @@ void SettingsUI::init() {
sfx.set_slider(&v.sfx, 0.0f, 1.0f); sfx.set_slider(&v.sfx, 0.0f, 1.0f);
set_default_slider_image(sfx); set_default_slider_image(sfx);
sfx.set_slider_text("settings.sfx_volume", "sfx"); sfx.set_slider_text("settings.sfx_volume", "sfx");
auto& player_voice = layout.add_child<Slider>();
player_voice.set_slider(&v.player_voice, 0.0f, 1.0f);
set_default_slider_image(player_voice);
player_voice.set_slider_text("settings.player_voice", "volume");
} }
auto& config = m_scene.scene_manager().app().config(); auto& config = m_scene.scene_manager().app().config();
{
auto& combo = layout.add_child<ComboButton>();
auto type = config.get("voice_input", "PTT");
if (type == "off") {
combo.set_index(0);
} else if (type == "PTT") {
combo.set_index(1);
} else if (type == "always") {
combo.set_index(2);
}
combo.set_combo_text("settings.voice_input", "type");
std::vector<ComboPair> comb;
comb.emplace_back(tr("common.off"),
[&config]() { config.set("voice_input", "off"); });
comb.emplace_back(tr("voice_input.ptt"),
[&config]() { config.set("voice_input", "PTT"); });
comb.emplace_back(tr("voice_input.always"),
[&config]() { config.set("voice_input", "always"); });
combo.set_default_image(texture_manager);
combo.set_combos(comb);
}
{ {
bool full = config.get("window.fullscreen", false); bool full = config.get("window.fullscreen", false);
auto& fullscreen = layout.add_child<ComboButton>(); auto& fullscreen = layout.add_child<ComboButton>();
fullscreen.set_index(!full ? 0 : 1); fullscreen.set_index(!full ? 0 : 1);
fullscreen.set_combo_text("settings.fullscreen", "state"); fullscreen.set_combo_text("settings.fullscreen", "state");
std::vector<std::pair<std::string, std::function<void()>>> comb; std::vector<ComboPair> comb;
comb.emplace_back(tr("common.off"), [&config, this]() { comb.emplace_back(tr("common.off"), [&config, this]() {
config.set("window.fullscreen", false); config.set("window.fullscreen", false);
m_scene.scene_manager().app().window().reload_config(); m_scene.scene_manager().app().window().reload_config();