Pepper.SFX
All Guidance pages

Guidance / Cookbook

Footsteps & Movement

Recipe: Basic Footsteps with Variations

📋 What: Player footsteps with natural variation

🎯 Use Case: Character locomotion

Asset Setup

1. Create Container:

  • Create → Random Container
  • Name: “Player_Footsteps_RnC”
  • Inspector:
    • Audio Clips: Add 6-8 footstep sounds
    • Avoid Repeat Last: 2
    • Use Weighting: ✓ (equal weights)
    • Volume: 0.6
    • Enable Volume Randomization: ✓ (Min: -2, Max: 2)
    • Enable Pitch Randomization: ✓ (Min: -100, Max: 100)
    • Is3D: ✓
    • Min Distance: 1
    • Max Distance: 15

2. Create Event:

  • Create → Audio Event
  • Name: “Play_Player_Footstep”
  • Inspector:
    • Priority: High
    • Max Instances: 4
    • Actions → Play Player_Footsteps_RnC → Footsteps_Bus

Code (Animation Events)

using UnityEngine;
using AudioSystem;

public class PlayerFootsteps : MonoBehaviour
{
    [SerializeField] private AudioEvent footstepEvent;

    // Called from animation event on footfall frames
    public void PlayFootstep()
    {
        if (footstepEvent != null)
        {
            footstepEvent.Post(gameObject, transform.position);
        }
    }
}

Setup:

  1. Attach to player GameObject
  2. Assign footstep event
  3. In Animation window, add Animation Event at each footfall
  4. Set Function: “PlayFootstep”

✅ Result: Natural-sounding footsteps synced to animation!


Recipe: Surface-Dependent Footsteps

📋 What: Different footstep sounds per surface type

🎯 Use Case: Realistic environmental feedback

Asset Setup

1. Create Surface Containers:

Grass:

  • Random Container: “Footsteps_Grass_RnC”
  • 6 grass footstep clips
  • Avoid Repeat Last: 2

Metal:

  • Random Container: “Footsteps_Metal_RnC”
  • 5 metal footstep clips
  • Volume: 0.8 (louder)

Wood:

  • Random Container: “Footsteps_Wood_RnC”
  • 6 wood footstep clips
  • Volume: 0.7

Water:

  • Random Container: “Footsteps_Water_RnC”
  • 5 water splash clips
  • Volume: 0.65

2. Create Switch Container:

  • Create → Switch Container
  • Name: “Player_Footsteps_Switch_SwC”
  • Inspector:
    • Switch Group Name: “Surface_Type”
    • Switch Entries:
      • [0] “Grass” → Footsteps_Grass_RnC
      • [1] “Metal” → Footsteps_Metal_RnC
      • [2] “Wood” → Footsteps_Wood_RnC
      • [3] “Water” → Footsteps_Water_RnC
    • Default Container: Footsteps_Grass_RnC
    • Is3D: ✓
    • Min Distance: 1
    • Max Distance: 15

3. Create Event:

  • Event: “Play_Player_Footstep”
  • Action: Play Player_Footsteps_Switch_SwC → Footsteps_Bus

Code

using UnityEngine;
using AudioSystem;

public class SurfaceFootsteps : MonoBehaviour
{
    [SerializeField] private AudioEvent footstepEvent;
    [SerializeField] private LayerMask groundLayers;
    [SerializeField] private float raycastDistance = 2f;

    // Called from animation event
    public void PlayFootstep()
    {
        string surface = DetectSurface();
        AudioManager.Instance.SetSwitch("Surface_Type", surface);

        if (footstepEvent != null)
        {
            footstepEvent.Post(gameObject, transform.position);
        }
    }

