From d0ad64418ddba8b4d60e66d484053140efa3d958 Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 19 May 2026 12:35:30 -0700 Subject: [PATCH] Add MVP frontend audio hooks --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 2 +- Docs/TechnicalDesignDocument.md | 5 ++ Scripts/verify_ui_sounds.py | 57 +++++++++++++++++++ .../AgrarianMvpFrontendWidget.cpp | 22 +++++++ .../AgrarianGame/AgrarianMvpFrontendWidget.h | 14 +++++ 5 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 Scripts/verify_ui_sounds.py diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 1e7b679..f6f155c 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -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 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. -- [ ] 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 volume sliders. diff --git a/Docs/TechnicalDesignDocument.md b/Docs/TechnicalDesignDocument.md index 71c548b..39be7a3 100644 --- a/Docs/TechnicalDesignDocument.md +++ b/Docs/TechnicalDesignDocument.md @@ -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 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 `AAgrarianGameCharacter`. The character owns a spatialized `FootstepAudioComponent` plus assignable walk, sprint, crouch, and prone sound diff --git a/Scripts/verify_ui_sounds.py b/Scripts/verify_ui_sounds.py new file mode 100644 index 0000000..19c8c3b --- /dev/null +++ b/Scripts/verify_ui_sounds.py @@ -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 UiConfirmSound;", + "TObjectPtr UiBackSound;", + "TObjectPtr UiSelectionSound;", + "TObjectPtr 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() diff --git a/Source/AgrarianGame/AgrarianMvpFrontendWidget.cpp b/Source/AgrarianGame/AgrarianMvpFrontendWidget.cpp index 7379308..01c794b 100644 --- a/Source/AgrarianGame/AgrarianMvpFrontendWidget.cpp +++ b/Source/AgrarianGame/AgrarianMvpFrontendWidget.cpp @@ -14,6 +14,7 @@ #include "Components/VerticalBoxSlot.h" #include "GameFramework/PlayerController.h" #include "InputCoreTypes.h" +#include "Kismet/GameplayStatics.h" #include "Styling/CoreStyle.h" #include "TimerManager.h" @@ -48,18 +49,21 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry, const FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Left || Key == EKeys::A) { + PlayUiSound(UiSelectionSound); SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultMale); return FReply::Handled(); } if (Key == EKeys::Right || Key == EKeys::D) { + PlayUiSound(UiSelectionSound); SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultFemale); return FReply::Handled(); } if (Key == EKeys::Enter || Key == EKeys::SpaceBar) { + PlayUiSound(UiConfirmSound); ConfirmActiveScreen(); return FReply::Handled(); } @@ -69,12 +73,14 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry, const FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Enter || Key == EKeys::SpaceBar) { + PlayUiSound(UiConfirmSound); ConfirmActiveScreen(); return FReply::Handled(); } if (Key == EKeys::BackSpace || Key == EKeys::Escape) { + PlayUiSound(UiBackSound); BackFromActiveScreen(); return FReply::Handled(); } @@ -84,6 +90,7 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry, const FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Enter || Key == EKeys::SpaceBar) { + PlayUiSound(UiConfirmSound); ConfirmActiveScreen(); return FReply::Handled(); } @@ -93,12 +100,14 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry, const FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Enter || Key == EKeys::SpaceBar || Key == EKeys::Escape) { + PlayUiSound(UiConfirmSound); ConfirmActiveScreen(); return FReply::Handled(); } if (Key == EKeys::Q) { + PlayUiSound(UiSaveQuitSound); SaveAndQuit(); return FReply::Handled(); } @@ -466,6 +475,14 @@ UButton* UAgrarianMvpFrontendWidget::AddButton(UVerticalBox* Parent, const FText return Button; } +void UAgrarianMvpFrontendWidget::PlayUiSound(USoundBase* Sound) const +{ + if (Sound) + { + UGameplayStatics::PlaySound2D(this, Sound); + } +} + void UAgrarianMvpFrontendWidget::FocusPrimaryButton() { if (PrimaryFocusButton) @@ -480,26 +497,31 @@ void UAgrarianMvpFrontendWidget::FocusPrimaryButton() void UAgrarianMvpFrontendWidget::HandlePrimaryActionClicked() { + PlayUiSound(UiConfirmSound); ConfirmActiveScreen(); } void UAgrarianMvpFrontendWidget::HandleBackClicked() { + PlayUiSound(UiBackSound); BackFromActiveScreen(); } void UAgrarianMvpFrontendWidget::HandleSaveAndQuitClicked() { + PlayUiSound(UiSaveQuitSound); SaveAndQuit(); } void UAgrarianMvpFrontendWidget::HandleMaleCharacterClicked() { + PlayUiSound(UiSelectionSound); SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultMale); } void UAgrarianMvpFrontendWidget::HandleFemaleCharacterClicked() { + PlayUiSound(UiSelectionSound); SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultFemale); } diff --git a/Source/AgrarianGame/AgrarianMvpFrontendWidget.h b/Source/AgrarianGame/AgrarianMvpFrontendWidget.h index 07bf34b..4761865 100644 --- a/Source/AgrarianGame/AgrarianMvpFrontendWidget.h +++ b/Source/AgrarianGame/AgrarianMvpFrontendWidget.h @@ -7,6 +7,7 @@ #include "AgrarianMvpFrontendWidget.generated.h" class UButton; +class USoundBase; class UTextBlock; class UVerticalBox; @@ -57,6 +58,18 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI") bool bUseHighContrast = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio") + TObjectPtr UiConfirmSound; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio") + TObjectPtr UiBackSound; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio") + TObjectPtr UiSelectionSound; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|MVP UI|Audio") + TObjectPtr UiSaveQuitSound; + UFUNCTION(BlueprintCallable, Category = "Agrarian|MVP UI") void SetActiveScreen(EAgrarianMvpFrontendScreen NewScreen); @@ -90,6 +103,7 @@ private: void RebuildFrontendTree(); 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; UFUNCTION() void FocusPrimaryButton();