Add MVP footstep audio placeholders

This commit is contained in:
2026-05-19 12:04:05 -07:00
parent fb658baff0
commit bb5ed3883b
5 changed files with 186 additions and 1 deletions
+1 -1
View File
@@ -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 ## 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. - [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 gathering sounds.
- [ ] Add fire sounds. - [ ] Add fire sounds.
- [ ] Add unattended and poorly maintained fire risk for campfires and other open-flame sources. - [ ] Add unattended and poorly maintained fire risk for campfires and other open-flame sources.
+7
View File
@@ -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 audio can be added by setting the exposed sound properties on the placed
controller or a Blueprint child. 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`. Campfires expose native extinguish logic through `AAgrarianCampfire::Extinguish`.
Extinguishing clears remaining fuel, turns off replicated lit state, and reuses Extinguishing clears remaining fuel, turns off replicated lit state, and reuses
the same visual update path as natural fuel depletion. the same visual update path as natural fuel depletion.
+59
View File
@@ -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<USoundBase> WalkFootstepSound;",
"TObjectPtr<USoundBase> SprintFootstepSound;",
"TObjectPtr<USoundBase> CrouchFootstepSound;",
"TObjectPtr<USoundBase> ProneFootstepSound;",
"WalkFootstepIntervalSeconds",
"SprintFootstepIntervalSeconds",
"CrouchFootstepIntervalSeconds",
"ProneFootstepIntervalSeconds",
"void UpdateFootstepAudio(float DeltaSeconds);",
],
CHAR_CPP: [
"#include \"Components/AudioComponent.h\"",
"FootstepAudioComponent = CreateDefaultSubobject<UAudioComponent>",
"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()
@@ -8,6 +8,7 @@
#include "AgrarianSurvivalComponent.h" #include "AgrarianSurvivalComponent.h"
#include "Engine/LocalPlayer.h" #include "Engine/LocalPlayer.h"
#include "Camera/CameraComponent.h" #include "Camera/CameraComponent.h"
#include "Components/AudioComponent.h"
#include "Components/CapsuleComponent.h" #include "Components/CapsuleComponent.h"
#include "GameFramework/CharacterMovementComponent.h" #include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/SpringArmComponent.h" #include "GameFramework/SpringArmComponent.h"
@@ -64,6 +65,10 @@ AAgrarianGameCharacter::AAgrarianGameCharacter()
InventoryComponent = CreateDefaultSubobject<UAgrarianInventoryComponent>(TEXT("InventoryComponent")); InventoryComponent = CreateDefaultSubobject<UAgrarianInventoryComponent>(TEXT("InventoryComponent"));
CraftingComponent = CreateDefaultSubobject<UAgrarianCraftingComponent>(TEXT("CraftingComponent")); CraftingComponent = CreateDefaultSubobject<UAgrarianCraftingComponent>(TEXT("CraftingComponent"));
BuildingPlacementComponent = CreateDefaultSubobject<UAgrarianBuildingPlacementComponent>(TEXT("BuildingPlacementComponent")); BuildingPlacementComponent = CreateDefaultSubobject<UAgrarianBuildingPlacementComponent>(TEXT("BuildingPlacementComponent"));
FootstepAudioComponent = CreateDefaultSubobject<UAudioComponent>(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) // 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++) // 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(); ApplyMovementSpeed();
UpdateFootstepAudio(DeltaSeconds);
if (!HasAuthority() || !bWantsToSprint) if (!HasAuthority() || !bWantsToSprint)
{ {
@@ -474,6 +480,78 @@ float AAgrarianGameCharacter::CalculateStanceMovementMultiplier() const
return 1.0f; 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() void AAgrarianGameCharacter::OnRep_SprintState()
{ {
ApplyMovementSpeed(); ApplyMovementSpeed();
@@ -14,6 +14,8 @@ class UAgrarianBuildingPlacementComponent;
class UAgrarianCraftingComponent; class UAgrarianCraftingComponent;
class UAgrarianInventoryComponent; class UAgrarianInventoryComponent;
class UAgrarianSurvivalComponent; class UAgrarianSurvivalComponent;
class UAudioComponent;
class USoundBase;
struct FInputActionValue; struct FInputActionValue;
DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All); DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);
@@ -50,6 +52,10 @@ class AAgrarianGameCharacter : public ACharacter
/** Server-authoritative primitive building placement component. */ /** Server-authoritative primitive building placement component. */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
UAgrarianBuildingPlacementComponent* BuildingPlacementComponent; UAgrarianBuildingPlacementComponent* BuildingPlacementComponent;
/** Local placeholder footstep playback point; silent until sound assets are assigned. */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
UAudioComponent* FootstepAudioComponent;
protected: protected:
@@ -153,6 +159,38 @@ protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_MovementModifierState, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "1.25")) UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_MovementModifierState, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "1.25"))
float TerrainMovementMultiplier = 1.0f; float TerrainMovementMultiplier = 1.0f;
/** Placeholder walk footstep cue, intended for final surface-aware audio replacement later. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Audio|Footsteps")
TObjectPtr<USoundBase> WalkFootstepSound;
/** Placeholder sprint footstep cue. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Audio|Footsteps")
TObjectPtr<USoundBase> SprintFootstepSound;
/** Placeholder crouch footstep cue. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Audio|Footsteps")
TObjectPtr<USoundBase> CrouchFootstepSound;
/** Placeholder prone shuffle cue. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Audio|Footsteps")
TObjectPtr<USoundBase> 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. */ /** Third-person spring arm distance used when returning from first person. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Camera", meta = (ClampMin = "0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Camera", meta = (ClampMin = "0"))
float ThirdPersonCameraDistance = 400.0f; float ThirdPersonCameraDistance = 400.0f;
@@ -231,6 +269,9 @@ protected:
float CalculateSurvivalMovementMultiplier() const; float CalculateSurvivalMovementMultiplier() const;
float CalculateCarryWeightMovementMultiplier() const; float CalculateCarryWeightMovementMultiplier() const;
float CalculateStanceMovementMultiplier() const; float CalculateStanceMovementMultiplier() const;
void UpdateFootstepAudio(float DeltaSeconds);
USoundBase* GetCurrentFootstepSound() const;
float GetCurrentFootstepIntervalSeconds() const;
UFUNCTION() UFUNCTION()
void OnRep_SprintState(); void OnRep_SprintState();