Add MVP audio volume sliders
This commit is contained in:
@@ -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 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 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.
|
- [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
|
## 0.1.Q MVP QA Gates
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
actions play the same cues so menu feedback stays consistent across input
|
||||||
methods while the widget remains silent until UI assets are assigned.
|
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
|
Player movement audio starts with native footstep placeholders on
|
||||||
`AAgrarianGameCharacter`. The character owns a spatialized
|
`AAgrarianGameCharacter`. The character owns a spatialized
|
||||||
`FootstepAudioComponent` plus assignable walk, sprint, crouch, and prone sound
|
`FootstepAudioComponent` plus assignable walk, sprint, crouch, and prone sound
|
||||||
|
|||||||
@@ -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/HorizontalBox.h"
|
||||||
#include "Components/HorizontalBoxSlot.h"
|
#include "Components/HorizontalBoxSlot.h"
|
||||||
#include "Components/SizeBox.h"
|
#include "Components/SizeBox.h"
|
||||||
|
#include "Components/Slider.h"
|
||||||
#include "Components/TextBlock.h"
|
#include "Components/TextBlock.h"
|
||||||
#include "Components/VerticalBox.h"
|
#include "Components/VerticalBox.h"
|
||||||
#include "Components/VerticalBoxSlot.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);
|
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);
|
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);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
@@ -479,10 +505,74 @@ void UAgrarianMvpFrontendWidget::PlayUiSound(USoundBase* Sound) const
|
|||||||
{
|
{
|
||||||
if (Sound)
|
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()
|
void UAgrarianMvpFrontendWidget::FocusPrimaryButton()
|
||||||
{
|
{
|
||||||
if (PrimaryFocusButton)
|
if (PrimaryFocusButton)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "AgrarianMvpFrontendWidget.generated.h"
|
#include "AgrarianMvpFrontendWidget.generated.h"
|
||||||
|
|
||||||
class UButton;
|
class UButton;
|
||||||
|
class USlider;
|
||||||
class USoundBase;
|
class USoundBase;
|
||||||
class UTextBlock;
|
class UTextBlock;
|
||||||
class UVerticalBox;
|
class UVerticalBox;
|
||||||
@@ -70,6 +71,24 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio")
|
||||||
TObjectPtr<USoundBase> UiSaveQuitSound;
|
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")
|
UFUNCTION(BlueprintCallable, Category = "Agrarian|MVP UI")
|
||||||
void SetActiveScreen(EAgrarianMvpFrontendScreen NewScreen);
|
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);
|
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);
|
UButton* AddButton(UVerticalBox* Parent, const FText& Text, const FLinearColor& NormalColor, const FLinearColor& HoveredColor, float BottomPadding);
|
||||||
void PlayUiSound(USoundBase* Sound) const;
|
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()
|
UFUNCTION()
|
||||||
void FocusPrimaryButton();
|
void FocusPrimaryButton();
|
||||||
|
|||||||
Reference in New Issue
Block a user