Add MVP frontend audio hooks

This commit is contained in:
2026-05-19 12:35:30 -07:00
parent f6e126aabb
commit d0ad64418d
5 changed files with 99 additions and 1 deletions
+1 -1
View File
@@ -847,7 +847,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [x] Add QA coverage for safe campfires, unsafe campfires, vegetation spread, shelter ignition, suppression, and save/load recovery. Added a fire-risk QA coverage document and verifier requiring safe/unsafe campfire, vegetation spread, shelter ignition, suppression, and save/load recovery scenarios plus the supporting fire-risk verification scripts. - [x] Add QA coverage for safe campfires, unsafe campfires, vegetation spread, shelter ignition, suppression, and save/load recovery. Added a fire-risk QA coverage document and verifier requiring safe/unsafe campfire, vegetation spread, shelter ignition, suppression, and save/load recovery scenarios plus the supporting fire-risk verification scripts.
- [x] Add weather sounds. Formalized the existing placed weather audio controller as the MVP weather-sound path, documenting rain, wind, storm, clear ambient, and biome loop slots plus verification that weather playback follows replicated weather state, provider wind speed, and day/night state while remaining silent until assets are assigned. - [x] Add weather sounds. Formalized the existing placed weather audio controller as the MVP weather-sound path, documenting rain, wind, storm, clear ambient, and biome loop slots plus verification that weather playback follows replicated weather state, provider wind speed, and day/night state while remaining silent until assets are assigned.
- [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.
- [ ] Add UI sounds. - [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.
- [ ] Add mix settings. - [ ] Add mix settings.
- [ ] Add volume sliders. - [ ] Add volume sliders.
+5
View File
@@ -323,6 +323,11 @@ Wildlife expose idle, flee/chase, death, and harvest sound slots. The server
multicasts state-change and harvest cues so clients hear wildlife reactions from multicasts state-change and harvest cues so clients hear wildlife reactions from
the authoritative AI state while the dedicated server remains silent. the authoritative AI state while the dedicated server remains silent.
UI sounds are optional 2D hooks on `UAgrarianMvpFrontendWidget`. The frontend
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.
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
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
"""Verify MVP frontend UI sound hooks 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: [
"TObjectPtr<USoundBase> UiConfirmSound;",
"TObjectPtr<USoundBase> UiBackSound;",
"TObjectPtr<USoundBase> UiSelectionSound;",
"TObjectPtr<USoundBase> UiSaveQuitSound;",
"void PlayUiSound(USoundBase* Sound) const;",
],
UI_CPP: [
"#include \"Kismet/GameplayStatics.h\"",
"UGameplayStatics::PlaySound2D(this, Sound);",
"PlayUiSound(UiConfirmSound);",
"PlayUiSound(UiBackSound);",
"PlayUiSound(UiSelectionSound);",
"PlayUiSound(UiSaveQuitSound);",
"HandlePrimaryActionClicked",
"HandleBackClicked",
"HandleMaleCharacterClicked",
"NativeOnKeyDown",
],
TDD: [
"UI sounds are optional 2D hooks",
"confirm, back, selection, and save/quit sound",
"Keyboard and mouse",
],
ROADMAP: [
"[x] Add UI sounds.",
],
}
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: UI sound hooks are shared across keyboard and mouse actions.")
if __name__ == "__main__":
main()
@@ -14,6 +14,7 @@
#include "Components/VerticalBoxSlot.h" #include "Components/VerticalBoxSlot.h"
#include "GameFramework/PlayerController.h" #include "GameFramework/PlayerController.h"
#include "InputCoreTypes.h" #include "InputCoreTypes.h"
#include "Kismet/GameplayStatics.h"
#include "Styling/CoreStyle.h" #include "Styling/CoreStyle.h"
#include "TimerManager.h" #include "TimerManager.h"
@@ -48,18 +49,21 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry,
const FKey Key = InKeyEvent.GetKey(); const FKey Key = InKeyEvent.GetKey();
if (Key == EKeys::Left || Key == EKeys::A) if (Key == EKeys::Left || Key == EKeys::A)
{ {
PlayUiSound(UiSelectionSound);
SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultMale); SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultMale);
return FReply::Handled(); return FReply::Handled();
} }
if (Key == EKeys::Right || Key == EKeys::D) if (Key == EKeys::Right || Key == EKeys::D)
{ {
PlayUiSound(UiSelectionSound);
SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultFemale); SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultFemale);
return FReply::Handled(); return FReply::Handled();
} }
if (Key == EKeys::Enter || Key == EKeys::SpaceBar) if (Key == EKeys::Enter || Key == EKeys::SpaceBar)
{ {
PlayUiSound(UiConfirmSound);
ConfirmActiveScreen(); ConfirmActiveScreen();
return FReply::Handled(); return FReply::Handled();
} }
@@ -69,12 +73,14 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry,
const FKey Key = InKeyEvent.GetKey(); const FKey Key = InKeyEvent.GetKey();
if (Key == EKeys::Enter || Key == EKeys::SpaceBar) if (Key == EKeys::Enter || Key == EKeys::SpaceBar)
{ {
PlayUiSound(UiConfirmSound);
ConfirmActiveScreen(); ConfirmActiveScreen();
return FReply::Handled(); return FReply::Handled();
} }
if (Key == EKeys::BackSpace || Key == EKeys::Escape) if (Key == EKeys::BackSpace || Key == EKeys::Escape)
{ {
PlayUiSound(UiBackSound);
BackFromActiveScreen(); BackFromActiveScreen();
return FReply::Handled(); return FReply::Handled();
} }
@@ -84,6 +90,7 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry,
const FKey Key = InKeyEvent.GetKey(); const FKey Key = InKeyEvent.GetKey();
if (Key == EKeys::Enter || Key == EKeys::SpaceBar) if (Key == EKeys::Enter || Key == EKeys::SpaceBar)
{ {
PlayUiSound(UiConfirmSound);
ConfirmActiveScreen(); ConfirmActiveScreen();
return FReply::Handled(); return FReply::Handled();
} }
@@ -93,12 +100,14 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry,
const FKey Key = InKeyEvent.GetKey(); const FKey Key = InKeyEvent.GetKey();
if (Key == EKeys::Enter || Key == EKeys::SpaceBar || Key == EKeys::Escape) if (Key == EKeys::Enter || Key == EKeys::SpaceBar || Key == EKeys::Escape)
{ {
PlayUiSound(UiConfirmSound);
ConfirmActiveScreen(); ConfirmActiveScreen();
return FReply::Handled(); return FReply::Handled();
} }
if (Key == EKeys::Q) if (Key == EKeys::Q)
{ {
PlayUiSound(UiSaveQuitSound);
SaveAndQuit(); SaveAndQuit();
return FReply::Handled(); return FReply::Handled();
} }
@@ -466,6 +475,14 @@ UButton* UAgrarianMvpFrontendWidget::AddButton(UVerticalBox* Parent, const FText
return Button; return Button;
} }
void UAgrarianMvpFrontendWidget::PlayUiSound(USoundBase* Sound) const
{
if (Sound)
{
UGameplayStatics::PlaySound2D(this, Sound);
}
}
void UAgrarianMvpFrontendWidget::FocusPrimaryButton() void UAgrarianMvpFrontendWidget::FocusPrimaryButton()
{ {
if (PrimaryFocusButton) if (PrimaryFocusButton)
@@ -480,26 +497,31 @@ void UAgrarianMvpFrontendWidget::FocusPrimaryButton()
void UAgrarianMvpFrontendWidget::HandlePrimaryActionClicked() void UAgrarianMvpFrontendWidget::HandlePrimaryActionClicked()
{ {
PlayUiSound(UiConfirmSound);
ConfirmActiveScreen(); ConfirmActiveScreen();
} }
void UAgrarianMvpFrontendWidget::HandleBackClicked() void UAgrarianMvpFrontendWidget::HandleBackClicked()
{ {
PlayUiSound(UiBackSound);
BackFromActiveScreen(); BackFromActiveScreen();
} }
void UAgrarianMvpFrontendWidget::HandleSaveAndQuitClicked() void UAgrarianMvpFrontendWidget::HandleSaveAndQuitClicked()
{ {
PlayUiSound(UiSaveQuitSound);
SaveAndQuit(); SaveAndQuit();
} }
void UAgrarianMvpFrontendWidget::HandleMaleCharacterClicked() void UAgrarianMvpFrontendWidget::HandleMaleCharacterClicked()
{ {
PlayUiSound(UiSelectionSound);
SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultMale); SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultMale);
} }
void UAgrarianMvpFrontendWidget::HandleFemaleCharacterClicked() void UAgrarianMvpFrontendWidget::HandleFemaleCharacterClicked()
{ {
PlayUiSound(UiSelectionSound);
SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultFemale); SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultFemale);
} }
@@ -7,6 +7,7 @@
#include "AgrarianMvpFrontendWidget.generated.h" #include "AgrarianMvpFrontendWidget.generated.h"
class UButton; class UButton;
class USoundBase;
class UTextBlock; class UTextBlock;
class UVerticalBox; class UVerticalBox;
@@ -57,6 +58,18 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI")
bool bUseHighContrast = false; bool bUseHighContrast = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio")
TObjectPtr<USoundBase> UiConfirmSound;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio")
TObjectPtr<USoundBase> UiBackSound;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio")
TObjectPtr<USoundBase> UiSelectionSound;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio")
TObjectPtr<USoundBase> UiSaveQuitSound;
UFUNCTION(BlueprintCallable, Category = "Agrarian|MVP UI") UFUNCTION(BlueprintCallable, Category = "Agrarian|MVP UI")
void SetActiveScreen(EAgrarianMvpFrontendScreen NewScreen); void SetActiveScreen(EAgrarianMvpFrontendScreen NewScreen);
@@ -90,6 +103,7 @@ private:
void RebuildFrontendTree(); void RebuildFrontendTree();
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;
UFUNCTION() UFUNCTION()
void FocusPrimaryButton(); void FocusPrimaryButton();