    private string DetectSurface()
    {
        RaycastHit hit;
        Vector3 origin = transform.position + Vector3.up * 0.1f;

        if (Physics.Raycast(origin, Vector3.down, out hit, raycastDistance, groundLayers))
        {
            // Method 1: By tag
            if (hit.collider.CompareTag("Metal")) return "Metal";
            if (hit.collider.CompareTag("Wood")) return "Wood";
            if (hit.collider.CompareTag("Water")) return "Water";

            // Method 2: By material name
            Renderer renderer = hit.collider.GetComponent<Renderer>();
            if (renderer != null && renderer.sharedMaterial != null)
            {
                string matName = renderer.sharedMaterial.name.ToLower();

                if (matName.Contains("metal")) return "Metal";
                if (matName.Contains("wood")) return "Wood";
                if (matName.Contains("water")) return "Water";
            }

            // Method 3: By layer
            int hitLayer = hit.collider.gameObject.layer;
            if (hitLayer == LayerMask.NameToLayer("Metal")) return "Metal";
            if (hitLayer == LayerMask.NameToLayer("Wood")) return "Wood";
        }

        return "Grass"; // Default
    }

    void OnDrawGizmos()
    {
        Gizmos.color = Color.yellow;
        Vector3 origin = transform.position + Vector3.up * 0.1f;
        Gizmos.DrawLine(origin, origin + Vector3.down * raycastDistance);
    }
}

Surface Tagging Methods:

Option 1 - Tags:

Tag ground objects: "Metal", "Wood", "Grass", "Water"
Pro: Simple
Con: Limited to small number of tags

Option 2 - Material Names:

Name materials: "Floor_Metal", "Terrain_Grass", etc.
Pro: Unlimited surfaces
Con: Requires naming convention

Option 3 - Layers:

Create layers: Metal, Wood, Grass
Pro: Clean, performant
Con: Limited layer count

✅ Result: Realistic surface-dependent footsteps!


Recipe: Footstep Timer (No Animation Events)

📋 What: Timer-based footsteps for procedural animation or simple movement

🎯 Use Case: Games without foot animation events, procedural animation

💻 Code:

using UnityEngine;
using AudioSystem;

[RequireComponent(typeof(CharacterController))]
public class FootstepTimer : MonoBehaviour
{
    [Header("Audio")]
    [SerializeField] private AudioEvent footstepEvent;

    [Header("Timing")]
    [SerializeField] private float walkStepInterval = 0.5f;
    [SerializeField] private float runStepInterval = 0.3f;
    [SerializeField] private float minSpeedForFootsteps = 0.5f;

    private CharacterController controller;
    private float stepTimer = 0f;
    private bool wasGroundedLastFrame = false;

    void Start()
    {
        controller = GetComponent<CharacterController>();
    }

    void Update()
    {
        HandleFootsteps();
    }

    void HandleFootsteps()
    {
        // Check if moving and grounded
        float speed = new Vector3(controller.velocity.x, 0, controller.velocity.z).magnitude;
        bool isGrounded = controller.isGrounded;

        if (isGrounded && speed > minSpeedForFootsteps)
        {
            // Determine interval based on speed
            bool isRunning = speed > 5f; // Adjust threshold as needed
            float interval = isRunning ? runStepInterval : walkStepInterval;

            stepTimer -= Time.deltaTime;

            if (stepTimer <= 0f)
            {
                PlayFootstep();
                stepTimer = interval;
            }
        }
        else
        {
            // Reset timer when not moving or airborne
            stepTimer = 0f;
        }

        // Landing sound
        if (isGrounded && !wasGroundedLastFrame && speed > 1f)
        {
            PlayFootstep();
            stepTimer = (speed > 5f ? runStepInterval : walkStepInterval);
        }

        wasGroundedLastFrame = isGrounded;
    }

    void PlayFootstep()
    {
        if (footstepEvent != null)
        {
            footstepEvent.Post(gameObject, transform.position);
        }
    }
}

Tuning Tips:

Walk Interval: 0.4-0.6s
Run Interval: 0.25-0.35s
Min Speed: 0.3-0.8 units/sec

✅ Result: Automatic footsteps based on movement speed!

From the Pepper.SFX 3.1 guides (USER_GUIDE.md). Something wrong or unclear? Tell me.