How to create a 3D sound of a large area (e.g. river) in Unity 3D

In our project, we have created a landscape with a large river passing through. The problem arrived, when we tried to add sound effect of running water which would trigger close to the water. Adding several sound effect zones seems highly inefficient and leads to problems when several sound zones overlap.

Instead we call upon help from scripting and some basic linear algebra. The idea is very simple: We will have only one sound source, which will move along a pre-defined linear path defined with a set of waypoints and at any given time it will move to the location closest to the player. We summarise this approach in a set of steps:

  1. Create a set of waypoints along the river bed. Here we have used the Simple Waypoint System
  2. Create an empty GameObject and attach AudioSource with the 3D sound to it.
  3. Attach SoundFollow behaviour, listed below
  4. Done

Following is a script that will follow the path made of waypoints.

using UnityEngine;
using System.Collections;
using SWS;

public class SoundFollow : MonoBehaviour {

    // path manager from the "Simple Waypoints" (http://www.rebound-games.com/?page_id=39)
    // you can assign waypoints directly in waypoints
    public PathManager manager;

    // this can be made public and assigned directly
    private Vector3[] waypoints;

    private Transform player;
    private Transform trans;

    void Awake()
    {
        // specific functionality
        waypoints = new Vector3[manager.waypoints.Length];
        for (var i=0; i<manager.waypoints.Length; i++) {
            waypoints[i] = manager.waypoints[i].position;
        }

        player = GameObject.FindGameObjectWithTag ("Player").transform;
        trans = transform;
    }

    // Update is called once per frame
    void Update () {

        // sort waypoints by distance
        System.Array.Sort<Vector3> (waypoints, delegate(Vector3 way1, Vector3 way2) {
            return Vector3.Distance(way1, player.position).CompareTo (Vector3.Distance(way2, player.position));
        });

        // get the two closest waypoints and find a point in between them
        trans.position = Vector3.Lerp(trans.position, ClosestPointOnLine (waypoints [0], waypoints [1], player.position), Time.deltaTime * 2);
    }

    // thanks to: http://forum.unity3d.com/threads/math-problem.8114/#post-59715
    Vector3 ClosestPointOnLine(Vector3 vA, Vector3 vB, Vector3 vPoint)
    {
        var vVector1 = vPoint - vA;
        var vVector2 = (vB - vA).normalized;

        var d = Vector3.Distance(vA, vB);
        var t = Vector3.Dot(vVector2, vVector1);

        if (t <= 0)
            return vA;

        if (t >= d)
            return vB;

        var vVector3 = vVector2 * t;

        var vClosestPoint = vA + vVector3;

        return vClosestPoint;
    }
}

You can easily drop the “Simple Waypoint System” and manually specify the set of waypoints.