Random Without Replacement in a BP

In the previous post we looked at how we can play back sounds from random locations as a way of improving the general ambience within an area.
This post will look at a way we can improve things further by allowing us to do ‘random without replacement’ so that the random locations chosen for playback aren’t repeated. This functionality already exists within SoundCues using the Random node.

Unfortunately, there is no standard node for doing this in a BP – so we’re going to have to build it ourselves…

The resulting system is a little large, in terms of BP nodes, so I’m going to create a new Macro and build within there – this will help to keep my BPs tidy and easily ‘readable’, it will also mean the new functionality can be reused across projects easily.
We will cover the creation of the random without replacement macro, but you can download the macro here if you want.

You can create a new macro directly from within a BP using the Add Macro option.

However, this macro would then only be accessible within that BP. If you want to create a macro that you can use across your project, and even share with other projects then you need to create a Macro Library within the Content Browser. Use the Add New – Blueprints – Blueprint Macro Library option.

You’ll then be asked to choose the ‘Parent Class’ for your macro – the advice from Epic is to use ‘Actor’ as this will enable you to do more in terms of accessing variables and functions.
Give your new macro library asset a name, and then open it for editing.

The first step is define the inputs and outputs of your macro. Select the Inputs node, and in the Details window create the following Inputs and Outputs:

The Exec In triggers the macro’s functionality.
The Input Array is the set of items that we want to randomly choose from. We’re using the Wildcard variable type so that the macro isn’t tied to a specific type of asset – the wildcard type changes to match whatever is connected to it.
The Rand Without Replace boolean will allow us to choose whether we want to turn the ‘without replacement’ functionality on or off.
The Exec Out is triggered after the macro has completed it’s task.
The Array Item output passes the randomly selected item.

The first step is generate a random number:

We get the Length of the input array, subtract ‘1’ from it (like we did in the previous blog post) and then use this to set the maximum of our random range – remember, array items are indexed starting with a value ‘0’, so the possible range needs to be 1 less than the length.
We’re using a Local Integer to store this random value, so that we can use it further down the chain.
Next we need to check the status of our ‘Rand Without Replace’ boolean:

And then set up what will happen for each state.
If the bool is set to ‘False’, it’s pretty simple – just use the randomly generated value to Get the appropriate item from the Input Array and pass it to the Output:

The system for ‘True’ is a bit more complex, so we’ll build it in stages…
First, create this:

We’ve create a Local Array of Integers to store the randomly generated value, and we check this array to see if it Contains the current random value already, if it does we simply generate another random value and check again (the Reroute node is useful when you want to loop back on yourself, or to help keep connections readable). If the random value is not present within the local array then we Add it for the next pass through.
Now extend to this:

We need to know when we’ve generated every possible value so that we can reset our system and go back through the whole Input Array again. So we compare the Length of the local array with that of the Input Array and if they are equal to each other (==) then we Clear the local array to empty it and start again. If the two lengths are not equal then there are still some values left and we can go straight to the output. At the output (like we did earlier) we simply Get the item from the Input Array and pass it out.

Now you can Save your macro, and access it from any BP within your project.

Here I’ve incorporated the macro into the random sound location system from the previous blog post.

By passing it out array of sound playback locations we can randomly select one every time we call the function but be sure that we won’t get any repetition of locations until the array has been exhausted.

Next time, we’ll look at a way of extending our random system further by incorporating the option to determine random weightings for each item in the input array…

Advertisement

So, you want to spawn a sound at random locations..?

Spawning sounds at random locations is a useful technique (especially when combined with random variations within the sounds themselves) as it can help reduce any sense of repetition and help to bring an environment to life. This is the first of a short series of posts that deal with this topic and will cover a few of the techniques involved, starting with the ability to randomly choose locations

As with any technique, there are multiple ways of achieving the same thing… You could do this:

But then you’d have to set up a system to define your x/y/z min/max values to appropriate ranges so that the sounds played back within the correct area of your level.

A better way might be this, as you can define specific locations by placing actors at the various points within your area:

Or this, using AmbientSound actors placed within the level:

Either approach produces the same end result, but it depends whether you want to have your sounds appear to come from specific actors within your level or from ‘unseen’ empty AmbientSound actors that you’ve placed appropriately.

These methods work fine, and there are probably more variations on this theme, but the problem comes if you want to add more possible locations within your level. You’d have to modify your BP to accommodate these changes by adding in more actor references, and extending the Select node.

Instead we can make our controlling Blueprint dynamically find all of our locations and store them in an array. We can then use a similar method, as illustrated above, to generate random numbers and select from the contents of the array – if we’re cunning about how we do this, we only need to build one system and we can add/remove locations as much we want without having to make any changes.

Firstly we need to ‘find’ all of our locations.

The Get All Actors Of Class does exactly what you would expect – it finds all of the actors within a level of a specified class and generates an array containing all found references. You can use whatever type of actor you want for your sound locations (as discussed above), but for this method it does make a bit of sense to create your own custom actor class so that you can be sure that the Get All node only finds those actors you specifically want to use as location markers.

Creating your own actor class is pretty simple – within your Content Browser, use Add New – Blueprint Class.

Then you can choose to base your custom actor off any class you like – I’ve chosen to use AmbientSound as this means I could use it’s available functionality to change how the sound is played in the future if I want.

Once you’ve got your Get All node configured, you can easily create a variable to store your generated array, by right clicking the ‘Out Actors’ outlet and selecting ‘Promote to Variable’. It would be useful to know how many actors are stored within the array – so that we can generate an appropriate random number, so we use the Length node and create an int variable to store the output.

Because the BP finds all of the specific actors within our level, and creates the array for us, we can add and remove actors and not have to worry about modifying the BP – which is nice!

The next step is to create the system that will select one of the locations at random and then play the sound from that location.

We subtract ‘1’ from the length of the array, and use this as the maximum of our random integer range, as the references within the array are stored at indices that start at ‘0’. So the first slot is ‘0’, the second is ‘1’, and so on…

We then use the Get node to access the reference stored at our random index and store that as a variable – we do this to ensure that any future use of this randomly generated reference remains consistent (if we just used the output of the Get node, then the Set Sound node would produce one random reference, and the Play node would produce another – they will likely be different!).

We then simply set the sound to be played by the actor and start it playing.

If at this point, you’ve been building and are testing your system, you may well notice that sometimes a currently playing sound location is retriggered – this is the beauty of ‘random’; the same thing can happen twice in a row (or more…). Ideally we would check to see if the randomly chosen location is playing, and if so generate another random number and try again.

This is pretty simple:

We can use the IsPlaying variable from the Audio Component to control a Branch node (this is one of the reasons I chose to base my custom actor off the AmbientSound class). If the randomly chosen location is not currently playing (ie. False) then we set and play the sound. If the chosen location is playing (ie. True) we just call the PlaySound function again and restart the system.

That’s the basics of playing back sounds at random locations. There will likely be other ways, this is just the way I’ve found works for me.

There are a number of improvements we could make to the system, the biggest one being the ability to generate random numbers without repetition (similarly to how the Random node within a SoundCue works), so the next blog post will deal with that.