Common Gameplay Patterns
These recipes combine general-purpose Components and Lua APIs. They are not genre modules: adapt the data, names, and rules to the game you are building.
Trigger Pickup With Persistent State
Author a visible GameObject with a trigger Collider and Lua Behaviour. Give the
player a stable Player tag.
pickupKey = "pickup.keycard"
function Start(gameObjectId)
if PlayerPrefs.GetInt(pickupKey, 0) == 1 then
GameObject.SetActive(gameObjectId, false)
end
end
function OnTriggerEnter(gameObjectId, otherGameObjectId)
if not GameObject.CompareTag(otherGameObjectId, "Player") then
return
end
local previous = PlayerPrefs.GetInt(pickupKey, 0)
if not PlayerPrefs.SetInt(pickupKey, 1) or not PlayerPrefs.Save() then
local reason = SaveData.GetLastError()
PlayerPrefs.SetInt(pickupKey, previous)
Log.Print("Pickup save failed: " .. reason)
return
end
AudioSource.PlayOneShot(gameObjectId)
GameObject.SetActive(gameObjectId, false)
end
This recipe leaves the pickup available after a save failure. Leave and re-enter its trigger to retry. Give it an Audio Source for the one-shot effect.
Raycast Interaction
Use Physics.Raycast when the project needs a custom interaction flow instead
of the built-in Interaction Ray and Interactable pairing.
cameraGameObjectId = -1
interactionDistance = 2.25
function TryInteract(gameObjectId)
local origin = Transform.GetPosition(cameraGameObjectId)
local rotation = Transform.GetRotation(cameraGameObjectId)
if origin == nil or rotation == nil then return end
local yaw = math.rad(rotation.y)
local pitch = math.rad(rotation.x)
local dx = math.sin(yaw) * math.cos(pitch)
local dy = -math.sin(pitch)
local dz = math.cos(yaw) * math.cos(pitch)
local hit, targetGameObjectId = Physics.Raycast(
origin.x, origin.y, origin.z,
dx, dy, dz,
interactionDistance,
nil,
false
)
if hit then
GameObject.Invoke(targetGameObjectId, "Interact", gameObjectId)
end
end
For most first-person projects, the native Components are simpler and provide prompt range, line-of-sight, input binding, and cooldown without script polling.
Locked Gate
Separate persistent state from presentation. The same function can drive a Collider, Portal, and transform animation.
requiredItemKey = "pickup.keycard"
portalGameObjectId = -1
function Interact(gameObjectId, sourceGameObjectId)
if PlayerPrefs.GetInt(requiredItemKey, 0) ~= 1 then
return
end
Collider.SetIsTrigger(gameObjectId, true)
Portal.SetOpen(portalGameObjectId, true)
TransformAnimator.Play(gameObjectId, "Open")
Interactable.SetEnabled(gameObjectId, false)
end
Canvas Menu
Canvas visibility and controller focus are separate responsibilities. Always select a valid Button after opening a menu.
menuCanvasName = "Pause Menu"
resumeButtonName = "Resume Button"
local menuCanvasId = -1
local resumeButtonId = -1
function Start(gameObjectId)
menuCanvasId = Canvas.Find(menuCanvasName)
resumeButtonId = UI.Find(resumeButtonName)
end
function SetMenuOpen(open)
if Canvas.SetEnabled(menuCanvasId, open) and open then
Selectable.Select(resumeButtonId)
end
end
UI Button callbacks receive the target GameObject ID, element ID, element name,
and "click". Configure the exact custom function name in the Button Inspector.
Scene Transition Guard
Prevent repeated input or Button submission from queuing the transition again:
targetScene = "Scenes/hub.json"
local loading = false
function OpenScene(gameObjectId)
if loading then return end
loading = true
AudioSource.Play(gameObjectId)
SceneManager.LoadScene(targetScene)
end
The target scene must be enabled in Build Settings.
Typed Settings
function ApplyAudioSettings(gameObjectId)
Music.SetVolume(PlayerPrefs.GetFloat("musicVolume", 0.8))
end
function SetMusicVolume(gameObjectId, value)
if PlayerPrefs.SetFloat("musicVolume", value) then
if not PlayerPrefs.Save() then
Log.Print("Volume preference not persisted: " .. SaveData.GetLastError())
end
Music.SetVolume(value)
end
end
Use raw SaveData only when string-level storage is intentional. PlayerPrefs
and SaveData share the same fixed Memory Card store.
Debugging Checklist
- confirm every GameObject owns the Component required by the API;
- cache and validate GameObject, Canvas, and UI IDs;
- match custom callback names exactly;
- include every
SceneManager.LoadScenetarget in Build Settings; - handle failed audio, navigation, persistence, and controller capabilities;
- run Validate Project, PS1 Budget, Production Acceptance, and the packaged CUE.