Add MVP audio volume sliders

This commit is contained in:
2026-05-19 12:41:48 -07:00
parent d36481e2d9
commit 7c74d4f766
5 changed files with 199 additions and 2 deletions
+1 -1
View File
@@ -849,7 +849,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [x] Add wildlife sounds. Added spatialized wildlife audio hooks with assignable idle, flee/chase, death, and harvest sound slots plus server-triggered multicast playback from authoritative wildlife state changes and harvest events.
- [x] Add UI sounds. Added optional 2D confirm, back, selection, and save/quit sound hooks to the MVP frontend widget, with keyboard and mouse actions sharing the same feedback path while remaining silent until UI audio assets are assigned.
- [x] Add mix settings. Added MVP audio mix settings for master, ambient, weather, foley, fire, wildlife, and UI buses with conservative investor-build defaults and documentation for future SoundClass/MetaSound replacement.
- [ ] Add volume sliders.
- [x] Add volume sliders. Added MVP frontend volume sliders for master, ambient, weather, effects, wildlife, and UI levels, with runtime value storage and immediate UI-volume application to frontend feedback sounds while leaving final SoundClass binding for authored audio assets.
## 0.1.Q MVP QA Gates
+6
View File
@@ -328,6 +328,12 @@ exposes confirm, back, selection, and save/quit sound slots. Keyboard and mouse
actions play the same cues so menu feedback stays consistent across input
methods while the widget remains silent until UI assets are assigned.
The MVP pause/main menu exposes volume sliders for master, ambient, weather,
effects, wildlife, and UI levels. The current native sliders store runtime
values and apply the UI bus to frontend sounds immediately; final SoundClass or
audio-subsystem work can bind the remaining buses to authored assets once those
assets exist.
Player movement audio starts with native footstep placeholders on
`AAgrarianGameCharacter`. The character owns a spatialized
`FootstepAudioComponent` plus assignable walk, sprint, crouch, and prone sound
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""Verify MVP frontend volume sliders are present."""
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
UI_H = ROOT / "Source" / "AgrarianGame" / "AgrarianMvpFrontendWidget.h"
UI_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianMvpFrontendWidget.cpp"
TDD = ROOT / "Docs" / "TechnicalDesignDocument.md"
ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md"
REQUIRED = {
UI_H: [
"class USlider;",
"float MasterVolume",
"float AmbientVolume",
"float WeatherVolume",
"float EffectsVolume",
"float WildlifeVolume",
"float UiVolume",
"USlider* AddVolumeSlider",
"void HandleMasterVolumeChanged(float Value);",
"void HandleUiVolumeChanged(float Value);",
],
UI_CPP: [
"#include \"Components/Slider.h\"",
"AddVolumeSlider(Panel, FText::FromString(TEXT(\"Master\"))",
"AddVolumeSlider(Panel, FText::FromString(TEXT(\"Ambient\"))",
"AddVolumeSlider(Panel, FText::FromString(TEXT(\"Weather\"))",
"AddVolumeSlider(Panel, FText::FromString(TEXT(\"Effects\"))",
"AddVolumeSlider(Panel, FText::FromString(TEXT(\"Wildlife\"))",
"AddVolumeSlider(Panel, FText::FromString(TEXT(\"UI\"))",
"Slider->OnValueChanged.AddDynamic",
"UGameplayStatics::PlaySound2D(this, Sound, FMath::Clamp(MasterVolume * UiVolume",
"UAgrarianMvpFrontendWidget::HandleMasterVolumeChanged",
"UAgrarianMvpFrontendWidget::HandleUiVolumeChanged",
],
TDD: [
"volume sliders for master, ambient, weather",
"apply the UI bus to frontend sounds immediately",
"SoundClass",
],
ROADMAP: [
"[x] Add volume sliders.",
],
}
def main() -> None:
missing = []
for path, snippets in REQUIRED.items():
text = path.read_text(encoding="utf-8")
for snippet in snippets:
if snippet not in text:
missing.append(f"{path.relative_to(ROOT)} missing {snippet!r}")
if missing:
raise SystemExit("FAILED: " + "; ".join(missing))
print("OK: MVP frontend volume sliders are present.")
if __name__ == "__main__":
main()
@@ -9,6 +9,7 @@
#include "Components/HorizontalBox.h"
#include "Components/HorizontalBoxSlot.h"
#include "Components/SizeBox.h"
#include "Components/Slider.h"
#include "Components/TextBlock.h"
#include "Components/VerticalBox.h"
#include "Components/VerticalBoxSlot.h"
@@ -303,6 +304,31 @@ void UAgrarianMvpFrontendWidget::RebuildFrontendTree()
UButton* QuitButton = AddButton(Panel, FText::FromString(TEXT("Save & Quit")), QuitButtonColor, FLinearColor(0.58f, 0.28f, 0.22f, 1.0f), 34.0f * Scale);
QuitButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleSaveAndQuitClicked);
AddText(Panel, FText::FromString(TEXT("Audio")), FMath::RoundToInt(18.0f * Scale), true, AccentColor, 8.0f * Scale);
if (USlider* Slider = AddVolumeSlider(Panel, FText::FromString(TEXT("Master")), MasterVolume, 8.0f * Scale))
{
Slider->OnValueChanged.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleMasterVolumeChanged);
}
if (USlider* Slider = AddVolumeSlider(Panel, FText::FromString(TEXT("Ambient")), AmbientVolume, 8.0f * Scale))
{
Slider->OnValueChanged.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleAmbientVolumeChanged);
}
if (USlider* Slider = AddVolumeSlider(Panel, FText::FromString(TEXT("Weather")), WeatherVolume, 8.0f * Scale))
{
Slider->OnValueChanged.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleWeatherVolumeChanged);
}
if (USlider* Slider = AddVolumeSlider(Panel, FText::FromString(TEXT("Effects")), EffectsVolume, 8.0f * Scale))
{
Slider->OnValueChanged.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleEffectsVolumeChanged);
}
if (USlider* Slider = AddVolumeSlider(Panel, FText::FromString(TEXT("Wildlife")), WildlifeVolume, 8.0f * Scale))
{
Slider->OnValueChanged.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleWildlifeVolumeChanged);
}
if (USlider* Slider = AddVolumeSlider(Panel, FText::FromString(TEXT("UI")), UiVolume, 24.0f * Scale))
{
Slider->OnValueChanged.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleUiVolumeChanged);
}
AddText(Panel, FText::FromString(TEXT("Escape opens this menu. Save & Quit writes the current world save before closing.")), FMath::RoundToInt(16.0f * Scale), false, MutedTextColor, 0.0f);
return;
}
@@ -479,10 +505,74 @@ void UAgrarianMvpFrontendWidget::PlayUiSound(USoundBase* Sound) const
{
if (Sound)
{
UGameplayStatics::PlaySound2D(this, Sound);
UGameplayStatics::PlaySound2D(this, Sound, FMath::Clamp(MasterVolume * UiVolume, 0.0f, 1.0f));
}
}
USlider* UAgrarianMvpFrontendWidget::AddVolumeSlider(UVerticalBox* Parent, const FText& Label, float Value, float BottomPadding)
{
if (!Parent || !WidgetTree)
{
return nullptr;
}
UHorizontalBox* Row = WidgetTree->ConstructWidget<UHorizontalBox>(UHorizontalBox::StaticClass());
if (UVerticalBoxSlot* RowSlot = Parent->AddChildToVerticalBox(Row))
{
RowSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, BottomPadding));
RowSlot->SetHorizontalAlignment(HAlign_Fill);
}
UTextBlock* LabelText = AddText(nullptr, Label, 15, false, FLinearColor(0.82f, 0.90f, 0.76f, 1.0f), 0.0f);
if (UHorizontalBoxSlot* LabelSlot = Row->AddChildToHorizontalBox(LabelText))
{
LabelSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill));
LabelSlot->SetHorizontalAlignment(HAlign_Left);
LabelSlot->SetVerticalAlignment(VAlign_Center);
}
USlider* Slider = WidgetTree->ConstructWidget<USlider>(USlider::StaticClass());
Slider->SetValue(FMath::Clamp(Value, 0.0f, 1.0f));
if (UHorizontalBoxSlot* SliderSlot = Row->AddChildToHorizontalBox(Slider))
{
SliderSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill));
SliderSlot->SetHorizontalAlignment(HAlign_Fill);
SliderSlot->SetVerticalAlignment(VAlign_Center);
}
return Slider;
}
void UAgrarianMvpFrontendWidget::HandleMasterVolumeChanged(float Value)
{
MasterVolume = FMath::Clamp(Value, 0.0f, 1.0f);
}
void UAgrarianMvpFrontendWidget::HandleAmbientVolumeChanged(float Value)
{
AmbientVolume = FMath::Clamp(Value, 0.0f, 1.0f);
}
void UAgrarianMvpFrontendWidget::HandleWeatherVolumeChanged(float Value)
{
WeatherVolume = FMath::Clamp(Value, 0.0f, 1.0f);
}
void UAgrarianMvpFrontendWidget::HandleEffectsVolumeChanged(float Value)
{
EffectsVolume = FMath::Clamp(Value, 0.0f, 1.0f);
}
void UAgrarianMvpFrontendWidget::HandleWildlifeVolumeChanged(float Value)
{
WildlifeVolume = FMath::Clamp(Value, 0.0f, 1.0f);
}
void UAgrarianMvpFrontendWidget::HandleUiVolumeChanged(float Value)
{
UiVolume = FMath::Clamp(Value, 0.0f, 1.0f);
}
void UAgrarianMvpFrontendWidget::FocusPrimaryButton()
{
if (PrimaryFocusButton)
@@ -7,6 +7,7 @@
#include "AgrarianMvpFrontendWidget.generated.h"
class UButton;
class USlider;
class USoundBase;
class UTextBlock;
class UVerticalBox;
@@ -70,6 +71,24 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio")
TObjectPtr<USoundBase> UiSaveQuitSound;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio", meta = (ClampMin = "0", ClampMax = "1"))
float MasterVolume = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio", meta = (ClampMin = "0", ClampMax = "1"))
float AmbientVolume = 0.70f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio", meta = (ClampMin = "0", ClampMax = "1"))
float WeatherVolume = 0.75f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio", meta = (ClampMin = "0", ClampMax = "1"))
float EffectsVolume = 0.80f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio", meta = (ClampMin = "0", ClampMax = "1"))
float WildlifeVolume = 0.70f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio", meta = (ClampMin = "0", ClampMax = "1"))
float UiVolume = 0.65f;
UFUNCTION(BlueprintCallable, Category = "Agrarian|MVP UI")
void SetActiveScreen(EAgrarianMvpFrontendScreen NewScreen);
@@ -104,6 +123,25 @@ private:
UTextBlock* AddText(UVerticalBox* Parent, const FText& Text, int32 FontSize, bool bBold, const FLinearColor& Color, float BottomPadding);
UButton* AddButton(UVerticalBox* Parent, const FText& Text, const FLinearColor& NormalColor, const FLinearColor& HoveredColor, float BottomPadding);
void PlayUiSound(USoundBase* Sound) const;
USlider* AddVolumeSlider(UVerticalBox* Parent, const FText& Label, float Value, float BottomPadding);
UFUNCTION()
void HandleMasterVolumeChanged(float Value);
UFUNCTION()
void HandleAmbientVolumeChanged(float Value);
UFUNCTION()
void HandleWeatherVolumeChanged(float Value);
UFUNCTION()
void HandleEffectsVolumeChanged(float Value);
UFUNCTION()
void HandleWildlifeVolumeChanged(float Value);
UFUNCTION()
void HandleUiVolumeChanged(float Value);
UFUNCTION()
void FocusPrimaryButton();