From bb5ed3883b9fdb7ab1bae74d98a87c1c71325fa7 Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 19 May 2026 12:04:05 -0700 Subject: [PATCH] Add MVP footstep audio placeholders --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 2 +- Docs/TechnicalDesignDocument.md | 7 ++ Scripts/verify_footstep_placeholders.py | 59 ++++++++++++++ Source/AgrarianGame/AgrarianGameCharacter.cpp | 78 +++++++++++++++++++ Source/AgrarianGame/AgrarianGameCharacter.h | 41 ++++++++++ 5 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 Scripts/verify_footstep_placeholders.py diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 12e18b1..a80cc8a 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -834,7 +834,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe ## 0.1.P MVP Audio And Atmosphere - [x] Add ambient biome audio. Extended the placed `AAgrarianWeatherAudioController` so its ambient component explicitly owns a Ground Zero coastal-scrub biome loop slot with separate day/night volume targets, keeping the current MVP silent until placeholder or final audio assets are assigned while giving the map a real ambient audio attachment point. -- [ ] Add footstep placeholders. +- [x] Add footstep placeholders. Added native player-character footstep hooks with assignable walk, sprint, crouch, and prone sound slots plus movement-state cadence, keeping the MVP silent until placeholder or final surface-aware audio assets are assigned. - [ ] Add gathering sounds. - [ ] Add fire sounds. - [ ] Add unattended and poorly maintained fire risk for campfires and other open-flame sources. diff --git a/Docs/TechnicalDesignDocument.md b/Docs/TechnicalDesignDocument.md index 26e0082..217318f 100644 --- a/Docs/TechnicalDesignDocument.md +++ b/Docs/TechnicalDesignDocument.md @@ -316,6 +316,13 @@ because the controller is silent until loops are assigned; placeholder or final audio can be added by setting the exposed sound properties on the placed controller or a Blueprint child. +Player movement audio starts with native footstep placeholders on +`AAgrarianGameCharacter`. The character owns a spatialized +`FootstepAudioComponent` plus assignable walk, sprint, crouch, and prone sound +slots. Cadence is state-aware and driven by horizontal movement, so the MVP can +remain silent until placeholder or final surface-aware cues are assigned while +still giving designers a real hook for step audio in packaged builds. + Campfires expose native extinguish logic through `AAgrarianCampfire::Extinguish`. Extinguishing clears remaining fuel, turns off replicated lit state, and reuses the same visual update path as natural fuel depletion. diff --git a/Scripts/verify_footstep_placeholders.py b/Scripts/verify_footstep_placeholders.py new file mode 100644 index 0000000..2b9fbb0 --- /dev/null +++ b/Scripts/verify_footstep_placeholders.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Verify MVP footstep placeholder hooks are present.""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +CHAR_H = ROOT / "Source" / "AgrarianGame" / "AgrarianGameCharacter.h" +CHAR_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianGameCharacter.cpp" +TDD = ROOT / "Docs" / "TechnicalDesignDocument.md" +ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md" + +REQUIRED = { + CHAR_H: [ + "UAudioComponent* FootstepAudioComponent;", + "TObjectPtr WalkFootstepSound;", + "TObjectPtr SprintFootstepSound;", + "TObjectPtr CrouchFootstepSound;", + "TObjectPtr ProneFootstepSound;", + "WalkFootstepIntervalSeconds", + "SprintFootstepIntervalSeconds", + "CrouchFootstepIntervalSeconds", + "ProneFootstepIntervalSeconds", + "void UpdateFootstepAudio(float DeltaSeconds);", + ], + CHAR_CPP: [ + "#include \"Components/AudioComponent.h\"", + "FootstepAudioComponent = CreateDefaultSubobject", + "FootstepAudioComponent->bAllowSpatialization = true", + "UpdateFootstepAudio(DeltaSeconds);", + "GetCurrentFootstepSound()", + "GetCurrentFootstepIntervalSeconds()", + "GetCharacterMovement()->IsFalling()", + ], + TDD: [ + "native footstep placeholders", + "`FootstepAudioComponent`", + "walk, sprint, crouch, and prone sound", + ], + ROADMAP: [ + "[x] Add footstep placeholders.", + ], +} + + +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: footstep placeholder hooks are implemented.") + + +if __name__ == "__main__": + main() diff --git a/Source/AgrarianGame/AgrarianGameCharacter.cpp b/Source/AgrarianGame/AgrarianGameCharacter.cpp index 13bd114..969e5d4 100644 --- a/Source/AgrarianGame/AgrarianGameCharacter.cpp +++ b/Source/AgrarianGame/AgrarianGameCharacter.cpp @@ -8,6 +8,7 @@ #include "AgrarianSurvivalComponent.h" #include "Engine/LocalPlayer.h" #include "Camera/CameraComponent.h" +#include "Components/AudioComponent.h" #include "Components/CapsuleComponent.h" #include "GameFramework/CharacterMovementComponent.h" #include "GameFramework/SpringArmComponent.h" @@ -64,6 +65,10 @@ AAgrarianGameCharacter::AAgrarianGameCharacter() InventoryComponent = CreateDefaultSubobject(TEXT("InventoryComponent")); CraftingComponent = CreateDefaultSubobject(TEXT("CraftingComponent")); BuildingPlacementComponent = CreateDefaultSubobject(TEXT("BuildingPlacementComponent")); + FootstepAudioComponent = CreateDefaultSubobject(TEXT("FootstepAudioComponent")); + FootstepAudioComponent->SetupAttachment(RootComponent); + FootstepAudioComponent->bAutoActivate = false; + FootstepAudioComponent->bAllowSpatialization = true; // Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character) // are set in the derived blueprint asset named ThirdPersonCharacter (to avoid direct content references in C++) @@ -79,6 +84,7 @@ void AAgrarianGameCharacter::Tick(float DeltaSeconds) } ApplyMovementSpeed(); + UpdateFootstepAudio(DeltaSeconds); if (!HasAuthority() || !bWantsToSprint) { @@ -474,6 +480,78 @@ float AAgrarianGameCharacter::CalculateStanceMovementMultiplier() const return 1.0f; } +void AAgrarianGameCharacter::UpdateFootstepAudio(float DeltaSeconds) +{ + if (GetNetMode() == NM_DedicatedServer || !FootstepAudioComponent || !SurvivalComponent || !SurvivalComponent->IsAlive()) + { + return; + } + + const FVector HorizontalVelocity = FVector(GetVelocity().X, GetVelocity().Y, 0.0f); + if (HorizontalVelocity.SizeSquared() < FMath::Square(20.0f) || !GetCharacterMovement() || GetCharacterMovement()->IsFalling()) + { + FootstepIntervalAccumulatorSeconds = 0.0f; + return; + } + + USoundBase* FootstepSound = GetCurrentFootstepSound(); + if (!FootstepSound) + { + return; + } + + FootstepIntervalAccumulatorSeconds += DeltaSeconds; + const float Interval = GetCurrentFootstepIntervalSeconds(); + if (FootstepIntervalAccumulatorSeconds < Interval) + { + return; + } + + FootstepIntervalAccumulatorSeconds = 0.0f; + FootstepAudioComponent->SetSound(FootstepSound); + FootstepAudioComponent->Play(); +} + +USoundBase* AAgrarianGameCharacter::GetCurrentFootstepSound() const +{ + if (bIsProne && ProneFootstepSound) + { + return ProneFootstepSound; + } + + if (bIsCrouched && CrouchFootstepSound) + { + return CrouchFootstepSound; + } + + if (IsSprinting() && SprintFootstepSound) + { + return SprintFootstepSound; + } + + return WalkFootstepSound; +} + +float AAgrarianGameCharacter::GetCurrentFootstepIntervalSeconds() const +{ + if (bIsProne) + { + return FMath::Max(0.1f, ProneFootstepIntervalSeconds); + } + + if (bIsCrouched) + { + return FMath::Max(0.1f, CrouchFootstepIntervalSeconds); + } + + if (IsSprinting()) + { + return FMath::Max(0.1f, SprintFootstepIntervalSeconds); + } + + return FMath::Max(0.1f, WalkFootstepIntervalSeconds); +} + void AAgrarianGameCharacter::OnRep_SprintState() { ApplyMovementSpeed(); diff --git a/Source/AgrarianGame/AgrarianGameCharacter.h b/Source/AgrarianGame/AgrarianGameCharacter.h index ab45765..4d7dbf6 100644 --- a/Source/AgrarianGame/AgrarianGameCharacter.h +++ b/Source/AgrarianGame/AgrarianGameCharacter.h @@ -14,6 +14,8 @@ class UAgrarianBuildingPlacementComponent; class UAgrarianCraftingComponent; class UAgrarianInventoryComponent; class UAgrarianSurvivalComponent; +class UAudioComponent; +class USoundBase; struct FInputActionValue; DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All); @@ -50,6 +52,10 @@ class AAgrarianGameCharacter : public ACharacter /** Server-authoritative primitive building placement component. */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) UAgrarianBuildingPlacementComponent* BuildingPlacementComponent; + + /** Local placeholder footstep playback point; silent until sound assets are assigned. */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) + UAudioComponent* FootstepAudioComponent; protected: @@ -153,6 +159,38 @@ protected: UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_MovementModifierState, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "1.25")) float TerrainMovementMultiplier = 1.0f; + /** Placeholder walk footstep cue, intended for final surface-aware audio replacement later. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Audio|Footsteps") + TObjectPtr WalkFootstepSound; + + /** Placeholder sprint footstep cue. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Audio|Footsteps") + TObjectPtr SprintFootstepSound; + + /** Placeholder crouch footstep cue. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Audio|Footsteps") + TObjectPtr CrouchFootstepSound; + + /** Placeholder prone shuffle cue. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Audio|Footsteps") + TObjectPtr ProneFootstepSound; + + /** Normal walking step cadence before sprint/crouch/prone modifiers. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Audio|Footsteps", meta = (ClampMin = "0.1")) + float WalkFootstepIntervalSeconds = 0.58f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Audio|Footsteps", meta = (ClampMin = "0.1")) + float SprintFootstepIntervalSeconds = 0.34f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Audio|Footsteps", meta = (ClampMin = "0.1")) + float CrouchFootstepIntervalSeconds = 0.86f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Audio|Footsteps", meta = (ClampMin = "0.1")) + float ProneFootstepIntervalSeconds = 1.15f; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Agrarian|Audio|Footsteps", meta = (AllowPrivateAccess = "true")) + float FootstepIntervalAccumulatorSeconds = 0.0f; + /** Third-person spring arm distance used when returning from first person. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Camera", meta = (ClampMin = "0")) float ThirdPersonCameraDistance = 400.0f; @@ -231,6 +269,9 @@ protected: float CalculateSurvivalMovementMultiplier() const; float CalculateCarryWeightMovementMultiplier() const; float CalculateStanceMovementMultiplier() const; + void UpdateFootstepAudio(float DeltaSeconds); + USoundBase* GetCurrentFootstepSound() const; + float GetCurrentFootstepIntervalSeconds() const; UFUNCTION() void OnRep_SprintState();