Defold does not ship with a dialogue system, a typewriter effect, or a voice blip feature. This is not an oversight; the engine is deliberately minimal, and the dialogue layer is something the developer builds from a small set of primitives. The upside is that a voice blip system in Defold can be built without fighting the engine. The downside is that every piece of the system has to be assembled deliberately, and the pieces that are easy to get wrong are the ones that determine whether the blips sound like a voice or like a broken printer.
This article covers the complete system: the sound component setup, the typewriter library integration, the message-passing architecture that decouples the dialogue logic from the audio logic, and the specific engine constraints — voice buffers, gating, and stream caching — that cause blips to break in ways that are hard to diagnose. The procedural blip generation guide covers the alternative approach of generating the blips at runtime; this article is about playing pre-rendered blip files through Defold's sound system.
The dialogue design itself — which characters speak, what text appears, how the player advances — is covered in the typewriter blip guide and the visual novel blip guide. What follows is the Defold implementation.
How do you set up the blip sound component in Defold?
Defold's sound system is built around the
Sound component, which is attached to a game
object and plays a single audio file. The component has a
small set of properties that determine how the file plays,
and three of them matter for voice blips[reference:0].
| Property | Setting for a blip | Why |
|---|---|---|
| Sound | The blip WAV or Ogg file | The engine supports Wave, Ogg Vorbis, and Ogg Opus at 16-bit depth |
| Looping | Off | A blip plays once per character; looping would smear the text reveal |
| Group | A custom “ui_blip” group | Allows independent volume control from music and sound effects |
| Gain | 0.5 to 0.8 | Blips should sit below dialogue and music, not compete with them |
| Pan | 0 (center) | Dialogue blips are not spatialized |
| Speed | 1.0 | Pitch variation is applied per-playback, not on the component |
The Group property is the one most projects
skip and later regret. Defold's sound groups allow a
single function call to change the gain of every sound
in the group, which is what makes it possible to duck the
blips under voice acting or to provide a separate volume
slider for UI audio[reference:1]. The group is assigned on
the component, and the gain is adjusted at runtime with
sound.set_group_gain().
The blip file itself should be short: 30 to 60 milliseconds, in the 800 to 1400 Hz range, with a sine or triangle waveform. The square-wave synthesis guide covers the parameter ranges; the Defold-specific part is that the file format matters. WAV at 44.1 kHz and 16-bit is the safest choice because it is the engine's native format and decodes without the CPU cost of a compressed stream[reference:2].
How does the typewriter library drive the blip timing?
The typewriter effect in Defold is provided by a
community library called
library-defold-typewriter, which handles the
character-by-character reveal and exposes a hook for
playing a sound on each character[reference:3]. The
alternative is defold-printer, which is
designed specifically for rich dialogue and has a
play_sound function that the developer
overrides[reference:4].
The typewriter library does not play sounds itself. It reveals characters and calls a function that the developer provides. The function is where the blip logic lives, and the logic has three responsibilities: filter non-alphanumeric characters, throttle the blip rate, and play the blip with a small random variation.
-- In the GUI script that owns the typewriter
local dtypewriter = require("dtypewriter.dtypewriter")
local function play_blip(self)
-- Throttle: enforce a minimum gap between blips
local now = os.clock()
if now - self.last_blip_time < 0.06 then
return
end
self.last_blip_time = now
-- Random pitch variation: ±3%
local speed = 0.97 + math.random() * 0.06
sound.play("blip#sound", {
gain = 0.6,
speed = speed
})
end
function init(self)
dtypewriter.init(self, "dialogue_text", "fonts/main_font")
self.last_blip_time = 0
end
function update(self, dt)
-- dtypewriter calls the callback per character
dtypewriter.update(self, dt, play_blip)
end
The speed parameter is Defold's playback-rate
control. A value of 1.0 is normal speed, 0.5 is half
speed, and 2.0 is double speed[reference:5]. For a blip,
the speed change affects both pitch and duration, which is
the same behavior as the tape-machine effect described in
the
variation system guide.
A range of 0.97 to 1.03 is enough to break repetition
without making the blip sound like a different sound.
The 0.06 second throttle is the maximum blip
rate. At the default text speed of 30 characters per
second, the typewriter fires a character every 33
milliseconds, which is faster than the throttle allows.
The result is a blip every 60 milliseconds, or about 16
blips per second, which is at the upper end of the range
the ear can distinguish as separate events. A throttle of
0.08 produces about 12 blips per second,
which is more comfortable for long dialogue sessions.
Why should the blip logic live in a separate game object?
Defold's message-passing architecture makes it natural to separate the dialogue logic from the audio logic. The GUI script that owns the typewriter sends a message to a separate “audio” game object, and that object owns the sound component and the playback logic[reference:6].
-- In the GUI script (dialogue logic)
local function on_character_revealed(char)
if not char:match("%w") then return end
msg.post("audio#blip", "play_blip")
end
-- In the audio game object's script
function on_message(self, message_id, message, sender)
if message_id == hash("play_blip") then
local speed = 0.97 + math.random() * 0.06
sound.play("#sound", { gain = 0.6, speed = speed })
end
end
The separation has three advantages. The audio object can be reused for other UI blips (menu clicks, hover feedback) without duplicating the playback logic. The dialogue script does not need to know about sound groups or gain settings. And the audio object can be moved to a different collection or excluded from a build without touching the dialogue code.
The message-passing pattern is the same one that the
Defold documentation uses for its own examples, where one
script sends a message and another script responds to it
in on_message[reference:7]. For a dialogue
system, the messages are frequent (one per character), so
the payload should be minimal. The example above sends
only the message ID and no data, because the audio object
does not need to know which character was revealed. A
project that wants per-character voices can include the
character name in the message data.
What are the engine constraints that cause blips to break?
Defold's sound system has two constraints that are not obvious from the API and that cause blips to fail in ways that are hard to trace. The first is the voice buffer limit. The second is the stream cache.
The voice buffer limit. Defold's sound
system has a fixed number of voices, and every
sound.play() call consumes one. The
documentation states that “each message sent to a
component will cause it to play another instance of the
sound, until the available sound buffer is full and the
engine will print errors in the console”[reference:8].
A typewriter that reveals 30 characters per second and
fires a blip on every character will exhaust the voice
buffer within a few seconds if the blips are longer than
the gap between them.
The fix is the throttle described earlier. A blip that plays once every 60 milliseconds uses a new voice every 60 milliseconds, and the voice is released when the blip finishes. A 50-millisecond blip never has more than one voice active at a time. Without the throttle, a 40-millisecond blip at 30 characters per second would have two voices active simultaneously, and the voice count would grow as the text reveals.
The stream cache. Defold streams sound
files rather than loading them entirely into memory. The
amount of memory the sound system uses is controlled by
the sound.stream_cache_size setting in
game.project[reference:9]. A project with
many small blip files can exhaust the cache if the cache
is set too small, and the engine will evict the least
recently used chunks, which can cause a blip to fail to
play or to play with a delay.
The practical fix is to set the stream cache to a size that comfortably holds every sound the game needs simultaneously, and to keep the blip files small. A 30-millisecond WAV at 44.1 kHz and 16-bit is about 2.6 KB, so a set of 20 blips is about 52 KB, which is well within any reasonable cache size. The file size reduction guide covers the file format tradeoffs; for Defold, WAV is the right choice for short blips because the decode cost is lower than a compressed format and the file size is small enough that compression is not necessary.
How do you handle the voice-acting conflict?
A dialogue-driven game often has voice acting for the same lines that have text blips. The two sounds serve the same function, and playing both at once produces a muddled result where the voice competes with the blips for the player's attention.
The standard fix is to disable the blips when the line is voiced. The dialogue system knows whether a line has a voice file, and the GUI script can set a flag before calling the typewriter:
-- In the dialogue script
self.is_voiced = line.has_voice
-- In the character callback
local function on_character_revealed(char)
if self.is_voiced then return end
-- ... blip logic
end
The alternative is to keep the blips at a reduced volume during voiced lines, using the sound group's gain to duck the blips when the voice channel is active. Defold does not have automatic sidechain compression, so the ducking has to be scripted: a function that checks whether the voice is playing and adjusts the blip group's gain accordingly. The ducking guide covers the general principle; in Defold, the implementation is a manual gain adjustment rather than an automatic sidechain.
The choice between disabling and ducking depends on the game's aesthetic. A visual novel with full voice acting usually disables the blips, because the voice is the primary audio. A game with partial voice acting (grunts and exclamations rather than full lines) keeps the blips at full volume and lets the two sounds coexist.
What does a complete blip system look like in a Defold project?
A complete system has four parts: a sound component on a dedicated audio game object, a typewriter library that reveals characters and calls a callback, a playback function that throttles and varies the blips, and a message that connects the dialogue script to the audio script.
| Component | Where it lives | Responsibility |
|---|---|---|
| Sound component | “audio” game object | Plays the blip file, assigned to a “ui_blip” group |
| Audio script | “audio” game object | Receives play_blip messages and calls sound.play() |
| Typewriter library | GUI script | Reveals characters and calls the per-character callback |
| Dialogue script | GUI script | Filters non-alphanumeric characters, throttles, sends the message |
The four parts are independent. The sound component can be reused for other UI sounds. The audio script can be extended to handle other messages (menu clicks, hover feedback) without changing the dialogue script. The typewriter library can be swapped for a different implementation without changing the audio logic. The message-passing pattern is what makes the independence possible, and it is the part that most tutorials on dialogue systems skip.
The typewriter library is the only external dependency, and it is optional. A project that does not want a dependency can implement the character reveal with a timer and a string counter, using the same callback pattern. The Web Audio blip tutorial covers the character-by-character approach in a different engine; the Defold version is simpler because the sound component and the message system do most of the work.
Voice blips in Defold are not a built-in feature, and they are not a single library. They are a pattern: a sound component in a dedicated audio object, a callback from the typewriter, a throttled playback function, and a message that connects the two. The pattern is small enough to implement in an afternoon, and it scales from a single speaking character to a full cast with per-character blip sets. The engine constraints — voice buffers and stream cache — are the parts that require attention, and the throttle is the single parameter that prevents both of them from becoming a problem.
Generate the Blip Files for the Defold System
Open the SfxMaker blip generator and create the blip set for the project's speaking characters, using the 800–1400 Hz range and a 30–50 ms duration. Export the files as 16-bit 44.1 kHz WAV, which is the format Defold decodes without the CPU cost of a compressed stream.
Open Blip Sound Generator →