Script Structure
This guide covers the recommended structure for ScheduleLua scripts, explaining the key components and organization patterns to help you write clean, maintainable scripts.
Basic Script Structure
A well-structured ScheduleLua script typically includes these key sections:
- Header comment block
- Global variables
- Lifecycle hook functions
- Helper functions
- Custom event handlers
Here's a skeleton template that you can use as a starting point:
--[[
ScriptName.lua
Description: Brief description of what this script does
Author: Your Name
Version: 1.0
]]
-- Global variables and state
local someState = nil
local someOtherState = false
local lastUpdate = 0
-- Initialize function - called when script is loaded
function Initialize()
Log("Script initializing...")
-- Setup code here
end
-- Update function - called every frame
function Update()
-- Performance optimization: Only run expensive operations every 10 frames
if Time.frameCount - lastUpdate < 10 then
return
end
lastUpdate = Time.frameCount
-- Main update logic here
end
-- Console ready hook
function OnConsoleReady()
-- Register commands
RegisterCommand("mycommand", "Description", "usage", CommandHandler)
end
-- Player ready hook
function OnPlayerReady()
-- Player initialization
someState = GetPlayerPosition()
Log("Player ready at position: " .. someState.x .. ", " .. someState.y .. ", " .. someState.z)
end
-- Other lifecycle hooks
function OnSceneLoaded(sceneName)
-- Scene-specific logic
end
function OnDayChanged(day)
-- Day change logic
end
-- Command handlers
function CommandHandler(args)
-- Handle command logic
Log("Command executed with args: " .. table.concat(args, ", "))
end
-- Custom helper functions
function CalculateSomething(value1, value2)
return value1 * value2
end
-- Custom event handlers
function OnCustomEvent()
-- Handle custom events
end
-- Shutdown function - called when script is unloaded
function Shutdown()
-- Cleanup code
UnregisterCommand("mycommand")
Log("Script shutdown complete")
end
Organizing State Management
For complex scripts, it's often helpful to group related state variables into tables:
-- Player state tracking
local playerState = {
position = nil,
health = 100,
energy = 100,
region = nil,
inventory = {}
}
-- NPC tracking
local npcState = {
tracked = {},
lastInteraction = nil
}
-- Game state tracking
local gameState = {
time = 0,
day = 1,
isNight = false
}
This approach makes it easier to pass state around and keep related data organized.
Handling Commands
When registering multiple commands, it's best to group them together in the OnConsoleReady
function:
function OnConsoleReady()
-- Player information commands
RegisterCommand("pos", "Shows player position", "pos", CommandPosition)
RegisterCommand("health", "Shows player health", "health", CommandHealth)
-- World information commands
RegisterCommand("time", "Shows current game time", "time", CommandTime)
RegisterCommand("npcs", "Shows nearby NPCs", "npcs [radius]", CommandNPCs)
-- Utility commands
RegisterCommand("help", "Shows available commands", "help", CommandHelp)
end
function CommandPosition(args)
local pos = GetPlayerPosition()
Log("Position: " .. pos.x .. ", " .. pos.y .. ", " .. pos.z)
end
function CommandHealth(args)
Log("Health: " .. GetPlayerHealth())
end
-- Other command handlers...
Managing Complex Update Logic
For scripts with complex update logic, split the functionality into separate functions:
function Update()
-- Only run expensive checks every few frames
if Time.frameCount % 5 == 0 then
CheckPlayerStatus()
end
-- Run less frequently
if Time.frameCount % 30 == 0 then
CheckWorldState()
end
-- Always run
HandleImportantEvents()
end
function CheckPlayerStatus()
-- Player status checking logic
end
function CheckWorldState()
-- World state checking logic
end
function HandleImportantEvents()
-- Critical event handling
end
Conditional Feature Enablement
For scripts with multiple features that can be enabled/disabled:
-- Configuration
local config = {
enablePlayerTracking = true,
enableNPCTracking = true,
enableEconomyFeatures = false,
logLevel = "normal" -- "minimal", "normal", "verbose"
}
function Initialize()
Log("Initializing with configuration:")
Log("- Player Tracking: " .. (config.enablePlayerTracking and "Enabled" or "Disabled"))
Log("- NPC Tracking: " .. (config.enableNPCTracking and "Enabled" or "Disabled"))
Log("- Economy Features: " .. (config.enableEconomyFeatures and "Enabled" or "Disabled"))
if config.enablePlayerTracking then
InitPlayerTracking()
end
if config.enableNPCTracking then
InitNPCTracking()
end
return true
end
function Update()
if config.enablePlayerTracking then
UpdatePlayerTracking()
end
if config.enableNPCTracking then
UpdateNPCTracking()
end
end
Error Handling
Proper error handling is important for script stability:
function TryGetNPC(npcName)
if not npcName or npcName == "" then
LogError("Invalid NPC name")
return nil
end
local npc = FindNPC(npcName)
if not npc then
LogWarning("Could not find NPC: " .. npcName)
return nil
end
return npc
end
-- Usage with error handling
function InteractWithNPC(npcName)
local npc = TryGetNPC(npcName)
if not npc then
return false
end
-- Safe to proceed with NPC interaction
Log("Interacting with NPC: " .. npc.fullName)
return true
end
Script Performance Optimization
For optimal performance, particularly in the Update
function:
- Throttle operations - Only perform expensive operations every X frames
- Cache results - Store and reuse values that don't change frequently
- Check conditions early - Return early if conditions aren't met
- Use efficient data structures - Choose appropriate tables and access patterns
- Minimize string operations - String concatenation can be expensive when done frequently
-- Example of performance optimization
local lastCheck = 0
local cachedResult = nil
function ExpensiveOperation()
-- Only run every 30 frames
if Time.frameCount - lastCheck < 30 then
return cachedResult
end
lastCheck = Time.frameCount
-- Perform expensive calculation
cachedResult = CalculateResult()
return cachedResult
end
Best Practices
- Keep scripts focused - Each script should have a clear, specific purpose
- Use comments - Document complex logic and the "why" behind your code
- Use local variables - Avoid globals to prevent conflicts with other scripts
- Follow naming conventions:
- Use camelCase for variables and functions
- Use PascalCase for lifecycle hooks
- Use descriptive names that indicate purpose
- Structure for readability - Group related functions together
- Test thoroughly - Test scripts under various game conditions
- Clean up resources - Always implement
Shutdown()
to clean up
By following these structural patterns and best practices, you'll create ScheduleLua scripts that are easier to maintain, debug, and extend.