2D audio is non-directional that doesn't emit from any particular location, remaining the same regardless of a listener's position or orientation in the 3D space. This means that as players move their listeners around the environment, they can consistently hear 2D audio from anywhere they turn.
Using the Gingerbread House - Start .rbxl file as a starting place and Gingerbread House - Complete Audio as a reference, this tutorial shows you how to add both looping and one shot 2D audio to your experiences, including guidance on:
- Looping background music that plays as soon as players connect to the server.
- Triggering audio to provide players instantaneous feedback according to their gameplay objectives.
- Activating various sounds to indicate different user interface interactions.
If at any point you become stuck in the process, you can use Gingerbread House - Complete Audio as a reference to compare your progress.
2D audio objects
To create non-directional audio, it's important to understand the audio objects that you will be working with throughout this tutorial. There are three main types of audio objects for 2D audio:
- The AudioPlayer object loads and plays the audio file.
- The AudioDeviceOutput object is a physical hardware device within the real world, such as a speaker or headphones.
- Wires carry audio streams from one object to another.
All of these audio objects work together to carry sound from file playback to each player's hardware. Let's take a look at how this works in practice using an example of a player wearing a headset while playing an experience with their laptop:
- The AudioPlayer loads the 127645268874265 audio assetID into the experience for a currency purchase sound.
- A Wire carries the stream from the AudioPlayer to the AudioDeviceOutput so that the stream comes out of the player's headset.
- The AudioDeviceOutput object carries the sound to the player's physical speaker, or in this case, their headphones.
The following sections dive deeper and reference these objects as you learn how to play both looping and one shot 2D audio. As you review these objects with the upcoming techniques, you can more accurately predict how to capture and feed sound from the experience to the player and vice-versa.
Looping audio
Looping 2D audio, or non-directional audio that repeats seamlessly as soon as players connect to the server, is a common sound design technique to provide a cohesive auditory backdrop regardless of what players are doing or where they're located within the 3D space. In addition, looping 2D audio ensures that your experience is never completely silent, which can disorient and distract players from their objectives.
To demonstrate this concept, review how the following 2D audio significantly enhances the quality of the Hazardous Space Station sample. Without background music, the space station feels hollow and lifeless. However, when the space station includes background music, the space station feels both ominous and dangerous, adding to the overall spooky atmosphere.
Looping audio is particularly useful for establishing how you want players to feel as soon as they join your experience. For example, the sample aims to provide players a cheerful adventure in a winter wonderland, so it uses an upbeat audio track to positively influence their mood the moment they enter the environment. To see how influential this technique can be, consider how the player's experience would change if you were to swap the looping audio out for a metal track.
To recreate the looping 2D audio in the sample Gingerbread House - Complete Audio place file:
Enable a default listener that's attached to your player character.
- In the Explorer window, select the SoundService.
- In the Properties window, set DefaultListenerLocation to Character. When you run the experience, the engine automatically creates an AudioDeviceOutput under SoundService.
In the Explorer window, navigate to SoundService, then:
- Insert an AudioPlayer object to create an audio source.
- Insert an AudioDeviceOutput object to create a speaker that plays throughout the experience.
- Insert a Wire object to carry the stream from the audio player to the speaker.
Select the AudioPlayer, then in the Properties window,
- Set AssetID to rbxassetid://1841461968 to play an upbeat audio track.
- Enable Looping so that the audio repeats seemlessly.
- Set Volume to 0.2 to play the audio at a low volume so you can still hear the other audio sources within the experience.
Select the Wire, then in the Properties window,
- Set SourceInstance to your new AudioPlayer to specify that you want the wire to carry audio from this specific audio player.
- Set TargetInstance to your new AudioDeviceOutput to specify that you want the wire to carry audio to this specific speaker.
Back in the Explorer window, insert a Script into AudioPlayer, rename it LoopBackgroundMusic, set its RunContext property to Client, then paste the following code into the script:
local audioPlayer = script.ParentaudioPlayer:Play()Code explanationThe script starts by declaring a variable to represent the script's parent AudioPlayer. The script then sets the audio source to play from the moment the player joins the experience to the moment they exit the experience.
Playtest the experience to hear the looping background music.
One shot audio
One shot 2D audio, or non-directional audio that plays once at a specific time and position unless a player triggers it again, provides players important context about anything you don't want them to miss. Using this type of auditory feedback in your experiences is essential because it provides an instant, clear response to crucial gameplay functions and player actions, such as starting a boss fight or losing a significant amount of health.
The following sections provide implementation details for common gameplay scenarios in which players need timely, non-directional feedback, including gameplay feedback, UI element interactions, and voiceover narration for tutorials.
Gameplay feedback
As players progress through your experience, it's important that they receive immediate auditory feedback about the status of anything that impacts their gameplay, such as their score, health, or level progress. Because this type of audio directly connects to player objectives, it's important that these sounds are clear, audible, and convey useful and accurate information.
To demonstrate this concept, let's review the following one shot 2D audio from the Platformer template that plays as players jump and collect coins:
- An airy whoosh sound plays as soon as the player presses the jump key on their device.
- A high-pitched ding and coins jostling sound play as soon as the player's character touches a coin.
Both of these sounds are instantly recognizable within the context of the 3D space, and they help players understand the success of their actions without relying solely on visual cues.
When you play multiple sounds at once, certain sounds may be more important for players to hear than others. In this instance, the coin collection sound is conveying more valuable gameplay information than the jump sound, so the experience plays it at a slightly higher volume. There are no standards or rules to achieve a great mix however, so tune the sounds in your experience to reflect what you feel is most important.
For example, the sample includes various audio triggers throughout the experience, but they aren't all at the same level of importance for understanding the experience's main objective: collect gumdrops to open the gingerbread house. To ensure that players are aware of what they need to do, the sample triples the amplitude of the auditory feedback whenever the player collects a gumdrop.
To recreate the one shot game state feedback audio in the sample Gingerbread House - Complete Audio place file:
In the Explorer window, navigate to Workspace > Gumdrops > Gumdrop, then:
- Insert an AudioPlayer object to create an audio source.
- Insert an AudioDeviceOutput object to create a speaker that plays throughout the experience.
- Insert a Wire object to carry the stream from the audio player to the speaker.
Select the AudioPlayer, then in the Properties window,
- Set AssetID to rbxassetid://9113723699 to play a gentle chomping audio track.
- Set TimePosition to 0.15 to start the audio track closer to the actual chomping sound.
- Set Volume to 3 to play the audio at a high volume so you hear the gumdrop sound over other audio sources within the experience.
Select the Wire, then in the Properties window,
- Set SourceInstance to your new AudioPlayer to specify that you want the wire to carry audio from this specific audio player.
- Set TargetInstance to your new AudioDeviceOutput to specify that you want the wire to carry audio to this specific speaker.
Duplicate Gumdrop until you have three gumdrops, then scatter them around the environment.
Back in the Explorer window, navigate to ServerScriptService, then insert a Script, rename it GumdropService, set its RunContext property to Server, then paste the following code into the script:
-- Initializing serviceslocal Workspace = game:GetService("Workspace")local Players = game:GetService("Players")local ServerStorage = game:GetService("ServerStorage")local TweenService = game:GetService("TweenService")-- Moduleslocal Leaderboard = require(ServerStorage.Leaderboard)local PlayerData = require(ServerStorage.PlayerData)-- Variableslocal gumdropsFolder = Workspace.Gumdropslocal gumdrops = gumdropsFolder:GetChildren()local GUMDROP_KEY_NAME = PlayerData.GUMDROP_KEY_NAMElocal GUMDROP_AMOUNT_TO_ADD = 1local function updatePlayerGumdrops(player, updateFunction)-- Update the gumdrop tablelocal newGumdropAmount = PlayerData.updateValue(player, GUMDROP_KEY_NAME, updateFunction)-- Update the gumdrop leaderboardLeaderboard.setStat(player, GUMDROP_KEY_NAME, newGumdropAmount)-- Check if the player has collected three gumdropsif newGumdropAmount >= 3 then-- Play the door event audio when the player collects three gumdropslocal audioPlayer = Workspace.Door.AudioPlayeraudioPlayer:Play()-- Animate the door to move downwardlocal doorPart = Workspace.Doorlocal tweenInfo = TweenInfo.new(2, Enum.EasingStyle.Linear)local tween = TweenService:Create(doorPart, tweenInfo, {Position = doorPart.Position + Vector3.new(0, -15, 0)})tween:Play()endend-- Defining the event handlerlocal function onGumdropTouched(otherPart, gumdrop)if gumdrop:GetAttribute("Active") thenlocal character = otherPart.Parentlocal player = Players:GetPlayerFromCharacter(character)if player then-- Player touched a gumdroplocal audioPlayer = gumdrop.AudioPlayeraudioPlayer:Play()gumdrop.Transparency = 1gumdrop:SetAttribute("Active", false)updatePlayerGumdrops(player, function(oldGumdropAmount)oldGumdropAmount = oldGumdropAmount or 0return oldGumdropAmount + GUMDROP_AMOUNT_TO_ADDend)endendend-- Setting up event listenersfor _, gumdrop in gumdrops dogumdrop:SetAttribute("Active", true)gumdrop.Touched:Connect(function(otherPart)onGumdropTouched(otherPart, gumdrop)end)endCode explanationThis script starts by initializing the Workspace, Players, ServerStorage, and Class.TweenService services so it can reference their children and functionality. Then, it requires the Leaderboard and PlayerData modules in ServerStorage; these modules are responsible for creating and updating a leaderboard in the upper-right corner of the screen that tracks the amount of gumballs a player collects in the environment.
The script's updatePlayerGumdrops function is largely in charge of both updating the leaderboard each time a player collects a gumdrop, and waiting for the moment that they collect all three gumdrops so that it can trigger a positional sound and animation for the gingerbread house's door. For more information on this part of the script, see Add 3D audio - Event feedback.
The script's onGumdropTouched function is where the bulk of the work occurs for triggering 2D audio for gameplay feedback, and it takes two arguments:
- otherPart - A part that collides with the gumdrop.
- gumdrop - A spherical part that represents a gumdrop.
When anything collides with the gumdrop part, the script checks first to see if the gumdrop is active. If so, it checks to verify that the colliding object is a player. If so, the script then:
- Plays the 2D audio track from the audio player.
- Turns the gumdrop invisible.
- Deactivates the gumdrop so that players cannot interact with the invisible gumdrop.
- Updates the leaderboard for the player that touched the gumdrop.
Finally, the script sets up event listeners so that for each gumdrop, it connects a function to the Touched event, which calls the onGumdropTouched function when a player touches the gumdrop.
Playtest the experience to hear the chomping sound after you collect a gumdrop.
UI interaction
When players interact with your user interface (UI) elements, such as hovering over a checkbox or selecting a purchase button, it's important to provide instant feedback so that they intuitively understand how they're interacting with your UI. For example, many game designers change the visual characteristics of UI when players enable or disable settings to communicate the status of the setting.
However, for players with visual impairments in which color changes or animations are more difficult to decipher on their own, it's useful to provide multiple forms of sensory feedback for your UI interactions so that they remain accessible and intuitive for as many players as possible. To expand on this concept, let's consider the following UI states that are important for the UGC Homestore template's purchase buttons:
- In focus - Indicates that the player is highlighting a UI element in preparation to select it.
- On hover - Indicates that the player is hovering over a UI element with their cursor.
- Selected - Indicates that the player has selected a UI element through their input.
All three of these interactions are essential for the template's main user flow of players browsing, selecting, and purchasing avatar clothing and accessories, so it's of the utmost importance that no one is confused about how to interact with the UI. To set players up for success, the template provides both visual and auditory feedback for each UI interaction.




