Script Lifecycle
Scripts in DDDBrowser follow a well-defined lifecycle. Understanding this lifecycle is essential for writing effective scripts.
Lifecycle Overview
The script lifecycle consists of these stages:
- Load - Script file is loaded and compiled
- Initialize - Script instance is created
- on_start - Called once for initialization
- on_update - Called every frame (if defined)
- on_interact - Called when player interacts (entity scripts)
- on_save - Called when scene state is saved (optional)
- on_load - Called when scene state is loaded (optional)
- on_shutdown - Called when script is unloaded
Lifecycle Methods
on_start()
Called once when the script is first initialized.
function MyScript:on_start()
-- Initialize variables
self.counter = 0
self.position = {x = 0, y = 0, z = 0}
-- Load initial data if provided
if self.data then
self.speed = self.data.speed or 1.0
end
end
When: Once, immediately after script is attached Parameters: None Use for:
- Initializing variables
- Setting up initial state
- Reading script data
- One-time setup
Example:
local Entity = {}
function Entity:on_start()
self.interactionCount = 0
self.lastInteraction = nil
-- Access entity information
print("Entity " .. tostring(self.entity) .. " started")
print("Asset ID: " .. tostring(self.asset_id))
end
return Entity
on_update(dt)
Called every frame while the scene is active.
function MyScript:on_update(dt)
-- Update logic here
self.time = self.time + dt
self.counter = self.counter + 1
end
When: Every frame (typically 60 times per second) Parameters:
dt(number): Delta time in seconds since last frame Use for:- Continuous updates
- Animations
- Timers
- State changes over time
Important:
- Use
dtfor frame-rate independent updates - Keep updates fast (avoid expensive operations)
- Scripts that take too long may be throttled
Example:
local Animated = {}
function Animated:on_start()
self.time = 0
self.amplitude = 5.0
self.speed = 2.0
end
function Animated:on_update(dt)
self.time = self.time + dt
local offset = math.sin(self.time * self.speed) * self.amplitude
if Engine and Engine.setEntityPosition then
Engine.setEntityPosition(self.entity, 0, offset, 0)
end
end
return Animated
on_interact(actorId)
Called when the player interacts with the entity (entity scripts only).
function MyScript:on_interact(actorId)
-- Handle interaction
self.interactionCount = self.interactionCount + 1
print("Interacted by actor " .. tostring(actorId))
end
When: Player presses interact key (E) while looking at the entity Parameters:
actorId(number): ID of the actor interacting (usually 0 for player) Use for:- Interaction handling
- Opening dialogs
- Triggering events
- Activating objects
Example:
local Interactive = {}
function Interactive:on_start()
self.interactionCount = 0
end
function Interactive:on_interact(actorId)
self.interactionCount = self.interactionCount + 1
if Engine and Engine.openTextBox then
Engine.openTextBox(
"interaction-" .. tostring(self.entity),
"Interaction",
"You've interacted " .. self.interactionCount .. " times!",
{"OK"}
)
end
end
return Interactive
on_shutdown()
Called when the script is being unloaded.
function MyScript:on_shutdown()
-- Cleanup code
if self.timer then
-- Stop timers, close connections, etc.
end
end
When: Scene is unloaded or script is detached Parameters: None Use for:
- Cleanup
- Stopping timers
- Releasing resources
- Final state updates
Example:
local Managed = {}
function Managed:on_start()
self.active = true
end
function Managed:on_shutdown()
self.active = false
print("Script shutting down, cleaning up...")
end
return Managed
on_save()
Called when the scene state is being saved. Returns save data.
function MyScript:on_save()
return {
counter = self.counter,
position = self.position,
state = self.state
}
end
When: Scene state is saved (manual or autosave) Parameters: None Returns: Table with save data (or nil to skip saving) Use for:
- Persisting script state
- Saving game progress
- Storing custom data
Example:
local Persistent = {}
function Persistent:on_start()
self.counter = 0
self.lastSave = nil
end
function Persistent:on_update(dt)
self.counter = self.counter + 1
end
function Persistent:on_save()
return {
counter = self.counter,
timestamp = os.time()
}
end
return Persistent
on_load(state)
Called when the scene state is being loaded. Receives previously saved state.
function MyScript:on_load(state)
if state then
self.counter = state.counter or 0
self.position = state.position or {x = 0, y = 0, z = 0}
end
end
When: Scene state is loaded (on scene load or after save) Parameters:
state(table): Previously saved state (or nil if no save data) Use for:- Restoring script state
- Loading game progress
- Resuming from saved state
Example:
local Restorable = {}
function Restorable:on_start()
self.counter = 0
end
function Restorable:on_load(state)
if state and state.counter then
self.counter = state.counter
print("Restored counter: " .. tostring(self.counter))
end
end
function Restorable:on_save()
return {
counter = self.counter
}
end
return Restorable
Gamemode Lifecycle
Gamemode scripts have the same lifecycle methods but different context:
- Scope: Scene-wide (not per-entity)
- State: Shared across all entities
- Events: Can trigger and listen to events
- Persistence: State is saved/loaded automatically
See Gamemode API for details.
Script Data
Scripts can receive initial data from the scene JSON:
"script": {
"file": "my-script",
"data": {
"speed": 2.0,
"amplitude": 10.0
}
}
Access in script:
function MyScript:on_start()
if self.data then
self.speed = self.data.speed or 1.0
self.amplitude = self.data.amplitude or 5.0
end
end
Lifecycle Best Practices
- Initialize in on_start: Set up all variables here
- Use on_update for animations: Frame-by-frame updates
- Handle interactions in on_interact: User input handling
- Save state in on_save: Return data to persist
- Restore state in on_load: Load previously saved data
- Clean up in on_shutdown: Release resources
Next Steps
- Engine API - Access engine functionality
- Gamemode API - Scene-wide scripting
- Examples - See lifecycle in action