Selection operations
Most commands you'll write are some flavor of "do X to whatever's selected." A few patterns reduce the boilerplate.
The skeleton
--!strict
local Types = require(script.Types)
return {
Arguments = { ... },
Run = function(props: Types.Props, arguments: Types.ArgumentResult)
if #props.Selected == 0 then
return props:SendNotification("Select something first")
end
for _, inst in props.Selected do
-- ...do work on inst...
end
end,
}
Three things to internalize:
- Always early-return on empty selection with a notification. Otherwise
Runsilently does nothing and the user has no idea why. - Iterate
props.Selected, notSelection:Get(). The selection can change whileRunis on the call stack.props.Selectedis the snapshot from execute-time and stays stable. - Type-guard before assuming.
props.Selectedis{Instance}. Check with:IsA("BasePart")(or whatever) before touching type-specific properties.
Filtering selection by type
Most commands only care about a subset of what could be selected. Pull the filter out:
local function partsIn(list: {Instance}): {BasePart}
local out: {BasePart} = {}
for _, inst in list do
if inst:IsA("BasePart") then
table.insert(out, inst)
end
end
return out
end
return {
Arguments = { ... },
Run = function(props)
local parts = partsIn(props.Selected)
if #parts == 0 then
return props:SendNotification("Select at least one BasePart")
end
for _, part in parts do
-- ...
end
end,
}
Acting on descendants
If your command should "treat the selection as a tree," walk it with :GetDescendants():
Run = function(props, arguments)
for _, root in props.Selected do
for _, descendant in root:GetDescendants() do
if descendant:IsA("BasePart") then
descendant.Anchored = arguments.Anchor
end
end
end
end,
For huge selections this can be slow. Consider using a paragraph argument with a Lua expression the user can tune, or a boolean "Recursive" toggle.
Replacing the selection after the fact
A common pattern: a "duplicate" command should leave the new copies selected, not the originals. Update the selection at the end of Run:
Run = function(props)
local copies = {}
for _, source in props.Selected do
local copy = source:Clone()
copy.Parent = source.Parent
table.insert(copies, copy)
end
props:SetSelection(copies)
end,
Framing the camera
If the command operates somewhere far from the camera (e.g. you spawned new copies offset by 100 studs), focus on the result so the user can see it:
local copies = {}
-- ...spawn copies...
props:SetSelection(copies)
props:FocusView(copies, 4) -- second arg is extra padding in studs
A complete recipe, "scatter"
A command that scatters cloned copies of the selection randomly within a radius:
--!strict
local Types = require(script.Types)
return {
Arguments = {
Count = {
Type = "number",
Default = 10,
Min = 1,
Max = 200,
},
Radius = {
Type = "number",
Default = 20,
Min = 1,
Step = 0.5,
},
Anchor = {
Type = "switch",
Default = true,
Description = "Anchor each scattered copy",
},
},
Run = function(props: Types.Props, arguments: Types.ArgumentResult)
local source = props.FirstSelected
if not (source and source:IsA("BasePart")) then
return props:SendNotification("Select a single BasePart first")
end
local copies = table.create(arguments.Count)
for i = 1, arguments.Count do
local copy = source:Clone()
local theta = math.random() * math.pi * 2
local r = math.sqrt(math.random()) * arguments.Radius
copy.Position = source.Position
+ Vector3.new(math.cos(theta) * r, 0, math.sin(theta) * r)
copy.Anchored = arguments.Anchor
copy.Parent = source.Parent
copies[i] = copy
end
props:SetSelection(copies)
end,
OnExecuted = function(props, args, _result)
props:SendNotification(
string.format("Scattered %d copies", args.Count)
)
end,
}