Creating a 60-second countdown triggered by door opening
This tutorial is intended for Horror Game Kit users.
Overview
This tutorial guides you through creating a 60-second countdown timer that starts automatically when a door in your scene is opened. The timer will display on the UI and count down to zero, triggering any subsequent game events you desire.
Prerequisites
- Familiarity with Horror Game Kit, especially the Event Trigger component and scripting.
- Prepare a countdown timer UI asset in ZIP or Bytes format. This tutorial uses a Bytes file named
Timer_fui.bytes
and you can click it to download.
Step 1: Set up the scene
- Create a new project using the Start from Scratch template.
- Add the Unlocked Door V1.0 smart asset to your scene to the scene by choosing Packages > Horror Game Assets > Unlocked Door V1.0. This smart asset includes a door model with built-in trigger logic to detect when the door is opened or closed.
- Adjust the door's position and rotation as needed.
Most doors in AI-generated scenes already include this component.
Step 2: Import the timer UI asset
Before scripting the countdown logic, upload the timer UI asset by going to My Resources > Uploaded, and clicking the Upload button to select and upload the UI file from the local device.
Step 3: Configure the trigger event
-
In Scene Explorer, add an empty object from Create > Empty Object to your scene and rename it Game Start Event.
-
Add the Custom Event Notifier component from Add Components > Event Trigger to the empty object. This component will send a custom event when triggered.
-
Add an event trigger to the empty object by choosing Add Event Trigger. Configure it as follows:
- Trigger Object: Set to the door asset.
- Trigger Event: Set to OnOpened.
- Action Object: Set to Self (the Game Start Event object).
- Action Function: Set to NotifyCustomEventNow. This setup ensures that when the door opens, a custom event is fired.
Step 4: Script the countdown timer logic
- Add a new empty object to the scene and rename it Countdown.
- In the object's Properties window, add a new script to the object by choosing Add scripts.
- In the
Countdown.editor.lua
, define the new fields on the Properties window:
local fieldDefs = {
{
name = "triggerEventObject",
type = "GameObject"
},
{
name = "seconds",
type = "integer",
default = 60,
extendedProperties = {
min = 1
}
},
{
name = "UIPackage",
label = "UI Package",
type = "File",
extendedProperties = {
includeTypes = {"bytes"}
}
},
}
script.DefineFields(fieldDefs)
triggerEventObject
: The object that triggers the countdown (the Game Start Event).seconds
: Duration of the countdown (default 60 seconds).UIPackage
: The UI asset package for the timer display.
- In the
Countdown.lua
, implement the runtime logic as follows:
local initialTotalSeconds = script.fields.seconds
local currentTotalSeconds = initialTotalSeconds
local showCountdown = false
local countdownRunning = false
local timerView
local function GetMinutesSeconds(totalSeconds)
local minutes = math.floor(totalSeconds / 60)
local seconds = math.ceil(totalSeconds - minutes * 60)
if seconds == 60 then
seconds = 0
minutes = minutes + 1
end
return minutes, seconds
end
local function ShowTimerUI()
-- Show Timer UI here
-- Mount the component object to the GRoot (so that it's on stage)
if timerView then
GRoot.inst:AddChild(timerView)
timerView.size = GRoot.inst.size
timerView:AddRelation(GRoot.inst, RelationType.Size)
end
end
local function RefreshTimerUI()
local minutes, seconds = GetMinutesSeconds(currentTotalSeconds)
-- Refresh UI content here
local minutesStr
if minutes < 10 then
minutesStr = "0" .. tostring(minutes)
else
minutesStr = tostring(minutes)
end
local secStr
if seconds < 10 then
secStr = "0" .. tostring(seconds)
else
secStr = tostring(seconds)
end
-- If you are not familiar with UI, you can log the time with print function
print("Countdown " .. tostring(minutesStr) .. ":" .. tostring(secStr))
if timerView then
timerView:GetChild("Minutes").text = minutesStr
timerView:GetChild("Seconds").text = secStr
end
end
local function HideTimerUI()
-- Hide Timer UI here
if timerView then
GRoot.inst:RemoveChild(timerView)
end
end
script.OnStart(function ()
-- Load FairyGUI package
YaUIPackage.AddCustomPackage(script.fields.UIPackage, function(path, package, packageItem)
local number = string.match(path, "(%d+).png$")
local index = number and tonumber(number) + 1
if index and script.fields.atlas[index] then
package:SetItemAsset(packageItem, script.fields.atlas[index], FairyGUI.DestroyMethod.None)
end
end)
-- create component
timerView = UIPackage.CreateObject("Timer", "Main")
end)
script.OnUpdate(function ()
if not countdownRunning then
return
end
local deltaTime = UnityEngine.Time.deltaTime
local currentTotalSecondsBefore = currentTotalSeconds
currentTotalSeconds = currentTotalSeconds - deltaTime
if currentTotalSeconds <= 0 then
-- Countdown hit the deadline
showCountdown = false
countdownRunning = false
HideTimerUI()
if script.fields.notifyEventObject then
yahaha.EventSystem:NotifyObjectEvent(script.fields.notifyEventObject, "com.yahaha.sdk.trigger.CustomEvent")
end
return
end
local needRefresh = math.ceil(currentTotalSecondsBefore) ~= math.ceil(currentTotalSeconds)
if needRefresh then
RefreshTimerUI()
end
end)
function StartCountdown()
if showCountdown then
return
end
currentTotalSeconds = initialTotalSeconds
showCountdown = true
countdownRunning = true
ShowTimerUI()
RefreshTimerUI()
end
function PauseCountdown()
if not showCountdown then
return
end
countdownRunning = false
end
function ResumeCountdown()
if not showCountdown then
return
end
countdownRunning = true
end
local eventObject = script.fields.triggerEventObject
local callback = function ()
StartCountdown()
end
if eventObject then
yahaha.EventSystem:RegisterObjectEvent(eventObject, "com.yahaha.sdk.trigger.CustomEvent", callback)
end
script.OnDispose(function()
yahaha.EventSystem:UnregisterObjectEvent(eventObject, "com.yahaha.sdk.trigger.CustomEvent", callback)
if timerView then
GRoot.inst:RemoveChild(timerView)
timerView:Dispose()
end
YaUIPackage.RemovePackage(script.fields.UIPackage)
end)
Key functions in the script:
GetMinutesSeconds
: Converts a total number of seconds into separate minutes and seconds values for display purposes.ShowTimerUI
: Adds the timer UI component to the screen by attaching the pre-createdGComponent
to theGRoot
, making the timer visible.RefreshTimerUI
: Updates the timer UI with the current countdown time. It retrieves twoGTextField
components representing minutes and seconds, then updates their displayed values based on the current time.HideTimerUI
: Removes the timer UI from the screen by detaching theGComponent
from theGRoot
, effectively hiding the timer.- In the callback function registered with
script.OnUpdate
,UnityEngine.Time.deltaTime
is used to obtain the time elapsed since the last frame. This elapsed time is added tocurrentTotalSeconds
, andRefreshTimerUI
is called as needed to update the UI. WhencurrentTotalSeconds
drops below zero, it indicates that the countdown has finished. StartCountdown
: Initiates the countdown timer. The function first checks theshowCountdown
flag; if the countdown is already displayed, it exits early. Otherwise, it initializes currentTotalSeconds, sets bothshowCountdown
andcountdownRunning
totrue
, and calls the UI control functions to display the countdown.PauseCountdown
: Pauses the countdown timer. This function only operates if the countdown is currently running, so it first verifies thatshowCountdown
istrue
.ResumeCountdown
: Resumes the countdown timer. LikePauseCountdown
, it only works if the countdown is active, so it checks thatshowCountdown
istrue
before proceeding.
Step 5: Finalize countdown settings
In the Properties window of the Countdown
object, make sure the scripted fields are filled in as follows:
- Trigger Event Object: the Game Start Event object.
- seconds: the desired countdown length (default 60).
- UIPackage: The uploaded timer UI asset.
Step 6: Playtest
Enter Play Mode and open the door. The countdown timer should appear and count down from 60 seconds to zero, updating every second.
Troubleshooting
- If events do not trigger:
- Check that the Custom Event Notifier is attached to the receiver object.
- Ensure the event name in your script matches the event configured in the Event Trigger.
- If no response or action:
- Verify that the Event Trigger is set up correctly and points to the right action (e.g., play audio).
- Make sure the referenced objects are correctly assigned in your script fields.
- If you encounter script errors or unexpected behavior:
- Use F12 to open the console for debug logs to confirm your script runs as expected.
- Double-check that all objects are active and available in the scene.