To give an example of how you can configure multiple forms of sensory feedback, the sample provides both visual and auditory feedback whenever players press the on-screen peppermint button. When players aren't interacting with the button, it appears like a typical peppermint candy, but when they press the button, the sample:
- Plays a delightful jingle audio track.
- Tints the button with a teal hue.
- Moves the button slightly downward on the screen.
From here, you can connect this interaction to all sorts of useful gameplay actions, such as opening a menu or purchasing an item.


To recreate the one shot UI interaction audio in the sample Gingerbread House - Complete Audio place file:
In the Explorer window, navigate to StarterGui > 2DAudioButton, then:
- Insert an AudioPlayer object to create an audio source.
- Insert an AudioDeviceOutput object to create a speaker that plays throughout the experience.
- Insert a Wire object to carry the stream from the audio player to the speaker.
Select the AudioPlayer, then in the Properties window, set AssetID to rbxassetid://3422389728 to play a retro jingle audio track.
Select the Wire, then in the Properties window,
- Set SourceInstance to your new AudioPlayer to specify that you want the wire to carry audio from this specific audio player.
- Set TargetInstance to your new AudioDeviceOutput to specify that you want the wire to carry audio to this specific speaker.
Back in the Explorer window, insert a LocalScript into 2DAudioButton, rename it PlayAudioWhenPressed, then paste the following code into the script:
local TweenService = game:GetService("TweenService")local buttonGui = script.Parentlocal buttonImageButton = buttonGui.ButtonFrame.ButtonImageButtonlocal buttonAudioPlayer = buttonGui.AudioPlayerlocal tweenInfo = TweenInfo.new(.2, Enum.EasingStyle.Exponential)local buttonTweenByIsPressed = {-- Pressed[true] = TweenService:Create(buttonImageButton, tweenInfo, {Position = buttonImageButton.Position + UDim2.fromScale(0, .1),ImageColor3 = Color3.fromRGB(117, 255, 255),}),-- Default[false] = TweenService:Create(buttonImageButton, tweenInfo, {Position = buttonImageButton.Position,ImageColor3 = Color3.fromRGB(255, 255, 255),}),}local function onIsPlayingChanged()local isPlaying = buttonAudioPlayer.IsPlayinglocal tween = buttonTweenByIsPressed[isPlaying]tween:Play()endonIsPlayingChanged()buttonAudioPlayer:GetPropertyChangedSignal("IsPlaying"):Connect(onIsPlayingChanged)buttonAudioPlayer.Ended:Connect(onIsPlayingChanged)buttonImageButton.Activated:Connect(function(_hit)buttonAudioPlayer:Play()end)Code explanationThe script starts by getting:
- The TweenService so it can animate UI elements.
- The script's parent 2DAudioButton ScreenGui object.
- The peppermint candy ImageButton.
- The relevant audio player with your jingle audio track.
The script then defines:
- A TweenInfo object that specifies that the button's animation will play with an exponential animation style.
- Two tweens that represent the button's pressed or unpressed state.
- The true pressed state moves the button slightly downward on the screen and tints it with a teal hue.
- The false unpressed state moves the button back to its original position on the screen and removes the previous tint.
The remainder of the script is where the bulk of the work occurs for triggering 2D UI interaction feedback, so let's review how the onIsPlayingChanged function and event listeners work together for the UI user flow:
- buttonImageButton.Activated listens for a player to click the button, then calls the Play() function to start playing the associated audio from the audio player. This process switches the AudioPlayer.IsPlaying property from false to true.
- buttonAudioPlayer:GetPropertyChangedSignal("IsPlaying") listens for the audio player's IsPlaying property to change, then calls the onIsPlayingChanged function.
- The onIsPlayingChanged function uses this information to trigger the tween that changes its visual appearance on the screen.
- To prevent the player from accidentally restarting the audio if they were to click the button in rapid succession, buttonAudioPlayer.Ended listens for the audio player to finish playing before calling the onIsPlayingChanged function again.
It's important to note that the onIsPlayingChanged event only fires when it changes from false to true, meaning that it doesn't fire when changing from true to false. This is intended behavior due to complications with timing of replicating properties from server to client. Because of this, the Ended event is provided and listened to in this example to cover both cases.
Playtest the experience to hear the jingle every time you press the on-screen button.