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
- [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.
+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
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.
+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 "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<UAgrarianInventoryComponent>(TEXT("InventoryComponent"));
CraftingComponent = CreateDefaultSubobject<UAgrarianCraftingComponent>(TEXT("CraftingComponent"));
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)
// 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();
@@ -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<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. */
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();