Hi!! Hello!!
I made a video tutorial on how to create the typewriter effect, where text is displayed letter by letter, like it’s being typed in real time. This effect is perfect for dialogue systems, narration, or adding some cinematic flair to your UI.
The video is split into two parts:
- Basic Example – a simple demonstration showing how to create the effect step by step.
- Practical Use – applying the effect in my dialogue system from my earlier forum post.
How the effect works
Here’s a quick summary of the approach:
- Inside a
StartTypeWriter
function, I create a loop that displays the text one letter at a time. - A coroutine handles the delay between each character, creating that smooth “typing” animation.
Example script
local fieldDefs = {
{
name = "UIField",
label = "UI Field",
hint = "UI Field",
description = "UI Field",
type = "UIPackageFile"
},
{
name = "NarratorText",
type = "string",
},
{
name = "TypeSpeed",
type = "float",
},
{
name = "Duration",
type = "float",
}
}
script.DefineFields(fieldDefs)
`-- Get the imported UI package field
local UIField = script.fields.UIField
local narratorText = script.fields.NarratorText
local typeSpeed = script.fields.TypeSpeed
local duration = script.fields.Duration
-- Access the YaResourceManager API
local YaResourceManager = YahahaMiddleLayerSlim.Resource.YaResourceManager
local mainPanel
local packageName
local audioPlayer
local co = require("Utils.CoroutineHelper")
local NarratorTextUI
script.OnStart(function ()
LoadResource()
audioPlayer = script.GetLuaComponent("com.yahaha.sdk.audio.components.AudioPlayer")
end)
function LoadResource()
YaResourceManager.LoadResourceByUIPackageField(UIField, function(state, name)
if state == AssetStatus.AllAssetCompleted then
packageName = name
-- Create the main panel
CreateMainPanel()
end
end)
end
function CreateMainPanel()
mainPanel = UIPackage.CreateObject(packageName, "Narrator")
GRoot.inst:SetContentScaleFactor(2436, 1125)
GRoot.inst:AddChild(mainPanel)
mainPanel.size = GRoot.inst.size
mainPanel:AddRelation(GRoot.inst, RelationType.Size)
NarratorTextUI = mainPanel:GetChild("Text")
StartTypeWritter()
end
function StartTypeWriter()
NarratorTextUI.text = ""
co.async(function()
for i = 1, #narratorText do
NarratorTextUI.text = string.sub(narratorText, 1, i)
if audioPlayer then
audioPlayer:Play()
end
co.wait(typeSpeed)
end
co.wait(duration)
mainPanel:Dispose() -- Dispose the panel after the duration
end)
end
script.OnDispose(function ()
-- Destroy the panel
mainPanel:Dispose()
-- Unload the UIField resource
YaResourceManager.RemoveResourceByUIPackageField(UIField)
end)`
Dialog Script
local fieldDefs = {
{
name = "UIField",
label = "UI Field",
hint = "UI Field",
description = "UI Field",
type = "UIPackageFile"
},
{
name = "EndTrigger",
type = "GameObject"
},
{
name = "CharacterName",
type = "string"
},
{
name = "typeSpeed",
type = "float",
default = 0.03,
},
{
name = "DialogList",
type = {
type = "list",
items = {
name = "Dialog",
type = "string",
}
}
}
}
script.DefineFields(fieldDefs)
local UIField = script.fields.UIField
local EndTrigger = script.fields.EndTrigger
local CharacterName = script.fields.CharacterName
local DialogList = script.fields.DialogList
local YaResourceManager = YahahaMiddleLayerSlim.Resource.YaResourceManager
local mainPanel
local packageName
local Self = script.gameObject
local dialogText
local characterNameText
local currentDialog = 1
local audioPlayer
local typing = false
local typeSpeed = script.fields.typeSpeed -- seconds between characters
local fullText = ""
local co = require("Utils.CoroutineHelper")
script.OnStart(function ()
LoadResource()
audioPlayer = script.GetLuaComponent("com.yahaha.sdk.audio.components.AudioPlayer")
end)
-- Function to load resources
function LoadResource()
YaResourceManager.LoadResourceByUIPackageField(UIField, function(state, name)
if state == AssetStatus.AllAssetCompleted then
packageName = name
CreateMainPanel()
end
end)
end
function CreateMainPanel()
mainPanel = UIPackage.CreateObject(packageName, "DialogBox")
GRoot.inst:SetContentScaleFactor(2436, 1125)
GRoot.inst:AddChild(mainPanel)
mainPanel.size = GRoot.inst.size
mainPanel:AddRelation(GRoot.inst, RelationType.Size)
dialogText = mainPanel:GetChild("DialogText")
characterNameText = mainPanel:GetChild("CharacterNameText")
characterNameText.text = CharacterName
StartTypewriter(DialogList[currentDialog])
end
function StartTypewriter(text)
typing = true
fullText = text
dialogText.text = ""
co.async(function()
for i = 1, #text do
if not typing then
return -- Cancel typing
end
if audioPlayer then
audioPlayer:Play()
end
dialogText.text = string.sub(text, 1, i)
co.wait(typeSpeed)
end
typing = false
end)
end
local Input = UnityEngine.Input
local KeyCode = UnityEngine.KeyCode
script.OnUpdate(function ()
if mainPanel == nil then
return -- Prevent interaction until UI is fully loaded
end
if Input.GetKeyDown(KeyCode.E) then
if typing then
typing = false -- Stop the async loop
dialogText.text = fullText -- Show full line
else
currentDialog = currentDialog + 1
if currentDialog > #DialogList then
HandleEndDialog()
else
StartTypewriter(DialogList[currentDialog])
end
end
end
end)
function HandleEndDialog()
mainPanel:Dispose()
Self:SetActive(false)
YaResourceManager.RemoveResourceByUIPackageField(UIField)
EndTrigger:SetActive(true)
end